#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);
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;
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 = __DECONST(char *, addrv4);
char *ptr = __DECONST(char *, addr);
if (IN_ZERONET(ntohl(addrv4->s_addr)) || IN_LOOPBACK(ntohl(addrv4->s_addr)) || IN_LINKLOCAL(ntohl(addrv4->s_addr)) || IN_DS_LITE(ntohl(addrv4->s_addr)) || IN_6TO4_RELAY_ANYCAST(ntohl(addrv4->s_addr)) || IN_MULTICAST(ntohl(addrv4->s_addr)) || INADDR_BROADCAST == addrv4->s_addr) { return -1;
}
if (prefix_len == NAT64_PREFIX_LEN_96 &&
IN6_ARE_ADDR_EQUAL(&prefix, &well_known_prefix)) { if (IN_PRIVATE(ntohl(addrv4->s_addr)) || IN_SHARED_ADDRESS_SPACE(ntohl(addrv4->s_addr))) { 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\n", 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;
}
int
nat464_synthesize_ipv4(ifnet_t ifp, const struct in6_addr *addr, struct in_addr *addrv4)
{
struct ipv6_prefix nat64prefixes[NAT64_MAX_NUM_PREFIXES];
int error = 0, i = 0;
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 = __DECONST(void *, addrv4);
char *ptr = __DECONST(void *, addr);
if (memcmp(addr, &prefix, prefix_len) != 0) {
return -1;
}
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\n",
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))));
}
return error;
}
#define PTR_IP(field) ((int32_t)offsetof(struct ip, field))
#define PTR_IP6(field) ((int32_t)offsetof(struct ip6_hdr, field))
int
nat464_translate_icmp(int naf, void *arg)
{
struct icmp *icmp4;
struct icmp6_hdr *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)) {
;
} 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;
icmp6->icmp6_mtu = htonl(mtu);
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;
mtu += 20;
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)) {
;
} 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(mtu);
if (ptr >= 0) {
icmp4->icmp_void = htonl(ptr);
}
break;
}
return 0;
}
int
nat464_translate_icmp_ip(pbuf_t *pbuf, uint32_t off, uint64_t *tot_len, uint32_t *off2,
uint8_t proto2, uint8_t ttl2, uint64_t tot_len2, struct nat464_addr *src,
struct nat464_addr *dst, protocol_family_t af, protocol_family_t naf)
{
struct ip *ip4 = NULL;
struct ip6_hdr *ip6 = NULL;
void *hdr = NULL;
int hlen = 0, olen = 0;
if (af == naf || (af != AF_INET && af != AF_INET6) ||
(naf != AF_INET && naf != AF_INET6)) {
return -1;
}
olen = *off2 - off;
hlen = naf == PF_INET ? sizeof(*ip4) : sizeof(*ip6);
hdr = pbuf_resize_segment(pbuf, off, olen, hlen);
if (hdr == NULL) {
return -1;
}
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(sizeof(*ip4) + tot_len2 - olen);
ip4->ip_id = rfc6864 ? 0 : htons(ip_randomid());
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(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;
}
*off2 += hlen - olen;
*tot_len += hlen - olen;
return 0;
}
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;
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;
}
p_ip6_frag->ip6f_nxt = p_ip6h->ip6_nxt;
p_ip6_frag->ip6f_reserved = 0;
p_ip6_frag->ip6f_offlg = (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;
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;
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(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) {
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);
}
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;
}
int
nat464_translate_46(pbuf_t *pbuf, int off, uint8_t tos,
uint8_t proto, uint8_t ttl, struct in6_addr src_v6,
struct in6_addr dst_v6, uint64_t tot_len)
{
struct ip6_hdr *ip6;
if (pbuf->pb_ifp == NULL) {
return NT_DROP;
}
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;
}
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;
uint32_t hlen = 0, plen = 0;
uint64_t tot_len = 0;
void *nsrc = NULL, *ndst = NULL;
uint8_t *proto = 0;
uint16_t *psum = NULL;
boolean_t do_ones_complement = FALSE;
VERIFY(af != naf);
VERIFY(af == PF_INET || af == PF_INET6);
switch (naf) {
case PF_INET: {
iph = pbuf->pb_data;
hlen = 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 = 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;
}
if (*proto != oproto) {
return NT_DROP;
}
if (only_csum) {
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;
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;
}
}
switch (*proto) {
case IPPROTO_UDP:
case IPPROTO_TCP:
{
if (direction == NT_OUT &&
(*pbuf->pb_csum_flags & CSUM_DELAY_DATA)) {
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) {
return NT_DROP;
}
struct icmp *icmph = NULL;
struct icmp6_hdr *icmp6h = NULL;
uint32_t ip2off = 0, hlen2 = 0, tot_len2 = 0;
icmph = (struct icmp*) pbuf_contig_segment(pbuf, hlen,
ICMP_MINLEN);
if (icmph == NULL) {
return NT_DROP;
}
if (nat464_translate_icmp(PF_INET6, icmph) != 0) {
return NT_DROP;
}
*proto = IPPROTO_ICMPV6;
icmp6h = (struct icmp6_hdr *)(uintptr_t)icmph;
pbuf_copy_back(pbuf, hlen, sizeof(struct icmp6_hdr),
icmp6h);
if (ICMP6_ERRORTYPE(icmp6h->icmp6_type)) {
ip2off = hlen + sizeof(*icmp6h);
struct ip *iph2;
iph2 = (struct ip*) pbuf_contig_segment(pbuf, ip2off,
sizeof(*iph2));
if (iph2 == NULL) {
return NT_DROP;
}
hlen2 = ip2off + (iph2->ip_hl << 2);
tot_len2 = ntohs(iph2->ip_len);
VERIFY(IN_ARE_ADDR_EQUAL(&odst->natv4addr, &iph2->ip_src));
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;
}
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) {
return NT_DROP;
}
struct icmp6_hdr *icmp6h = NULL;
struct icmp *icmph = NULL;
uint32_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;
}
if (nat464_translate_icmp(PF_INET, icmp6h) != 0) {
return NT_DROP;
}
*proto = IPPROTO_ICMP;
icmph = (struct icmp *)(uintptr_t)icmp6h;
pbuf_copy_back(pbuf, hlen, ICMP_MINLEN,
icmph);
if (ICMP_ERRORTYPE(icmph->icmp_type)) {
ip2off = hlen + ICMP_MINLEN;
struct ip6_hdr *iph2;
iph2 = (struct ip6_hdr*) pbuf_contig_segment(pbuf, ip2off,
sizeof(*iph2));
if (iph2 == NULL) {
return NT_DROP;
}
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;
}
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;
}
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;}
case IPPROTO_HOPOPTS:
case IPPROTO_ROUTING:
case IPPROTO_DSTOPTS:
case IPPROTO_AH:
return NT_DROP;
case IPPROTO_FRAGMENT:
VERIFY(FALSE);
case IPPROTO_ESP:
break;
default:
return NT_DROP;
}
done:
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;
}
*pbuf->pb_csum_flags &= ~(CSUM_IP | CSUM_IP_FRAGS | CSUM_DELAY_DATA | CSUM_FRAGMENT);
} else if (direction == NT_IN) {
*pbuf->pb_csum_flags = 0;
#if 0
if (*pbuf->pb_csum_flags & CSUM_PARTIAL) {
(pbuf->pb_mbuf)->m_pkthdr.csum_rx_start -= CLAT46_HDR_EXPANSION_OVERHD;
}
#endif
}
return NT_NAT64;
}
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)
{
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:
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 l;
}
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);
}
static void
in6_clat46_event_callback(void *arg)
{
struct kev_netevent_clat46_data *p_in6_clat46_ev =
(struct kev_netevent_clat46_data *)arg;
EVENTHANDLER_INVOKE(&in6_clat46_evhdlr_ctxt, in6_clat46_event,
p_in6_clat46_ev->clat46_event_code, p_in6_clat46_ev->epid,
p_in6_clat46_ev->euuid);
}
struct in6_clat46_event_nwk_wq_entry {
struct nwk_wq_entry nwk_wqe;
struct kev_netevent_clat46_data in6_clat46_ev_arg;
};
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;
MALLOC(p_ev, struct in6_clat46_event_nwk_wq_entry *,
sizeof(struct in6_clat46_event_nwk_wq_entry),
M_NWKWQ, M_WAITOK | M_ZERO);
p_ev->nwk_wqe.func = in6_clat46_event_callback;
p_ev->nwk_wqe.is_arg_managed = TRUE;
p_ev->nwk_wqe.arg = &p_ev->in6_clat46_ev_arg;
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);
nwk_wq_enqueue((struct nwk_wq_entry*)p_ev);
}