/*
 * Copyright (c) 2018-2025 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*
 * Copyright (c) 2001 Daniel Hartmeier
 * Copyright (c) 2002 - 2013 Henning Brauer
 * NAT64 - Copyright (c) 2010 Viagenie Inc. (http://www.viagenie.ca)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *	notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *	copyright notice, this list of conditions and the following
 *	disclaimer in the documentation and/or other materials provided
 *	with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 *
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mbuf.h>

#include <net/if.h>
#include <net/if_types.h>
#include <net/dlil.h>
#include <net/nat464_utils.h>
#include <net/nwk_wq.h>

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_pcb.h>
#include <netinet/icmp_var.h>
#include <netinet/icmp6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <os/log.h>

int clat_debug = 0;

os_log_t nat_log_handle;

static void
nat464_addr_cksum_fixup(uint16_t *, struct nat464_addr *, struct nat464_addr *,
    protocol_family_t, protocol_family_t, uint8_t, boolean_t);

/* Synthesize ipv6 from ipv4 */
int
nat464_synthesize_ipv6(ifnet_t ifp, const struct in_addr *addrv4, struct in6_addr *addr)
{
	static const struct in6_addr well_known_prefix = {
		.__u6_addr.__u6_addr8 = {0x00, 0x64, 0xff, 0x9b, 0x00, 0x00,
			                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			                 0x00, 0x00, 0x00, 0x00},
	};

	struct ipv6_prefix nat64prefixes[NAT64_MAX_NUM_PREFIXES];
	int error = 0, i = 0;
	/* Below call is not optimized as it creates a copy of prefixes */
	if ((error = ifnet_get_nat64prefix(ifp, nat64prefixes)) != 0) {
		return error;
	}

	for (i = 0; i < NAT64_MAX_NUM_PREFIXES; i++) {
		if (nat64prefixes[i].prefix_len != 0) {
			break;
		}
	}

	VERIFY(i < NAT64_MAX_NUM_PREFIXES);

	struct in6_addr prefix = nat64prefixes[i].ipv6_prefix;
	int prefix_len = nat64prefixes[i].prefix_len;

	char *ptrv4 = (char *)__DECONST(struct in_addr *__indexable, addrv4);
	char *ptr = (char *)__DECONST(struct in6_addr *__indexable, addr);

	if (IN_ZERONET(ntohl(addrv4->s_addr)) || // 0.0.0.0/8 Source hosts on local network
	    IN_LOOPBACK(ntohl(addrv4->s_addr)) || // 127.0.0.0/8 Loopback
	    IN_LINKLOCAL(ntohl(addrv4->s_addr)) || // 169.254.0.0/16 Link Local
	    IN_DS_LITE(ntohl(addrv4->s_addr)) || // 192.0.0.0/29 DS-Lite
	    IN_6TO4_RELAY_ANYCAST(ntohl(addrv4->s_addr)) || // 192.88.99.0/24 6to4 Relay Anycast
	    IN_MULTICAST(ntohl(addrv4->s_addr)) || // 224.0.0.0/4 Multicast
	    INADDR_BROADCAST == addrv4->s_addr) { // 255.255.255.255/32 Limited Broadcast
		return -1;
	}

	/* Check for the well-known prefix */
	if (prefix_len == NAT64_PREFIX_LEN_96 &&
	    IN6_ARE_ADDR_EQUAL(&prefix, &well_known_prefix)) { // https://tools.ietf.org/html/rfc6052#section-3.1
		if (IN_PRIVATE(ntohl(addrv4->s_addr)) || // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 Private-Use
		    IN_SHARED_ADDRESS_SPACE(ntohl(addrv4->s_addr))) { // 100.64.0.0/10 Shared Address Space
			return -1;
		}
	}

	memcpy(ptr, (char *)&prefix, prefix_len);

	switch (prefix_len) {
	case NAT64_PREFIX_LEN_96:
		memcpy(ptr + 12, ptrv4, 4);
		break;
	case NAT64_PREFIX_LEN_64:
		memcpy(ptr + 9, ptrv4, 4);
		break;
	case NAT64_PREFIX_LEN_56:
		memcpy(ptr + 7, ptrv4, 1);
		memcpy(ptr + 9, ptrv4 + 1, 3);
		break;
	case NAT64_PREFIX_LEN_48:
		memcpy(ptr + 6, ptrv4, 2);
		memcpy(ptr + 9, ptrv4 + 2, 2);
		break;
	case NAT64_PREFIX_LEN_40:
		memcpy(ptr + 5, ptrv4, 3);
		memcpy(ptr + 9, ptrv4 + 3, 1);
		break;
	case NAT64_PREFIX_LEN_32:
		memcpy(ptr + 4, ptrv4, 4);
		break;
	default:
		panic("NAT64-prefix len is wrong: %u", prefix_len);
	}

	if (clat_debug) {
		char buf[MAX_IPv6_STR_LEN];
		clat_log2((LOG_DEBUG, "%s synthesized  %s\n", __func__,
		    inet_ntop(AF_INET6, (void *)addr, buf, sizeof(buf))));
	}

	return error;
}

/* Synthesize ipv4 from ipv6 */
int
nat464_synthesize_ipv4(ifnet_t ifp, const struct in6_addr *addr,
    struct in_addr *addrv4, bool * translate_p)
{
	struct ipv6_prefix nat64prefixes[NAT64_MAX_NUM_PREFIXES];
	int error = 0, i = 0;
	bool translate = false;

	/* Below call is not optimized as it creates a copy of prefixes */
	if ((error = ifnet_get_nat64prefix(ifp, nat64prefixes)) != 0) {
		goto done;
	}

	for (i = 0; i < NAT64_MAX_NUM_PREFIXES; i++) {
		if (nat64prefixes[i].prefix_len != 0) {
			break;
		}
	}

	VERIFY(i < NAT64_MAX_NUM_PREFIXES);

	struct in6_addr prefix = nat64prefixes[i].ipv6_prefix;
	int prefix_len = nat64prefixes[i].prefix_len;

	VERIFY(prefix_len < sizeof(prefix));

