Index: sbin/ipfw/Makefile =================================================================== --- sbin/ipfw/Makefile +++ sbin/ipfw/Makefile @@ -5,6 +5,7 @@ PACKAGE=ipfw PROG= ipfw SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c +SRCS+= nptv6.c WARNS?= 2 .if ${MK_PF} != "no" Index: sbin/ipfw/ipfw.8 =================================================================== --- sbin/ipfw/ipfw.8 +++ sbin/ipfw/ipfw.8 @@ -113,6 +113,19 @@ .Oc .Oc .Ar pathname +.Ss IPv6-to-IPv6 NETWORK PREFIX TRANSLATION +.Nm +.Oo Cm set Ar N Oc Cm nptv6 Ar name Cm create Ar create-options +.Nm +.Oo Cm set Ar N Oc Cm nptv6 +.Brq Ar name | all +.Brq Cm list | show +.Nm +.Oo Cm set Ar N Oc Cm nptv6 +.Brq Ar name | all +.Cm destroy +.Nm +.Oo Cm set Ar N Oc Cm nptv6 Ar name Cm stats .Ss INTERNAL DIAGNOSTICS .Nm .Cm internal iflist @@ -813,6 +826,11 @@ see the .Sx NETWORK ADDRESS TRANSLATION (NAT) Section for further information. +.It Cm nptv6 Ar name +Pass packet to a NPTv6 instance (for IPv6-to-IPv6 network prefix translation): +see the +.Sx IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6) +Section for further information. .It Cm pipe Ar pipe_nr Pass packet to a .Nm dummynet @@ -2885,6 +2903,41 @@ See .Sx SYSCTL VARIABLES for more info. +.Sh IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6) +.Nm +support in-kernel IPv6-to-IPv6 network prefix translation as described +in RFC6296. +The kernel module +.Cm ipfw_nptv6 +should be loaded or kernel should has +.Cm options IPFIREWALL_NPTV6 +to be able use NPTv6 translator. +.Pp +The NPTv6 configuration command is the following: +.Bd -ragged -offset indent +.Bk -words +.Cm nptv6 +.Ar name +.Cm create +.Ar create-options +.Ek +.Ed +.Pp +The following parameters can be configured: +.Bl -tag -width indent +.It Cm int_prefix Ar ipv6_prefix +IPv6 prefix used in internal network. +NPTv6 module translates source address when it matches this prefix. +.It Cm ext_prefix Ar ipv6_prefix +IPv6 prefix used in external network. +NPTv6 module translates destination address when it matches this prefix. +.It Cm prefixlen Ar length +The length of specified IPv6 prefixes. It must be in range from 8 to 64. +.El +.Pp +To let the packet continue after being translated, set the sysctl variable +.Va net.inet.ip.fw.one_pass +to 0. .Sh LOADER TUNABLES Tunables can be set in .Xr loader 8 Index: sbin/ipfw/ipfw2.h =================================================================== --- sbin/ipfw/ipfw2.h +++ sbin/ipfw/ipfw2.h @@ -229,6 +229,13 @@ TOK_UNLOCK, TOK_VLIST, TOK_OLIST, + TOK_STATS, + + /* NPTv6 tokens */ + TOK_NPTV6, + TOK_INTPREFIX, + TOK_EXTPREFIX, + TOK_PREFIXLEN, }; /* @@ -315,6 +322,7 @@ 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_nptv6_handler(int ac, char *av[]); int ipfw_check_object_name(const char *name); #ifdef PF Index: sbin/ipfw/ipfw2.c =================================================================== --- sbin/ipfw/ipfw2.c +++ sbin/ipfw/ipfw2.c @@ -235,6 +235,7 @@ }; static struct _s_x rule_eactions[] = { + { "nptv6", TOK_NPTV6 }, { NULL, 0 } /* terminator */ }; Index: sbin/ipfw/main.c =================================================================== --- sbin/ipfw/main.c +++ sbin/ipfw/main.c @@ -425,6 +425,8 @@ if (co.use_set || try_next) { if (_substrcmp(*av, "delete") == 0) ipfw_delete(av); + else if (!strncmp(*av, "nptv6", strlen(*av))) + ipfw_nptv6_handler(ac, av); else if (_substrcmp(*av, "flush") == 0) ipfw_flush(co.do_force); else if (_substrcmp(*av, "zero") == 0) Index: sbin/ipfw/nptv6.c =================================================================== --- /dev/null +++ sbin/ipfw/nptv6.c @@ -0,0 +1,393 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 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 "ipfw2.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set); +static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, + int sort); + +static void nptv6_create(const char *name, uint8_t set, int ac, char **av); +static void nptv6_destroy(const char *name, uint8_t set); +static void nptv6_stats(const char *name, uint8_t set); +static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); +static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set); + +static struct _s_x nptv6cmds[] = { + { "create", TOK_CREATE }, + { "destroy", TOK_DESTROY }, + { "list", TOK_LIST }, + { "show", TOK_LIST }, + { "stats", TOK_STATS }, + { NULL, 0 } +}; + +/* + * This one handles all NPTv6-related commands + * ipfw [set N] nptv6 NAME {create | config} ... + * ipfw [set N] nptv6 NAME stats + * ipfw [set N] nptv6 {NAME | all} destroy + * ipfw [set N] nptv6 {NAME | all} {list | show} + */ +void +ipfw_nptv6_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("nptv6 needs instance name"); + name = *av; + if (ipfw_check_object_name(name) != 0) { + if (strcmp(name, "all") == 0) { + name = NULL; + } else + errx(EX_USAGE, "nptv6 instance name %s is invalid", + name); + } + ac--; av++; + NEED1("nptv6 needs command"); + + tcmd = get_token(nptv6cmds, *av, "nptv6 command"); + if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) + errx(EX_USAGE, "nptv6 instance name required"); + switch (tcmd) { + case TOK_CREATE: + ac--; av++; + nptv6_create(name, set, ac, av); + break; + case TOK_LIST: + nptv6_foreach(nptv6_show_cb, name, set, 1); + break; + case TOK_DESTROY: + if (name == NULL) + nptv6_foreach(nptv6_destroy_cb, NULL, set, 0); + else + nptv6_destroy(name, set); + break; + case TOK_STATS: + nptv6_stats(name, set); + } +} + + +static void +nptv6_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 nptv6newcmds[] = { + { "int_prefix", TOK_INTPREFIX }, + { "ext_prefix", TOK_EXTPREFIX }, + { "prefixlen", TOK_PREFIXLEN }, + { NULL, 0 } +}; + + +static void +nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len) +{ + char *p, *l; + + p = strdup(arg); + if (p == NULL) + err(EX_OSERR, NULL); + if ((l = strchr(p, '/')) != NULL) + *l++ = '\0'; + if (inet_pton(AF_INET6, p, prefix) != 1) + errx(EX_USAGE, "Bad prefix: %s", p); + if (l != NULL) { + *len = (int)strtol(l, &l, 10); + if (*l != '\0' || *len <= 0 || *len > 64) + errx(EX_USAGE, "Bad prefix length: %s", arg); + } + free(p); +} +/* + * Creates new nptv6 instance + * ipfw nptv6 create int_prefix ext_prefix + * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ] + */ +#define NPTV6_HAS_INTPREFIX 0x01 +#define NPTV6_HAS_EXTPREFIX 0x02 +#define NPTV6_HAS_PREFIXLEN 0x04 +static void +nptv6_create(const char *name, uint8_t set, int ac, char *av[]) +{ + char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)]; + struct in6_addr mask; + ipfw_nptv6_cfg *cfg; + ipfw_obj_lheader *olh; + int tcmd, flags, plen; + char *p; + + memset(buf, 0, sizeof(buf)); + olh = (ipfw_obj_lheader *)buf; + cfg = (ipfw_nptv6_cfg *)(olh + 1); + cfg->set = set; + flags = 0; + while (ac > 0) { + tcmd = get_token(nptv6newcmds, *av, "option"); + ac--; av++; + + switch (tcmd) { + case TOK_INTPREFIX: + NEED1("IPv6 prefix required"); + plen = 0; + nptv6_parse_prefix(*av, &cfg->internal, &plen); + flags |= NPTV6_HAS_INTPREFIX; + if (plen > 0) + goto check_prefix; + ac--; av++; + break; + case TOK_EXTPREFIX: + NEED1("IPv6 prefix required"); + plen = 0; + nptv6_parse_prefix(*av, &cfg->external, &plen); + flags |= NPTV6_HAS_EXTPREFIX; + if (plen > 0) + goto check_prefix; + ac--; av++; + break; + case TOK_PREFIXLEN: + NEED1("IPv6 prefix length required"); + plen = strtol(*av, &p, 10); +check_prefix: + if (p != NULL || plen < 8 || plen > 64) + errx(EX_USAGE, "wrong prefix length: %s", *av); + if (cfg->plen > 0 && cfg->plen > plen) + warn("Prefix length extended up to %d", plen); + cfg->plen = plen; + flags |= NPTV6_HAS_PREFIXLEN; + ac--; av++; + break; + } + } + + /* Check validness */ + if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX) + errx(EX_USAGE, "int_prefix required"); + if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX) + errx(EX_USAGE, "ext_prefix required"); + if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN) + errx(EX_USAGE, "prefixlen required"); + + n2mask(&mask, cfg->plen); + APPLY_MASK(&cfg->internal, &mask); + APPLY_MASK(&cfg->external, &mask); + + olh->count = 1; + olh->objsize = sizeof(*cfg); + olh->size = sizeof(buf); + strlcpy(cfg->name, name, sizeof(cfg->name)); + if (do_set3(IP_FW_NPTV6_CREATE, &olh->opheader, sizeof(buf)) != 0) + err(EX_OSERR, "nptv6 instance creation failed"); +} + +/* + * Destroys NPTv6 instance. + * Request: [ ipfw_obj_header ] + */ +static void +nptv6_destroy(const char *name, uint8_t set) +{ + ipfw_obj_header oh; + + memset(&oh, 0, sizeof(oh)); + nptv6_fill_ntlv(&oh.ntlv, name, set); + if (do_set3(IP_FW_NPTV6_DESTROY, &oh.opheader, sizeof(oh)) != 0) + err(EX_OSERR, "failed to destroy nat instance %s", name); +} + +/* + * Get NPTv6 instance statistics. + * Request: [ ipfw_obj_header ] + * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ] + */ +static int +nptv6_get_stats(const char *name, uint8_t set, struct ipfw_nptv6_stats *stats) +{ + ipfw_obj_header *oh; + ipfw_obj_ctlv *oc; + size_t sz; + + sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats); + oh = calloc(1, sz); + nptv6_fill_ntlv(&oh->ntlv, name, set); + if (do_get3(IP_FW_NPTV6_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 +nptv6_stats(const char *name, uint8_t set) +{ + struct ipfw_nptv6_stats stats; + + if (nptv6_get_stats(name, set, &stats) != 0) + err(EX_OSERR, "Error retrieving stats"); + + printf("Number of packets translated (internal to external): %ju\n", + (uintmax_t)stats.in2ex); + printf("Number of packets translated (external to internal): %ju\n", + (uintmax_t)stats.ex2in); + printf("Number of packets dropped due to some error: %ju\n", + (uintmax_t)stats.dropped); +} + +static int +nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set) +{ + char abuf[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->internal, abuf, sizeof(abuf)); + printf("nptv6 %s int_prefix %s ", cfg->name, abuf); + inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf)); + printf("ext_prefix %s prefixlen %u\n", abuf, cfg->plen); + return (0); +} + +static int +nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set) +{ + + if (co.use_set != 0 && cfg->set != set) + return (ESRCH); + + nptv6_destroy(cfg->name, cfg->set); + return (0); +} + + +/* + * Compare NPTv6 instances names. + * Honor number comparison. + */ +static int +nptv6name_cmp(const void *a, const void *b) +{ + ipfw_nptv6_cfg *ca, *cb; + + ca = (ipfw_nptv6_cfg *)a; + cb = (ipfw_nptv6_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 NPTv6 instance list from kernel, + * Request: [ ipfw_obj_lheader ] + * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ] + */ +static int +nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, int sort) +{ + ipfw_obj_lheader *olh; + ipfw_nptv6_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_NPTV6_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, nptv6name_cmp); + + cfg = (ipfw_nptv6_cfg *)(olh + 1); + for (i = 0; i < olh->count; i++) { + error = f(cfg, name, set); + cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize); + } + free(olh); + break; + } + return (0); +} + Index: sys/conf/NOTES =================================================================== --- sys/conf/NOTES +++ sys/conf/NOTES @@ -967,6 +967,8 @@ # IPFIREWALL_NAT adds support for in kernel nat in ipfw, and it requires # LIBALIAS. # +# IPFIREWALL_NPTV6 adds support for in kernel NPTv6 in ipfw. +# # IPSTEALTH enables code to support stealth forwarding (i.e., forwarding # packets without touching the TTL). This can be useful to hide firewalls # from traceroute and similar tools. @@ -988,6 +990,7 @@ options IPFIREWALL_VERBOSE_LIMIT=100 #limit verbosity options IPFIREWALL_DEFAULT_TO_ACCEPT #allow everything by default options IPFIREWALL_NAT #ipfw kernel nat support +options IPFIREWALL_NPTV6 #ipfw kernel IPv6 NPT support options IPDIVERT #divert sockets options IPFILTER #ipfilter support options IPFILTER_LOG #ipfilter logging @@ -3063,4 +3066,4 @@ options GZIO # BHND(4) drivers -options BHND_LOGLEVEL # Logging threshold level \ No newline at end of file +options BHND_LOGLEVEL # Logging threshold level Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -3813,6 +3813,10 @@ netpfil/ipfw/ip_fw_table_value.c optional inet ipfirewall netpfil/ipfw/ip_fw_iface.c optional inet ipfirewall netpfil/ipfw/ip_fw_nat.c optional inet ipfirewall_nat +netpfil/ipfw/nptv6/ip_fw_nptv6.c optional inet inet6 ipfirewall \ + ipfirewall_nptv6 +netpfil/ipfw/nptv6/nptv6.c optional inet inet6 ipfirewall \ + ipfirewall_nptv6 netpfil/pf/if_pflog.c optional pflog pf inet netpfil/pf/if_pfsync.c optional pfsync pf inet netpfil/pf/pf.c optional pf inet Index: sys/conf/options =================================================================== --- sys/conf/options +++ sys/conf/options @@ -417,6 +417,7 @@ IPFIREWALL opt_ipfw.h IPFIREWALL_DEFAULT_TO_ACCEPT opt_ipfw.h IPFIREWALL_NAT opt_ipfw.h +IPFIREWALL_NPTV6 opt_ipfw.h IPFIREWALL_VERBOSE opt_ipfw.h IPFIREWALL_VERBOSE_LIMIT opt_ipfw.h IPSEC opt_ipsec.h Index: sys/modules/Makefile =================================================================== --- sys/modules/Makefile +++ sys/modules/Makefile @@ -166,6 +166,7 @@ ${_ipfilter} \ ${_ipfw} \ ipfw_nat \ + ${_ipfw_nptv6} \ ${_ipmi} \ ip6_mroute_mod \ ip_mroute_mod \ @@ -455,6 +456,10 @@ _ipfw= ipfw .endif +.if ${MK_INET6_SUPPORT} != "no" || defined(ALL_MODULES) +_ipfw_nptv6= ipfw_nptv6 +.endif + .if ${MK_IPFILTER} != "no" || defined(ALL_MODULES) _ipfilter= ipfilter .endif Index: sys/modules/ipfw_nptv6/Makefile =================================================================== --- /dev/null +++ sys/modules/ipfw_nptv6/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../netpfil/ipfw/nptv6 + +KMOD= ipfw_nptv6 +SRCS= ip_fw_nptv6.c nptv6.c opt_ipfw.h + +.include Index: sys/netinet/ip_fw.h =================================================================== --- sys/netinet/ip_fw.h +++ sys/netinet/ip_fw.h @@ -109,6 +109,12 @@ #define IP_FW_DUMP_SOPTCODES 116 /* Dump available sopts/versions */ #define IP_FW_DUMP_SRVOBJECTS 117 /* Dump existing named objects */ +#define IP_FW_NPTV6_CREATE 150 /* Create NPTv6 instance */ +#define IP_FW_NPTV6_DESTROY 151 /* Destroy NPTv6 instance */ +#define IP_FW_NPTV6_CONFIG 152 /* Modify NPTv6 instance */ +#define IP_FW_NPTV6_LIST 153 /* List NPTv6 instances */ +#define IP_FW_NPTV6_STATS 154 /* Get NPTv6 instance statistics */ + /* * The kernel representation of ipfw rules is made of a list of * 'instructions' (for all practical purposes equivalent to BPF @@ -783,6 +789,7 @@ #define IPFW_TLV_TBLENT_LIST 8 #define IPFW_TLV_RANGE 9 #define IPFW_TLV_EACTION 10 +#define IPFW_TLV_COUNTERS 11 #define IPFW_TLV_EACTION_BASE 1000 #define IPFW_TLV_EACTION_NAME(arg) (IPFW_TLV_EACTION_BASE + (arg)) Index: sys/netinet6/ip_fw_nptv6.h =================================================================== --- /dev/null +++ sys/netinet6/ip_fw_nptv6.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 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. + */ + +#ifndef _NETINET6_IP_FW_NPTV6_H_ +#define _NETINET6_IP_FW_NPTV6_H_ + +struct ipfw_nptv6_stats { + uint64_t in2ex; /* Int->Ext packets translated */ + uint64_t ex2in; /* Ext->Int packets translated */ + uint64_t dropped; /* dropped due to some errors */ + uint64_t reserved[5]; +}; + +typedef struct _ipfw_nptv6_cfg { + char name[64]; /* NPTv6 instance name */ + struct in6_addr internal; /* NPTv6 internal prefix */ + struct in6_addr external; /* NPTv6 external prefix */ + uint8_t plen; /* Prefix length */ + uint8_t set; /* Named instance set [0..31] */ + uint8_t spare[2]; + uint32_t flags; +} ipfw_nptv6_cfg; + +#endif /* _NETINET6_IP_FW_NPTV6_H_ */ + Index: sys/netpfil/ipfw/nptv6/ip_fw_nptv6.c =================================================================== --- /dev/null +++ sys/netpfil/ipfw/nptv6/ip_fw_nptv6.c @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 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 + +static int +vnet_ipfw_nptv6_init(const void *arg __unused) +{ + + return (nptv6_init(&V_layer3_chain, IS_DEFAULT_VNET(curvnet))); +} + +static int +vnet_ipfw_nptv6_uninit(const void *arg __unused) +{ + + nptv6_uninit(&V_layer3_chain, IS_DEFAULT_VNET(curvnet)); + return (0); +} + +static int +ipfw_nptv6_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_nptv6_mod = { + "ipfw_nptv6", + ipfw_nptv6_modevent, + 0 +}; + +/* Define startup order. */ +#define IPFW_NPTV6_SI_SUB_FIREWALL SI_SUB_PROTO_IFATTACHDOMAIN +#define IPFW_NPTV6_MODEVENT_ORDER (SI_ORDER_ANY - 128) /* after ipfw */ +#define IPFW_NPTV6_MODULE_ORDER (IPFW_NPTV6_MODEVENT_ORDER + 1) +#define IPFW_NPTV6_VNET_ORDER (IPFW_NPTV6_MODEVENT_ORDER + 2) + +DECLARE_MODULE(ipfw_nptv6, ipfw_nptv6_mod, IPFW_NPTV6_SI_SUB_FIREWALL, + IPFW_NPTV6_MODULE_ORDER); +MODULE_DEPEND(ipfw_nptv6, ipfw, 3, 3, 3); +MODULE_VERSION(ipfw_nptv6, 1); + +VNET_SYSINIT(vnet_ipfw_nptv6_init, IPFW_NPTV6_SI_SUB_FIREWALL, + IPFW_NPTV6_VNET_ORDER, vnet_ipfw_nptv6_init, NULL); +VNET_SYSUNINIT(vnet_ipfw_nptv6_uninit, IPFW_NPTV6_SI_SUB_FIREWALL, + IPFW_NPTV6_VNET_ORDER, vnet_ipfw_nptv6_uninit, NULL); Index: sys/netpfil/ipfw/nptv6/nptv6.h =================================================================== --- /dev/null +++ sys/netpfil/ipfw/nptv6/nptv6.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 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. + */ + +#ifndef _IP_FW_NPTV6_H_ +#define _IP_FW_NPTV6_H_ + +#include + +#ifdef _KERNEL +#define NPTV6STATS (sizeof(struct ipfw_nptv6_stats) / sizeof(uint64_t)) +#define NPTV6STAT_ADD(c, f, v) \ + counter_u64_add((c)->stats[ \ + offsetof(struct ipfw_nptv6_stats, f) / sizeof(uint64_t)], (v)) +#define NPTV6STAT_INC(c, f) NPTV6STAT_ADD(c, f, 1) +#define NPTV6STAT_FETCH(c, f) \ + counter_u64_fetch((c)->stats[ \ + offsetof(struct ipfw_nptv6_stats, f) / sizeof(uint64_t)]) + +struct nptv6_cfg { + struct named_object no; + + struct in6_addr internal; /* Internal IPv6 prefix */ + struct in6_addr external; /* External IPv6 prefix */ + struct in6_addr mask; /* IPv6 prefix mask */ + uint16_t adjustment; /* Checksum adjustment value */ + uint8_t plen; /* Prefix length */ + uint8_t flags; /* Flags for internal use */ +#define NPTV6_48PLEN 0x0001 + char name[64]; /* Instance name */ + counter_u64_t stats[NPTV6STATS]; /* Statistics counters */ +}; +#define NPTV6_FLAGSMASK 0 + +VNET_DECLARE(uint16_t, nptv6_eid); +#define V_nptv6_eid VNET(nptv6_eid) +#define IPFW_TLV_NPTV6_NAME IPFW_TLV_EACTION_NAME(V_nptv6_eid) + +int nptv6_init(struct ip_fw_chain *ch, int first); +void nptv6_uninit(struct ip_fw_chain *ch, int last); +#endif /* _KERNEL */ + +#endif /* _IP_FW_NPTV6_H_ */ + Index: sys/netpfil/ipfw/nptv6/nptv6.c =================================================================== --- /dev/null +++ sys/netpfil/ipfw/nptv6/nptv6.c @@ -0,0 +1,851 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 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 + +VNET_DEFINE(uint16_t, nptv6_eid) = 0; + +static struct nptv6_cfg *nptv6_alloc_config(const char *name, uint8_t set); +static void nptv6_free_config(struct nptv6_cfg *cfg); +static struct nptv6_cfg *nptv6_find(struct namedobj_instance *ni, + const char *name, uint8_t set); +static int nptv6_rewrite_internal(struct nptv6_cfg *cfg, struct mbuf **mp, + int offset); +static int nptv6_rewrite_external(struct nptv6_cfg *cfg, struct mbuf **mp, + int offset); + +#define NPTV6_LOOKUP(chain, cmd) \ + (struct nptv6_cfg *)SRV_OBJECT((chain), (cmd)->arg1) + +#if 0 +#define NPTV6_DEBUG(fmt, ...) do { \ + printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ +} while (0) +#define NPTV6_IPDEBUG(fmt, ...) do { \ + char _s[INET6_ADDRSTRLEN], _d[INET6_ADDRSTRLEN]; \ + printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ +} while (0) +#else +#define NPTV6_DEBUG(fmt, ...) +#define NPTV6_IPDEBUG(fmt, ...) +#endif + +static int +nptv6_getlasthdr(struct nptv6_cfg *cfg, struct mbuf *m, int *offset) +{ + struct ip6_hdr *ip6; + struct ip6_hbh *hbh; + int proto, hlen; + + hlen = (offset == NULL) ? 0: *offset; + if (m->m_len < hlen) + return (-1); + ip6 = mtodo(m, hlen); + hlen += sizeof(*ip6); + proto = ip6->ip6_nxt; + while (proto == IPPROTO_HOPOPTS || proto == IPPROTO_ROUTING || + proto == IPPROTO_DSTOPTS) { + hbh = mtodo(m, hlen); + if (m->m_len < hlen) + return (-1); + proto = hbh->ip6h_nxt; + hlen += hbh->ip6h_len << 3; + } + if (offset != NULL) + *offset = hlen; + return (proto); +} + +static int +nptv6_translate_icmpv6(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) +{ + struct icmp6_hdr *icmp6; + struct ip6_hdr *ip6; + struct mbuf *m; + + m = *mp; + if (offset > m->m_len) + return (-1); + icmp6 = mtodo(m, offset); + NPTV6_DEBUG("ICMPv6 type %d", icmp6->icmp6_type); + switch (icmp6->icmp6_type) { + case ICMP6_DST_UNREACH: + case ICMP6_PACKET_TOO_BIG: + case ICMP6_TIME_EXCEEDED: + case ICMP6_PARAM_PROB: + break; + case ICMP6_ECHO_REQUEST: + case ICMP6_ECHO_REPLY: + /* nothing to translate */ + return (0); + default: + /* + * XXX: We can add some checks to not translate NDP and MLD + * messages. Currently user must explicitly allow these message + * types, otherwise packets will be dropped. + */ + return (-1); + } + offset += sizeof(*icmp6); + if (offset + sizeof(*ip6) > m->m_pkthdr.len) + return (-1); + if (offset + sizeof(*ip6) > m->m_len) + *mp = m = m_pullup(m, offset + sizeof(*ip6)); + if (m == NULL) + return (-1); + ip6 = mtodo(m, offset); + NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, + inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), + inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), + ip6->ip6_nxt); + if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_src, + &cfg->external, &cfg->mask)) + return (nptv6_rewrite_external(cfg, mp, offset)); + else if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, + &cfg->internal, &cfg->mask)) + return (nptv6_rewrite_internal(cfg, mp, offset)); + /* + * Addresses in the inner IPv6 header doesn't matched to + * our prefixes. + */ + return (-1); +} + +static int +nptv6_search_index(struct nptv6_cfg *cfg, struct in6_addr *a) +{ + int idx; + + if (cfg->flags & NPTV6_48PLEN) + return (3); + + /* Search suitable word index for adjustment */ + for (idx = 4; idx < 8; idx++) + if (a->s6_addr16[idx] != 0xffff) + break; + /* + * RFC 6296 p3.7: If an NPTv6 Translator discovers a datagram with + * an IID of all-zeros while performing address mapping, that + * datagram MUST be dropped, and an ICMPv6 Parameter Problem error + * SHOULD be generated. + */ + if (idx == 8 || + (a->s6_addr32[2] == 0 && a->s6_addr32[3] == 0)) + return (-1); + return (idx); +} + +static void +nptv6_copy_addr(struct in6_addr *src, struct in6_addr *dst, + struct in6_addr *mask) +{ + int i; + + for (i = 0; i < 8 && mask->s6_addr8[i] != 0; i++) { + dst->s6_addr8[i] &= ~mask->s6_addr8[i]; + dst->s6_addr8[i] |= src->s6_addr8[i] & mask->s6_addr8[i]; + } +} + +static int +nptv6_rewrite_internal(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) +{ + struct in6_addr *addr; + struct ip6_hdr *ip6; + int idx, proto; + uint16_t adj; + + ip6 = mtodo(*mp, offset); + NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, + inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), + inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), + ip6->ip6_nxt); + if (offset == 0) + addr = &ip6->ip6_src; + else { + /* + * When we rewriting inner IPv6 header, we need to rewrite + * destination address back to external prefix. The datagram in + * the ICMPv6 payload should looks like it was send from + * external prefix. + */ + addr = &ip6->ip6_dst; + } + idx = nptv6_search_index(cfg, addr); + if (idx < 0) { + /* + * Do not send ICMPv6 error when offset isn't zero. + * This means we are rewriting inner IPv6 header in the + * ICMPv6 error message. + */ + if (offset == 0) { + icmp6_error2(*mp, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0, (*mp)->m_pkthdr.rcvif); + *mp = NULL; + } + return (IP_FW_DENY); + } + adj = addr->s6_addr16[idx]; + nptv6_copy_addr(&cfg->external, addr, &cfg->mask); + adj = cksum_add(adj, cfg->adjustment); + if (adj == 0xffff) + adj = 0; + addr->s6_addr16[idx] = adj; + if (offset == 0) { + /* + * We may need to translate addresses in the inner IPv6 + * header for ICMPv6 error messages. + */ + proto = nptv6_getlasthdr(cfg, *mp, &offset); + if (proto < 0 || (proto == IPPROTO_ICMPV6 && + nptv6_translate_icmpv6(cfg, mp, offset) != 0)) + return (IP_FW_DENY); + NPTV6STAT_INC(cfg, in2ex); + } + return (0); +} + +static int +nptv6_rewrite_external(struct nptv6_cfg *cfg, struct mbuf **mp, int offset) +{ + struct in6_addr *addr; + struct ip6_hdr *ip6; + int idx, proto; + uint16_t adj; + + ip6 = mtodo(*mp, offset); + NPTV6_IPDEBUG("offset %d, %s -> %s %d", offset, + inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), + inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), + ip6->ip6_nxt); + if (offset == 0) + addr = &ip6->ip6_dst; + else { + /* + * When we rewriting inner IPv6 header, we need to rewrite + * source address back to internal prefix. The datagram in + * the ICMPv6 payload should looks like it was send from + * internal prefix. + */ + addr = &ip6->ip6_src; + } + idx = nptv6_search_index(cfg, addr); + if (idx < 0) { + /* + * Do not send ICMPv6 error when offset isn't zero. + * This means we are rewriting inner IPv6 header in the + * ICMPv6 error message. + */ + if (offset == 0) { + icmp6_error2(*mp, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0, (*mp)->m_pkthdr.rcvif); + *mp = NULL; + } + return (IP_FW_DENY); + } + adj = addr->s6_addr16[idx]; + nptv6_copy_addr(&cfg->internal, addr, &cfg->mask); + adj = cksum_add(adj, ~cfg->adjustment); + if (adj == 0xffff) + adj = 0; + addr->s6_addr16[idx] = adj; + if (offset == 0) { + /* + * We may need to translate addresses in the inner IPv6 + * header for ICMPv6 error messages. + */ + proto = nptv6_getlasthdr(cfg, *mp, &offset); + if (proto < 0 || (proto == IPPROTO_ICMPV6 && + nptv6_translate_icmpv6(cfg, mp, offset) != 0)) + return (IP_FW_DENY); + NPTV6STAT_INC(cfg, ex2in); + } + return (0); +} + +/* + * ipfw external action handler. + */ +static int +ipfw_nptv6(struct ip_fw_chain *chain, struct ip_fw_args *args, + ipfw_insn *cmd, int *done) +{ + struct ip6_hdr *ip6; + struct nptv6_cfg *cfg; + ipfw_insn *icmd; + int ret; + + *done = 0; /* try next rule if not matched */ + icmd = cmd + 1; + if (cmd->opcode != O_EXTERNAL_ACTION || + cmd->arg1 != V_nptv6_eid || + icmd->opcode != O_EXTERNAL_INSTANCE || + (cfg = NPTV6_LOOKUP(chain, icmd)) == NULL) + return (0); + /* + * We need act as router, so when forwarding is disabled - + * do nothing. + */ + if (V_ip6_forwarding == 0 || args->f_id.addr_type != 6) + return (0); + /* + * 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. + */ + ret = IP_FW_DENY; + ip6 = mtod(args->m, struct ip6_hdr *); + NPTV6_IPDEBUG("eid %u, oid %u, %s -> %s %d", + cmd->arg1, icmd->arg1, + inet_ntop(AF_INET6, &ip6->ip6_src, _s, sizeof(_s)), + inet_ntop(AF_INET6, &ip6->ip6_dst, _d, sizeof(_d)), + ip6->ip6_nxt); + if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_src, + &cfg->internal, &cfg->mask)) { + /* + * XXX: Do not translate packets when both src and dst + * are from internal prefix. + */ + if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, + &cfg->internal, &cfg->mask)) + return (0); + ret = nptv6_rewrite_internal(cfg, &args->m, 0); + } else if (IN6_ARE_MASKED_ADDR_EQUAL(&ip6->ip6_dst, + &cfg->external, &cfg->mask)) + ret = nptv6_rewrite_external(cfg, &args->m, 0); + else + return (0); + /* + * If address wasn't rewrited - free mbuf. + */ + if (ret != 0) { + if (args->m != NULL) { + m_freem(args->m); + args->m = NULL; /* mark mbuf as consumed */ + } + NPTV6STAT_INC(cfg, dropped); + } + /* Terminate the search if one_pass is set */ + *done = V_fw_one_pass; + /* Update args->f_id when one_pass is off */ + if (*done == 0 && ret == 0) { + ip6 = mtod(args->m, struct ip6_hdr *); + args->f_id.src_ip6 = ip6->ip6_src; + args->f_id.dst_ip6 = ip6->ip6_dst; + } + return (ret); +} + +static struct nptv6_cfg * +nptv6_alloc_config(const char *name, uint8_t set) +{ + struct nptv6_cfg *cfg; + + cfg = malloc(sizeof(struct nptv6_cfg), M_IPFW, M_WAITOK | M_ZERO); + COUNTER_ARRAY_ALLOC(cfg->stats, NPTV6STATS, M_WAITOK); + cfg->no.name = cfg->name; + cfg->no.etlv = IPFW_TLV_NPTV6_NAME; + cfg->no.set = set; + strlcpy(cfg->name, name, sizeof(cfg->name)); + return (cfg); +} + +static void +nptv6_free_config(struct nptv6_cfg *cfg) +{ + + COUNTER_ARRAY_FREE(cfg->stats, NPTV6STATS); + free(cfg, M_IPFW); +} + +static void +nptv6_export_config(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, + ipfw_nptv6_cfg *uc) +{ + + uc->internal = cfg->internal; + uc->external = cfg->external; + uc->plen = cfg->plen; + uc->flags = cfg->flags & NPTV6_FLAGSMASK; + uc->set = cfg->no.set; + strlcpy(uc->name, cfg->no.name, sizeof(uc->name)); +} + +struct nptv6_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 nptv6_dump_arg *da = (struct nptv6_dump_arg *)arg; + ipfw_nptv6_cfg *uc; + + uc = (ipfw_nptv6_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc)); + nptv6_export_config(da->ch, (struct nptv6_cfg *)no, uc); + return (0); +} + +static struct nptv6_cfg * +nptv6_find(struct namedobj_instance *ni, const char *name, uint8_t set) +{ + struct nptv6_cfg *cfg; + + cfg = (struct nptv6_cfg *)ipfw_objhash_lookup_name_type(ni, set, + IPFW_TLV_NPTV6_NAME, name); + + return (cfg); +} + +static void +nptv6_calculate_adjustment(struct nptv6_cfg *cfg) +{ + uint16_t i, e; + uint16_t *p; + + /* Calculate checksum of internal prefix */ + for (i = 0, p = (uint16_t *)&cfg->internal; + p < (uint16_t *)(&cfg->internal + 1); p++) + i = cksum_add(i, *p); + + /* Calculate checksum of external prefix */ + for (e = 0, p = (uint16_t *)&cfg->external; + p < (uint16_t *)(&cfg->external + 1); p++) + e = cksum_add(e, *p); + + /* Adjustment value for Int->Ext direction */ + cfg->adjustment = cksum_add(~e, i); +} + +#ifndef IN6_MASK_ADDR +#define IN6_MASK_ADDR(a, m) do { \ + (a)->s6_addr32[0] &= (m)->s6_addr32[0]; \ + (a)->s6_addr32[1] &= (m)->s6_addr32[1]; \ + (a)->s6_addr32[2] &= (m)->s6_addr32[2]; \ + (a)->s6_addr32[3] &= (m)->s6_addr32[3]; \ +} while (0) +#endif +#ifndef IN6_ARE_MASKED_ADDR_EQUAL +#define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \ + (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \ + (((d)->s6_addr32[1] ^ (a)->s6_addr32[1]) & (m)->s6_addr32[1]) == 0 && \ + (((d)->s6_addr32[2] ^ (a)->s6_addr32[2]) & (m)->s6_addr32[2]) == 0 && \ + (((d)->s6_addr32[3] ^ (a)->s6_addr32[3]) & (m)->s6_addr32[3]) == 0 ) +#endif +/* + * Creates new NPTv6 instance. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ] + * + * Returns 0 on success + */ +static int +nptv6_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + struct in6_addr mask; + ipfw_obj_lheader *olh; + ipfw_nptv6_cfg *uc; + struct namedobj_instance *ni; + struct nptv6_cfg *cfg; + + if (sd->valsize != sizeof(*olh) + sizeof(*uc)) + return (EINVAL); + + olh = (ipfw_obj_lheader *)sd->kbuf; + uc = (ipfw_nptv6_cfg *)(olh + 1); + if (ipfw_check_object_name_generic(uc->name) != 0) + return (EINVAL); + if (uc->plen < 8 || uc->plen > 64 || uc->set >= IPFW_MAX_SETS) + return (EINVAL); + if (IN6_IS_ADDR_MULTICAST(&uc->internal) || + IN6_IS_ADDR_MULTICAST(&uc->external) || + IN6_IS_ADDR_UNSPECIFIED(&uc->internal) || + IN6_IS_ADDR_UNSPECIFIED(&uc->external) || + IN6_IS_ADDR_LINKLOCAL(&uc->internal) || + IN6_IS_ADDR_LINKLOCAL(&uc->external)) + return (EINVAL); + in6_prefixlen2mask(&mask, uc->plen); + if (IN6_ARE_MASKED_ADDR_EQUAL(&uc->internal, &uc->external, &mask)) + return (EINVAL); + + ni = CHAIN_TO_SRV(ch); + IPFW_UH_RLOCK(ch); + if (nptv6_find(ni, uc->name, uc->set) != NULL) { + IPFW_UH_RUNLOCK(ch); + return (EEXIST); + } + IPFW_UH_RUNLOCK(ch); + + cfg = nptv6_alloc_config(uc->name, uc->set); + cfg->plen = uc->plen; + if (cfg->plen <= 48) + cfg->flags |= NPTV6_48PLEN; + cfg->internal = uc->internal; + cfg->external = uc->external; + cfg->mask = mask; + IN6_MASK_ADDR(&cfg->internal, &mask); + IN6_MASK_ADDR(&cfg->external, &mask); + nptv6_calculate_adjustment(cfg); + + IPFW_UH_WLOCK(ch); + if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) { + IPFW_UH_WUNLOCK(ch); + nptv6_free_config(cfg); + return (ENOSPC); + } + ipfw_objhash_add(ni, &cfg->no); + IPFW_WLOCK(ch); + SRV_OBJECT(ch, cfg->no.kidx) = cfg; + IPFW_WUNLOCK(ch); + IPFW_UH_WUNLOCK(ch); + return (0); +} + +/* + * Destroys NPTv6 instance. + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ] + * + * Returns 0 on success + */ +static int +nptv6_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_header *oh; + struct nptv6_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 = nptv6_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_WUNLOCK(ch); + return (ESRCH); + } + if (cfg->no.refcnt > 0) { + IPFW_UH_WUNLOCK(ch); + return (EBUSY); + } + + IPFW_WLOCK(ch); + SRV_OBJECT(ch, cfg->no.kidx) = NULL; + IPFW_WUNLOCK(ch); + + ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no); + ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx); + IPFW_UH_WUNLOCK(ch); + + nptv6_free_config(cfg); + return (0); +} + +/* + * Get or change nptv6 instance config. + * Request: [ ipfw_obj_header [ ipfw_nptv6_cfg ] ] + */ +static int +nptv6_config(struct ip_fw_chain *chain, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + + return (EOPNOTSUPP); +} + +/* + * Lists all NPTv6 instances currently available in kernel. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ] + * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ] + * + * Returns 0 on success + */ +static int +nptv6_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_lheader *olh; + struct nptv6_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_NPTV6_NAME); + olh->objsize = sizeof(ipfw_nptv6_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_NPTV6_NAME); + IPFW_UH_RUNLOCK(ch); + + return (0); +} + +#define __COPY_STAT_FIELD(_cfg, _stats, _field) \ + (_stats)->_field = NPTV6STAT_FETCH(_cfg, _field) +static void +export_stats(struct ip_fw_chain *ch, struct nptv6_cfg *cfg, + struct ipfw_nptv6_stats *stats) +{ + + __COPY_STAT_FIELD(cfg, stats, in2ex); + __COPY_STAT_FIELD(cfg, stats, ex2in); + __COPY_STAT_FIELD(cfg, stats, dropped); +} + +/* + * Get NPTv6 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 +nptv6_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op, + struct sockopt_data *sd) +{ + struct ipfw_nptv6_stats stats; + struct nptv6_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 = nptv6_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set); + if (cfg == NULL) { + IPFW_UH_RUNLOCK(ch); + return (ESRCH); + } + 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 = 1; + memcpy(ctlv + 1, &stats, sizeof(stats)); + return (0); +} + +static struct ipfw_sopt_handler scodes[] = { + { IP_FW_NPTV6_CREATE, 0, HDIR_SET, nptv6_create }, + { IP_FW_NPTV6_DESTROY,0, HDIR_SET, nptv6_destroy }, + { IP_FW_NPTV6_CONFIG, 0, HDIR_BOTH, nptv6_config }, + { IP_FW_NPTV6_LIST, 0, HDIR_GET, nptv6_list }, + { IP_FW_NPTV6_STATS, 0, HDIR_GET, nptv6_stats }, +}; + +static int +nptv6_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + ipfw_insn *icmd; + + icmd = cmd - 1; + NPTV6_DEBUG("opcode %d, arg1 %d, opcode0 %d, arg1 %d", + cmd->opcode, cmd->arg1, icmd->opcode, icmd->arg1); + if (icmd->opcode != O_EXTERNAL_ACTION || + icmd->arg1 != V_nptv6_eid) + return (1); + + *puidx = cmd->arg1; + *ptype = 0; + return (0); +} + +static void +nptv6_update_arg1(ipfw_insn *cmd, uint16_t idx) +{ + + cmd->arg1 = idx; + NPTV6_DEBUG("opcode %d, arg1 -> %d", cmd->opcode, cmd->arg1); +} + +static int +nptv6_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_NPTV6_NAME, pno); + NPTV6_DEBUG("uidx %u, type %u, err %d", ti->uidx, ti->type, err); + return (err); +} + +static struct named_object * +nptv6_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, ("NPT with index %d not found", idx)); + + NPTV6_DEBUG("kidx %u -> %s", idx, no->name); + return (no); +} + +static int +nptv6_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_NPTV6_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 = nptv6_classify, + .update = nptv6_update_arg1, + .find_byname = nptv6_findbyname, + .find_bykidx = nptv6_findbykidx, + .manage_sets = nptv6_manage_sets, + }, +}; + +static int +destroy_config_cb(struct namedobj_instance *ni, struct named_object *no, + void *arg) +{ + struct nptv6_cfg *cfg; + struct ip_fw_chain *ch; + + ch = (struct ip_fw_chain *)arg; + IPFW_UH_WLOCK_ASSERT(ch); + + cfg = (struct nptv6_cfg *)SRV_OBJECT(ch, no->kidx); + SRV_OBJECT(ch, no->kidx) = NULL; + ipfw_objhash_del(ni, &cfg->no); + ipfw_objhash_free_idx(ni, cfg->no.kidx); + nptv6_free_config(cfg); + return (0); +} + +int +nptv6_init(struct ip_fw_chain *ch, int first) +{ + + V_nptv6_eid = ipfw_add_eaction(ch, ipfw_nptv6, "nptv6"); + if (V_nptv6_eid == 0) + return (ENXIO); + IPFW_ADD_SOPT_HANDLER(first, scodes); + IPFW_ADD_OBJ_REWRITER(first, opcodes); + return (0); +} + +void +nptv6_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_nptv6_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_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch, + IPFW_TLV_NPTV6_NAME); + V_nptv6_eid = 0; +} +