15c04f73eSAndrey V. Elsukov /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
35c04f73eSAndrey V. Elsukov *
45c04f73eSAndrey V. Elsukov * Copyright (c) 2019 Yandex LLC
55c04f73eSAndrey V. Elsukov * Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
65c04f73eSAndrey V. Elsukov * Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
75c04f73eSAndrey V. Elsukov *
85c04f73eSAndrey V. Elsukov * Redistribution and use in source and binary forms, with or without
95c04f73eSAndrey V. Elsukov * modification, are permitted provided that the following conditions
105c04f73eSAndrey V. Elsukov * are met:
115c04f73eSAndrey V. Elsukov *
125c04f73eSAndrey V. Elsukov * 1. Redistributions of source code must retain the above copyright
135c04f73eSAndrey V. Elsukov * notice, this list of conditions and the following disclaimer.
145c04f73eSAndrey V. Elsukov * 2. Redistributions in binary form must reproduce the above copyright
155c04f73eSAndrey V. Elsukov * notice, this list of conditions and the following disclaimer in the
165c04f73eSAndrey V. Elsukov * documentation and/or other materials provided with the distribution.
175c04f73eSAndrey V. Elsukov *
185c04f73eSAndrey V. Elsukov * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
195c04f73eSAndrey V. Elsukov * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
205c04f73eSAndrey V. Elsukov * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
215c04f73eSAndrey V. Elsukov * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
225c04f73eSAndrey V. Elsukov * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
235c04f73eSAndrey V. Elsukov * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
245c04f73eSAndrey V. Elsukov * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
255c04f73eSAndrey V. Elsukov * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
265c04f73eSAndrey V. Elsukov * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
275c04f73eSAndrey V. Elsukov * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
285c04f73eSAndrey V. Elsukov */
295c04f73eSAndrey V. Elsukov
305c04f73eSAndrey V. Elsukov #include <sys/param.h>
315c04f73eSAndrey V. Elsukov #include <sys/systm.h>
325c04f73eSAndrey V. Elsukov #include <sys/counter.h>
335c04f73eSAndrey V. Elsukov #include <sys/kernel.h>
345c04f73eSAndrey V. Elsukov #include <sys/lock.h>
355c04f73eSAndrey V. Elsukov #include <sys/mbuf.h>
365c04f73eSAndrey V. Elsukov #include <sys/module.h>
375c04f73eSAndrey V. Elsukov #include <sys/rmlock.h>
385c04f73eSAndrey V. Elsukov #include <sys/rwlock.h>
395c04f73eSAndrey V. Elsukov #include <sys/socket.h>
405c04f73eSAndrey V. Elsukov #include <sys/sysctl.h>
415c04f73eSAndrey V. Elsukov
425c04f73eSAndrey V. Elsukov #include <net/if.h>
435c04f73eSAndrey V. Elsukov #include <net/if_var.h>
445c04f73eSAndrey V. Elsukov #include <net/if_pflog.h>
455c04f73eSAndrey V. Elsukov #include <net/pfil.h>
465c04f73eSAndrey V. Elsukov
475c04f73eSAndrey V. Elsukov #include <netinet/in.h>
485c04f73eSAndrey V. Elsukov #include <netinet/ip.h>
495c04f73eSAndrey V. Elsukov #include <netinet/ip_icmp.h>
505c04f73eSAndrey V. Elsukov #include <netinet/ip_var.h>
515c04f73eSAndrey V. Elsukov #include <netinet/ip_fw.h>
525c04f73eSAndrey V. Elsukov #include <netinet/ip6.h>
535c04f73eSAndrey V. Elsukov #include <netinet/icmp6.h>
545c04f73eSAndrey V. Elsukov #include <netinet6/ip_fw_nat64.h>
555c04f73eSAndrey V. Elsukov
565c04f73eSAndrey V. Elsukov #include <netpfil/ipfw/ip_fw_private.h>
575c04f73eSAndrey V. Elsukov #include <netpfil/pf/pf.h>
585c04f73eSAndrey V. Elsukov
595c04f73eSAndrey V. Elsukov #include "nat64clat.h"
605c04f73eSAndrey V. Elsukov
615c04f73eSAndrey V. Elsukov #define NAT64_LOOKUP(chain, cmd) \
62*4a77657cSAndrey V. Elsukov (struct nat64clat_cfg *)SRV_OBJECT((chain), insntod(cmd, kidx)->kidx)
635c04f73eSAndrey V. Elsukov
645c04f73eSAndrey V. Elsukov static void
nat64clat_log(struct pfloghdr * plog,struct mbuf * m,sa_family_t family,uint32_t kidx)655c04f73eSAndrey V. Elsukov nat64clat_log(struct pfloghdr *plog, struct mbuf *m, sa_family_t family,
665c04f73eSAndrey V. Elsukov uint32_t kidx)
675c04f73eSAndrey V. Elsukov {
685c04f73eSAndrey V. Elsukov static uint32_t pktid = 0;
695c04f73eSAndrey V. Elsukov
705c04f73eSAndrey V. Elsukov memset(plog, 0, sizeof(*plog));
71*4a77657cSAndrey V. Elsukov plog->length = PFLOG_REAL_HDRLEN;
725c04f73eSAndrey V. Elsukov plog->af = family;
735c04f73eSAndrey V. Elsukov plog->action = PF_NAT;
745c04f73eSAndrey V. Elsukov plog->dir = PF_IN;
755c04f73eSAndrey V. Elsukov plog->rulenr = htonl(kidx);
765c04f73eSAndrey V. Elsukov pktid++;
775c04f73eSAndrey V. Elsukov plog->subrulenr = htonl(pktid);
785c04f73eSAndrey V. Elsukov plog->ruleset[0] = '\0';
795c04f73eSAndrey V. Elsukov strlcpy(plog->ifname, "NAT64CLAT", sizeof(plog->ifname));
805c04f73eSAndrey V. Elsukov ipfw_bpf_mtap2(plog, PFLOG_HDRLEN, m);
815c04f73eSAndrey V. Elsukov }
825c04f73eSAndrey V. Elsukov
835c04f73eSAndrey V. Elsukov static int
nat64clat_handle_ip4(struct ip_fw_chain * chain,struct nat64clat_cfg * cfg,struct mbuf * m)845c04f73eSAndrey V. Elsukov nat64clat_handle_ip4(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
855c04f73eSAndrey V. Elsukov struct mbuf *m)
865c04f73eSAndrey V. Elsukov {
875c04f73eSAndrey V. Elsukov struct pfloghdr loghdr, *logdata;
885c04f73eSAndrey V. Elsukov struct in6_addr saddr, daddr;
895c04f73eSAndrey V. Elsukov struct ip *ip;
905c04f73eSAndrey V. Elsukov
915c04f73eSAndrey V. Elsukov ip = mtod(m, struct ip*);
925c04f73eSAndrey V. Elsukov /* source address for CLAT may be private with no harm */
935c04f73eSAndrey V. Elsukov if (nat64_check_ip4(ip->ip_src.s_addr) != 0 ||
945c04f73eSAndrey V. Elsukov nat64_check_ip4(ip->ip_dst.s_addr) != 0 ||
955c04f73eSAndrey V. Elsukov nat64_check_private_ip4(&cfg->base, ip->ip_dst.s_addr) != 0)
965c04f73eSAndrey V. Elsukov return (NAT64SKIP);
975c04f73eSAndrey V. Elsukov
985c04f73eSAndrey V. Elsukov memcpy(&saddr, &cfg->base.clat_prefix, sizeof(saddr));
995c04f73eSAndrey V. Elsukov nat64_embed_ip4(&saddr, cfg->base.clat_plen, ip->ip_src.s_addr);
1005c04f73eSAndrey V. Elsukov memcpy(&daddr, &cfg->base.plat_prefix, sizeof(daddr));
1015c04f73eSAndrey V. Elsukov nat64_embed_ip4(&daddr, cfg->base.plat_plen, ip->ip_dst.s_addr);
1025c04f73eSAndrey V. Elsukov if (cfg->base.flags & NAT64_LOG) {
1035c04f73eSAndrey V. Elsukov logdata = &loghdr;
1045c04f73eSAndrey V. Elsukov nat64clat_log(logdata, m, AF_INET, cfg->no.kidx);
1055c04f73eSAndrey V. Elsukov } else
1065c04f73eSAndrey V. Elsukov logdata = NULL;
1075c04f73eSAndrey V. Elsukov return (nat64_do_handle_ip4(m, &saddr, &daddr, 0, &cfg->base,
1085c04f73eSAndrey V. Elsukov logdata));
1095c04f73eSAndrey V. Elsukov }
1105c04f73eSAndrey V. Elsukov
1115c04f73eSAndrey V. Elsukov static int
nat64clat_handle_ip6(struct ip_fw_chain * chain,struct nat64clat_cfg * cfg,struct mbuf * m)1125c04f73eSAndrey V. Elsukov nat64clat_handle_ip6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
1135c04f73eSAndrey V. Elsukov struct mbuf *m)
1145c04f73eSAndrey V. Elsukov {
1155c04f73eSAndrey V. Elsukov struct pfloghdr loghdr, *logdata;
1165c04f73eSAndrey V. Elsukov struct ip6_hdr *ip6;
1175c04f73eSAndrey V. Elsukov uint32_t aaddr;
1185c04f73eSAndrey V. Elsukov
1195c04f73eSAndrey V. Elsukov /*
1205c04f73eSAndrey V. Elsukov * NOTE: we expect ipfw_chk() did m_pullup() up to upper level
1215c04f73eSAndrey V. Elsukov * protocol's headers. Also we skip some checks, that ip6_input(),
1225c04f73eSAndrey V. Elsukov * ip6_forward(), ip6_fastfwd() and ipfw_chk() already did.
1235c04f73eSAndrey V. Elsukov */
1245c04f73eSAndrey V. Elsukov ip6 = mtod(m, struct ip6_hdr *);
1255c04f73eSAndrey V. Elsukov /* Check ip6_dst matches configured prefix */
1265c04f73eSAndrey V. Elsukov if (memcmp(&ip6->ip6_dst, &cfg->base.clat_prefix,
1275c04f73eSAndrey V. Elsukov cfg->base.clat_plen / 8) != 0)
1285c04f73eSAndrey V. Elsukov return (NAT64SKIP);
1295c04f73eSAndrey V. Elsukov /* Check ip6_src matches configured prefix */
1305c04f73eSAndrey V. Elsukov if (memcmp(&ip6->ip6_src, &cfg->base.plat_prefix,
1315c04f73eSAndrey V. Elsukov cfg->base.plat_plen / 8) != 0)
1325c04f73eSAndrey V. Elsukov return (NAT64SKIP);
1335c04f73eSAndrey V. Elsukov
1345c04f73eSAndrey V. Elsukov if (cfg->base.flags & NAT64_LOG) {
1355c04f73eSAndrey V. Elsukov logdata = &loghdr;
1365c04f73eSAndrey V. Elsukov nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx);
1375c04f73eSAndrey V. Elsukov } else
1385c04f73eSAndrey V. Elsukov logdata = NULL;
1395c04f73eSAndrey V. Elsukov
1405c04f73eSAndrey V. Elsukov aaddr = nat64_extract_ip4(&ip6->ip6_src, cfg->base.plat_plen);
1415c04f73eSAndrey V. Elsukov return (nat64_do_handle_ip6(m, aaddr, 0, &cfg->base, logdata));
1425c04f73eSAndrey V. Elsukov }
1435c04f73eSAndrey V. Elsukov
1445c04f73eSAndrey V. Elsukov static int
nat64clat_handle_icmp6(struct ip_fw_chain * chain,struct nat64clat_cfg * cfg,struct mbuf * m)1455c04f73eSAndrey V. Elsukov nat64clat_handle_icmp6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
1465c04f73eSAndrey V. Elsukov struct mbuf *m)
1475c04f73eSAndrey V. Elsukov {
1485c04f73eSAndrey V. Elsukov struct pfloghdr loghdr, *logdata;
1495c04f73eSAndrey V. Elsukov struct nat64_counters *stats;
1505c04f73eSAndrey V. Elsukov struct ip6_hdr *ip6i;
1515c04f73eSAndrey V. Elsukov struct icmp6_hdr *icmp6;
1525c04f73eSAndrey V. Elsukov uint32_t daddr;
1535c04f73eSAndrey V. Elsukov int hlen, proto;
1545c04f73eSAndrey V. Elsukov
1555c04f73eSAndrey V. Elsukov hlen = 0;
1565c04f73eSAndrey V. Elsukov stats = &cfg->base.stats;
1575c04f73eSAndrey V. Elsukov proto = nat64_getlasthdr(m, &hlen);
1585c04f73eSAndrey V. Elsukov if (proto != IPPROTO_ICMPV6) {
1595c04f73eSAndrey V. Elsukov NAT64STAT_INC(stats, dropped);
1605c04f73eSAndrey V. Elsukov return (NAT64MFREE);
1615c04f73eSAndrey V. Elsukov }
1625c04f73eSAndrey V. Elsukov icmp6 = mtodo(m, hlen);
1635c04f73eSAndrey V. Elsukov switch (icmp6->icmp6_type) {
1645c04f73eSAndrey V. Elsukov case ICMP6_DST_UNREACH:
1655c04f73eSAndrey V. Elsukov case ICMP6_PACKET_TOO_BIG:
1665c04f73eSAndrey V. Elsukov case ICMP6_TIME_EXCEED_TRANSIT:
1675c04f73eSAndrey V. Elsukov case ICMP6_PARAM_PROB:
1685c04f73eSAndrey V. Elsukov break;
1695c04f73eSAndrey V. Elsukov default:
1705c04f73eSAndrey V. Elsukov NAT64STAT_INC(stats, dropped);
1715c04f73eSAndrey V. Elsukov return (NAT64MFREE);
1725c04f73eSAndrey V. Elsukov }
1735c04f73eSAndrey V. Elsukov hlen += sizeof(struct icmp6_hdr);
1745c04f73eSAndrey V. Elsukov if (m->m_pkthdr.len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN) {
1755c04f73eSAndrey V. Elsukov NAT64STAT_INC(stats, dropped);
1765c04f73eSAndrey V. Elsukov return (NAT64MFREE);
1775c04f73eSAndrey V. Elsukov }
1785c04f73eSAndrey V. Elsukov if (m->m_len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN)
1795c04f73eSAndrey V. Elsukov m = m_pullup(m, hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN);
1805c04f73eSAndrey V. Elsukov if (m == NULL) {
1815c04f73eSAndrey V. Elsukov NAT64STAT_INC(stats, nomem);
1825c04f73eSAndrey V. Elsukov return (NAT64RETURN);
1835c04f73eSAndrey V. Elsukov }
1845c04f73eSAndrey V. Elsukov /*
1855c04f73eSAndrey V. Elsukov * Use destination address from inner IPv6 header to determine
1865c04f73eSAndrey V. Elsukov * IPv4 mapped address.
1875c04f73eSAndrey V. Elsukov */
1885c04f73eSAndrey V. Elsukov ip6i = mtodo(m, hlen);
1895c04f73eSAndrey V. Elsukov daddr = nat64_extract_ip4(&ip6i->ip6_dst, cfg->base.clat_plen);
1905c04f73eSAndrey V. Elsukov if (daddr == 0) {
1915c04f73eSAndrey V. Elsukov NAT64STAT_INC(stats, dropped);
1925c04f73eSAndrey V. Elsukov return (NAT64MFREE);
1935c04f73eSAndrey V. Elsukov }
1945c04f73eSAndrey V. Elsukov if (cfg->base.flags & NAT64_LOG) {
1955c04f73eSAndrey V. Elsukov logdata = &loghdr;
1965c04f73eSAndrey V. Elsukov nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx);
1975c04f73eSAndrey V. Elsukov } else
1985c04f73eSAndrey V. Elsukov logdata = NULL;
1995c04f73eSAndrey V. Elsukov return (nat64_handle_icmp6(m, 0, daddr, 0, &cfg->base, logdata));
2005c04f73eSAndrey V. Elsukov }
2015c04f73eSAndrey V. Elsukov
2025c04f73eSAndrey V. Elsukov int
ipfw_nat64clat(struct ip_fw_chain * chain,struct ip_fw_args * args,ipfw_insn * cmd,int * done)2035c04f73eSAndrey V. Elsukov ipfw_nat64clat(struct ip_fw_chain *chain, struct ip_fw_args *args,
2045c04f73eSAndrey V. Elsukov ipfw_insn *cmd, int *done)
2055c04f73eSAndrey V. Elsukov {
2065c04f73eSAndrey V. Elsukov ipfw_insn *icmd;
2075c04f73eSAndrey V. Elsukov struct nat64clat_cfg *cfg;
2085c04f73eSAndrey V. Elsukov int ret;
2095c04f73eSAndrey V. Elsukov
2105c04f73eSAndrey V. Elsukov IPFW_RLOCK_ASSERT(chain);
2115c04f73eSAndrey V. Elsukov
2125c04f73eSAndrey V. Elsukov *done = 0; /* try next rule if not matched */
213*4a77657cSAndrey V. Elsukov icmd = cmd + F_LEN(cmd);
2145c04f73eSAndrey V. Elsukov if (cmd->opcode != O_EXTERNAL_ACTION ||
215*4a77657cSAndrey V. Elsukov insntod(cmd, kidx)->kidx != V_nat64clat_eid ||
2165c04f73eSAndrey V. Elsukov icmd->opcode != O_EXTERNAL_INSTANCE ||
2175c04f73eSAndrey V. Elsukov (cfg = NAT64_LOOKUP(chain, icmd)) == NULL)
2185c04f73eSAndrey V. Elsukov return (0);
2195c04f73eSAndrey V. Elsukov
2205c04f73eSAndrey V. Elsukov switch (args->f_id.addr_type) {
2215c04f73eSAndrey V. Elsukov case 4:
2225c04f73eSAndrey V. Elsukov ret = nat64clat_handle_ip4(chain, cfg, args->m);
2235c04f73eSAndrey V. Elsukov break;
2245c04f73eSAndrey V. Elsukov case 6:
2255c04f73eSAndrey V. Elsukov ret = nat64clat_handle_ip6(chain, cfg, args->m);
2265c04f73eSAndrey V. Elsukov break;
2275c04f73eSAndrey V. Elsukov default:
2285c04f73eSAndrey V. Elsukov return (0);
2295c04f73eSAndrey V. Elsukov }
2305c04f73eSAndrey V. Elsukov
2315c04f73eSAndrey V. Elsukov if (ret == NAT64SKIP) {
2325c04f73eSAndrey V. Elsukov /*
2335c04f73eSAndrey V. Elsukov * In case when packet is ICMPv6 message from an intermediate
2345c04f73eSAndrey V. Elsukov * router, the source address of message will not match the
2355c04f73eSAndrey V. Elsukov * addresses from configured prefixes.
2365c04f73eSAndrey V. Elsukov */
2375c04f73eSAndrey V. Elsukov if (args->f_id.proto != IPPROTO_ICMPV6)
2385c04f73eSAndrey V. Elsukov return (0);
2395c04f73eSAndrey V. Elsukov
2405c04f73eSAndrey V. Elsukov ret = nat64clat_handle_icmp6(chain, cfg, args->m);
2415c04f73eSAndrey V. Elsukov }
2425c04f73eSAndrey V. Elsukov
2435c04f73eSAndrey V. Elsukov if (ret == NAT64SKIP)
2445c04f73eSAndrey V. Elsukov return (0);
2455c04f73eSAndrey V. Elsukov
2465c04f73eSAndrey V. Elsukov *done = 1; /* terminate the search */
2475c04f73eSAndrey V. Elsukov if (ret == NAT64MFREE)
2485c04f73eSAndrey V. Elsukov m_freem(args->m);
2495c04f73eSAndrey V. Elsukov
2505c04f73eSAndrey V. Elsukov args->m = NULL;
2515c04f73eSAndrey V. Elsukov return (IP_FW_NAT64);
2525c04f73eSAndrey V. Elsukov }
253