	char *ptrv4 = (char *)__DECONST(struct in_addr *__indexable, addrv4);
	char *ptr = (char *)__DECONST(struct in6_addr *__indexable, addr);

	/* -fbounds-safety:
	 * Override a warning about prefix_len being > 16 because
	 * we already checked that above.
	 */
	if (memcmp((const struct in6_addr *__indexable)addr, &prefix, prefix_len) != 0) {
		/* it's not the NAT64 prefix, so let it pass */
		goto done;
	}

	switch (prefix_len) {
	case NAT64_PREFIX_LEN_96:
		memcpy(ptrv4, ptr + 12, 4);
		break;
	case NAT64_PREFIX_LEN_64:
		memcpy(ptrv4, ptr + 9, 4);
		break;
	case NAT64_PREFIX_LEN_56:
		memcpy(ptrv4, ptr + 7, 1);
		memcpy(ptrv4 + 1, ptr + 9, 3);
		break;
	case NAT64_PREFIX_LEN_48:
		memcpy(ptrv4, ptr + 6, 2);
		memcpy(ptrv4 + 2, ptr + 9, 2);
		break;
	case NAT64_PREFIX_LEN_40:
		memcpy(ptrv4, ptr + 5, 3);
		memcpy(ptrv4 + 3, ptr + 9, 1);
		break;
	case NAT64_PREFIX_LEN_32:
		memcpy(ptrv4, ptr + 4, 4);
		break;
	default:
		panic("NAT64-prefix len is wrong: %u",
		    prefix_len);
	}

	if (clat_debug) {
		char buf[MAX_IPv4_STR_LEN];
		clat_log2((LOG_DEBUG, "%s desynthesized to %s\n", __func__,
		    inet_ntop(AF_INET, (void *)addrv4, buf, sizeof(buf))));
	}
	translate = true;
done:
	*translate_p = translate;
	return error;
}

#define PTR_IP(field)   ((int32_t)offsetof(struct ip, field))
#define PTR_IP6(field)  ((int32_t)offsetof(struct ip6_hdr, field))

/*
 *  Translate the ICMP header
 */
int
nat464_translate_icmp(int naf, void *arg)
{
	struct icmp             *__single icmp4;
	struct icmp6_hdr        *__single icmp6;
	uint32_t                 mtu;
	int32_t                  ptr = -1;
	uint8_t          type;
	uint8_t          code;

	switch (naf) {
	case AF_INET:
		icmp6 = arg;
		type  = icmp6->icmp6_type;
		code  = icmp6->icmp6_code;
		mtu   = ntohl(icmp6->icmp6_mtu);

		switch (type) {
		case ICMP6_ECHO_REQUEST:
			type = ICMP_ECHO;
			break;
		case ICMP6_ECHO_REPLY:
			type = ICMP_ECHOREPLY;
			break;
		case ICMP6_DST_UNREACH:
			type = ICMP_UNREACH;
			switch (code) {
			case ICMP6_DST_UNREACH_NOROUTE:
			case ICMP6_DST_UNREACH_BEYONDSCOPE:
			case ICMP6_DST_UNREACH_ADDR:
				code = ICMP_UNREACH_HOST;
				break;
			case ICMP6_DST_UNREACH_ADMIN:
				code = ICMP_UNREACH_HOST_PROHIB;
				break;
			case ICMP6_DST_UNREACH_NOPORT:
				code = ICMP_UNREACH_PORT;
				break;
			default:
				return -1;
			}
			break;
		case ICMP6_PACKET_TOO_BIG:
			type = ICMP_UNREACH;
			code = ICMP_UNREACH_NEEDFRAG;
			mtu -= 20;
			break;
		case ICMP6_TIME_EXCEEDED:
			type = ICMP_TIMXCEED;
			break;
		case ICMP6_PARAM_PROB:
			switch (code) {
			case ICMP6_PARAMPROB_HEADER:
				type = ICMP_PARAMPROB;
				code = ICMP_PARAMPROB_ERRATPTR;
				ptr  = ntohl(icmp6->icmp6_pptr);

				if (ptr == PTR_IP6(ip6_vfc)) {
					; /* preserve */
				} else if (ptr == PTR_IP6(ip6_vfc) + 1) {
					ptr = PTR_IP(ip_tos);
				} else if (ptr == PTR_IP6(ip6_plen) ||
				    ptr == PTR_IP6(ip6_plen) + 1) {
					ptr = PTR_IP(ip_len);
				} else if (ptr == PTR_IP6(ip6_nxt)) {
					ptr = PTR_IP(ip_p);
				} else if (ptr == PTR_IP6(ip6_hlim)) {
					ptr = PTR_IP(ip_ttl);
				} else if (ptr >= PTR_IP6(ip6_src) &&
				    ptr < PTR_IP6(ip6_dst)) {
					ptr = PTR_IP(ip_src);
				} else if (ptr >= PTR_IP6(ip6_dst) &&
				    ptr < (int32_t)sizeof(struct ip6_hdr)) {
					ptr = PTR_IP(ip_dst);
				} else {
					return -1;
				}
				break;
			case ICMP6_PARAMPROB_NEXTHEADER:
				type = ICMP_UNREACH;
				code = ICMP_UNREACH_PROTOCOL;
				break;
			default:
				return -1;
			}
			break;
		default:
			return -1;
		}
		icmp6->icmp6_type = type;
		icmp6->icmp6_code = code;
		/* aligns well with a icmpv4 nextmtu */
		icmp6->icmp6_mtu = htonl(mtu);
		/* icmpv4 pptr is a one most significant byte */
		if (ptr >= 0) {
			icmp6->icmp6_pptr = htonl(ptr << 24);
		}
		break;

	case AF_INET6:
		icmp4 = arg;
		type  = icmp4->icmp_type;
		code  = icmp4->icmp_code;
		mtu   = ntohs(icmp4->icmp_nextmtu);

		switch (type) {
		case ICMP_ECHO:
			type = ICMP6_ECHO_REQUEST;
			break;
		case ICMP_ECHOREPLY:
			type = ICMP6_ECHO_REPLY;
			break;
		case ICMP_UNREACH:
			type = ICMP6_DST_UNREACH;
			switch (code) {
			case ICMP_UNREACH_NET:
			case ICMP_UNREACH_HOST:
			case ICMP_UNREACH_NET_UNKNOWN:
			case ICMP_UNREACH_HOST_UNKNOWN:
			case ICMP_UNREACH_ISOLATED:
			case ICMP_UNREACH_TOSNET:
			case ICMP_UNREACH_TOSHOST:
				code = ICMP6_DST_UNREACH_NOROUTE;
				break;
			case ICMP_UNREACH_PORT:
				code = ICMP6_DST_UNREACH_NOPORT;
				break;
			case ICMP_UNREACH_NET_PROHIB:
			case ICMP_UNREACH_HOST_PROHIB:
			case ICMP_UNREACH_FILTER_PROHIB:
			case ICMP_UNREACH_PRECEDENCE_CUTOFF:
				code = ICMP6_DST_UNREACH_ADMIN;
				break;
			case ICMP_UNREACH_PROTOCOL:
				type = ICMP6_PARAM_PROB;
				code = ICMP6_PARAMPROB_NEXTHEADER;
				ptr  = offsetof(struct ip6_hdr, ip6_nxt);
				break;
			case ICMP_UNREACH_NEEDFRAG:
				type = ICMP6_PACKET_TOO_BIG;
				code = 0;
				/*
				 * Make sure we don't overflow adjusting for
				 * translation overhead.
				 * If we do, just work with a lower mtu as is.
				 */
				if (mtu <= (UINT16_MAX - CLAT46_HDR_EXPANSION_OVERHD)) {
					mtu += CLAT46_HDR_EXPANSION_OVERHD;
				}
				break;
			default:
				return -1;
			}
			break;
		case ICMP_TIMXCEED:
			type = ICMP6_TIME_EXCEEDED;
			break;
		case ICMP_PARAMPROB:
			type = ICMP6_PARAM_PROB;
			switch (code) {
			case ICMP_PARAMPROB_ERRATPTR:
				code = ICMP6_PARAMPROB_HEADER;
				break;
			case ICMP_PARAMPROB_LENGTH:
				code = ICMP6_PARAMPROB_HEADER;
				break;
			default:
				return -1;
			}

			ptr = icmp4->icmp_pptr;
			if (ptr == 0 || ptr == PTR_IP(ip_tos)) {
				; /* preserve */
			} else if (ptr == PTR_IP(ip_len) ||
			    ptr == PTR_IP(ip_len) + 1) {
				ptr = PTR_IP6(ip6_plen);
			} else if (ptr == PTR_IP(ip_ttl)) {
				ptr = PTR_IP6(ip6_hlim);
			} else if (ptr == PTR_IP(ip_p)) {
				ptr = PTR_IP6(ip6_nxt);
			} else if (ptr >= PTR_IP(ip_src) &&
			    ptr < PTR_IP(ip_dst)) {
				ptr = PTR_IP6(ip6_src);
			} else if (ptr >= PTR_IP(ip_dst) &&
			    ptr < (int32_t)sizeof(struct ip)) {
				ptr = PTR_IP6(ip6_dst);
			} else {
				return -1;
			}
			break;
		default:
			return -1;
		}
		icmp4->icmp_type = type;
		icmp4->icmp_code = code;
		icmp4->icmp_nextmtu = htons((uint16_t)mtu);

		if (ptr >= 0) {
			icmp4->icmp_void = htonl(ptr);
		}
		break;
	}

	return 0;
}

