Add ipfw_nptv6 module that implements Network Prefix Translation for IPv6

as defined in RFC 6296. The module works together with ipfw(4) and
implemented as its external action module. When it is loaded, it registers
as eaction and can be used in rules. The usage pattern is similar to
ipfw_nat(4). All matched by rule traffic goes to the NPT module.

Reviewed by:	hrs
Obtained from:	Yandex LLC
MFC after:	1 month
Relnotes:	yes
Sponsored by:	Yandex LLC
Differential Revision:	https://reviews.freebsd.org/D6420
This commit is contained in:
Andrey V. Elsukov 2016-07-18 19:46:31 +00:00
parent d67355c507
commit b867e84e95
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=303012
16 changed files with 1570 additions and 1 deletions

View file

@ -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"

View file

@ -1,7 +1,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd May 26, 2016
.Dd July 18, 2016
.Dt IPFW 8
.Os
.Sh NAME
@ -113,6 +113,19 @@ in-kernel NAT.
.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 @@ nat instance
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,47 @@ instances.
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
Note that the prefix translation rules are silently ignored when IPv6 packet
forwarding is disabled.
To enable the packet forwarding, set the sysctl variable
.Va net.inet6.ip6.forwarding
to 1.
.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

View file

@ -235,6 +235,7 @@ static struct _s_x ether_types[] = {
};
static struct _s_x rule_eactions[] = {
{ "nptv6", TOK_NPTV6 },
{ NULL, 0 } /* terminator */
};

View file

@ -254,6 +254,13 @@ enum tokens {
TOK_UNLOCK,
TOK_VLIST,
TOK_OLIST,
TOK_STATS,
/* NPTv6 tokens */
TOK_NPTV6,
TOK_INTPREFIX,
TOK_EXTPREFIX,
TOK_PREFIXLEN,
};
/*
@ -340,6 +347,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_nptv6_handler(int ac, char *av[]);
int ipfw_check_object_name(const char *name);
#ifdef PF

View file

@ -425,6 +425,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, "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)

399
sbin/ipfw/nptv6.c Normal file
View file

@ -0,0 +1,399 @@
/*-
* Copyright (c) 2016 Yandex LLC
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/socket.h>
#include "ipfw2.h"
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip_fw.h>
#include <netinet6/ip_fw_nptv6.h>
#include <arpa/inet.h>
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}
*/
#define nptv6_check_name table_check_name
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 (nptv6_check_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);
} else
*len = 0;
free(p);
}
/*
* Creates new nptv6 instance
* ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <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 = "\0";
plen = 0;
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");
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");
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 != '\0' || plen < 8 || plen > 64)
errx(EX_USAGE, "wrong prefix length: %s", *av);
/* RFC 6296 Sec. 3.1 */
if (cfg->plen > 0 && cfg->plen != plen) {
warnx("Prefix length mismatch (%d vs %d). "
"It was extended up to %d",
cfg->plen, plen, MAX(plen, cfg->plen));
plen = MAX(plen, cfg->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);
}

View file

@ -965,6 +965,8 @@ device lagg
# 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.
@ -986,6 +988,7 @@ options IPFIREWALL_VERBOSE #enable logging to syslogd(8)
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

View file

@ -3859,6 +3859,10 @@ netpfil/ipfw/ip_fw_table_algo.c optional inet ipfirewall
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

View file

@ -417,6 +417,7 @@ IPFILTER_LOOKUP opt_ipfilter.h
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

View file

@ -167,6 +167,7 @@ SUBDIR= \
${_ipfilter} \
${_ipfw} \
ipfw_nat \
${_ipfw_nptv6} \
${_ipmi} \
ip6_mroute_mod \
ip_mroute_mod \
@ -460,6 +461,10 @@ _ipdivert= ipdivert
_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

View file

@ -0,0 +1,8 @@
# $FreeBSD$
.PATH: ${.CURDIR}/../../netpfil/ipfw/nptv6
KMOD= ipfw_nptv6
SRCS= ip_fw_nptv6.c nptv6.c opt_ipfw.h
.include <bsd.kmod.mk>

View file

@ -109,6 +109,12 @@ typedef struct _ip_fw3_opheader {
#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 @@ typedef struct _ipfw_obj_tlv {
#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))

View file

@ -0,0 +1,51 @@
/*-
* Copyright (c) 2016 Yandex LLC
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
* 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.
*
* $FreeBSD$
*/
#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_ */

View file

@ -0,0 +1,99 @@
/*-
* Copyright (c) 2016 Yandex LLC
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rwlock.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include <netpfil/ipfw/nptv6/nptv6.h>
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);

View file

@ -0,0 +1,856 @@
/*-
* Copyright (c) 2016 Yandex LLC
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/counter.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/rmlock.h>
#include <sys/rwlock.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/netisr.h>
#include <net/pfil.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet6/in6_var.h>
#include <netinet6/ip6_var.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include <netpfil/ipfw/nptv6/nptv6.h>
static VNET_DEFINE(uint16_t, nptv6_eid) = 0;
#define V_nptv6_eid VNET(nptv6_eid)
#define IPFW_TLV_NPTV6_NAME IPFW_TLV_EACTION_NAME(V_nptv6_eid)
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)
#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
#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);
}
/*
* 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_UH_WLOCK(ch);
ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch,
IPFW_TLV_NPTV6_NAME);
V_nptv6_eid = 0;
IPFW_UH_WUNLOCK(ch);
}

View file

@ -0,0 +1,65 @@
/*-
* Copyright (c) 2016 Yandex LLC
* Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
* 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.
*
* $FreeBSD$
*/
#ifndef _IP_FW_NPTV6_H_
#define _IP_FW_NPTV6_H_
#include <netinet6/ip_fw_nptv6.h>
#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
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_ */