From 93a7173b744f01de6f104418db8654872cb618da Mon Sep 17 00:00:00 2001 From: ae Date: Mon, 18 Mar 2019 11:44:53 +0000 Subject: [PATCH] Add NAT64 CLAT implementation as defined in RFC6877. CLAT is customer-side translator that algorithmically translates 1:1 private IPv4 addresses to global IPv6 addresses, and vice versa. It is implemented as part of ipfw_nat64 kernel module. When module is loaded or compiled into the kernel, it registers "nat64clat" external action. External action named instance can be created using `create` command and then used in ipfw rules. The create command accepts two IPv6 prefixes `plat_prefix` and `clat_prefix`. If plat_prefix is ommitted, IPv6 NAT64 Well-Known prefix 64:ff9b::/96 will be used. # ipfw nat64clat CLAT create clat_prefix SRC_PFX plat_prefix DST_PFX # ipfw add nat64clat CLAT ip4 from IPv4_PFX to any out # ipfw add nat64clat CLAT ip6 from DST_PFX to SRC_PFX in Obtained from: Yandex LLC Submitted by: Boris N. Lytochkin MFC after: 1 month Relnotes: yes Sponsored by: Yandex LLC --- sbin/ipfw/Makefile | 2 +- sbin/ipfw/ipfw.8 | 71 +++ sbin/ipfw/ipfw2.c | 1 + sbin/ipfw/ipfw2.h | 6 + sbin/ipfw/main.c | 2 + sbin/ipfw/nat64clat.c | 535 ++++++++++++++++++ sys/conf/files | 4 + sys/modules/ipfw_nat64/Makefile | 1 + sys/netinet/ip_fw.h | 7 + sys/netinet6/ip_fw_nat64.h | 24 + sys/netpfil/ipfw/nat64/ip_fw_nat64.c | 7 + sys/netpfil/ipfw/nat64/ip_fw_nat64.h | 2 + sys/netpfil/ipfw/nat64/nat64clat.c | 255 +++++++++ sys/netpfil/ipfw/nat64/nat64clat.h | 54 ++ sys/netpfil/ipfw/nat64/nat64clat_control.c | 614 +++++++++++++++++++++ 15 files changed, 1584 insertions(+), 1 deletion(-) create mode 100644 sbin/ipfw/nat64clat.c create mode 100644 sys/netpfil/ipfw/nat64/nat64clat.c create mode 100644 sys/netpfil/ipfw/nat64/nat64clat.h create mode 100644 sys/netpfil/ipfw/nat64/nat64clat_control.c diff --git a/sbin/ipfw/Makefile b/sbin/ipfw/Makefile index 014f19df7578..326a050cce2e 100644 --- a/sbin/ipfw/Makefile +++ b/sbin/ipfw/Makefile @@ -5,7 +5,7 @@ PACKAGE=ipfw PROG= ipfw SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c -SRCS+= nat64lsn.c nat64stl.c nptv6.c +SRCS+= nat64clat.c nat64lsn.c nat64stl.c nptv6.c WARNS?= 2 .if ${MK_PF} != "no" diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 9b7cecd8db44..31448aff92bb 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -136,6 +136,21 @@ in-kernel NAT. .Cm destroy .Nm .Oo Cm set Ar N Oc Cm nat64stl Ar name Cm stats Op Cm reset +.Ss XLAT464 CLAT IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION +.Nm +.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm create Ar create-options +.Nm +.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm config Ar config-options +.Nm +.Oo Cm set Ar N Oc Cm nat64clat +.Brq Ar name | all +.Brq Cm list | show +.Nm +.Oo Cm set Ar N Oc Cm nat64clat +.Brq Ar name | all +.Cm destroy +.Nm +.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm stats Op Cm reset .Ss IPv6-to-IPv6 NETWORK PREFIX TRANSLATION .Nm .Oo Cm set Ar N Oc Cm nptv6 Ar name Cm create Ar create-options @@ -924,6 +939,11 @@ Pass packet to a stateless NAT64 instance (for IPv6/IPv4 network address and protocol translation): see the .Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION Section for further information. +.It Cm nat64clat Ar name +Pass packet to a CLAT NAT64 instance (for client-side IPv6/IPv4 network address and +protocol translation): see the +.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION +Section for further information. .It Cm nptv6 Ar name Pass packet to a NPTv6 instance (for IPv6-to-IPv6 network prefix translation): see the @@ -3482,6 +3502,57 @@ Note that the behavior of stateless translator with respect to not matched packets differs from stateful translator. If corresponding addresses was not found in the lookup tables, the packet will not be dropped and the search continues. +.Pp +.Pp +.Ss XLAT464 CLAT translation +XLAT464 CLAT NAT64 translator implements client-side stateless translation as +defined in RFC6877 and is very similar to statless NAT64 translator +explained above. Instead of lookup tables it uses one-to-one mapping +between IPv4 and IPv6 addresses using configured prefixes. +This mode can be used as a replacement of DNS64 service for applications +that are not using it (e.g. VoIP) allowing them to access IPv4-only Internet +over IPv6-only networks with help of remote NAT64 translator. +.Pp +The CLAT NAT64 configuration command is the following: +.Bd -ragged -offset indent +.Bk -words +.Cm nat64clat +.Ar name +.Cm create +.Ar create-options +.Ek +.Ed +.Pp +The following parameters can be configured: +.Bl -tag -width indent +.It Cm clat_prefix Ar ipv6_prefix/length +The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator +to represent source IPv4 addresses. +.It Cm plat_prefix Ar ipv6_prefix/length +The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator +to represent destination IPv4 addresses. This IPv6 prefix should be configured +on a remote NAT64 translator. +.It Cm log +Turn on logging of all handled packets via BPF through +.Ar ipfwlog0 +interface. +.It Cm -log +Turn off logging of all handled packets via BPF. +.It Cm allow_private +Turn on processing private IPv4 addresses. By default +.Nm nat64clat +instance will not process IPv4 packets with destination address from private +ranges as defined in RFC1918. +.It Cm -allow_private +Turn off private address handling in +.Nm nat64clat +instance. +.El +.Pp +Note that the behavior of CLAT translator with respect to not matched +packets differs from stateful translator. +If corresponding addresses were not matched against prefixes configured, +the packet will not be dropped and the search continues. .Sh IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6) .Nm supports in-kernel IPv6-to-IPv6 network prefix translation as described diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 775096c9cb98..c57b4b6f6a1e 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -237,6 +237,7 @@ static struct _s_x ether_types[] = { }; static struct _s_x rule_eactions[] = { + { "nat64clat", TOK_NAT64CLAT }, { "nat64lsn", TOK_NAT64LSN }, { "nat64stl", TOK_NAT64STL }, { "nptv6", TOK_NPTV6 }, diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 8f07335de9f6..ff6990ae1c06 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -291,6 +291,11 @@ enum tokens { TOK_PRIVATE, TOK_PRIVATEOFF, + /* NAT64 CLAT tokens */ + TOK_NAT64CLAT, + TOK_PLAT_PREFIX, + TOK_CLAT_PREFIX, + /* NPTv6 tokens */ TOK_NPTV6, TOK_INTPREFIX, @@ -387,6 +392,7 @@ void ipfw_flush(int force); void ipfw_zero(int ac, char *av[], int optname); void ipfw_list(int ac, char *av[], int show_counters); void ipfw_internal_handler(int ac, char *av[]); +void ipfw_nat64clat_handler(int ac, char *av[]); void ipfw_nat64lsn_handler(int ac, char *av[]); void ipfw_nat64stl_handler(int ac, char *av[]); void ipfw_nptv6_handler(int ac, char *av[]); diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c index 0a9791b77981..b1a410a8412e 100644 --- a/sbin/ipfw/main.c +++ b/sbin/ipfw/main.c @@ -429,6 +429,8 @@ ipfw_main(int oldac, char **oldav) if (co.use_set || try_next) { if (_substrcmp(*av, "delete") == 0) ipfw_delete(av); + else if (!strncmp(*av, "nat64clat", strlen(*av))) + ipfw_nat64clat_handler(ac, av); else if (!strncmp(*av, "nat64stl", strlen(*av))) ipfw_nat64stl_handler(ac, av); else if (!strncmp(*av, "nat64lsn", strlen(*av))) diff --git a/sbin/ipfw/nat64clat.c b/sbin/ipfw/nat64clat.c new file mode 100644 index 000000000000..bf91f70d6f23 --- /dev/null +++ b/sbin/ipfw/nat64clat.c @@ -0,0 +1,535 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Yandex LLC + * Copyright (c) 2019 Andrey V. Elsukov + * Copyright (c) 2019 Boris N. Lytochkin + * + * 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 "ipfw2.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef int (nat64clat_cb_t)(ipfw_nat64clat_cfg *i, const char *name, + uint8_t set); +static int nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set, + int sort); + +static void nat64clat_create(const char *name, uint8_t set, int ac, char **av); +static void nat64clat_config(const char *name, uint8_t set, int ac, char **av); +static void nat64clat_destroy(const char *name, uint8_t set); +static void nat64clat_stats(const char *name, uint8_t set); +static void nat64clat_reset_stats(const char *name, uint8_t set); +static int nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name, + uint8_t set); +static int nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name, + uint8_t set); + +static struct _s_x nat64cmds[] = { + { "create", TOK_CREATE }, + { "config", TOK_CONFIG }, + { "destroy", TOK_DESTROY }, + { "list", TOK_LIST }, + { "show", TOK_LIST }, + { "stats", TOK_STATS }, + { NULL, 0 } +}; + +static struct _s_x nat64statscmds[] = { + { "reset", TOK_RESET }, + { NULL, 0 } +}; + +/* + * This one handles all nat64clat-related commands + * ipfw [set N] nat64clat NAME {create | config} ... + * ipfw [set N] nat64clat NAME stats [reset] + * ipfw [set N] nat64clat {NAME | all} destroy + * ipfw [set N] nat64clat {NAME | all} {list | show} + */ +#define nat64clat_check_name table_check_name +void +ipfw_nat64clat_handler(int ac, char *av[]) +{ + const char *name; + int tcmd; + uint8_t set; + + if (co.use_set != 0) + set = co.use_set - 1; + else + set = 0; + ac--; av++; + + NEED1("nat64clat needs instance name"); + name = *av; + if (nat64clat_check_name(name) != 0) { + if (strcmp(name, "all") == 0) + name = NULL; + else + errx(EX_USAGE, "nat64clat instance name %s is invalid", + name); + } + ac--; av++; + NEED1("nat64clat needs command"); + + tcmd = get_token(nat64cmds, *av, "nat64clat command"); + if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) + errx(EX_USAGE, "nat64clat instance name required"); + switch (tcmd) { + case TOK_CREATE: + ac--; av++; + nat64clat_create(name, set, ac, av); + break; + case TOK_CONFIG: + ac--; av++; + nat64clat_config(name, set, ac, av); + break; + case TOK_LIST: + nat64clat_foreach(nat64clat_show_cb, name, set, 1); + break; + case TOK_DESTROY: + if (name == NULL) + nat64clat_foreach(nat64clat_destroy_cb, NULL, set, 0); + else + nat64clat_destroy(name, set); + break; + case TOK_STATS: + ac--; av++; + if (ac == 0) { + nat64clat_stats(name, set); + break; + } + tcmd = get_token(nat64statscmds, *av, "stats command"); + if (tcmd == TOK_RESET) + nat64clat_reset_stats(name, set); + } +} + + +static void +nat64clat_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set) +{ + + ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */ + ntlv->head.length = sizeof(ipfw_obj_ntlv); + ntlv->idx = 1; + ntlv->set = set; + strlcpy(ntlv->name, name, sizeof(ntlv->name)); +} + +static struct _s_x nat64newcmds[] = { + { "plat_prefix", TOK_PLAT_PREFIX }, + { "clat_prefix", TOK_CLAT_PREFIX }, + { "log", TOK_LOG }, + { "-log", TOK_LOGOFF }, + { "allow_private", TOK_PRIVATE }, + { "-allow_private", TOK_PRIVATEOFF }, + { NULL, 0 } +}; + +/* + * Creates new nat64clat instance + * ipfw nat64clat create clat_prefix plat_prefix + * Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ] + */ +#define NAT64CLAT_HAS_CLAT_PREFIX 0x01 +#define NAT64CLAT_HAS_PLAT_PREFIX 0x02 +static void +nat64clat_create(const char *name, uint8_t set, int ac, char *av[]) +{ + char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64clat_cfg)]; + ipfw_nat64clat_cfg *cfg; + ipfw_obj_lheader *olh; + int tcmd, flags; + char *p; + struct in6_addr prefix; + uint8_t plen; + + memset(buf, 0, sizeof(buf)); + olh = (ipfw_obj_lheader *)buf; + cfg = (ipfw_nat64clat_cfg *)(olh + 1); + + /* Some reasonable defaults */ + inet_pton(AF_INET6, "64:ff9b::", &cfg->plat_prefix); + cfg->plat_plen = 96; + cfg->set = set; + flags = NAT64CLAT_HAS_PLAT_PREFIX; + while (ac > 0) { + tcmd = get_token(nat64newcmds, *av, "option"); + ac--; av++; + + switch (tcmd) { + case TOK_PLAT_PREFIX: + case TOK_CLAT_PREFIX: + if (tcmd == TOK_PLAT_PREFIX) { + NEED1("IPv6 plat_prefix required"); + } else { + NEED1("IPv6 clat_prefix required"); + } + + if ((p = strchr(*av, '/')) != NULL) + *p++ = '\0'; + if (inet_pton(AF_INET6, *av, &prefix) != 1) + errx(EX_USAGE, + "Bad prefix: %s", *av); + plen = strtol(p, NULL, 10); + if (ipfw_check_nat64prefix(&prefix, plen) != 0) + errx(EX_USAGE, + "Bad prefix length: %s", p); + if (tcmd == TOK_PLAT_PREFIX) { + flags |= NAT64CLAT_HAS_PLAT_PREFIX; + cfg->plat_prefix = prefix; + cfg->plat_plen = plen; + } else { + flags |= NAT64CLAT_HAS_CLAT_PREFIX; + cfg->clat_prefix = prefix; + cfg->clat_plen = plen; + } + ac--; av++; + break; + case TOK_LOG: + cfg->flags |= NAT64_LOG; + break; + case TOK_LOGOFF: + cfg->flags &= ~NAT64_LOG; + break; + case TOK_PRIVATE: + cfg->flags |= NAT64_ALLOW_PRIVATE; + break; + case TOK_PRIVATEOFF: + cfg->flags &= ~NAT64_ALLOW_PRIVATE; + break; + } + } + + /* Check validness */ + if ((flags & NAT64CLAT_HAS_PLAT_PREFIX) != NAT64CLAT_HAS_PLAT_PREFIX) + errx(EX_USAGE, "plat_prefix required"); + if ((flags & NAT64CLAT_HAS_CLAT_PREFIX) != NAT64CLAT_HAS_CLAT_PREFIX) + errx(EX_USAGE, "clat_prefix required"); + + olh->count = 1; + olh->objsize = sizeof(*cfg); + olh->size = sizeof(buf); + strlcpy(cfg->name, name, sizeof(cfg->name)); + if (do_set3(IP_FW_NAT64CLAT_CREATE, &olh->opheader, sizeof(buf)) != 0) + err(EX_OSERR, "nat64clat instance creation failed"); +} + +/* + * Configures existing nat64clat instance + * ipfw nat64clat config + * Request: [ ipfw_obj_header ipfw_nat64clat_cfg ] + */ +static void +nat64clat_config(const char *name, uint8_t set, int ac, char **av) +{ + char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64clat_cfg)]; + ipfw_nat64clat_cfg *cfg; + ipfw_obj_header *oh; + char *opt; + char *p; + size_t sz; + int tcmd; + struct in6_addr prefix; + uint8_t plen; + + if (ac == 0) + errx(EX_USAGE, "config options required"); + memset(&buf, 0, sizeof(buf)); + oh = (ipfw_obj_header *)buf; + cfg = (ipfw_nat64clat_cfg *)(oh + 1); + sz = sizeof(buf); + + nat64clat_fill_ntlv(&oh->ntlv, name, set); + if (do_get3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, &sz) != 0) + err(EX_OSERR, "failed to get config for instance %s", name); + + while (ac > 0) { + tcmd = get_token(nat64newcmds, *av, "option"); + opt = *av; + ac--; av++; + + switch (tcmd) { + case TOK_PLAT_PREFIX: + case TOK_CLAT_PREFIX: + if (tcmd == TOK_PLAT_PREFIX) { + NEED1("IPv6 plat_prefix required"); + } else { + NEED1("IPv6 clat_prefix required"); + } + + if ((p = strchr(*av, '/')) != NULL) + *p++ = '\0'; + if (inet_pton(AF_INET6, *av, &prefix) != 1) + errx(EX_USAGE, + "Bad prefix: %s", *av); + plen = strtol(p, NULL, 10); + if (ipfw_check_nat64prefix(&prefix, plen) != 0) + errx(EX_USAGE, + "Bad prefix length: %s", p); + if (tcmd == TOK_PLAT_PREFIX) { + cfg->plat_prefix = prefix; + cfg->plat_plen = plen; + } else { + cfg->clat_prefix = prefix; + cfg->clat_plen = plen; + } + ac--; av++; + break; + case TOK_LOG: + cfg->flags |= NAT64_LOG; + break; + case TOK_LOGOFF: + cfg->flags &= ~NAT64_LOG; + break; + case TOK_PRIVATE: + cfg->flags |= NAT64_ALLOW_PRIVATE; + break; + case TOK_PRIVATEOFF: + cfg->flags &= ~NAT64_ALLOW_PRIVATE; + break; + default: + errx(EX_USAGE, "Can't change %s option", opt); + } + } + + if (do_set3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, sizeof(buf)) != 0) + err(EX_OSERR, "nat64clat instance configuration failed"); +} + +/* + * Destroys nat64clat instance. + * Request: [ ipfw_obj_header ] + */ +static void +nat64clat_destroy(const char *name, uint8_t set) +{ + ipfw_obj_header oh; + + memset(&oh, 0, sizeof(oh)); + nat64clat_fill_ntlv(&oh.ntlv, name, set); + if (do_set3(IP_FW_NAT64CLAT_DESTROY, &oh.opheader, sizeof(oh)) != 0) + err(EX_OSERR, "failed to destroy nat instance %s", name); +} + +/* + * Get nat64clat instance statistics. + * Request: [ ipfw_obj_header ] + * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ] + */ +static int +nat64clat_get_stats(const char *name, uint8_t set, + struct ipfw_nat64clat_stats *stats) +{ + ipfw_obj_header *oh; + ipfw_obj_ctlv *oc; + size_t sz; + + sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats); + oh = calloc(1, sz); + nat64clat_fill_ntlv(&oh->ntlv, name, set); + if (do_get3(IP_FW_NAT64CLAT_STATS, &oh->opheader, &sz) == 0) { + oc = (ipfw_obj_ctlv *)(oh + 1); + memcpy(stats, oc + 1, sizeof(*stats)); + free(oh); + return (0); + } + free(oh); + return (-1); +} + +static void +nat64clat_stats(const char *name, uint8_t set) +{ + struct ipfw_nat64clat_stats stats; + + if (nat64clat_get_stats(name, set, &stats) != 0) + err(EX_OSERR, "Error retrieving stats"); + + if (co.use_set != 0 || set != 0) + printf("set %u ", set); + printf("nat64clat %s\n", name); + + printf("\t%ju packets translated from IPv6 to IPv4\n", + (uintmax_t)stats.opcnt64); + printf("\t%ju packets translated from IPv4 to IPv6\n", + (uintmax_t)stats.opcnt46); + printf("\t%ju IPv6 fragments created\n", + (uintmax_t)stats.ofrags); + printf("\t%ju IPv4 fragments received\n", + (uintmax_t)stats.ifrags); + printf("\t%ju output packets dropped due to no bufs, etc.\n", + (uintmax_t)stats.oerrors); + printf("\t%ju output packets discarded due to no IPv4 route\n", + (uintmax_t)stats.noroute4); + printf("\t%ju output packets discarded due to no IPv6 route\n", + (uintmax_t)stats.noroute6); + printf("\t%ju packets discarded due to unsupported protocol\n", + (uintmax_t)stats.noproto); + printf("\t%ju packets discarded due to memory allocation problems\n", + (uintmax_t)stats.nomem); + printf("\t%ju packets discarded due to some errors\n", + (uintmax_t)stats.dropped); +} + +/* + * Reset nat64clat instance statistics specified by @oh->ntlv. + * Request: [ ipfw_obj_header ] + */ +static void +nat64clat_reset_stats(const char *name, uint8_t set) +{ + ipfw_obj_header oh; + + memset(&oh, 0, sizeof(oh)); + nat64clat_fill_ntlv(&oh.ntlv, name, set); + if (do_set3(IP_FW_NAT64CLAT_RESET_STATS, &oh.opheader, sizeof(oh)) != 0) + err(EX_OSERR, "failed to reset stats for instance %s", name); +} + +static int +nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set) +{ + char plat_buf[INET6_ADDRSTRLEN], clat_buf[INET6_ADDRSTRLEN]; + + if (name != NULL && strcmp(cfg->name, name) != 0) + return (ESRCH); + + if (co.use_set != 0 && cfg->set != set) + return (ESRCH); + + if (co.use_set != 0 || cfg->set != 0) + printf("set %u ", cfg->set); + + inet_ntop(AF_INET6, &cfg->clat_prefix, clat_buf, sizeof(clat_buf)); + inet_ntop(AF_INET6, &cfg->plat_prefix, plat_buf, sizeof(plat_buf)); + printf("nat64clat %s clat_prefix %s/%u plat_prefix %s/%u", + cfg->name, clat_buf, cfg->clat_plen, plat_buf, cfg->plat_plen); + if (cfg->flags & NAT64_LOG) + printf(" log"); + if (cfg->flags & NAT64_ALLOW_PRIVATE) + printf(" allow_private"); + printf("\n"); + return (0); +} + +static int +nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set) +{ + + if (co.use_set != 0 && cfg->set != set) + return (ESRCH); + + nat64clat_destroy(cfg->name, cfg->set); + return (0); +} + + +/* + * Compare nat64clat instances names. + * Honor number comparison. + */ +static int +nat64name_cmp(const void *a, const void *b) +{ + ipfw_nat64clat_cfg *ca, *cb; + + ca = (ipfw_nat64clat_cfg *)a; + cb = (ipfw_nat64clat_cfg *)b; + + if (ca->set > cb->set) + return (1); + else if (ca->set < cb->set) + return (-1); + return (stringnum_cmp(ca->name, cb->name)); +} + +/* + * Retrieves nat64clat instance list from kernel, + * optionally sorts it and calls requested function for each instance. + * + * Request: [ ipfw_obj_lheader ] + * Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ] + */ +static int +nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set, int sort) +{ + ipfw_obj_lheader *olh; + ipfw_nat64clat_cfg *cfg; + size_t sz; + int i, error; + + /* Start with reasonable default */ + sz = sizeof(*olh) + 16 * sizeof(*cfg); + for (;;) { + if ((olh = calloc(1, sz)) == NULL) + return (ENOMEM); + + olh->size = sz; + if (do_get3(IP_FW_NAT64CLAT_LIST, &olh->opheader, &sz) != 0) { + sz = olh->size; + free(olh); + if (errno != ENOMEM) + return (errno); + continue; + } + + if (sort != 0) + qsort(olh + 1, olh->count, olh->objsize, + nat64name_cmp); + + cfg = (ipfw_nat64clat_cfg *)(olh + 1); + for (i = 0; i < olh->count; i++) { + error = f(cfg, name, set); /* Ignore errors for now */ + cfg = (ipfw_nat64clat_cfg *)((caddr_t)cfg + + olh->objsize); + } + free(olh); + break; + } + return (0); +} + diff --git a/sys/conf/files b/sys/conf/files index a667993df230..45968c43852c 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -4393,6 +4393,10 @@ netpfil/ipfw/ip_fw_iface.c optional inet ipfirewall netpfil/ipfw/ip_fw_nat.c optional inet ipfirewall_nat netpfil/ipfw/nat64/ip_fw_nat64.c optional inet inet6 ipfirewall \ ipfirewall_nat64 +netpfil/ipfw/nat64/nat64clat.c optional inet inet6 ipfirewall \ + ipfirewall_nat64 +netpfil/ipfw/nat64/nat64clat_control.c optional inet inet6 ipfirewall \ + ipfirewall_nat64 netpfil/ipfw/nat64/nat64lsn.c optional inet inet6 ipfirewall \ ipfirewall_nat64 netpfil/ipfw/nat64/nat64lsn_control.c optional inet inet6 ipfirewall \ diff --git a/sys/modules/ipfw_nat64/Makefile b/sys/modules/ipfw_nat64/Makefile index e6a3260d48ff..ee2ad7da15af 100644 --- a/sys/modules/ipfw_nat64/Makefile +++ b/sys/modules/ipfw_nat64/Makefile @@ -4,6 +4,7 @@ KMOD= ipfw_nat64 SRCS= ip_fw_nat64.c nat64_translate.c +SRCS+= nat64clat.c nat64clat_control.c SRCS+= nat64lsn.c nat64lsn_control.c SRCS+= nat64stl.c nat64stl_control.c diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 41351215f96e..de0cc29db1d2 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -134,6 +134,13 @@ typedef struct _ip_fw3_opheader { #define IP_FW_NPTV6_STATS 154 /* Get NPTv6 instance statistics */ #define IP_FW_NPTV6_RESET_STATS 155 /* Reset NPTv6 instance statistics */ +#define IP_FW_NAT64CLAT_CREATE 160 /* Create clat NAT64 instance */ +#define IP_FW_NAT64CLAT_DESTROY 161 /* Destroy clat NAT64 instance */ +#define IP_FW_NAT64CLAT_CONFIG 162 /* Modify clat NAT64 instance */ +#define IP_FW_NAT64CLAT_LIST 163 /* List clat NAT64 instances */ +#define IP_FW_NAT64CLAT_STATS 164 /* Get NAT64CLAT instance statistics */ +#define IP_FW_NAT64CLAT_RESET_STATS 165 /* Reset NAT64CLAT instance statistics */ + /* * The kernel representation of ipfw rules is made of a list of * 'instructions' (for all practical purposes equivalent to BPF diff --git a/sys/netinet6/ip_fw_nat64.h b/sys/netinet6/ip_fw_nat64.h index 390970a9cd37..47c0a70d167f 100644 --- a/sys/netinet6/ip_fw_nat64.h +++ b/sys/netinet6/ip_fw_nat64.h @@ -45,6 +45,19 @@ struct ipfw_nat64stl_stats { uint64_t dropped; /* dropped due to some errors */ }; +struct ipfw_nat64clat_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; + uint64_t noproto; /* Protocol not supported */ + uint64_t nomem; /* mbuf allocation failed */ + uint64_t dropped; /* dropped due to some errors */ +}; + struct ipfw_nat64lsn_stats { uint64_t opcnt64; /* 6to4 of packets translated */ uint64_t opcnt46; /* 4to6 of packets translated */ @@ -95,6 +108,17 @@ typedef struct _ipfw_nat64stl_cfg { uint32_t flags; } ipfw_nat64stl_cfg; +typedef struct _ipfw_nat64clat_cfg { + char name[64]; /* NAT name */ + struct in6_addr plat_prefix; /* NAT64 (PLAT) prefix */ + struct in6_addr clat_prefix; /* Client (CLAT) prefix */ + uint8_t plat_plen; /* PLAT Prefix length */ + uint8_t clat_plen; /* CLAT Prefix length */ + uint8_t set; /* Named instance set [0..31] */ + uint8_t spare; + uint32_t flags; +} ipfw_nat64clat_cfg; + /* * NAT64LSN default configuration values */ diff --git a/sys/netpfil/ipfw/nat64/ip_fw_nat64.c b/sys/netpfil/ipfw/nat64/ip_fw_nat64.c index a0a975ac6b86..ea5ff597a7bf 100644 --- a/sys/netpfil/ipfw/nat64/ip_fw_nat64.c +++ b/sys/netpfil/ipfw/nat64/ip_fw_nat64.c @@ -86,9 +86,15 @@ vnet_ipfw_nat64_init(const void *arg __unused) error = nat64stl_init(ch, first); if (error != 0) return (error); + error = nat64clat_init(ch, first); + if (error != 0) { + nat64stl_uninit(ch, first); + return (error); + } error = nat64lsn_init(ch, first); if (error != 0) { nat64stl_uninit(ch, first); + nat64clat_uninit(ch, first); return (error); } return (0); @@ -103,6 +109,7 @@ vnet_ipfw_nat64_uninit(const void *arg __unused) ch = &V_layer3_chain; last = IS_DEFAULT_VNET(curvnet) ? 1: 0; nat64stl_uninit(ch, last); + nat64clat_uninit(ch, last); nat64lsn_uninit(ch, last); return (0); } diff --git a/sys/netpfil/ipfw/nat64/ip_fw_nat64.h b/sys/netpfil/ipfw/nat64/ip_fw_nat64.h index 11b60b873675..d6f38405cfba 100644 --- a/sys/netpfil/ipfw/nat64/ip_fw_nat64.h +++ b/sys/netpfil/ipfw/nat64/ip_fw_nat64.h @@ -54,5 +54,7 @@ int nat64stl_init(struct ip_fw_chain *ch, int first); void nat64stl_uninit(struct ip_fw_chain *ch, int last); int nat64lsn_init(struct ip_fw_chain *ch, int first); void nat64lsn_uninit(struct ip_fw_chain *ch, int last); +int nat64clat_init(struct ip_fw_chain *ch, int first); +void nat64clat_uninit(struct ip_fw_chain *ch, int last); #endif /* _IP_FW_NAT64_H_ */ diff --git a/sys/netpfil/ipfw/nat64/nat64clat.c b/sys/netpfil/ipfw/nat64/nat64clat.c new file mode 100644 index 000000000000..fcc922726d02 --- /dev/null +++ b/sys/netpfil/ipfw/nat64/nat64clat.c @@ -0,0 +1,255 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Yandex LLC + * Copyright (c) 2019 Andrey V. Elsukov + * Copyright (c) 2019 Boris N. Lytochkin + * + * 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 "nat64clat.h" + +#define NAT64_LOOKUP(chain, cmd) \ + (struct nat64clat_cfg *)SRV_OBJECT((chain), (cmd)->arg1) + +static void +nat64clat_log(struct pfloghdr *plog, struct mbuf *m, sa_family_t family, + uint32_t kidx) +{ + static uint32_t pktid = 0; + + memset(plog, 0, sizeof(*plog)); + plog->length = PFLOG_REAL_HDRLEN; + plog->af = family; + plog->action = PF_NAT; + plog->dir = PF_IN; + plog->rulenr = htonl(kidx); + pktid++; + plog->subrulenr = htonl(pktid); + plog->ruleset[0] = '\0'; + strlcpy(plog->ifname, "NAT64CLAT", sizeof(plog->ifname)); + ipfw_bpf_mtap2(plog, PFLOG_HDRLEN, m); +} + +static int +nat64clat_handle_ip4(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg, + struct mbuf *m) +{ + struct pfloghdr loghdr, *logdata; + struct in6_addr saddr, daddr; + struct ip *ip; + + ip = mtod(m, struct ip*); + /* source address for CLAT may be private with no harm */ + if (nat64_check_ip4(ip->ip_src.s_addr) != 0 || + nat64_check_ip4(ip->ip_dst.s_addr) != 0 || + nat64_check_private_ip4(&cfg->base, ip->ip_dst.s_addr) != 0) + return (NAT64SKIP); + + memcpy(&saddr, &cfg->base.clat_prefix, sizeof(saddr)); + nat64_embed_ip4(&saddr, cfg->base.clat_plen, ip->ip_src.s_addr); + memcpy(&daddr, &cfg->base.plat_prefix, sizeof(daddr)); + nat64_embed_ip4(&daddr, cfg->base.plat_plen, ip->ip_dst.s_addr); + if (cfg->base.flags & NAT64_LOG) { + logdata = &loghdr; + nat64clat_log(logdata, m, AF_INET, cfg->no.kidx); + } else + logdata = NULL; + return (nat64_do_handle_ip4(m, &saddr, &daddr, 0, &cfg->base, + logdata)); +} + +static int +nat64clat_handle_ip6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg, + struct mbuf *m) +{ + struct pfloghdr loghdr, *logdata; + struct ip6_hdr *ip6; + uint32_t aaddr; + + /* + * NOTE: 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 *); + /* Check ip6_dst matches configured prefix */ + if (memcmp(&ip6->ip6_dst, &cfg->base.clat_prefix, + cfg->base.clat_plen / 8) != 0) + return (NAT64SKIP); + /* Check ip6_src matches configured prefix */ + if (memcmp(&ip6->ip6_src, &cfg->base.plat_prefix, + cfg->base.plat_plen / 8) != 0) + return (NAT64SKIP); + + if (cfg->base.flags & NAT64_LOG) { + logdata = &loghdr; + nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx); + } else + logdata = NULL; + + aaddr = nat64_extract_ip4(&ip6->ip6_src, cfg->base.plat_plen); + return (nat64_do_handle_ip6(m, aaddr, 0, &cfg->base, logdata)); +} + +static int +nat64clat_handle_icmp6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg, + struct mbuf *m) +{ + struct pfloghdr loghdr, *logdata; + struct nat64_counters *stats; + struct ip6_hdr *ip6i; + struct icmp6_hdr *icmp6; + uint32_t daddr; + int hlen, proto; + + hlen = 0; + stats = &cfg->base.stats; + proto = nat64_getlasthdr(m, &hlen); + if (proto != IPPROTO_ICMPV6) { + NAT64STAT_INC(stats, dropped); + return (NAT64MFREE); + } + icmp6 = mtodo(m, hlen); + switch (icmp6->icmp6_type) { + case ICMP6_DST_UNREACH: + case ICMP6_PACKET_TOO_BIG: + case ICMP6_TIME_EXCEED_TRANSIT: + case ICMP6_PARAM_PROB: + break; + default: + NAT64STAT_INC(stats, dropped); + return (NAT64MFREE); + } + hlen += sizeof(struct icmp6_hdr); + if (m->m_pkthdr.len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN) { + NAT64STAT_INC(stats, dropped); + return (NAT64MFREE); + } + if (m->m_len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN) + m = m_pullup(m, hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN); + if (m == NULL) { + NAT64STAT_INC(stats, nomem); + return (NAT64RETURN); + } + /* + * Use destination address from inner IPv6 header to determine + * IPv4 mapped address. + */ + ip6i = mtodo(m, hlen); + daddr = nat64_extract_ip4(&ip6i->ip6_dst, cfg->base.clat_plen); + if (daddr == 0) { + NAT64STAT_INC(stats, dropped); + return (NAT64MFREE); + } + if (cfg->base.flags & NAT64_LOG) { + logdata = &loghdr; + nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx); + } else + logdata = NULL; + return (nat64_handle_icmp6(m, 0, daddr, 0, &cfg->base, logdata)); +} + +int +ipfw_nat64clat(struct ip_fw_chain *chain, struct ip_fw_args *args, + ipfw_insn *cmd, int *done) +{ + ipfw_insn *icmd; + struct nat64clat_cfg *cfg; + int ret; + + IPFW_RLOCK_ASSERT(chain); + + *done = 0; /* try next rule if not matched */ + icmd = cmd + 1; + if (cmd->opcode != O_EXTERNAL_ACTION || + cmd->arg1 != V_nat64clat_eid || + icmd->opcode != O_EXTERNAL_INSTANCE || + (cfg = NAT64_LOOKUP(chain, icmd)) == NULL) + return (0); + + switch (args->f_id.addr_type) { + case 4: + ret = nat64clat_handle_ip4(chain, cfg, args->m); + break; + case 6: + ret = nat64clat_handle_ip6(chain, cfg, args->m); + break; + default: + return (0); + } + + if (ret == NAT64SKIP) { + /* + * In case when packet is ICMPv6 message from an intermediate + * router, the source address of message will not match the + * addresses from configured prefixes. + */ + if (args->f_id.proto != IPPROTO_ICMPV6) + return (0); + + ret = nat64clat_handle_icmp6(chain, cfg, args->m); + } + + if (ret == NAT64SKIP) + return (0); + + *done = 1; /* terminate the search */ + if (ret == NAT64MFREE) + m_freem(args->m); + + args->m = NULL; + return (IP_FW_NAT64); +} diff --git a/sys/netpfil/ipfw/nat64/nat64clat.h b/sys/netpfil/ipfw/nat64/nat64clat.h new file mode 100644 index 000000000000..70ca5ce5beb6 --- /dev/null +++ b/sys/netpfil/ipfw/nat64/nat64clat.h @@ -0,0 +1,54 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Yandex LLC + * Copyright (c) 2019 Andrey V. Elsukov + * Copyright (c) 2019 Boris N. Lytochkin + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _IP_FW_NAT64CLAT_H_ +#define _IP_FW_NAT64CLAT_H_ + +#include "ip_fw_nat64.h" +#include "nat64_translate.h" + +struct nat64clat_cfg { + struct named_object no; + + struct nat64_config base; +#define NAT64CLAT_FLAGSMASK \ + (NAT64_LOG | NAT64_ALLOW_PRIVATE) /* flags to pass to userland */ + char name[64]; +}; + +VNET_DECLARE(uint16_t, nat64clat_eid); +#define V_nat64clat_eid VNET(nat64clat_eid) +#define IPFW_TLV_NAT64CLAT_NAME IPFW_TLV_EACTION_NAME(V_nat64clat_eid) + +int ipfw_nat64clat(struct ip_fw_chain *chain, struct ip_fw_args *args, + ipfw_insn *cmd, int *done); + +#endif diff --git a/sys/netpfil/ipfw/nat64/nat64clat_control.c b/sys/netpfil/ipfw/nat64/nat64clat_control.c new file mode 100644 index 000000000000..2f6ac812bdd4 --- /dev/null +++ b/sys/netpfil/ipfw/nat64/nat64clat_control.c @@ -0,0 +1,614 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Yandex LLC + * Copyright (c) 2019 Andrey V. Elsukov + * Copyright (c) 2019 Boris N. Lytochkin + * + * 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 "nat64clat.h" + +VNET_DEFINE(uint16_t, nat64clat_eid) = 0; + +static struct nat64clat_cfg *nat64clat_alloc_config(const char *name, + uint8_t set); +static void nat64clat_free_config(struct nat64clat_cfg *cfg); +static struct nat64clat_cfg *nat64clat_find(struct namedobj_instance *ni, + const char *name, uint8_t set); + +static struct nat64clat_cfg * +nat64clat_alloc_config(const char *name, uint8_t set) +{ + struct nat64clat_cfg *cfg; + + cfg = malloc(sizeof(struct nat64clat_cfg), M_IPFW, M_WAITOK | M_ZERO); + COUNTER_ARRAY_ALLOC(cfg->base.stats.cnt, NAT64STATS, M_WAITOK); + cfg->no.name = cfg->name; + cfg->no.etlv = IPFW_TLV_NAT64CLAT_NAME; + cfg->no.set = set; + strlcpy(cfg->name, name, sizeof(cfg->name)); + return (cfg); +} + +static void +nat64clat_free_config(struct nat64clat_cfg *cfg) +{ + + COUNTER_ARRAY_FREE(cfg->base.stats.cnt, NAT64STATS); + free(cfg, M_IPFW); +} + +static void +nat64clat_export_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg, + ipfw_nat64clat_cfg *uc) +{ + uc->plat_prefix = cfg->base.plat_prefix; + uc->plat_plen = cfg->base.plat_plen; + uc->clat_prefix = cfg->base.clat_prefix; + uc->clat_plen = cfg->base.clat_plen; + uc->flags = cfg->base.flags & NAT64CLAT_FLAGSMASK; + uc->set = cfg->no.set; + strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); +} + +struct nat64clat_dump_arg { + struct ip_fw_chain *ch; + struct sockopt_data *sd; +}; + +static int +export_config_cb(struct namedobj_instance *ni, struct named_object *no, + void *arg) +{ + struct nat64clat_dump_arg *da = (struct nat64clat_dump_arg *)arg; + ipfw_nat64clat_cfg *uc; + + uc = (ipfw_nat64clat_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); + nat64clat_export_config(da->ch, (struct nat64clat_cfg *)no, uc); + return (0); +} + +static struct nat64clat_cfg * +nat64clat_find(struct namedobj_instance *ni, const char *name, uint8_t set) +{ + struct nat64clat_cfg *cfg; + + cfg = (struct nat64clat_cfg *)ipfw_objhash_lookup_name_type(ni, set, + IPFW_TLV_NAT64CLAT_NAME, name); + + return (cfg); +} + +/* + * Creates new consumer-side nat64 translator instance. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ] + * + * Returns 0 on success + */ +static int +nat64clat_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_lheader *olh; + ipfw_nat64clat_cfg *uc; + struct namedobj_instance *ni; + struct nat64clat_cfg *cfg; + + if (sd->valsize != sizeof(*olh) + sizeof(*uc)) + return (EINVAL); + + olh = (ipfw_obj_lheader *)sd->kbuf; + uc = (ipfw_nat64clat_cfg *)(olh + 1); + + if (ipfw_check_object_name_generic(uc->name) != 0) + return (EINVAL); + + if (uc->set >= IPFW_MAX_SETS || + nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0 || + nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0) + return (EINVAL); + + ni = CHAIN_TO_SRV(ch); + + IPFW_UH_RLOCK(ch); + if (nat64clat_find(ni, uc->name, uc->set) != NULL) { + IPFW_UH_RUNLOCK(ch); + return (EEXIST); + } + IPFW_UH_RUNLOCK(ch); + + cfg = nat64clat_alloc_config(uc->name, uc->set); + cfg->base.plat_prefix = uc->plat_prefix; + cfg->base.plat_plen = uc->plat_plen; + cfg->base.clat_prefix = uc->clat_prefix; + cfg->base.clat_plen = uc->clat_plen; + cfg->base.flags = (uc->flags & NAT64CLAT_FLAGSMASK) | + NAT64_CLATPFX | NAT64_PLATPFX; + if (IN6_IS_ADDR_WKPFX(&cfg->base.plat_prefix)) + cfg->base.flags |= NAT64_WKPFX; + + IPFW_UH_WLOCK(ch); + + if (nat64clat_find(ni, uc->name, uc->set) != NULL) { + IPFW_UH_WUNLOCK(ch); + nat64clat_free_config(cfg); + return (EEXIST); + } + + if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) { + IPFW_UH_WUNLOCK(ch); + nat64clat_free_config(cfg); + return (ENOSPC); + } + ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no); + /* Okay, let's link data */ + SRV_OBJECT(ch, cfg->no.kidx) = cfg; + IPFW_UH_WUNLOCK(ch); + + return (0); +} + +/* + * Change existing nat64clat instance configuration. + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ipfw_nat64clat_cfg ] + * Reply: [ ipfw_obj_header ipfw_nat64clat_cfg ] + * + * Returns 0 on success + */ +static int +nat64clat_config(struct ip_fw_chain *ch, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + ipfw_obj_header *oh; + ipfw_nat64clat_cfg *uc; + struct nat64clat_cfg *cfg; + struct namedobj_instance *ni; + uint32_t flags; + + if (sd->valsize != sizeof(*oh) + sizeof(*uc)) + return (EINVAL); + + oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd, + sizeof(*oh) + sizeof(*uc)); + uc = (ipfw_nat64clat_cfg *)(oh + 1); + + if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || + oh->ntlv.set >= IPFW_MAX_SETS) + return (EINVAL); + + ni = CHAIN_TO_SRV(ch); + if (sd->sopt->sopt_dir == SOPT_GET) { + IPFW_UH_RLOCK(ch); + cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_RUNLOCK(ch); + return (ENOENT); + } + nat64clat_export_config(ch, cfg, uc); + IPFW_UH_RUNLOCK(ch); + return (0); + } + + IPFW_UH_WLOCK(ch); + cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_WUNLOCK(ch); + return (ENOENT); + } + + /* + * For now allow to change only following values: + * plat_prefix, plat_plen, clat_prefix, clat_plen, flags. + */ + flags = 0; + if (uc->plat_plen != cfg->base.plat_plen || + !IN6_ARE_ADDR_EQUAL(&uc->plat_prefix, &cfg->base.plat_prefix)) { + if (nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0) { + IPFW_UH_WUNLOCK(ch); + return (EINVAL); + } + flags |= NAT64_PLATPFX; + } + + if (uc->clat_plen != cfg->base.clat_plen || + !IN6_ARE_ADDR_EQUAL(&uc->clat_prefix, &cfg->base.clat_prefix)) { + if (nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0) { + IPFW_UH_WUNLOCK(ch); + return (EINVAL); + } + flags |= NAT64_CLATPFX; + } + + if (flags != 0) { + IPFW_WLOCK(ch); + if (flags & NAT64_PLATPFX) { + cfg->base.plat_prefix = uc->plat_prefix; + cfg->base.plat_plen = uc->plat_plen; + } + if (flags & NAT64_CLATPFX) { + cfg->base.clat_prefix = uc->clat_prefix; + cfg->base.clat_plen = uc->clat_plen; + } + IPFW_WUNLOCK(ch); + } + + cfg->base.flags &= ~NAT64CLAT_FLAGSMASK; + cfg->base.flags |= uc->flags & NAT64CLAT_FLAGSMASK; + + IPFW_UH_WUNLOCK(ch); + return (0); +} + +static void +nat64clat_detach_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg) +{ + + IPFW_UH_WLOCK_ASSERT(ch); + + ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); + ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); +} + +/* + * Destroys nat64 instance. + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ] + * + * Returns 0 on success + */ +static int +nat64clat_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_header *oh; + struct nat64clat_cfg *cfg; + + if (sd->valsize != sizeof(*oh)) + return (EINVAL); + + oh = (ipfw_obj_header *)sd->kbuf; + if (ipfw_check_object_name_generic(oh->ntlv.name) != 0) + return (EINVAL); + + IPFW_UH_WLOCK(ch); + cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_WUNLOCK(ch); + return (ENOENT); + } + if (cfg->no.refcnt > 0) { + IPFW_UH_WUNLOCK(ch); + return (EBUSY); + } + + ipfw_reset_eaction_instance(ch, V_nat64clat_eid, cfg->no.kidx); + SRV_OBJECT(ch, cfg->no.kidx) = NULL; + nat64clat_detach_config(ch, cfg); + IPFW_UH_WUNLOCK(ch); + + nat64clat_free_config(cfg); + return (0); +} + +/* + * Lists all nat64clat instances currently available in kernel. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ] + * Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ] + * + * Returns 0 on success + */ +static int +nat64clat_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_lheader *olh; + struct nat64clat_dump_arg da; + + /* Check minimum header size */ + if (sd->valsize < sizeof(ipfw_obj_lheader)) + return (EINVAL); + + olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh)); + + IPFW_UH_RLOCK(ch); + olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch), + IPFW_TLV_NAT64CLAT_NAME); + olh->objsize = sizeof(ipfw_nat64clat_cfg); + olh->size = sizeof(*olh) + olh->count * olh->objsize; + + if (sd->valsize < olh->size) { + IPFW_UH_RUNLOCK(ch); + return (ENOMEM); + } + memset(&da, 0, sizeof(da)); + da.ch = ch; + da.sd = sd; + ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb, + &da, IPFW_TLV_NAT64CLAT_NAME); + IPFW_UH_RUNLOCK(ch); + + return (0); +} + +#define __COPY_STAT_FIELD(_cfg, _stats, _field) \ + (_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field) +static void +export_stats(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg, + struct ipfw_nat64clat_stats *stats) +{ + + __COPY_STAT_FIELD(cfg, stats, opcnt64); + __COPY_STAT_FIELD(cfg, stats, opcnt46); + __COPY_STAT_FIELD(cfg, stats, ofrags); + __COPY_STAT_FIELD(cfg, stats, ifrags); + __COPY_STAT_FIELD(cfg, stats, oerrors); + __COPY_STAT_FIELD(cfg, stats, noroute4); + __COPY_STAT_FIELD(cfg, stats, noroute6); + __COPY_STAT_FIELD(cfg, stats, noproto); + __COPY_STAT_FIELD(cfg, stats, nomem); + __COPY_STAT_FIELD(cfg, stats, dropped); +} + +/* + * Get nat64clat statistics. + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ] + * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]] + * + * Returns 0 on success + */ +static int +nat64clat_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + struct ipfw_nat64clat_stats stats; + struct nat64clat_cfg *cfg; + ipfw_obj_header *oh; + ipfw_obj_ctlv *ctlv; + size_t sz; + + sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats); + if (sd->valsize % sizeof(uint64_t)) + return (EINVAL); + if (sd->valsize < sz) + return (ENOMEM); + oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz); + if (oh == NULL) + return (EINVAL); + memset(&stats, 0, sizeof(stats)); + + IPFW_UH_RLOCK(ch); + cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_RUNLOCK(ch); + return (ENOENT); + } + export_stats(ch, cfg, &stats); + IPFW_UH_RUNLOCK(ch); + + ctlv = (ipfw_obj_ctlv *)(oh + 1); + memset(ctlv, 0, sizeof(*ctlv)); + ctlv->head.type = IPFW_TLV_COUNTERS; + ctlv->head.length = sz - sizeof(ipfw_obj_header); + ctlv->count = sizeof(stats) / sizeof(uint64_t); + ctlv->objsize = sizeof(uint64_t); + ctlv->version = IPFW_NAT64_VERSION; + memcpy(ctlv + 1, &stats, sizeof(stats)); + return (0); +} + +/* + * Reset nat64clat statistics. + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ] + * + * Returns 0 on success + */ +static int +nat64clat_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + struct nat64clat_cfg *cfg; + ipfw_obj_header *oh; + + if (sd->valsize != sizeof(*oh)) + return (EINVAL); + oh = (ipfw_obj_header *)sd->kbuf; + if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 || + oh->ntlv.set >= IPFW_MAX_SETS) + return (EINVAL); + + IPFW_UH_WLOCK(ch); + cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_WUNLOCK(ch); + return (ENOENT); + } + COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS); + IPFW_UH_WUNLOCK(ch); + return (0); +} + +static struct ipfw_sopt_handler scodes[] = { + + { IP_FW_NAT64CLAT_CREATE, 0, HDIR_SET, nat64clat_create }, + { IP_FW_NAT64CLAT_DESTROY,0, HDIR_SET, nat64clat_destroy }, + { IP_FW_NAT64CLAT_CONFIG, 0, HDIR_BOTH, nat64clat_config }, + { IP_FW_NAT64CLAT_LIST, 0, HDIR_GET, nat64clat_list }, + { IP_FW_NAT64CLAT_STATS, 0, HDIR_GET, nat64clat_stats }, + { IP_FW_NAT64CLAT_RESET_STATS,0, HDIR_SET, nat64clat_reset_stats }, +}; + +static int +nat64clat_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + ipfw_insn *icmd; + + icmd = cmd - 1; + if (icmd->opcode != O_EXTERNAL_ACTION || + icmd->arg1 != V_nat64clat_eid) + return (1); + + *puidx = cmd->arg1; + *ptype = 0; + return (0); +} + +static void +nat64clat_update_arg1(ipfw_insn *cmd, uint16_t idx) +{ + + cmd->arg1 = idx; +} + +static int +nat64clat_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, + struct named_object **pno) +{ + int err; + + err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, + IPFW_TLV_NAT64CLAT_NAME, pno); + return (err); +} + +static struct named_object * +nat64clat_findbykidx(struct ip_fw_chain *ch, uint16_t idx) +{ + struct namedobj_instance *ni; + struct named_object *no; + + IPFW_UH_WLOCK_ASSERT(ch); + ni = CHAIN_TO_SRV(ch); + no = ipfw_objhash_lookup_kidx(ni, idx); + KASSERT(no != NULL, ("NAT with index %d not found", idx)); + + return (no); +} + +static int +nat64clat_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set, + enum ipfw_sets_cmd cmd) +{ + + return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64CLAT_NAME, + set, new_set, cmd)); +} + +static struct opcode_obj_rewrite opcodes[] = { + { + .opcode = O_EXTERNAL_INSTANCE, + .etlv = IPFW_TLV_EACTION /* just show it isn't table */, + .classifier = nat64clat_classify, + .update = nat64clat_update_arg1, + .find_byname = nat64clat_findbyname, + .find_bykidx = nat64clat_findbykidx, + .manage_sets = nat64clat_manage_sets, + }, +}; + +static int +destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, + void *arg) +{ + struct nat64clat_cfg *cfg; + struct ip_fw_chain *ch; + + ch = (struct ip_fw_chain *)arg; + cfg = (struct nat64clat_cfg *)SRV_OBJECT(ch, no->kidx); + SRV_OBJECT(ch, no->kidx) = NULL; + nat64clat_detach_config(ch, cfg); + nat64clat_free_config(cfg); + return (0); +} + +int +nat64clat_init(struct ip_fw_chain *ch, int first) +{ + + V_nat64clat_eid = ipfw_add_eaction(ch, ipfw_nat64clat, "nat64clat"); + if (V_nat64clat_eid == 0) + return (ENXIO); + IPFW_ADD_SOPT_HANDLER(first, scodes); + IPFW_ADD_OBJ_REWRITER(first, opcodes); + return (0); +} + +void +nat64clat_uninit(struct ip_fw_chain *ch, int last) +{ + + IPFW_DEL_OBJ_REWRITER(last, opcodes); + IPFW_DEL_SOPT_HANDLER(last, scodes); + ipfw_del_eaction(ch, V_nat64clat_eid); + /* + * Since we already have deregistered external action, + * our named objects become unaccessible via rules, because + * all rules were truncated by ipfw_del_eaction(). + * So, we can unlink and destroy our named objects without holding + * IPFW_WLOCK(). + */ + IPFW_UH_WLOCK(ch); + ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, + IPFW_TLV_NAT64CLAT_NAME); + V_nat64clat_eid = 0; + IPFW_UH_WUNLOCK(ch); +} +