/*
 * @brief This routine is called to perform address family translation on the
 *     inner IP header (that may come as payload) of an ICMP(v4/v6) error
 *     response.
 *
 * @param pbuf Pointer to packet buffer
 * @param off Points to end of ICMP header
 * @param tot_len Pointer to total length of the outer IP header
 * @param off2 Points to end of inner IP header
 * @param proto2 Inner IP proto field
 * @param ttl2 Inner IP ttl field
 * @param tot_len2 Inner IP total length
 * @param src Pointer to the generic v4/v6 src address
 * @param dst Pointer to the generic v4/v6 dst address
 * @param af Old protocol family
 * @param naf New protocol family
 *
 * @return -1 on error and 0 on success
 */
int
nat464_translate_icmp_ip(pbuf_t *pbuf, uint16_t off, uint16_t *tot_len, uint16_t *off2,
    uint8_t proto2, uint8_t ttl2, uint16_t tot_len2, struct nat464_addr *src,
    struct nat464_addr *dst, protocol_family_t af, protocol_family_t naf)
{
	struct ip *__single ip4 = NULL;
	struct ip6_hdr *__single ip6 = NULL;
	void *__single hdr = NULL;
	int hlen = 0, olen = 0;
	uint64_t ipid_salt = (uint64_t)pbuf_get_packet_buffer_address(pbuf);

	if (af == naf || (af != AF_INET && af != AF_INET6) ||
	    (naf != AF_INET && naf != AF_INET6)) {
		return -1;
	}

	/* old header */
	olen = *off2 - off;
	/* new header */
	hlen = naf == PF_INET ? sizeof(*ip4) : sizeof(*ip6);

	/* Modify the pbuf to accommodate the new header */
	hdr = pbuf_resize_segment(pbuf, off, olen, hlen);
	if (hdr == NULL) {
		return -1;
	}

	/* translate inner ip/ip6 header */
	switch (naf) {
	case AF_INET:
		ip4 = hdr;
		bzero(ip4, sizeof(*ip4));
		ip4->ip_v = IPVERSION;
		ip4->ip_hl = sizeof(*ip4) >> 2;
		ip4->ip_len = htons((uint16_t)(sizeof(*ip4) + tot_len2 - olen));
		ip4->ip_id = rfc6864 ? 0 : htons(ip_randomid(ipid_salt));
		ip4->ip_off = htons(IP_DF);
		ip4->ip_ttl = ttl2;
		if (proto2 == IPPROTO_ICMPV6) {
			ip4->ip_p = IPPROTO_ICMP;
		} else {
			ip4->ip_p = proto2;
		}
		ip4->ip_src = src->natv4addr;
		ip4->ip_dst = dst->natv4addr;
		ip4->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, ip4->ip_hl << 2);

