diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 4f32ea9..b0bd265 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -151,6 +151,7 @@ SUBDIR= \ ${_ipfilter} \ ${_ipfw} \ ipfw_nat \ + ${_ipfw_nat64} \ ${_ipmi} \ ip6_mroute_mod \ ip_mroute_mod \ @@ -415,6 +416,9 @@ _ipfilter= ipfilter .if ${MK_INET_SUPPORT} != "no" || defined(ALL_MODULES) _ipfw= ipfw +.if ${MK_INET6_SUPPORT} != "no" || defined(ALL_MODULES) +_ipfw_nat64= ipfw_nat64 +.endif .endif .if ${MK_NETGRAPH} != "no" || defined(ALL_MODULES) diff --git a/sys/modules/ipfw_nat64/Makefile b/sys/modules/ipfw_nat64/Makefile new file mode 100644 index 0000000..1af1719 --- /dev/null +++ b/sys/modules/ipfw_nat64/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../netpfil/ipfw + +KMOD= ipfw_nat64 +SRCS= ip_fw_nat64.c + +.include diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index eed104b..2a17c21 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -106,6 +106,10 @@ typedef struct _ip_fw3_opheader { #define IP_FW_DUMP_SOPTCODES 116 /* Dump available sopts/versions */ +#define IP_FW_NAT64_CONFIG 117 /* Create/modify NAT64 instance */ +#define IP_FW_NAT64_LIST 118 /* List all NAT64 instances */ +#define IP_FW_NAT64_STATUS 119 /* Get info about NAT64 instance */ + /* * The kernel representation of ipfw rules is made of a list of * 'instructions' (for all practical purposes equivalent to BPF @@ -253,6 +257,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */ O_SETDSCP, /* arg1=DSCP value */ O_IP_FLOW_LOOKUP, /* arg1=table number, u32=value */ + O_NAT64, /* u32 */ + O_LAST_OPCODE /* not an opcode! */ }; @@ -595,6 +601,15 @@ struct ip_fw_bcounter { uint64_t bcnt; /* Byte counter */ }; +struct ip_fw_nat64_stats { + uint64_t opcnt64; /* 6to4 of packets translated */ + uint64_t opcnt46; /* 4to6 of packets translated */ + uint64_t ofrags; /* number of fragments generated */ + uint64_t ifrags; /* number of fragments received */ + uint64_t oerrors; /* number of output errors */ + uint64_t noroute4; + uint64_t noroute6; +}; #ifndef _KERNEL /* diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index 438785c..d05e7c6 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -873,6 +873,7 @@ jump_linear(struct ip_fw_chain *chain, struct ip_fw *f, int num, * IP_FW_NETGRAPH into netgraph, cookie args->cookie * args->rule contains the matching rule, * args->rule.info has additional information. + * IP_FW_CONSUMED the packet must be dropped without m_freem(). * */ int @@ -2519,6 +2520,17 @@ do { \ done = 1; /* exit outer loop */ break; } + case O_NAT64: + l = 0; /* in any case exit inner loop */ +#ifdef INET6 + if (IPFW_NAT64_LOADED) + break; + set_match(args, f_pos, chain); + retval = ipfw_nat64_ptr(args, cmd, m); + if (retval != 0) + done = 1; +#endif + break; default: panic("-- unknown opcode %d\n", cmd->opcode); diff --git a/sys/netpfil/ipfw/ip_fw_nat64.c b/sys/netpfil/ipfw/ip_fw_nat64.c new file mode 100644 index 0000000..5279a58 --- /dev/null +++ b/sys/netpfil/ipfw/ip_fw_nat64.c @@ -0,0 +1,1223 @@ +/*- + * Copyright (c) 2015 Yandex LLC + * Copyright (c) 2015 Andrey V. Elsukov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 AUTHOR ``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 AUTHOR 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IPFW_NAT64_VERSION 1 + +#define DPRINTF(fmt, ...) \ + if (nat64_debug != 0) \ + printf("NAT64: %s: " fmt "\n", __func__, ## __VA_ARGS__) + +static int nat64_debug = 0; +SYSCTL_DECL(_net_inet_ip_fw); +SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, nat64_debug, CTLFLAG_RW, + &nat64_debug, 0, "Debug level for NAT64 module"); + +/* RFC 6052 p2.2: The prefixes can only have one of the following length: */ +#define IN6_PREFIX64_PLEN_IS_VALID(plen) ((plen) == 32 ||\ + (plen) == 40 || (plen) == 48 || (plen) == 56 || (plen) == 64 ||\ + (plen) == 96) + +/* Ensure that the bits 64 to 71 are set to zero. */ +#define IN6_PREFIX64_IS_VALID(ia) ((ia)->s6_addr8[8] == 0) + +/* Well-known prefix 64:ff9b::/96 */ +#define IPV6_ADDR_INT32_WKPFX htonl(0x64ff9b) +#define IN6_IS_ADDR_WKPFX(a) \ + ((a)->s6_addr32[0] == IPV6_ADDR_INT32_WKPFX && \ + (a)->s6_addr32[1] == 0 && (a)->s6_addr32[2] == 0) + +#define TCP(p) ((struct tcphdr *)(p)) +#define UDP(p) ((struct udphdr *)(p)) +#define ICMP(p) ((struct icmphdr *)(p)) +#define ICMP6(p) ((struct icmp6_hdr *)(p)) + +#define NAT64STATS (sizeof(struct ip_fw_nat64_stats) / sizeof(uint64_t)) +#define NAT64STAT_ADD(cfg, f, v) \ + counter_u64_add((cfg)->stats[ \ + offsetof(struct ip_fw_nat64_stats, f) / sizeof(uint64_t)], (v)) +#define NAT64STAT_INC(cfg, f) NAT64STAT_ADD(cfg, f, 1) + +struct nat64_cfg { + struct named_object no; + + uint16_t map64; /* table with 6to4 mapping */ + uint16_t map46; /* table with 4to6 mapping */ + + struct in6_addr prefix; /* IPv6 prefix */ + uint8_t plen; /* prefix length */ + uint8_t flags; /* flags for internal use */ +#define NAT64_WKPFX 0x0001 + char name[64]; + counter_u64_t stats[NAT64STATS]; +}; + +static struct nat64_cfg nat64_instance; /* XXX: one instance just for testing */ + +#define NAT64_LOOKUP(chain, cmd) nat64_lookup_config((chain), (cmd)) +static struct nat64_cfg* +nat64_lookup_config(struct ip_fw_chain *chain, ipfw_insn_u32 *cmd) +{ + + return (&nat64_instance); /* XXX: real lookup + VNET */ +} + +static int +nat64_init_config(struct nat64_cfg *cfg) +{ + + COUNTER_ARRAY_ALLOC(cfg->stats, NAT64STATS, M_WAITOK); + return (0); +} + +static void +nat64_free_config(struct nat64_cfg *cfg) +{ + + COUNTER_ARRAY_FREE(cfg->stats, NAT64STATS); +} + +static int +nat64_config(struct ip_fw_chain *chain, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + + return (EOPNOTSUPP); +} +static int +nat64_list(struct ip_fw_chain *chain, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + + return (EOPNOTSUPP); +} + +static int +nat64_status(struct ip_fw_chain *chain, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + + return (EOPNOTSUPP); +} + +static struct ipfw_sopt_handler scodes[] = { + { IP_FW_NAT64_CONFIG, 0, HDIR_BOTH, nat64_config }, + { IP_FW_NAT64_LIST, 0, HDIR_GET, nat64_list }, + { IP_FW_NAT64_STATUS, 0, HDIR_GET, nat64_status }, +}; + +static int +nat64_check_ip6(struct nat64_cfg *cfg, struct in6_addr *addr) +{ + + if (addr->s6_addr8[0] == 0 || /* 0000::/8 Reserved by IETF */ + IN6_IS_ADDR_MULTICAST(addr) || IN6_IS_ADDR_LINKLOCAL(addr)) + return (1); + return (0); +} + +static int +nat64_check_private_ip4(struct nat64_cfg *cfg, in_addr_t ia) +{ + + /* WKPFX must not be used to represent non-global IPv4 addresses */ + if (cfg->flags & NAT64_WKPFX) { + /* IN_PRIVATE */ + if ((ia & htonl(0xff000000)) == htonl(0x0a000000) || + (ia & htonl(0xfff00000)) == htonl(0xac100000) || + (ia & htonl(0xffff0000)) == htonl(0xc0a80000)) + return (1); + /* + * RFC 5735: + * 192.0.0.0/24 - reserved for IETF protocol assignments + * 192.88.99.0/24 - for use as 6to4 relay anycast addresses + * 198.18.0.0/15 - for use in benchmark tests + * 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 - for use + * in documentation and example code + */ + if ((ia & htonl(0xffffff00)) == htonl(0xc0000000) || + (ia & htonl(0xffffff00)) == htonl(0xc0586300) || + (ia & htonl(0xfffffe00)) == htonl(0xc6120000) || + (ia & htonl(0xffffff00)) == htonl(0xc0000200) || + (ia & htonl(0xfffffe00)) == htonl(0xc6336400) || + (ia & htonl(0xffffff00)) == htonl(0xcb007100)) + return (1); + } + return (0); +} + +static int +nat64_check_ip4(struct nat64_cfg *cfg, in_addr_t ia) +{ + + /* IN_LOOPBACK */ + if ((ia & htonl(0xff000000)) == htonl(0x7f000000)) + return (1); + /* IN_LINKLOCAL */ + if ((ia & htonl(0xffff0000)) == htonl(0xa9fe0000)) + return (1); + /* IN_MULTICAST & IN_EXPERIMENTAL */ + if ((ia & htonl(0xe0000000)) == htonl(0xe0000000)) + return (1); + return (0); +} + +static int +nat64_embed_ip4(struct nat64_cfg *cfg, in_addr_t ia, struct in6_addr *ip6) +{ + + /* assume the prefix is properly filled with zeros */ + bcopy(&cfg->prefix, ip6, sizeof(*ip6)); + switch (cfg->plen) { + case 32: + case 96: + ip6->s6_addr32[cfg->plen / 32] = ia; + break; + case 40: + case 48: + case 56: +#if BYTE_ORDER == BIG_ENDIAN + ip6->s6_addr32[1] = cfg->prefix.s6_addr32[1] | + (ia >> (cfg->plen % 32)); + ip6->s6_addr32[2] = ia << (24 - cfg->plen % 32); +#elif BYTE_ORDER == LITTLE_ENDIAN + ip6->s6_addr32[1] = cfg->prefix.s6_addr32[1] | + (ia << (cfg->plen % 32)); + ip6->s6_addr32[2] = ia >> (24 - cfg->plen % 32); +#endif + break; + case 64: +#if BYTE_ORDER == BIG_ENDIAN + ip6->s6_addr32[2] = ia >> 8; + ip6->s6_addr32[3] = ia << 24; +#elif BYTE_ORDER == LITTLE_ENDIAN + ip6->s6_addr32[2] = ia << 8; + ip6->s6_addr32[3] = ia >> 24; +#endif + break; + default: + return (0); + }; + ip6->s6_addr8[8] = 0; + return (1); +} + +static in_addr_t +nat64_extract_ip4(struct nat64_cfg *cfg, struct in6_addr *ip6) +{ + in_addr_t ia; + + /* + * According to RFC 6052 p2.2: + * IPv4-embedded IPv6 addresses are composed of a variable-length + * prefix, the embedded IPv4 address, and a variable length suffix. + * The suffix bits are reserved for future extensions and SHOULD + * be set to zero. + */ + switch (cfg->plen) { + case 32: + if (ip6->s6_addr32[3] != 0 || ip6->s6_addr32[2] != 0) + goto badip6; + break; + case 40: + if (ip6->s6_addr32[3] != 0 || + (ip6->s6_addr32[2] & htonl(0xff00ffff)) != 0) + goto badip6; + break; + case 48: + if (ip6->s6_addr32[3] != 0 || + (ip6->s6_addr32[2] & htonl(0xff0000ff)) != 0) + goto badip6; + break; + case 56: + if (ip6->s6_addr32[3] != 0 || ip6->s6_addr8[8] != 0) + goto badip6; + break; + case 64: + if (ip6->s6_addr8[8] != 0 || + (ip6->s6_addr32[3] & htonl(0x00ffffff)) != 0) + goto badip6; + }; + switch (cfg->plen) { + case 32: + case 96: + ia = ip6->s6_addr32[cfg->plen / 32]; + break; + case 40: + case 48: + case 56: +#if BYTE_ORDER == BIG_ENDIAN + ia = (ip6->s6_addr32[1] << (cfg->plen % 32)) | + (ip6->s6_addr32[2] >> (24 - cfg->plen % 32)); +#elif BYTE_ORDER == LITTLE_ENDIAN + ia = (ip6->s6_addr32[1] >> (cfg->plen % 32)) | + (ip6->s6_addr32[2] << (24 - cfg->plen % 32)); +#endif + break; + case 64: +#if BYTE_ORDER == BIG_ENDIAN + ia = (ip6->s6_addr32[2] << 8) | (ip6->s6_addr32[3] >> 24); +#elif BYTE_ORDER == LITTLE_ENDIAN + ia = (ip6->s6_addr32[2] >> 8) | (ip6->s6_addr32[3] << 24); +#endif + break; + default: + return (0); + }; + if (nat64_check_ip4(cfg, ia) != 0 || + nat64_check_private_ip4(cfg, ia) != 0) + goto badip4; + + return (ia); +badip4: + DPRINTF("invalid destination address: %08x", ia); + return (0); +badip6: + DPRINTF("invalid IPv4-embedded IPv6 address"); + return (0); +} + +static uint16_t +cksum_add(uint16_t sum, uint16_t a) +{ + uint16_t res; + + res = sum + a; + return (res + (res < a)); +} + +static uint16_t +cksum_adjust(uint16_t oldsum, uint16_t old, uint16_t new) +{ + + return (~cksum_add(cksum_add(~oldsum, ~old), new)); +} + +/* + * According to RFC 1624 the equation for incremental checksum update is: + * HC' = ~(~HC + ~m + m') -- [Eqn. 3] + * HC' = HC - ~m - m' -- [Eqn. 4] + * So, when we are replacing IPv4 addresses to IPv6, we + * can assume, that new bytes previously were zeros, and vise versa - + * when we replacing IPv6 addresses to IPv4, now unused bytes become + * zeros. The payload length in pseudo header has bigger size, but one + * half of it should be zero. Using the equation 4 we get: + * HC' = HC - (~m0 + m0') -- m0 is first changed word + * HC' = (HC - (~m0 + m0')) - (~m1 + m1') -- m1 is second changed word + * HC' = HC - ~m0 - m0' - ~m1 - m1' - ... = + * = HC - sum(~m[i] + m'[i]) + * + * The function result should be used as follows: + * IPv6 to IPv4: HC' = cksum_add(HC, result) + * IPv4 to IPv6: HC' = cksum_add(HC, ~result) + */ +static uint16_t +nat64_cksum_convert(const struct ip6_hdr *ip6, const struct ip *ip) +{ + uint32_t sum; + uint16_t *p; + + sum = ~ip->ip_src.s_addr >> 16; + sum += ~ip->ip_src.s_addr & 0xffff; + sum += ~ip->ip_dst.s_addr >> 16; + sum += ~ip->ip_dst.s_addr & 0xffff; + + for (p = (uint16_t *)&ip6->ip6_src; + p < (uint16_t *)(&ip6->ip6_src + 2); p++) + sum += *p; + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + return (sum); +} + +static void +nat64_init_ip4hdr(const struct ip6_hdr *ip6, const struct ip6_frag *frag, + uint16_t plen, uint8_t proto, struct ip *ip) +{ + + /* assume addresses are already initialized */ + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(*ip) >> 2; + ip->ip_tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; + ip->ip_len = htons(sizeof(*ip) + plen); + ip->ip_ttl = ip6->ip6_hlim - IPV6_HLIMDEC; + ip->ip_sum = 0; + ip->ip_p = (proto == IPPROTO_ICMPV6) ? IPPROTO_ICMP: proto; + ip->ip_id = ip_newid(); + if (frag != NULL) { + ip->ip_off = htons(ntohs(frag->ip6f_offlg) >> 3); + if (frag->ip6f_offlg & IP6F_MORE_FRAG) + ip->ip_off |= htons(IP_MF); + } else { + ip->ip_off = htons(IP_DF); + } + ip->ip_sum = in_cksum_hdr(ip); +} + +static void +nat64_init_ip6hdr(const struct ip *ip, uint16_t plen, uint8_t proto, + struct ip6_hdr *ip6) +{ + + /* assume addresses are already initialized */ + ip6->ip6_vfc = IPV6_VERSION; + ip6->ip6_flow |= htonl(ip->ip_tos << 20); + ip6->ip6_hlim = ip->ip_ttl - IPTTLDEC; + ip6->ip6_plen = htons(plen); + ip6->ip6_nxt = proto; +} + +#define FRAGSZ(mtu) ((mtu) - sizeof(struct ip6_hdr) - sizeof(struct ip6_frag)) +static int +nat64_fragment6(struct nat64_cfg *cfg, struct ip6_hdr *ip6, struct mbufq *mq, + struct mbuf *m, uint32_t mtu, uint16_t ip_id, uint16_t ip_off) +{ + struct ip6_frag ip6f; + struct mbuf *n; + uint16_t hlen, len, offset; + int plen; + + plen = ntohs(ip6->ip6_plen); + hlen = sizeof(struct ip6_hdr); + + /* Fragmentation isn't needed */ + if (ip_off == 0 && plen <= mtu - hlen) { + M_PREPEND(m, hlen, M_NOWAIT); + if (m == NULL) + return (ENOMEM); + bcopy(ip6, mtod(m, void *), hlen); + if (mbufq_enqueue(mq, m) != 0) { + m_freem(m); + return (ENOBUFS); + } + } + + hlen += sizeof(struct ip6_frag); + ip6f.ip6f_reserved = 0; + ip6f.ip6f_nxt = ip6->ip6_nxt; + ip6->ip6_nxt = IPPROTO_FRAGMENT; + if (ip_off != 0) { + /* + * We have got an IPv4 fragment. + * Use offset value and ip_id from original fragment. + */ + ip6f.ip6f_ident = htonl(ntohs(ip_id)); + offset = (ntohs(ip_off) & IP_OFFMASK) << 3; + NAT64STAT_INC(cfg, ifrags); + } else { + /* The packet size exceeds interface MTU */ + ip6f.ip6f_ident = htonl(ip6_randomid()); + offset = 0; /* First fragment*/ + } + while (plen > 0 && m != NULL) { + n = NULL; + len = FRAGSZ(mtu) & ~7; + if (len > plen) + len = plen; + ip6->ip6_plen = htons(len + sizeof(ip6f)); + ip6f.ip6f_offlg = ntohs(offset); + if (len < plen || (ip_off & htons(IP_MF)) != 0) + ip6f.ip6f_offlg |= IP6F_MORE_FRAG; + offset += len; + plen -= len; + if (plen > 0) { + n = m_split(m, len, M_NOWAIT); + if (n == NULL) + goto fail; + } + M_PREPEND(m, hlen, M_NOWAIT); + if (m == NULL) + goto fail; + bcopy(ip6, mtod(m, void *), sizeof(struct ip6_hdr)); + bcopy(&ip6f, mtodo(m, sizeof(struct ip6_hdr)), + sizeof(struct ip6_frag)); + if (mbufq_enqueue(mq, m) != 0) + goto fail; + m = n; + } + NAT64STAT_ADD(cfg, ofrags, mbufq_len(mq)); + return (0); +fail: + if (m != NULL) + m_freem(m); + if (n != NULL) + m_freem(n); + mbufq_drain(mq); + return (ENOMEM); +} + +static struct sockaddr* +nat64_find_route6(struct route_in6 *ro, struct in6_addr *dest, struct mbuf *m) +{ + struct sockaddr_in6 *dst; + struct rtentry *rt; + + bzero(ro, sizeof(*ro)); + dst = (struct sockaddr_in6 *)&ro->ro_dst; + dst->sin6_family = AF_INET6; + dst->sin6_len = sizeof(*dst); + dst->sin6_addr = *dest; + in6_rtalloc_nolock(ro, M_GETFIB(m)); + rt = ro->ro_rt; + if (rt && (rt->rt_flags & RTF_UP) && + (rt->rt_ifp->if_flags & IFF_UP) && + (rt->rt_ifp->if_drv_flags & IFF_DRV_RUNNING)) { + if (rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in6 *)rt->rt_gateway; + } else + return (NULL); + if (((rt->rt_flags & RTF_REJECT) && + (rt->rt_rmx.rmx_expire == 0 || + time_uptime < rt->rt_rmx.rmx_expire)) || + rt->rt_ifp->if_link_state == LINK_STATE_DOWN) + return (NULL); + return ((struct sockaddr *)dst); +} + +#define NAT64_ICMP6_PLEN 64 +static void +nat64_icmp6_reflect(struct mbuf *m, uint8_t type, uint8_t code, uint32_t mtu) +{ + char buf[NAT64_ICMP6_PLEN]; + struct route_in6 ro; + struct icmp6_hdr *icmp6; + struct ip6_hdr *ip6, *oip6; + struct sockaddr *dst; + int len, plen; + + ip6 = mtod(m, struct ip6_hdr *); + if (ip6->ip6_nxt == IPPROTO_ICMPV6) + goto freeit; + /* + if (icmp6_ratelimit(&ip6->ip6_src, type, code)) + goto freeit; + */ + switch (type) { + case ICMP6_DST_UNREACH: + case ICMP6_PACKET_TOO_BIG: + case ICMP6_TIME_EXCEEDED: + case ICMP6_PARAM_PROB: + break; + default: + goto freeit; + } + len = (m->m_pkthdr.len > NAT64_ICMP6_PLEN) ? NAT64_ICMP6_PLEN: + m->m_pkthdr.len; + plen = len + sizeof(struct icmp6_hdr); + m_copydata(m, 0, len, buf); + m->m_pkthdr.len = sizeof(struct ip6_hdr) + plen; + m = m_pullup(m, sizeof(struct ip6_hdr) + plen); + if (m == NULL) + return; + ip6 = (struct ip6_hdr *)buf; + oip6 = mtod(m, struct ip6_hdr *); + oip6->ip6_src = ip6->ip6_dst; + oip6->ip6_dst = ip6->ip6_src; + oip6->ip6_nxt = IPPROTO_ICMPV6; + oip6->ip6_flow = 0; + oip6->ip6_vfc |= IPV6_VERSION; + oip6->ip6_hlim = V_ip6_defhlim; + oip6->ip6_plen = htons(plen); + + icmp6 = mtodo(m, sizeof(struct ip6_hdr)); + icmp6->icmp6_cksum = 0; + icmp6->icmp6_type = type; + icmp6->icmp6_code = code; + icmp6->icmp6_mtu = htonl(mtu); + + bcopy(buf, mtodo(m, sizeof(struct ip6_hdr) + + sizeof(struct icmp6_hdr)), len); + icmp6->icmp6_cksum = in6_cksum(m, IPPROTO_ICMPV6, + sizeof(struct ip6_hdr), plen); + dst = nat64_find_route6(&ro, &oip6->ip6_dst, m); + if (dst == NULL) + goto freeit; + (*ro.ro_rt->rt_ifp->if_output)(ro.ro_rt->rt_ifp, m, dst, + (struct route *)&ro); + return; +freeit: + m_freem(m); +} + +static int +nat64_icmp6_error(struct ip_fw_chain *chain, struct nat64_cfg *cfg, + struct mbuf *m, int offset, int len) +{ +#if 0 + struct icmp6_hdr *icmp6; + struct ip6_hdr *eip6; + + if (len < sizeof(struct ip6_hdr)) + return (IP_FW_DENY); + /* XXX: RFC 4884, RFC 5837 */ + icmp6 = mtodo(m, offset); +#endif + + return (IP_FW_DENY); +} + +static struct sockaddr* +nat64_find_route4(struct route *ro, in_addr_t dest, struct mbuf *m) +{ + struct sockaddr_in *dst; + struct rtentry *rt; + + bzero(ro, sizeof(*ro)); + dst = (struct sockaddr_in *)&ro->ro_dst; + dst->sin_family = AF_INET; + dst->sin_len = sizeof(*dst); + dst->sin_addr.s_addr = dest; + rtalloc_fib_nolock(ro, 0, M_GETFIB(m)); + rt = ro->ro_rt; + if (rt && (rt->rt_flags & RTF_UP) && + (rt->rt_ifp->if_flags & IFF_UP) && + (rt->rt_ifp->if_drv_flags & IFF_DRV_RUNNING)) { + if (rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in *)rt->rt_gateway; + } else + return (NULL); + if (((rt->rt_flags & RTF_REJECT) && + (rt->rt_rmx.rmx_expire == 0 || + time_uptime < rt->rt_rmx.rmx_expire)) || + rt->rt_ifp->if_link_state == LINK_STATE_DOWN) + return (NULL); + return ((struct sockaddr *)dst); +} + +#define NAT64_ICMP_PLEN 64 +static void +nat64_icmp_reflect(struct mbuf *m, uint8_t type, uint8_t code, uint16_t mtu) +{ + char buf[NAT64_ICMP_PLEN]; + struct route ro; + struct icmp *icmp; + struct ip *ip, *oip; + struct sockaddr *dst; + int len, plen; + + ip = mtod(m, struct ip *); + if (ip->ip_p == IPPROTO_ICMP || (ip->ip_off & ~ntohs(IP_MF|IP_DF))) + goto freeit; + switch (type) { + case ICMP_UNREACH: + case ICMP_TIMXCEED: + case ICMP_PARAMPROB: + break; + default: + goto freeit; + } + len = (m->m_pkthdr.len > NAT64_ICMP_PLEN) ? (ip->ip_hl << 2) + 8: + m->m_pkthdr.len; + plen = len + sizeof(struct icmphdr) + sizeof(uint32_t); + m_copydata(m, 0, len, buf); + m->m_pkthdr.len = sizeof(struct ip) + plen; + m = m_pullup(m, sizeof(struct ip) + plen); + if (m == NULL) + return; + ip = (struct ip *)buf; + oip = mtod(m, struct ip *); + oip->ip_v = IPVERSION; + oip->ip_hl = sizeof(struct ip) >> 2; + oip->ip_tos = 0; + oip->ip_len = htons(sizeof(struct ip) + plen); + oip->ip_ttl = V_ip_defttl; + oip->ip_p = IPPROTO_ICMP; + oip->ip_id = ip_newid(); + oip->ip_off = htons(IP_DF); + oip->ip_src = ip->ip_dst; + oip->ip_dst = ip->ip_src; + oip->ip_sum = 0; + oip->ip_sum = in_cksum_hdr(oip); + + icmp = mtodo(m, sizeof(struct ip)); + icmp->icmp_type = type; + icmp->icmp_code = code; + icmp->icmp_cksum = 0; + icmp->icmp_pmvoid = 0; + icmp->icmp_nextmtu = htons(mtu); + bcopy(buf, mtodo(m, sizeof(struct ip) + sizeof(struct icmphdr) + + sizeof(uint32_t)), len); + icmp->icmp_cksum = in_cksum_skip(m, sizeof(struct ip) + plen, + sizeof(struct ip)); + dst = nat64_find_route4(&ro, oip->ip_dst.s_addr, m); + if (dst == NULL) + goto freeit; + (*ro.ro_rt->rt_ifp->if_output)(ro.ro_rt->rt_ifp, m, dst, &ro); + return; +freeit: + m_freem(m); +} + +static int +nat64_handle_ip4(struct ip_fw_chain *chain, struct nat64_cfg *cfg, + struct mbuf *m, uint32_t tablearg) +{ + struct route_in6 ro; + struct ip6_hdr ip6; + struct ifnet *ifp; + struct ip *ip; + struct mbufq mq; + struct sockaddr *dst; + uint32_t mtu; + uint16_t ip_id, ip_off; + int plen, hlen; + uint8_t proto; + + ip = mtod(m, struct ip*); + if (nat64_check_ip4(cfg, ip->ip_src.s_addr) != 0 || + nat64_check_ip4(cfg, ip->ip_dst.s_addr) != 0 || + nat64_check_private_ip4(cfg, ip->ip_src.s_addr) != 0 || + nat64_check_private_ip4(cfg, ip->ip_dst.s_addr) != 0) + return (0); + + ip6.ip6_dst = TARG_VAL(chain, tablearg, nh6); + if (nat64_check_ip6(cfg, &ip6.ip6_dst) != 0) + return (IP_FW_DENY); + if (nat64_embed_ip4(cfg, ip->ip_src.s_addr, &ip6.ip6_src) == 0) + return (IP_FW_DENY); + if (ip->ip_ttl <= IPTTLDEC) { + nat64_icmp_reflect(m, ICMP_TIMXCEED, + ICMP_TIMXCEED_INTRANS, 0); + return (IP_FW_CONSUMED); + } + + hlen = ip->ip_hl << 2; + plen = ntohs(ip->ip_len) - hlen; + proto = ip->ip_p; + + /* Save ip_id and ip_off, both are in network byte order */ + ip_id = ip->ip_id; + ip_off = ip->ip_off & htons(IP_OFFMASK | IP_MF); + + /* Fragment length must be multiple of 8 octets */ + if ((ip->ip_off & htons(IP_MF)) != 0 && (plen & 0x7) != 0) { + nat64_icmp_reflect(m, ICMP_PARAMPROB, + ICMP_PARAMPROB_LENGTH, 0); + return (IP_FW_CONSUMED); + } + /* Fragmented ICMP is unsupported */ + if (proto == IPPROTO_ICMP && ip_off != 0) + return (IP_FW_DENY); + + dst = nat64_find_route6(&ro, &ip6.ip6_dst, m); + if (dst == NULL) { + NAT64STAT_INC(cfg, noroute6); + nat64_icmp6_reflect(m, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_NOROUTE, 0); + return (IP_FW_CONSUMED); + } + ifp = ro.ro_rt->rt_ifp; + if (ro.ro_rt->rt_rmx.rmx_mtu != 0) + mtu = min(ro.ro_rt->rt_rmx.rmx_mtu, ifp->if_mtu); + else + mtu = ifp->if_mtu; + if (mtu < plen + sizeof(ip6) && (ip->ip_off & htons(IP_DF)) != 0) { + nat64_icmp_reflect(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, + FRAGSZ(mtu) + sizeof(struct ip)); + return (IP_FW_CONSUMED); + } + + nat64_init_ip6hdr(ip, plen, proto, &ip6); + m_adj(m, hlen); + mbufq_init(&mq, 255); + nat64_fragment6(cfg, &ip6, &mq, m, mtu, ip_id, ip_off); + while ((m = mbufq_dequeue(&mq)) != NULL) { + if ((*ifp->if_output)(ifp, m, dst, (struct route *)&ro) != 0) { + NAT64STAT_INC(cfg, oerrors); + break; + } + NAT64STAT_INC(cfg, opcnt46); + } + mbufq_drain(&mq); + return (IP_FW_CONSUMED); +} + +static int +nat64_handle_ip6(struct ip_fw_chain *chain, struct nat64_cfg *cfg, + struct mbuf *m, uint32_t tablearg) +{ + struct route ro; + struct ip ip; + struct ifnet *ifp; + struct ip6_frag *frag; + struct ip6_hbh *hbh; + struct ip6_hdr *ip6; + struct icmp6_hdr *icmp6; + struct sockaddr *dst; + uint16_t *csum; + uint32_t mtu; + int plen, hlen; + uint8_t proto; + + /* + * XXX: we expect ipfw_chk() did m_pullup() up to upper level + * protocol's headers. Also we skip some checks, that ip6_input(), + * ip6_forward(), ip6_fastfwd() and ipfw_chk() already did. + */ + ip6 = mtod(m, struct ip6_hdr *); + if (nat64_check_ip6(cfg, &ip6->ip6_src) != 0 || + nat64_check_ip6(cfg, &ip6->ip6_dst) != 0) + return (0); + /* Check ip6_dst matches configured prefix */ + if (bcmp(&ip6->ip6_dst, &cfg->prefix, cfg->plen / 8) != 0) + return (0); + + /* Starting from this point we must not return zero */ + ip.ip_src.s_addr = htonl(TARG_VAL(chain, tablearg, nh4)); + if (nat64_check_ip4(cfg, ip.ip_src.s_addr) != 0) { + DPRINTF("invalid source address: %08x", + ip.ip_src.s_addr); + return (IP_FW_DENY); + } + + ip.ip_dst.s_addr = nat64_extract_ip4(cfg, &ip6->ip6_dst); + if (ip.ip_dst.s_addr == 0) + return (IP_FW_DENY); + + if (ip6->ip6_hlim <= IPV6_HLIMDEC) { + nat64_icmp6_reflect(m, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_TRANSIT, 0); + return (IP_FW_CONSUMED); + } + + hlen = sizeof(struct ip6_hdr); + plen = ntohs(ip6->ip6_plen); + proto = ip6->ip6_nxt; + /* Skip extension headers */ + while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || + proto == IPPROTO_DSTOPTS) { + hbh = mtodo(m, hlen); + if (plen == 0 && proto == IPPROTO_HOPOPTS) { + /* XXX: jumbo payload option */ + return (IP_FW_DENY); + } + proto = hbh->ip6h_nxt; + hlen += hbh->ip6h_len << 3; + } + frag = NULL; + if (proto == IPPROTO_FRAGMENT) { + frag = mtodo(m, hlen); + proto = frag->ip6f_nxt; + hlen += sizeof(*frag); + /* Fragmented ICMPv6 is unsupported */ + if (proto == IPPROTO_ICMPV6) + return (IP_FW_DENY); + /* Fragment length must be multiple of 8 octets */ + if ((frag->ip6f_offlg & IP6F_MORE_FRAG) != 0 && + ((plen + sizeof(struct ip6_hdr) - hlen) & 0x7) != 0) { + nat64_icmp6_reflect(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + offsetof(struct ip6_hdr, ip6_plen)); + return (IP_FW_CONSUMED); + } + } + plen -= hlen; + if (plen < 0 || m->m_pkthdr.len < plen + hlen) + return (IP_FW_DENY); + + if (proto == IPPROTO_ICMPV6) { + icmp6 = mtodo(m, hlen); + if (icmp6->icmp6_type != ICMP6_ECHO_REQUEST && + icmp6->icmp6_type != ICMP6_ECHO_REPLY) + return (nat64_icmp6_error(chain, cfg, m, hlen, plen)); + } + dst = nat64_find_route4(&ro, ip.ip_dst.s_addr, m); + if (dst == NULL) { + NAT64STAT_INC(cfg, noroute4); + nat64_icmp6_reflect(m, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_NOROUTE, 0); + return (IP_FW_CONSUMED); + } + + ifp = ro.ro_rt->rt_ifp; + if (ro.ro_rt->rt_rmx.rmx_mtu != 0) + mtu = min(ro.ro_rt->rt_rmx.rmx_mtu, ifp->if_mtu); + else + mtu = ifp->if_mtu; + if (mtu < plen + sizeof(ip)) { + nat64_icmp6_reflect(m, ICMP6_PACKET_TOO_BIG, + ICMP6_TIME_EXCEED_TRANSIT, mtu); + return (IP_FW_CONSUMED); + } + nat64_init_ip4hdr(ip6, frag, plen, proto, &ip); + /* Convert checksums. */ + switch (proto) { + case IPPROTO_TCP: + csum = &TCP(mtodo(m, hlen))->th_sum; + break; + case IPPROTO_UDP: + csum = &UDP(mtodo(m, hlen))->uh_sum; + break; + case IPPROTO_ICMPV6: + /* Checksum in ICMPv6 covers pseudo header */ + csum = &icmp6->icmp6_cksum; + *csum = cksum_add(*csum, in6_cksum_pseudo(ip6, plen, + IPPROTO_ICMPV6, 0)); + /* Convert ICMPv6 types to ICMP */ + mtu = *(uint16_t *)icmp6; /* save old word for cksum_adjust */ + if (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) + icmp6->icmp6_type = ICMP_ECHO; + else /* ICMP6_ECHO_REPLY */ + icmp6->icmp6_type = ICMP_ECHOREPLY; + *csum = cksum_adjust(*csum, (uint16_t)mtu, *(uint16_t *)icmp6); + /* FALLTHROUGH */ + default: + csum = NULL; + }; + if (csum != NULL) + *csum = cksum_add(*csum, nat64_cksum_convert(ip6, &ip)); + + m_adj(m, hlen - sizeof(ip)); + bcopy(&ip, mtod(m, void *), sizeof(ip)); + if ((*ifp->if_output)(ifp, m, dst, &ro) == 0) + NAT64STAT_INC(cfg, opcnt64); + return (IP_FW_CONSUMED); +} + +static int +nat64_handle_icmp6(struct ip_fw_chain *chain, struct nat64_cfg *cfg, + struct mbuf *m) +{ + struct route ro; + struct ip ip; + struct icmp6_hdr *icmp6; + struct ip6_frag *ip6f; + struct ip6_hbh *hbh; + struct ip6_hdr *ip6; + uint32_t tablearg, mtu; + int plen, hlen; + uint8_t proto, type, code; + + ip6 = mtod(m, struct ip6_hdr *); + if (nat64_check_ip6(cfg, &ip6->ip6_src) != 0 || + nat64_check_ip6(cfg, &ip6->ip6_dst) != 0) + return (0); + /* Check ip6_dst matches configured prefix */ + if (bcmp(&ip6->ip6_dst, &cfg->prefix, cfg->plen / 8) != 0) + return (0); + + hlen = sizeof(struct ip6_hdr); + plen = ntohs(ip6->ip6_plen); + proto = ip6->ip6_nxt; + /* Skip extension headers */ + while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || + proto == IPPROTO_DSTOPTS) { + hbh = mtodo(m, hlen); + if (plen == 0 && proto == IPPROTO_HOPOPTS) { + /* XXX: jumbo payload option */ + return (IP_FW_DENY); + } + proto = hbh->ip6h_nxt; + hlen += hbh->ip6h_len << 3; + } + if (proto != IPPROTO_ICMPV6) + return (IP_FW_DENY); + + icmp6 = mtodo(m, hlen); + /* Translate ICMPv6 type and code to ICMPv4 */ + mtu = 0; + switch (icmp6->icmp6_type) { + case ICMP6_DST_UNREACH: + type = ICMP_UNREACH; + switch (icmp6->icmp6_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 (IP_FW_DENY); + } + break; + case ICMP6_PACKET_TOO_BIG: + type = ICMP_UNREACH; + code = ICMP_UNREACH_NEEDFRAG; + mtu = ntohl(icmp6->icmp6_mtu); + if (mtu < 576) + return (IP_FW_DENY); + mtu -= sizeof(struct ip6_hdr) + sizeof(struct ip); + break; + case ICMP6_TIME_EXCEED_TRANSIT: + type = ICMP_TIMXCEED; + code = ICMP_TIMXCEED_INTRANS; + break; + case ICMP6_PARAM_PROB: + if (icmp6->icmp6_code == ICMP6_PARAMPROB_NEXTHEADER) { + type = ICMP_UNREACH; + code = ICMP_UNREACH_PROTOCOL; + break; + } + /* FALLTHROUGH */ + default: + return (IP_FW_DENY); + } + + hlen += sizeof(struct icmp6_hdr); + if (m->m_pkthdr.len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN) + return (IP_FW_DENY); + /* + * We need at least ICMP_MINLEN bytes of original datagram payload + * to generate ICMP message. It is nice that ICMP_MINLEN is equal + * to sizeof(struct ip6_frag). So, if embedded datagram had a fragment + * header we will not have to do m_pullup() again. + */ + m = m_pullup(m, hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN); + if (m == NULL) + return (IP_FW_CONSUMED); + ip6 = mtodo(m, hlen); + ip6f = NULL; + proto = ip6->ip6_nxt; + plen = ntohs(ip6->ip6_plen); + hlen += sizeof(struct ip6_hdr); + if (proto == IPPROTO_FRAGMENT) { + if (m->m_pkthdr.len < hlen + sizeof(struct ip6_frag) + + ICMP_MINLEN) + goto fail; + ip6f = mtodo(m, hlen); + proto = ip6f->ip6f_nxt; + plen -= sizeof(struct ip6_frag); + hlen += sizeof(struct ip6_frag); + if (mtu > 0) + mtu -= sizeof(struct ip6_frag); + } + if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) + goto fail; + if (nat64_check_ip6(cfg, &ip6->ip6_src) != 0 || + nat64_check_ip6(cfg, &ip6->ip6_dst) != 0) + goto fail; + /* Check ip6_src matches configured prefix */ + if (bcmp(&ip6->ip6_src, &cfg->prefix, cfg->plen / 8) != 0) + goto fail; + if (ipfw_lookup_table_extended(chain, cfg->map64, + sizeof(struct in6_addr), &ip6->ip6_dst, &tablearg) == 0) + goto fail; + + /* Now we need to make a fake IPv4 packet to generate ICMP message */ + ip.ip_dst.s_addr = htonl(TARG_VAL(chain, tablearg, nh4)); + if (nat64_check_ip4(cfg, ip.ip_dst.s_addr) != 0) + goto fail; + ip.ip_src.s_addr = nat64_extract_ip4(cfg, &ip6->ip6_src); + if (ip.ip_dst.s_addr == 0) + goto fail; + ip6->ip6_hlim += IPV6_HLIMDEC; /* init_ip4hdr will decrement it */ + nat64_init_ip4hdr(ip6, ip6f, plen, proto, &ip); + + /* Determine interface where we could receive this fake IPv4 packet */ + if (nat64_find_route4(&ro, ip.ip_src.s_addr, m) == NULL) { + NAT64STAT_INC(cfg, noroute4); + goto fail; + } + m->m_pkthdr.rcvif = ro.ro_rt->rt_ifp; + m_adj(m, hlen - sizeof(struct ip)); + bcopy(&ip, mtod(m, void *), sizeof(ip)); + nat64_icmp_reflect(m, type, code, (uint16_t)mtu); + return (IP_FW_CONSUMED); +fail: + /* + * We must call m_freem() because mbuf pointer could be + * changed with m_pullup(). + */ + m_freem(m); + return (IP_FW_CONSUMED); +} + +static int +ipfw_nat64(struct ip_fw_args *args, ipfw_insn *cmd0, struct mbuf *m) +{ + struct ip_fw_chain *chain; + struct nat64_cfg *cfg; + ipfw_insn_u32 *cmd; + uint32_t tablearg; + int ret; + + chain = &V_layer3_chain; + IPFW_RLOCK_ASSERT(chain); + + cmd = (ipfw_insn_u32 *)cmd0; + if (cmd->o.opcode != O_NAT64 || + cmd->o.arg1 != IPFW_NAT64_VERSION || + (cfg = NAT64_LOOKUP(chain, cmd)) == NULL) + return (0); + + switch (args->f_id.addr_type) { + case 4: + ret = ipfw_lookup_table(chain, cfg->map46, + htonl(args->f_id.dst_ip), &tablearg); + break; + case 6: + ret = ipfw_lookup_table_extended(chain, cfg->map64, + sizeof(struct in6_addr), &args->f_id.src_ip6, &tablearg); + break; + default: + return (0); + } + if (ret == 0) { + /* + * In case when packet is ICMPv6 message from an intermediate + * router, the source address of message will not match the + * addresses from our map64 table. + */ + if (args->f_id.proto == IPPROTO_ICMPV6) + return (nat64_handle_icmp6(chain, cfg, m)); + return (0); /* not matched */ + } + + if (args->f_id.addr_type == 4) + ret = nat64_handle_ip4(chain, cfg, m, tablearg); + else + ret = nat64_handle_ip6(chain, cfg, m, tablearg); + + return (ret); +} + +static int +vnet_ipfw_nat64_init(const void *arg __unused) +{ + + nat64_init_config(&nat64_instance); /* XXX */ + V_ipfw_nat64_ready = 1; + return (0); +} + +static void +ipfw_nat64_init(void) +{ + + /* init ipfw hooks */ + ipfw_nat64_ptr = ipfw_nat64; + IPFW_ADD_SOPT_HANDLER(1, scodes); +} + +static int +vnet_ipfw_nat64_uninit(const void *arg __unused) +{ + struct ip_fw_chain *chain; + + chain = &V_layer3_chain; + IPFW_WLOCK(chain); + V_ipfw_nat64_ready = 0; + IPFW_WUNLOCK(chain); + nat64_free_config(&nat64_instance); /* XXX */ + return (0); +} + +static void +ipfw_nat64_destroy(void) +{ + + /* deregister ipfw_nat64 */ + IPFW_DEL_SOPT_HANDLER(1, scodes); + ipfw_nat64_ptr = NULL; +} + +static int +ipfw_nat64_modevent(module_t mod, int type, void *unused) +{ + + switch (type) { + case MOD_LOAD: + case MOD_UNLOAD: + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +static moduledata_t ipfw_nat64_mod = { + "ipfw_nat64", + ipfw_nat64_modevent, + 0 +}; + +/* Define startup order. */ +#define IPFW_NAT64_SI_SUB_FIREWALL SI_SUB_PROTO_IFATTACHDOMAIN +#define IPFW_NAT64_MODEVENT_ORDER (SI_ORDER_ANY - 128) /* after ipfw */ +#define IPFW_NAT64_MODULE_ORDER (IPFW_NAT64_MODEVENT_ORDER + 1) +#define IPFW_NAT64_VNET_ORDER (IPFW_NAT64_MODEVENT_ORDER + 2) + +DECLARE_MODULE(ipfw_nat64, ipfw_nat64_mod, IPFW_NAT64_SI_SUB_FIREWALL, + SI_ORDER_ANY); +MODULE_DEPEND(ipfw_nat64, ipfw, 3, 3, 3); +MODULE_VERSION(ipfw_nat64, 1); + +SYSINIT(ipfw_nat64_init, IPFW_NAT64_SI_SUB_FIREWALL, IPFW_NAT64_MODULE_ORDER, + ipfw_nat64_init, NULL); +VNET_SYSINIT(vnet_ipfw_nat64_init, IPFW_NAT64_SI_SUB_FIREWALL, + IPFW_NAT64_VNET_ORDER, vnet_ipfw_nat64_init, NULL); +SYSUNINIT(ipfw_nat64_destroy, IPFW_NAT64_SI_SUB_FIREWALL, + IPFW_NAT64_MODULE_ORDER, ipfw_nat64_destroy, NULL); +VNET_SYSUNINIT(vnet_ipfw_nat64_uninit, IPFW_NAT64_SI_SUB_FIREWALL, + IPFW_NAT64_VNET_ORDER, vnet_ipfw_nat64_uninit, NULL); + diff --git a/sys/netpfil/ipfw/ip_fw_pfil.c b/sys/netpfil/ipfw/ip_fw_pfil.c index 40fb237..a93cc07 100644 --- a/sys/netpfil/ipfw/ip_fw_pfil.c +++ b/sys/netpfil/ipfw/ip_fw_pfil.c @@ -289,7 +289,11 @@ again: case IP_FW_REASS: goto again; /* continue with packet */ - + + case IP_FW_CONSUMED: + *m0 = NULL; + break; + default: KASSERT(0, ("%s: unknown retval", __func__)); } diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index 61b279b..7f15fa7 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -59,6 +59,7 @@ enum { IP_FW_NGTEE, IP_FW_NAT, IP_FW_REASS, + IP_FW_CONSUMED, }; /* @@ -618,5 +619,16 @@ extern ipfw_nat_cfg_t *ipfw_nat_del_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_log_ptr; + +/* ip_fw_nat64 */ + +typedef int ipfw_nat64_t(struct ip_fw_args *, ipfw_insn *, struct mbuf *); + +VNET_DECLARE(int, ipfw_nat64_ready); +#define V_ipfw_nat64_ready VNET(ipfw_nat64_ready) +#define IPFW_NAT64_LOADED V_ipfw_nat64_ready + +extern ipfw_nat64_t *ipfw_nat64_ptr; + #endif /* _KERNEL */ #endif /* _IPFW2_PRIVATE_H */