Changeset View
Changeset View
Standalone View
Standalone View
sys/netpfil/ipfw/nat64/nat64_translate.c
/*- | /*- | ||||
* Copyright (c) 2015-2018 Yandex LLC | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD | ||||
* Copyright (c) 2015-2018 Andrey V. Elsukov <ae@FreeBSD.org> | |||||
* All rights reserved. | |||||
* | * | ||||
* Copyright (c) 2015-2019 Yandex LLC | |||||
* Copyright (c) 2015-2019 Andrey V. Elsukov <ae@FreeBSD.org> | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* | * | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | |||||
#include <netinet/ip6.h> | #include <netinet/ip6.h> | ||||
#include <netinet/icmp6.h> | #include <netinet/icmp6.h> | ||||
#include <netinet/ip_icmp.h> | #include <netinet/ip_icmp.h> | ||||
#include <netinet/tcp.h> | #include <netinet/tcp.h> | ||||
#include <netinet/udp.h> | #include <netinet/udp.h> | ||||
#include <netinet6/in6_var.h> | #include <netinet6/in6_var.h> | ||||
#include <netinet6/in6_fib.h> | #include <netinet6/in6_fib.h> | ||||
#include <netinet6/ip6_var.h> | #include <netinet6/ip6_var.h> | ||||
#include <netinet6/ip_fw_nat64.h> | |||||
#include <netpfil/pf/pf.h> | #include <netpfil/pf/pf.h> | ||||
#include <netpfil/ipfw/ip_fw_private.h> | #include <netpfil/ipfw/ip_fw_private.h> | ||||
#include <machine/in_cksum.h> | #include <machine/in_cksum.h> | ||||
#include "ip_fw_nat64.h" | #include "ip_fw_nat64.h" | ||||
#include "nat64_translate.h" | #include "nat64_translate.h" | ||||
▲ Show 20 Lines • Show All 195 Lines • ▼ Show 20 Lines | nat64_check_prefix6(const struct in6_addr *prefix, int length) | ||||
} | } | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
int | int | ||||
nat64_check_private_ip4(const struct nat64_config *cfg, in_addr_t ia) | nat64_check_private_ip4(const struct nat64_config *cfg, in_addr_t ia) | ||||
{ | { | ||||
if (V_nat64_allow_private) | if (cfg->flags & NAT64_ALLOW_PRIVATE) | ||||
return (0); | return (0); | ||||
/* WKPFX must not be used to represent non-global IPv4 addresses */ | /* WKPFX must not be used to represent non-global IPv4 addresses */ | ||||
if (cfg->flags & NAT64_WKPFX) { | if (cfg->flags & NAT64_WKPFX) { | ||||
/* IN_PRIVATE */ | /* IN_PRIVATE */ | ||||
if ((ia & htonl(0xff000000)) == htonl(0x0a000000) || | if ((ia & htonl(0xff000000)) == htonl(0x0a000000) || | ||||
(ia & htonl(0xfff00000)) == htonl(0xac100000) || | (ia & htonl(0xfff00000)) == htonl(0xac100000) || | ||||
(ia & htonl(0xffff0000)) == htonl(0xc0a80000)) | (ia & htonl(0xffff0000)) == htonl(0xc0a80000)) | ||||
Show All 12 Lines | if ((ia & htonl(0xffffff00)) == htonl(0xc0000000) || | ||||
(ia & htonl(0xffffff00)) == htonl(0xc0000200) || | (ia & htonl(0xffffff00)) == htonl(0xc0000200) || | ||||
(ia & htonl(0xfffffe00)) == htonl(0xc6336400) || | (ia & htonl(0xfffffe00)) == htonl(0xc6336400) || | ||||
(ia & htonl(0xffffff00)) == htonl(0xcb007100)) | (ia & htonl(0xffffff00)) == htonl(0xcb007100)) | ||||
return (1); | return (1); | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
/* | |||||
* Embed @ia IPv4 address into @ip6 IPv6 address. | |||||
* Place to embedding determined from prefix length @plen. | |||||
*/ | |||||
void | void | ||||
nat64_embed_ip4(const struct nat64_config *cfg, in_addr_t ia, | nat64_embed_ip4(struct in6_addr *ip6, int plen, in_addr_t ia) | ||||
struct in6_addr *ip6) | |||||
{ | { | ||||
/* assume the prefix6 is properly filled with zeros */ | switch (plen) { | ||||
bcopy(&cfg->prefix6, ip6, sizeof(*ip6)); | |||||
switch (cfg->plen6) { | |||||
case 32: | case 32: | ||||
case 96: | case 96: | ||||
ip6->s6_addr32[cfg->plen6 / 32] = ia; | ip6->s6_addr32[plen / 32] = ia; | ||||
break; | break; | ||||
case 40: | case 40: | ||||
case 48: | case 48: | ||||
case 56: | case 56: | ||||
/* | |||||
* Preserve prefix bits. | |||||
* Since suffix bits should be zero and reserved for future | |||||
* use, we just overwrite the whole word, where they are. | |||||
*/ | |||||
ip6->s6_addr32[1] &= 0xffffffff << (32 - plen % 32); | |||||
#if BYTE_ORDER == BIG_ENDIAN | #if BYTE_ORDER == BIG_ENDIAN | ||||
ip6->s6_addr32[1] = cfg->prefix6.s6_addr32[1] | | ip6->s6_addr32[1] |= ia >> (plen % 32); | ||||
(ia >> (cfg->plen6 % 32)); | ip6->s6_addr32[2] = ia << (24 - plen % 32); | ||||
ip6->s6_addr32[2] = ia << (24 - cfg->plen6 % 32); | |||||
#elif BYTE_ORDER == LITTLE_ENDIAN | #elif BYTE_ORDER == LITTLE_ENDIAN | ||||
ip6->s6_addr32[1] = cfg->prefix6.s6_addr32[1] | | ip6->s6_addr32[1] |= ia << (plen % 32); | ||||
(ia << (cfg->plen6 % 32)); | ip6->s6_addr32[2] = ia >> (24 - plen % 32); | ||||
ip6->s6_addr32[2] = ia >> (24 - cfg->plen6 % 32); | |||||
#endif | #endif | ||||
break; | break; | ||||
case 64: | case 64: | ||||
#if BYTE_ORDER == BIG_ENDIAN | #if BYTE_ORDER == BIG_ENDIAN | ||||
ip6->s6_addr32[2] = ia >> 8; | ip6->s6_addr32[2] = ia >> 8; | ||||
ip6->s6_addr32[3] = ia << 24; | ip6->s6_addr32[3] = ia << 24; | ||||
#elif BYTE_ORDER == LITTLE_ENDIAN | #elif BYTE_ORDER == LITTLE_ENDIAN | ||||
ip6->s6_addr32[2] = ia << 8; | ip6->s6_addr32[2] = ia << 8; | ||||
ip6->s6_addr32[3] = ia >> 24; | ip6->s6_addr32[3] = ia >> 24; | ||||
#endif | #endif | ||||
break; | break; | ||||
default: | default: | ||||
panic("Wrong plen6"); | panic("Wrong plen6"); | ||||
}; | }; | ||||
/* | |||||
* Bits 64 to 71 of the address are reserved for compatibility | |||||
* with the host identifier format defined in the IPv6 addressing | |||||
* architecture [RFC4291]. These bits MUST be set to zero. | |||||
*/ | |||||
ip6->s6_addr8[8] = 0; | ip6->s6_addr8[8] = 0; | ||||
} | } | ||||
in_addr_t | in_addr_t | ||||
nat64_extract_ip4(const struct nat64_config *cfg, const struct in6_addr *ip6) | nat64_extract_ip4(const struct in6_addr *ip6, int plen) | ||||
{ | { | ||||
in_addr_t ia; | in_addr_t ia; | ||||
/* | /* | ||||
* According to RFC 6052 p2.2: | * According to RFC 6052 p2.2: | ||||
* IPv4-embedded IPv6 addresses are composed of a variable-length | * IPv4-embedded IPv6 addresses are composed of a variable-length | ||||
* prefix, the embedded IPv4 address, and a variable length suffix. | * prefix, the embedded IPv4 address, and a variable length suffix. | ||||
* The suffix bits are reserved for future extensions and SHOULD | * The suffix bits are reserved for future extensions and SHOULD | ||||
* be set to zero. | * be set to zero. | ||||
*/ | */ | ||||
switch (cfg->plen6) { | switch (plen) { | ||||
case 32: | case 32: | ||||
if (ip6->s6_addr32[3] != 0 || ip6->s6_addr32[2] != 0) | if (ip6->s6_addr32[3] != 0 || ip6->s6_addr32[2] != 0) | ||||
goto badip6; | goto badip6; | ||||
break; | break; | ||||
case 40: | case 40: | ||||
if (ip6->s6_addr32[3] != 0 || | if (ip6->s6_addr32[3] != 0 || | ||||
(ip6->s6_addr32[2] & htonl(0xff00ffff)) != 0) | (ip6->s6_addr32[2] & htonl(0xff00ffff)) != 0) | ||||
goto badip6; | goto badip6; | ||||
break; | break; | ||||
case 48: | case 48: | ||||
if (ip6->s6_addr32[3] != 0 || | if (ip6->s6_addr32[3] != 0 || | ||||
(ip6->s6_addr32[2] & htonl(0xff0000ff)) != 0) | (ip6->s6_addr32[2] & htonl(0xff0000ff)) != 0) | ||||
goto badip6; | goto badip6; | ||||
break; | break; | ||||
case 56: | case 56: | ||||
if (ip6->s6_addr32[3] != 0 || ip6->s6_addr8[8] != 0) | if (ip6->s6_addr32[3] != 0 || ip6->s6_addr8[8] != 0) | ||||
goto badip6; | goto badip6; | ||||
break; | break; | ||||
case 64: | case 64: | ||||
if (ip6->s6_addr8[8] != 0 || | if (ip6->s6_addr8[8] != 0 || | ||||
(ip6->s6_addr32[3] & htonl(0x00ffffff)) != 0) | (ip6->s6_addr32[3] & htonl(0x00ffffff)) != 0) | ||||
goto badip6; | goto badip6; | ||||
}; | }; | ||||
switch (cfg->plen6) { | switch (plen) { | ||||
case 32: | case 32: | ||||
case 96: | case 96: | ||||
ia = ip6->s6_addr32[cfg->plen6 / 32]; | ia = ip6->s6_addr32[plen / 32]; | ||||
break; | break; | ||||
case 40: | case 40: | ||||
case 48: | case 48: | ||||
case 56: | case 56: | ||||
#if BYTE_ORDER == BIG_ENDIAN | #if BYTE_ORDER == BIG_ENDIAN | ||||
ia = (ip6->s6_addr32[1] << (cfg->plen6 % 32)) | | ia = (ip6->s6_addr32[1] << (plen % 32)) | | ||||
(ip6->s6_addr32[2] >> (24 - cfg->plen6 % 32)); | (ip6->s6_addr32[2] >> (24 - plen % 32)); | ||||
#elif BYTE_ORDER == LITTLE_ENDIAN | #elif BYTE_ORDER == LITTLE_ENDIAN | ||||
ia = (ip6->s6_addr32[1] >> (cfg->plen6 % 32)) | | ia = (ip6->s6_addr32[1] >> (plen % 32)) | | ||||
(ip6->s6_addr32[2] << (24 - cfg->plen6 % 32)); | (ip6->s6_addr32[2] << (24 - plen % 32)); | ||||
#endif | #endif | ||||
break; | break; | ||||
case 64: | case 64: | ||||
#if BYTE_ORDER == BIG_ENDIAN | #if BYTE_ORDER == BIG_ENDIAN | ||||
ia = (ip6->s6_addr32[2] << 8) | (ip6->s6_addr32[3] >> 24); | ia = (ip6->s6_addr32[2] << 8) | (ip6->s6_addr32[3] >> 24); | ||||
#elif BYTE_ORDER == LITTLE_ENDIAN | #elif BYTE_ORDER == LITTLE_ENDIAN | ||||
ia = (ip6->s6_addr32[2] >> 8) | (ip6->s6_addr32[3] << 24); | ia = (ip6->s6_addr32[2] >> 8) | (ip6->s6_addr32[3] << 24); | ||||
#endif | #endif | ||||
break; | break; | ||||
default: | default: | ||||
return (0); | return (0); | ||||
}; | }; | ||||
if (nat64_check_ip4(ia) != 0 || | if (nat64_check_ip4(ia) == 0) | ||||
nat64_check_private_ip4(cfg, ia) != 0) | |||||
goto badip4; | |||||
return (ia); | return (ia); | ||||
badip4: | |||||
DPRINTF(DP_GENERIC | DP_DROPS, | DPRINTF(DP_GENERIC | DP_DROPS, | ||||
"invalid destination address: %08x", ia); | "invalid destination address: %08x", ia); | ||||
return (0); | return (0); | ||||
badip6: | badip6: | ||||
DPRINTF(DP_GENERIC | DP_DROPS, "invalid IPv4-embedded IPv6 address"); | DPRINTF(DP_GENERIC | DP_DROPS, "invalid IPv4-embedded IPv6 address"); | ||||
return (0); | return (0); | ||||
} | } | ||||
Show All 10 Lines | |||||
* HC' = (HC - (~m0 + m0')) - (~m1 + m1') -- m1 is second changed word | * HC' = (HC - (~m0 + m0')) - (~m1 + m1') -- m1 is second changed word | ||||
* HC' = HC - ~m0 - m0' - ~m1 - m1' - ... = | * HC' = HC - ~m0 - m0' - ~m1 - m1' - ... = | ||||
* = HC - sum(~m[i] + m'[i]) | * = HC - sum(~m[i] + m'[i]) | ||||
* | * | ||||
* The function result should be used as follows: | * The function result should be used as follows: | ||||
* IPv6 to IPv4: HC' = cksum_add(HC, result) | * IPv6 to IPv4: HC' = cksum_add(HC, result) | ||||
* IPv4 to IPv6: HC' = cksum_add(HC, ~result) | * IPv4 to IPv6: HC' = cksum_add(HC, ~result) | ||||
*/ | */ | ||||
static NAT64NOINLINE uint16_t | static uint16_t | ||||
nat64_cksum_convert(struct ip6_hdr *ip6, struct ip *ip) | nat64_cksum_convert(struct ip6_hdr *ip6, struct ip *ip) | ||||
{ | { | ||||
uint32_t sum; | uint32_t sum; | ||||
uint16_t *p; | uint16_t *p; | ||||
sum = ~ip->ip_src.s_addr >> 16; | sum = ~ip->ip_src.s_addr >> 16; | ||||
sum += ~ip->ip_src.s_addr & 0xffff; | sum += ~ip->ip_src.s_addr & 0xffff; | ||||
sum += ~ip->ip_dst.s_addr >> 16; | sum += ~ip->ip_dst.s_addr >> 16; | ||||
sum += ~ip->ip_dst.s_addr & 0xffff; | sum += ~ip->ip_dst.s_addr & 0xffff; | ||||
for (p = (uint16_t *)&ip6->ip6_src; | for (p = (uint16_t *)&ip6->ip6_src; | ||||
p < (uint16_t *)(&ip6->ip6_src + 2); p++) | p < (uint16_t *)(&ip6->ip6_src + 2); p++) | ||||
sum += *p; | sum += *p; | ||||
while (sum >> 16) | while (sum >> 16) | ||||
sum = (sum & 0xffff) + (sum >> 16); | sum = (sum & 0xffff) + (sum >> 16); | ||||
return (sum); | return (sum); | ||||
} | } | ||||
static NAT64NOINLINE void | static void | ||||
nat64_init_ip4hdr(const struct ip6_hdr *ip6, const struct ip6_frag *frag, | nat64_init_ip4hdr(const struct ip6_hdr *ip6, const struct ip6_frag *frag, | ||||
uint16_t plen, uint8_t proto, struct ip *ip) | uint16_t plen, uint8_t proto, struct ip *ip) | ||||
{ | { | ||||
/* assume addresses are already initialized */ | /* assume addresses are already initialized */ | ||||
ip->ip_v = IPVERSION; | ip->ip_v = IPVERSION; | ||||
ip->ip_hl = sizeof(*ip) >> 2; | ip->ip_hl = sizeof(*ip) >> 2; | ||||
ip->ip_tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; | ip->ip_tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; | ||||
▲ Show 20 Lines • Show All 553 Lines • ▼ Show 20 Lines | #endif | ||||
m_move_pkthdr(n, m); | m_move_pkthdr(n, m); | ||||
M_ALIGN(n, offset + plen + max_hdr); | M_ALIGN(n, offset + plen + max_hdr); | ||||
n->m_len = n->m_pkthdr.len = offset + plen; | n->m_len = n->m_pkthdr.len = offset + plen; | ||||
/* Adjust ip6_plen in outer header */ | /* Adjust ip6_plen in outer header */ | ||||
ip6->ip6_plen = htons(plen); | ip6->ip6_plen = htons(plen); | ||||
/* Construct new inner IPv6 header */ | /* Construct new inner IPv6 header */ | ||||
eip6 = mtodo(n, offset + sizeof(struct icmp6_hdr)); | eip6 = mtodo(n, offset + sizeof(struct icmp6_hdr)); | ||||
eip6->ip6_src = ip6->ip6_dst; | eip6->ip6_src = ip6->ip6_dst; | ||||
/* Use the fact that we have single /96 prefix for IPv4 map */ | |||||
/* Use the same prefix that we have in outer header */ | |||||
eip6->ip6_dst = ip6->ip6_src; | eip6->ip6_dst = ip6->ip6_src; | ||||
nat64_embed_ip4(cfg, ip.ip_dst.s_addr, &eip6->ip6_dst); | MPASS(cfg->flags & NAT64_PLATPFX); | ||||
nat64_embed_ip4(&eip6->ip6_dst, cfg->plat_plen, ip.ip_dst.s_addr); | |||||
eip6->ip6_flow = htonl(ip.ip_tos << 20); | eip6->ip6_flow = htonl(ip.ip_tos << 20); | ||||
eip6->ip6_vfc |= IPV6_VERSION; | eip6->ip6_vfc |= IPV6_VERSION; | ||||
eip6->ip6_hlim = ip.ip_ttl; | eip6->ip6_hlim = ip.ip_ttl; | ||||
eip6->ip6_plen = htons(ntohs(ip.ip_len) - (ip.ip_hl << 2)); | eip6->ip6_plen = htons(ntohs(ip.ip_len) - (ip.ip_hl << 2)); | ||||
eip6->ip6_nxt = (ip.ip_p == IPPROTO_ICMP) ? IPPROTO_ICMPV6: ip.ip_p; | eip6->ip6_nxt = (ip.ip_p == IPPROTO_ICMP) ? IPPROTO_ICMPV6: ip.ip_p; | ||||
m_copydata(m, hlen, len, (char *)(eip6 + 1)); | m_copydata(m, hlen, len, (char *)(eip6 + 1)); | ||||
/* | /* | ||||
▲ Show 20 Lines • Show All 406 Lines • ▼ Show 20 Lines | nat64_handle_icmp6(struct mbuf *m, int hlen, uint32_t aaddr, uint16_t aport, | ||||
/* Check if outer dst is the same as inner src */ | /* Check if outer dst is the same as inner src */ | ||||
if (!IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &ip6i->ip6_src)) { | if (!IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &ip6i->ip6_src)) { | ||||
DPRINTF(DP_DROPS, "Inner src doesn't match outer dst"); | DPRINTF(DP_DROPS, "Inner src doesn't match outer dst"); | ||||
goto fail; | goto fail; | ||||
} | } | ||||
/* Now we need to make a fake IPv4 packet to generate ICMP message */ | /* Now we need to make a fake IPv4 packet to generate ICMP message */ | ||||
ip.ip_dst.s_addr = aaddr; | ip.ip_dst.s_addr = aaddr; | ||||
ip.ip_src.s_addr = nat64_extract_ip4(cfg, &ip6i->ip6_src); | ip.ip_src.s_addr = nat64_extract_ip4(&ip6i->ip6_src, cfg->plat_plen); | ||||
if (ip.ip_src.s_addr == 0) | |||||
goto fail; | |||||
/* XXX: Make fake ulp header */ | /* XXX: Make fake ulp header */ | ||||
if (V_nat64out == &nat64_direct) /* init_ip4hdr will decrement it */ | if (V_nat64out == &nat64_direct) /* init_ip4hdr will decrement it */ | ||||
ip6i->ip6_hlim += IPV6_HLIMDEC; | ip6i->ip6_hlim += IPV6_HLIMDEC; | ||||
nat64_init_ip4hdr(ip6i, ip6f, plen, proto, &ip); | nat64_init_ip4hdr(ip6i, ip6f, plen, proto, &ip); | ||||
m_adj(m, hlen - sizeof(struct ip)); | m_adj(m, hlen - sizeof(struct ip)); | ||||
bcopy(&ip, mtod(m, void *), sizeof(ip)); | bcopy(&ip, mtod(m, void *), sizeof(ip)); | ||||
nat64_icmp_reflect(m, type, code, (uint16_t)mtu, &cfg->stats, | nat64_icmp_reflect(m, type, code, (uint16_t)mtu, &cfg->stats, | ||||
logdata); | logdata); | ||||
Show All 36 Lines | nat64_do_handle_ip6(struct mbuf *m, uint32_t aaddr, uint16_t aport, | ||||
ip.ip_src.s_addr = aaddr; | ip.ip_src.s_addr = aaddr; | ||||
if (nat64_check_ip4(ip.ip_src.s_addr) != 0) { | if (nat64_check_ip4(ip.ip_src.s_addr) != 0) { | ||||
DPRINTF(DP_GENERIC | DP_DROPS, "invalid source address: %08x", | DPRINTF(DP_GENERIC | DP_DROPS, "invalid source address: %08x", | ||||
ip.ip_src.s_addr); | ip.ip_src.s_addr); | ||||
NAT64STAT_INC(&cfg->stats, dropped); | NAT64STAT_INC(&cfg->stats, dropped); | ||||
return (NAT64MFREE); | return (NAT64MFREE); | ||||
} | } | ||||
ip.ip_dst.s_addr = nat64_extract_ip4(cfg, &ip6->ip6_dst); | ip.ip_dst.s_addr = nat64_extract_ip4(&ip6->ip6_dst, cfg->plat_plen); | ||||
if (ip.ip_dst.s_addr == 0) { | if (ip.ip_dst.s_addr == 0) { | ||||
NAT64STAT_INC(&cfg->stats, dropped); | NAT64STAT_INC(&cfg->stats, dropped); | ||||
return (NAT64MFREE); | return (NAT64MFREE); | ||||
} | } | ||||
if (ip6->ip6_hlim <= IPV6_HLIMDEC) { | if (ip6->ip6_hlim <= IPV6_HLIMDEC) { | ||||
nat64_icmp6_reflect(m, ICMP6_TIME_EXCEEDED, | nat64_icmp6_reflect(m, ICMP6_TIME_EXCEEDED, | ||||
ICMP6_TIME_EXCEED_TRANSIT, 0, &cfg->stats, logdata); | ICMP6_TIME_EXCEED_TRANSIT, 0, &cfg->stats, logdata); | ||||
▲ Show 20 Lines • Show All 117 Lines • Show Last 20 Lines |