		if (clat_debug) {
			char buf[MAX_IPv4_STR_LEN];
			clat_log2((LOG_DEBUG, "%s translated to IPv4 (inner) "
			    "ip_len: %#x ip_p: %d ip_sum: %#x ip_src: %s ip_dst: %s \n",
			    __func__, ntohs(ip4->ip_len), ip4->ip_p, ntohs(ip4->ip_sum),
			    inet_ntop(AF_INET, (void *)&ip4->ip_src, buf, sizeof(buf)),
			    inet_ntop(AF_INET, (void *)&ip4->ip_dst, buf, sizeof(buf))));
		}
		break;
	case AF_INET6:
		ip6 = hdr;
		bzero(ip6, sizeof(*ip6));
		ip6->ip6_vfc  = IPV6_VERSION;
		ip6->ip6_plen = htons((uint16_t)(tot_len2 - olen));
		if (proto2 == IPPROTO_ICMP) {
			ip6->ip6_nxt = IPPROTO_ICMPV6;
		} else {
			ip6->ip6_nxt = proto2;
		}
		if (!ttl2 || ttl2 > IPV6_DEFHLIM) {
			ip6->ip6_hlim = IPV6_DEFHLIM;
		} else {
			ip6->ip6_hlim = ttl2;
		}
		ip6->ip6_src  = src->natv6addr;
		ip6->ip6_dst  = dst->natv6addr;

		if (clat_debug) {
			char buf2[MAX_IPv6_STR_LEN];
			clat_log2((LOG_DEBUG, "%s translated to IPv6 (inner) "
			    "ip6_plen: %#x ip6_nxt: %d ip6_src: %s ip6_dst: %s \n",
			    __func__, ntohs(ip6->ip6_plen), ip6->ip6_nxt,
			    inet_ntop(AF_INET6, (void *)&ip6->ip6_src, buf2, sizeof(buf2)),
			    inet_ntop(AF_INET6, (void *)&ip6->ip6_dst, buf2, sizeof(buf2))));
		}
		break;
	}

	/* adjust payload offset and total packet length */
	*off2 += hlen - olen;
	*tot_len += hlen - olen;

	return 0;
}
/*
 * @brief The function inserts IPv6 fragmentation header
 *     and populates it with the passed parameters.
 *
 * @param pbuf Pointer to the packet buffer
 * @param ip_id IP identifier (in network byte order)
 * @param frag_offset Fragment offset (in network byte order)
 * @param is_last_frag Boolean indicating if the fragment header is for
 *     last fragment or not.
 *
 * @return -1 on error and 0 on success.
 */
int
nat464_insert_frag46(pbuf_t *pbuf, uint16_t ip_id_val, uint16_t frag_offset,
    boolean_t is_last_frag)
{
	struct ip6_frag *p_ip6_frag = NULL;
	struct ip6_hdr *p_ip6h = NULL;

	/* Insert IPv6 fragmentation header */
	if (pbuf_resize_segment(pbuf, sizeof(struct ip6_hdr), 0,
	    sizeof(struct ip6_frag)) == NULL) {
		return -1;
	}

	p_ip6h = mtod(pbuf->pb_mbuf, struct ip6_hdr *);
	p_ip6_frag = (struct ip6_frag *)pbuf_contig_segment(pbuf,
	    sizeof(struct ip6_hdr), sizeof(struct ip6_frag));

	if (p_ip6_frag == NULL) {
		return -1;
	}

	/* Populate IPv6 fragmentation header */
	p_ip6_frag->ip6f_nxt = p_ip6h->ip6_nxt;
	p_ip6_frag->ip6f_reserved = 0;
	p_ip6_frag->ip6f_offlg = (uint16_t)(frag_offset << 3);
	if (!is_last_frag) {
		p_ip6_frag->ip6f_offlg |= 0x1;
	}
	p_ip6_frag->ip6f_offlg = htons(p_ip6_frag->ip6f_offlg);
	p_ip6_frag->ip6f_ident = ip_id_val;

	/* Update IPv6 header */
	p_ip6h->ip6_nxt = IPPROTO_FRAGMENT;
	p_ip6h->ip6_plen = htons(ntohs(p_ip6h->ip6_plen) +
	    sizeof(struct ip6_frag));

	return 0;
}

int
nat464_translate_64(pbuf_t *pbuf, int off, uint8_t tos,
    uint8_t *proto, uint8_t ttl, struct in_addr src_v4,
    struct in_addr dst_v4, uint64_t tot_len, boolean_t *p_is_first_frag)
{
	struct ip *ip4;
	struct ip6_frag *p_frag6 = NULL;
	struct ip6_frag frag6 = {};
	boolean_t is_frag = FALSE;
	uint16_t ip_frag_off = 0;

	/*
	 * ip_input asserts for rcvif to be not NULL
	 * That may not be true for two corner cases
	 * 1. If for some reason a local app sends DNS
	 * AAAA query to local host
	 * 2. If IPv6 stack in kernel internally generates a
	 * message destined for a synthesized IPv6 end-point.
	 */
	if (pbuf->pb_ifp == NULL) {
		return NT_DROP;
	}

	if (*proto == IPPROTO_FRAGMENT) {
		p_frag6 = (struct ip6_frag *)pbuf_contig_segment(pbuf,
		    sizeof(struct ip6_hdr), sizeof(struct ip6_frag));
		if (p_frag6 == NULL) {
			ip6stat.ip6s_clat464_in_64frag_transfail_drop++;
			return NT_DROP;
		}

		frag6 = *p_frag6;
		p_frag6 = NULL;
		*proto = frag6.ip6f_nxt;
		off += sizeof(struct ip6_frag);
		is_frag = TRUE;
		ip_frag_off = (ntohs(frag6.ip6f_offlg & IP6F_OFF_MASK)) >> 3;
		if (ip_frag_off != 0) {
			*p_is_first_frag = FALSE;
		}
	}

	ip4 = (struct ip *)pbuf_resize_segment(pbuf, 0, off, sizeof(*ip4));
	if (ip4 == NULL) {
		return NT_DROP;
	}
	ip4->ip_v   = 4;
	ip4->ip_hl  = 5;
	ip4->ip_tos = tos;
	ip4->ip_len = htons((uint16_t)(sizeof(*ip4) + (tot_len - off)));
	ip4->ip_id  = 0;
	ip4->ip_off = 0;
	ip4->ip_ttl = ttl;
	ip4->ip_p   = *proto;
	ip4->ip_sum = 0;
	ip4->ip_src = src_v4;
	ip4->ip_dst = dst_v4;
	if (is_frag) {
		/*
		 * https://tools.ietf.org/html/rfc7915#section-5.1.1
		 * Identification:  Copied from the low-order 16 bits in the
		 * Identification field in the Fragment Header.
		 */
		ip4->ip_id = ntohl(frag6.ip6f_ident) & 0xffff;
		ip4->ip_id = htons(ip4->ip_id);
		if (frag6.ip6f_offlg & IP6F_MORE_FRAG) {
			ip_frag_off |= IP_MF;
		}
		ip4->ip_off = htons(ip_frag_off);
	} else {
		ip4->ip_off |= htons(IP_DF);
	}

	/*
	 * Defer calculating ip_sum for ICMPv6 as we do it
	 * later in Protocol translation
	 */
	if (*proto != IPPROTO_ICMPV6) {
		ip4->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, ip4->ip_hl << 2);
	}

	if (clat_debug) {
		char buf1[MAX_IPv4_STR_LEN], buf2[MAX_IPv4_STR_LEN];
		clat_log2((LOG_DEBUG, "%s translated to IPv4 ip_len: %#x "
		    "ip_p: %d ip_sum: %#x ip_src: %s ip_dst: %s \n", __func__,
		    ntohs(ip4->ip_len), ip4->ip_p, ntohs(ip4->ip_sum),
		    inet_ntop(AF_INET, (void *)&ip4->ip_src, buf1, sizeof(buf1)),
		    inet_ntop(AF_INET, (void *)&ip4->ip_dst, buf2, sizeof(buf2))));
	}
	return NT_NAT64;
}
/*
 * @brief The routine translates the IPv4 header to IPv6 header.
 *
 * @param pbuf Pointer to the generic packet buffer
 * @param off Offset to the end of IP header
 * @param tos Type of service
 * @param proto Protocol running over IP
 * @param ttl Time to live
 * @param src_v6 Source IPv6 address
 * @param dst_v6 Destination IPv6 address
 * @param tot_len Total payload length
 *
 * @return NT_NAT64 if IP header translation is successful, else error
 */
int
nat464_translate_46(pbuf_t *pbuf, uint16_t off, uint8_t tos,
    uint8_t proto, uint8_t ttl, struct in6_addr src_v6,
    struct in6_addr dst_v6, uint16_t tot_len)
{
	struct ip6_hdr *ip6;

	if (pbuf->pb_ifp == NULL) {
		return NT_DROP;
	}

	/*
	 * Trim the buffer from head of size equal to to off (which is equal to
	 * the size of IP header and prepend IPv6 header length to the buffer
	 */
	ip6 = (struct ip6_hdr *)pbuf_resize_segment(pbuf, 0, off, sizeof(*ip6));
	if (ip6 == NULL) {
		return NT_DROP;
	}
	ip6->ip6_flow = htonl((6 << 28) | (tos << 20));
	ip6->ip6_plen = htons(tot_len - off);
	ip6->ip6_nxt  = proto;
	ip6->ip6_hlim = ttl;
	ip6->ip6_src = src_v6;
	ip6->ip6_dst = dst_v6;

	if (clat_debug) {
		char buf1[MAX_IPv6_STR_LEN], buf2[MAX_IPv6_STR_LEN];
		clat_log2((LOG_DEBUG, "%s translated to IPv6 ip6_plen: %#x "
		    " ip6_nxt: %d ip6_src: %s ip6_dst: %s \n", __func__,
		    ntohs(ip6->ip6_plen), ip6->ip6_nxt,
		    inet_ntop(AF_INET6, (void *)&ip6->ip6_src, buf1, sizeof(buf1)),
		    inet_ntop(AF_INET6, (void *)&ip6->ip6_dst, buf2, sizeof(buf2))));
	}
	return NT_NAT64;
}

/* Handle the next protocol checksum */
/*
 * @brief This routine translates the Proto running over IP and updates the checksum
 *     for IP header translation. It also updates pbuf checksum flags and related fields.
 *
 * @param pbuf Pointer to protocol buffer
 * @param nsrc New source address
 * @param ndst New destination address
 * @param af Old family
 * @param naf New family
 *
 * @return void
 */
int
nat464_translate_proto(pbuf_t *pbuf, struct nat464_addr *osrc,
    struct nat464_addr *odst, uint8_t oproto, protocol_family_t af,
    protocol_family_t naf, int direction, boolean_t only_csum)
{
	struct ip *iph = NULL;
	struct ip6_hdr *ip6h = NULL;
	uint16_t hlen = 0, plen = 0;
	uint16_t tot_len = 0;
	void *nsrc = NULL, *ndst = NULL;
	uint8_t *proto = 0;
	uint16_t *psum = NULL;
	boolean_t do_ones_complement = FALSE;

	/* For now these routines only support 464 translations */
	VERIFY(af != naf);
	VERIFY(af == PF_INET || af == PF_INET6);

	/*
	 * For now out must be for v4 to v6 translation
	 * and in must be for v6 to v4 translation.
	 */
	switch (naf) {
	case PF_INET: {
		iph = pbuf->pb_data;
		hlen = (uint16_t)(iph->ip_hl << 2);
		plen = ntohs(iph->ip_len) - hlen;
		tot_len = ntohs(iph->ip_len);
		nsrc = &iph->ip_src;
		ndst = &iph->ip_dst;
		proto = &iph->ip_p;
		break;
	}
	case PF_INET6: {
		ip6h = pbuf->pb_data;
		hlen = (uint16_t)sizeof(*ip6h);
		plen = ntohs(ip6h->ip6_plen);
		tot_len = hlen + plen;
		nsrc = &ip6h->ip6_src;
		ndst = &ip6h->ip6_dst;
		proto = &ip6h->ip6_nxt;
		break;
	}
	default:
		return NT_DROP; /* We should never come here */
	}

	if (*proto != oproto) {
		return NT_DROP;
	}

	/*
	 * We may want to manipulate csum flags in some cases
	 * and not act on the protocol header as it may not
	 * carry protocol checksums.
	 * For example, fragments other than the first one would
	 * not carry protocol headers.
	 */
	if (only_csum) {
		/*
		 * Only translate ICMP proto in the header
		 * and adjust checksums
		 */
		if (*proto == IPPROTO_ICMP) {
			if (naf != PF_INET6) {
				return NT_DROP;
			}

			*proto = IPPROTO_ICMPV6;
		} else if (*proto == IPPROTO_ICMPV6) {
			if (naf != PF_INET) {
				return NT_DROP;
			}

			*proto = IPPROTO_ICMP;
			/* Recalculate IP checksum as proto field has changed */
			iph->ip_sum = 0;
			iph->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, hlen);
		}
		goto done;
	}

	switch (*proto) {
	case IPPROTO_UDP: {
		struct udphdr *uh = (struct udphdr *)pbuf_contig_segment(pbuf, hlen,
		    sizeof(*uh));

		if (uh == NULL) {
			return NT_DROP;
		}

		if (!(*pbuf->pb_csum_flags & (CSUM_UDP | CSUM_PARTIAL)) &&
		    uh->uh_sum == 0 && af == PF_INET && naf == PF_INET6) {
			uh->uh_sum = pbuf_inet6_cksum(pbuf, IPPROTO_UDP,
			    hlen, ntohs(ip6h->ip6_plen));
			if (uh->uh_sum == 0) {
				uh->uh_sum = 0xffff;
			}
			goto done;
		}

		psum = &uh->uh_sum;
		break;
	}
	case IPPROTO_TCP: {
		struct tcphdr *th = (struct tcphdr *)pbuf_contig_segment(pbuf, hlen,
		    sizeof(*th));

		if (th == NULL) {
			return NT_DROP;
		}

		psum = &th->th_sum;
		break;
	}
	}

	/*
	 * Translate the protocol header, update IP header if needed,
	 * calculate checksums and update the checksum flags.
	 */
	switch (*proto) {
	case IPPROTO_UDP:
	/* Fall through */
	case IPPROTO_TCP:
	{
		/*
		 * If it is a locally generated and has CSUM flags set
		 * for TCP and UDP it means we have pseudo header checksum
		 * that has not yet been one's complemented.
		 */
		if (direction == NT_OUT &&
		    (*pbuf->pb_csum_flags & CSUM_PARTIAL)) {
			do_ones_complement = TRUE;
		}

		nat464_addr_cksum_fixup(psum, osrc, (struct nat464_addr *)nsrc,
		    af, naf, (*proto == IPPROTO_UDP) ? 1 : 0, do_ones_complement);
		nat464_addr_cksum_fixup(psum, odst, (struct nat464_addr *)ndst,
		    af, naf, (*proto == IPPROTO_UDP) ? 1 : 0, do_ones_complement);

		break;
	}
	case IPPROTO_ICMP: {
		if (naf != PF_INET6) {  /* allow only v6 as naf for ICMP */
			return NT_DROP;
		}

		struct icmp *__single icmph = NULL;
		struct icmp6_hdr *__single icmp6h = NULL;
		uint16_t ip2off = 0, hlen2 = 0, tot_len2 = 0;

		icmph = (struct icmp*) pbuf_contig_segment(pbuf, hlen,
		    ICMP_MINLEN);
		if (icmph == NULL) {
			return NT_DROP;
		}

		/* Translate the ICMP header */
		if (nat464_translate_icmp(PF_INET6, icmph) != 0) {
			return NT_DROP;
		}

		*proto = IPPROTO_ICMPV6;
		icmp6h = (struct icmp6_hdr *__single)(void *)icmph;
		pbuf_copy_back(pbuf, hlen, sizeof(struct icmp6_hdr),
		    icmp6h, sizeof(*icmp6h));

		/*Translate the inner IP header only for error messages */
		if (ICMP6_ERRORTYPE(icmp6h->icmp6_type)) {
			ip2off = (uint16_t)(hlen + sizeof(*icmp6h));
			struct ip *iph2 = NULL;
			iph2 = (struct ip*) pbuf_contig_segment(pbuf, ip2off,
			    sizeof(*iph2));
			if (iph2 == NULL) {
				return NT_DROP;
			}

			hlen2 = (uint16_t)(ip2off + (iph2->ip_hl << 2));
			tot_len2 = ntohs(iph2->ip_len);

			/*
			 * Destination in outer IP should be Source in inner IP,
			 * otherwise the ICMP is likely errnoneous.
			 */
			if (!IN_ARE_ADDR_EQUAL(&odst->natv4addr, &iph2->ip_src)) {
				return NT_DROP;
			}

			if (nat464_translate_icmp_ip(pbuf, ip2off, &tot_len,
			    &hlen2, iph2->ip_p, iph2->ip_ttl, tot_len2,
			    (struct nat464_addr *)ndst, (struct nat464_addr *)nsrc,
			    PF_INET, PF_INET6) != 0) {
				return NT_DROP;
			}
			/* Update total length/payload length for outer header */
			switch (naf) {
			case PF_INET:
				iph->ip_len = htons(tot_len);
				break;
			case PF_INET6:
				ip6h->ip6_plen = htons(tot_len - hlen);
				break;
			}
			iph2 = NULL;
		}

		icmp6h->icmp6_cksum = 0;
		icmp6h->icmp6_cksum = pbuf_inet6_cksum(pbuf, IPPROTO_ICMPV6, hlen,
		    ntohs(ip6h->ip6_plen));

		clat_log2((LOG_DEBUG, "%s translated to ICMPV6 type: %d "
		    "code: %d checksum: %#x \n", __func__, icmp6h->icmp6_type,
		    icmp6h->icmp6_code, icmp6h->icmp6_cksum));

		icmph = NULL;
		icmp6h = NULL;
		break;
	}
	case IPPROTO_ICMPV6:
	{       if (naf != PF_INET) {           /* allow only v4 as naf for ICMPV6 */
			return NT_DROP;
		}

		struct icmp6_hdr *__single icmp6h = NULL;
		struct icmp *__single icmph = NULL;
		uint16_t ip2off = 0, hlen2 = 0, tot_len2 = 0;

		icmp6h = (struct icmp6_hdr*) pbuf_contig_segment(pbuf, hlen,
		    sizeof(*icmp6h));
		if (icmp6h == NULL) {
			return NT_DROP;
		}

		/* Translate the ICMP header */
		if (nat464_translate_icmp(PF_INET, icmp6h) != 0) {
			return NT_DROP;
		}

		*proto = IPPROTO_ICMP;
		icmph = (struct icmp *__single)(void *)icmp6h;
		pbuf_copy_back(pbuf, hlen, ICMP_MINLEN,
		    icmph, sizeof(*icmph));

		/*Translate the inner IP header only for error messages */
		if (ICMP_ERRORTYPE(icmph->icmp_type)) {
			ip2off = hlen + ICMP_MINLEN;
			struct ip6_hdr *iph2 = NULL;
			iph2 = (struct ip6_hdr*) pbuf_contig_segment(pbuf, ip2off,
			    sizeof(*iph2));
			if (iph2 == NULL) {
				return NT_DROP;
			}

			/* hlen2 points to end of inner IP header from the beginning */
			hlen2 = ip2off + sizeof(struct ip6_hdr);
			tot_len2 = ntohs(iph2->ip6_plen) + sizeof(struct ip6_hdr);

			if (nat464_translate_icmp_ip(pbuf, ip2off, &tot_len,
			    &hlen2, iph2->ip6_nxt, iph2->ip6_hlim, tot_len2,
			    (struct nat464_addr *)ndst, (struct nat464_addr *)nsrc,
			    PF_INET6, PF_INET) != 0) {
				return NT_DROP;
			}

			/* Update total length for outer header */
			switch (naf) {
			case PF_INET:
				iph->ip_len = htons(tot_len);
				break;
			case PF_INET6:
				ip6h->ip6_plen = htons(tot_len - hlen);
				break;
			}
			iph2 = NULL;
		}
		/* Recalculate IP checksum as some IP fields might have changed */
		iph->ip_sum = 0;
		iph->ip_sum = pbuf_inet_cksum(pbuf, 0, 0, iph->ip_hl << 2);
		icmph->icmp_cksum = 0;
		icmph->icmp_cksum = pbuf_inet_cksum(pbuf, 0, hlen,
		    ntohs(iph->ip_len) - hlen);

		clat_log2((LOG_DEBUG, "%s translated to ICMP type: %d "
		    "code: %d checksum: %#x \n", __func__, icmph->icmp_type,
		    icmph->icmp_code, icmph->icmp_cksum));

		icmp6h = NULL;
		icmph = NULL;
		break;}

	/*
	 * https://tools.ietf.org/html/rfc7915#section-5.1.1
	 * If the Next Header field of the Fragment Header is an
	 * extension header (except ESP, but including the Authentication
	 * Header (AH)), then the packet SHOULD be dropped and logged.
	 */
	case IPPROTO_HOPOPTS:
	case IPPROTO_ROUTING:
	case IPPROTO_DSTOPTS:
	case IPPROTO_AH:
		return NT_DROP;

	case IPPROTO_FRAGMENT:
		/*
		 * The fragment header is appended after or removed before
		 * calling into this routine.
		 */
		VERIFY(FALSE);
	case IPPROTO_ESP:
		break;

	default:
		return NT_DROP;
	}

done:
	/* Update checksum flags and offsets based on direction */
	if (direction == NT_OUT) {
		if ((*pbuf->pb_csum_flags & (CSUM_DATA_VALID | CSUM_PARTIAL)) ==
		    (CSUM_DATA_VALID | CSUM_PARTIAL)) {
			(pbuf->pb_mbuf)->m_pkthdr.csum_tx_start += CLAT46_HDR_EXPANSION_OVERHD;
			(pbuf->pb_mbuf)->m_pkthdr.csum_tx_stuff += CLAT46_HDR_EXPANSION_OVERHD;
		}

		if (*pbuf->pb_csum_flags & CSUM_TCP) {
			*pbuf->pb_csum_flags |= CSUM_TCPIPV6;
		}
		if (*pbuf->pb_csum_flags & CSUM_UDP) {
			*pbuf->pb_csum_flags |= CSUM_UDPIPV6;
		}
		if (*pbuf->pb_csum_flags & CSUM_FRAGMENT) {
			*pbuf->pb_csum_flags |= CSUM_FRAGMENT_IPV6;
		}

		/* Clear IPv4 checksum flags */
		*pbuf->pb_csum_flags &= ~(CSUM_IP | CSUM_IP_FRAGS | CSUM_DELAY_DATA | CSUM_FRAGMENT);
		/*
		 * If the packet requires TCP segmentation due to TSO offload,
		 * then change the checksum flag to indicate that an IPv6
		 * TCP segmentation is needed now.
		 */
		if (*pbuf->pb_csum_flags & CSUM_TSO_IPV4) {
			*pbuf->pb_csum_flags &= ~CSUM_TSO_IPV4;
			*pbuf->pb_csum_flags |= CSUM_TSO_IPV6;
		}
	} else if (direction == NT_IN) {
		/* XXX On input just reset csum flags */
		*pbuf->pb_csum_flags = 0; /* Reset all flags for now */
#if 0
		/* Update csum flags and offsets for rx */
		if (*pbuf->pb_csum_flags & CSUM_PARTIAL) {
			(pbuf->pb_mbuf)->m_pkthdr.csum_rx_start -= CLAT46_HDR_EXPANSION_OVERHD;
		}
#endif
	}
	return NT_NAT64;
}

/* Fix the proto checksum for address change */
static void
nat464_addr_cksum_fixup(uint16_t *pc, struct nat464_addr *ao, struct nat464_addr *an,
    protocol_family_t af, protocol_family_t naf, uint8_t u, boolean_t do_ones_complement)
{
	/* Currently we only support v4 to v6 and vice versa */
	VERIFY(af != naf);

	switch (af) {
	case PF_INET:
		switch (naf) {
		case PF_INET6:
			if (do_ones_complement) {
				*pc = ~nat464_cksum_fixup(nat464_cksum_fixup(
					    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(
						    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(~*pc,
						    ao->nataddr16[0], an->nataddr16[0], u),
						    ao->nataddr16[1], an->nataddr16[1], u),
						    0, an->nataddr16[2], u),
						    0, an->nataddr16[3], u),
					    0, an->nataddr16[4], u),
					    0, an->nataddr16[5], u),
					    0, an->nataddr16[6], u),
				    0, an->nataddr16[7], u);
			} else {
				*pc = nat464_cksum_fixup(nat464_cksum_fixup(
					    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(
						    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(*pc,
						    ao->nataddr16[0], an->nataddr16[0], u),
						    ao->nataddr16[1], an->nataddr16[1], u),
						    0, an->nataddr16[2], u),
						    0, an->nataddr16[3], u),
					    0, an->nataddr16[4], u),
					    0, an->nataddr16[5], u),
					    0, an->nataddr16[6], u),
				    0, an->nataddr16[7], u);
			}
			break;
		}
		break;
	case PF_INET6:
		/*
		 * XXX For NAT464 this only applies to the incoming path.
		 * The checksum therefore is already ones complemented.
		 * Therefore we just perform normal fixup.
		 */
		switch (naf) {
		case PF_INET:
			*pc = nat464_cksum_fixup(nat464_cksum_fixup(
				    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(
					    nat464_cksum_fixup(nat464_cksum_fixup(nat464_cksum_fixup(*pc,
					    ao->nataddr16[0], an->nataddr16[0], u),
					    ao->nataddr16[1], an->nataddr16[1], u),
					    ao->nataddr16[2], 0, u),
					    ao->nataddr16[3], 0, u),
				    ao->nataddr16[4], 0, u),
				    ao->nataddr16[5], 0, u),
				    ao->nataddr16[6], 0, u),
			    ao->nataddr16[7], 0, u);
			break;
		}
		break;
	}
}

uint16_t
nat464_cksum_fixup(uint16_t cksum, uint16_t old, uint16_t new, uint8_t udp)
{
	uint32_t l;

	if (udp && !cksum) {
		return 0;
	}
	l = cksum + old - new;
	l = (l >> 16) + (l & 0xffff);
	l = l & 0xffff;
	if (udp && !l) {
		return 0xffff;
	}
	return (uint16_t)l;
}

/* CLAT46 event handlers */
void
in6_clat46_eventhdlr_callback(struct eventhandler_entry_arg arg0 __unused,
    in6_clat46_evhdlr_code_t in6_clat46_ev_code, pid_t epid, uuid_t euuid)
{
	struct kev_msg ev_msg;
	struct kev_netevent_clat46_data clat46_event_data;

	bzero(&ev_msg, sizeof(ev_msg));
	bzero(&clat46_event_data, sizeof(clat46_event_data));

	ev_msg.vendor_code      = KEV_VENDOR_APPLE;
	ev_msg.kev_class        = KEV_NETWORK_CLASS;
	ev_msg.kev_subclass     = KEV_NETEVENT_SUBCLASS;
	ev_msg.event_code       = KEV_NETEVENT_CLAT46_EVENT;

	bzero(&clat46_event_data, sizeof(clat46_event_data));
	clat46_event_data.clat46_event_code = in6_clat46_ev_code;
	clat46_event_data.epid = epid;
	uuid_copy(clat46_event_data.euuid, euuid);

	ev_msg.dv[0].data_ptr = &clat46_event_data;
	ev_msg.dv[0].data_length = sizeof(clat46_event_data);

	kev_post_msg(&ev_msg);
}

struct in6_clat46_event_nwk_wq_entry {
	struct nwk_wq_entry nwk_wqe;
	struct kev_netevent_clat46_data in6_clat46_ev_arg;
};

static void
in6_clat46_event_callback(struct nwk_wq_entry *nwk_item)
{
	struct in6_clat46_event_nwk_wq_entry *p_ev;

	p_ev = __container_of(nwk_item,
	    struct in6_clat46_event_nwk_wq_entry, nwk_wqe);

	EVENTHANDLER_INVOKE(&in6_clat46_evhdlr_ctxt, in6_clat46_event,
	    p_ev->in6_clat46_ev_arg.clat46_event_code, p_ev->in6_clat46_ev_arg.epid,
	    p_ev->in6_clat46_ev_arg.euuid);

	kfree_type(struct in6_clat46_event_nwk_wq_entry, p_ev);
}

void
in6_clat46_event_enqueue_nwk_wq_entry(in6_clat46_evhdlr_code_t in6_clat46_event_code,
    pid_t epid, uuid_t euuid)
{
	struct in6_clat46_event_nwk_wq_entry *p_ev = NULL;

	p_ev = kalloc_type(struct in6_clat46_event_nwk_wq_entry,
	    Z_WAITOK | Z_ZERO | Z_NOFAIL);

	p_ev->nwk_wqe.func = in6_clat46_event_callback;
	p_ev->in6_clat46_ev_arg.clat46_event_code = in6_clat46_event_code;
	p_ev->in6_clat46_ev_arg.epid = epid;
	uuid_copy(p_ev->in6_clat46_ev_arg.euuid, euuid);

	evhlog(debug, "%s: eventhandler enqueuing event of type=in6_clat46_event event_code=%s",
	    __func__, in6_clat46_evhdlr_code2str(in6_clat46_event_code));

	nwk_wq_enqueue(&p_ev->nwk_wqe);
}

extern const char*
in6_clat46_evhdlr_code2str(enum in6_clat46_evhdlr_code_t code)
{
	switch (code) {
#define CLAT46_CODE_TO_STRING(type) case type: return #type;
		CLAT46_CODE_TO_STRING(IN6_CLAT46_EVENT_V4_FLOW)
		CLAT46_CODE_TO_STRING(IN6_CLAT46_EVENT_V6_ADDR_CONFFAIL)
#undef CLAT46_CODE_TO_STRING
	}
	return "UNKNOWN_IN6_CLAT46_EVHDLR_CODE";
}
