Add NAT64 CLAT implementation as defined in RFC6877.

CLAT is customer-side translator that algorithmically translates 1:1
private IPv4 addresses to global IPv6 addresses, and vice versa.
It is implemented as part of ipfw_nat64 kernel module. When module
is loaded or compiled into the kernel, it registers "nat64clat" external
action. External action named instance can be created using `create`
command and then used in ipfw rules. The create command accepts two
IPv6 prefixes `plat_prefix` and `clat_prefix`. If plat_prefix is ommitted,
IPv6 NAT64 Well-Known prefix 64:ff9b::/96 will be used.

  # ipfw nat64clat CLAT create clat_prefix SRC_PFX plat_prefix DST_PFX
  # ipfw add nat64clat CLAT ip4 from IPv4_PFX to any out
  # ipfw add nat64clat CLAT ip6 from DST_PFX to SRC_PFX in

Obtained from:	Yandex LLC
Submitted by:	Boris N. Lytochkin
MFC after:	1 month
Relnotes:	yes
Sponsored by:	Yandex LLC
This commit is contained in:
ae 2019-03-18 11:44:53 +00:00
parent 2770fa04e1
commit 93a7173b74
15 changed files with 1584 additions and 1 deletions

View File

@ -5,7 +5,7 @@
PACKAGE=ipfw
PROG= ipfw
SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c
SRCS+= nat64lsn.c nat64stl.c nptv6.c
SRCS+= nat64clat.c nat64lsn.c nat64stl.c nptv6.c
WARNS?= 2
.if ${MK_PF} != "no"

View File

@ -136,6 +136,21 @@ in-kernel NAT.
.Cm destroy
.Nm
.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm stats Op Cm reset
.Ss XLAT464 CLAT IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
.Nm
.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm create Ar create-options
.Nm
.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm config Ar config-options
.Nm
.Oo Cm set Ar N Oc Cm nat64clat
.Brq Ar name | all
.Brq Cm list | show
.Nm
.Oo Cm set Ar N Oc Cm nat64clat
.Brq Ar name | all
.Cm destroy
.Nm
.Oo Cm set Ar N Oc Cm nat64clat Ar name Cm stats Op Cm reset
.Ss IPv6-to-IPv6 NETWORK PREFIX TRANSLATION
.Nm
.Oo Cm set Ar N Oc Cm nptv6 Ar name Cm create Ar create-options
@ -924,6 +939,11 @@ Pass packet to a stateless NAT64 instance (for IPv6/IPv4 network address and
protocol translation): see the
.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
Section for further information.
.It Cm nat64clat Ar name
Pass packet to a CLAT NAT64 instance (for client-side IPv6/IPv4 network address and
protocol translation): see the
.Sx IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION
Section for further information.
.It Cm nptv6 Ar name
Pass packet to a NPTv6 instance (for IPv6-to-IPv6 network prefix translation):
see the
@ -3482,6 +3502,57 @@ Note that the behavior of stateless translator with respect to not matched
packets differs from stateful translator.
If corresponding addresses was not found in the lookup tables, the packet
will not be dropped and the search continues.
.Pp
.Pp
.Ss XLAT464 CLAT translation
XLAT464 CLAT NAT64 translator implements client-side stateless translation as
defined in RFC6877 and is very similar to statless NAT64 translator
explained above. Instead of lookup tables it uses one-to-one mapping
between IPv4 and IPv6 addresses using configured prefixes.
This mode can be used as a replacement of DNS64 service for applications
that are not using it (e.g. VoIP) allowing them to access IPv4-only Internet
over IPv6-only networks with help of remote NAT64 translator.
.Pp
The CLAT NAT64 configuration command is the following:
.Bd -ragged -offset indent
.Bk -words
.Cm nat64clat
.Ar name
.Cm create
.Ar create-options
.Ek
.Ed
.Pp
The following parameters can be configured:
.Bl -tag -width indent
.It Cm clat_prefix Ar ipv6_prefix/length
The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
to represent source IPv4 addresses.
.It Cm plat_prefix Ar ipv6_prefix/length
The IPv6 prefix defines IPv4-embedded IPv6 addresses used by translator
to represent destination IPv4 addresses. This IPv6 prefix should be configured
on a remote NAT64 translator.
.It Cm log
Turn on logging of all handled packets via BPF through
.Ar ipfwlog0
interface.
.It Cm -log
Turn off logging of all handled packets via BPF.
.It Cm allow_private
Turn on processing private IPv4 addresses. By default
.Nm nat64clat
instance will not process IPv4 packets with destination address from private
ranges as defined in RFC1918.
.It Cm -allow_private
Turn off private address handling in
.Nm nat64clat
instance.
.El
.Pp
Note that the behavior of CLAT translator with respect to not matched
packets differs from stateful translator.
If corresponding addresses were not matched against prefixes configured,
the packet will not be dropped and the search continues.
.Sh IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6)
.Nm
supports in-kernel IPv6-to-IPv6 network prefix translation as described

View File

@ -237,6 +237,7 @@ static struct _s_x ether_types[] = {
};
static struct _s_x rule_eactions[] = {
{ "nat64clat", TOK_NAT64CLAT },
{ "nat64lsn", TOK_NAT64LSN },
{ "nat64stl", TOK_NAT64STL },
{ "nptv6", TOK_NPTV6 },

View File

@ -291,6 +291,11 @@ enum tokens {
TOK_PRIVATE,
TOK_PRIVATEOFF,
/* NAT64 CLAT tokens */
TOK_NAT64CLAT,
TOK_PLAT_PREFIX,
TOK_CLAT_PREFIX,
/* NPTv6 tokens */
TOK_NPTV6,
TOK_INTPREFIX,
@ -387,6 +392,7 @@ void ipfw_flush(int force);
void ipfw_zero(int ac, char *av[], int optname);
void ipfw_list(int ac, char *av[], int show_counters);
void ipfw_internal_handler(int ac, char *av[]);
void ipfw_nat64clat_handler(int ac, char *av[]);
void ipfw_nat64lsn_handler(int ac, char *av[]);
void ipfw_nat64stl_handler(int ac, char *av[]);
void ipfw_nptv6_handler(int ac, char *av[]);

View File

@ -429,6 +429,8 @@ ipfw_main(int oldac, char **oldav)
if (co.use_set || try_next) {
if (_substrcmp(*av, "delete") == 0)
ipfw_delete(av);
else if (!strncmp(*av, "nat64clat", strlen(*av)))
ipfw_nat64clat_handler(ac, av);
else if (!strncmp(*av, "nat64stl", strlen(*av)))
ipfw_nat64stl_handler(ac, av);
else if (!strncmp(*av, "nat64lsn", strlen(*av)))

535
sbin/ipfw/nat64clat.c Normal file
View File

@ -0,0 +1,535 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Yandex LLC
* Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
* Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
*
* 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/types.h>
#include <sys/socket.h>
#include "ipfw2.h"
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <netdb.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_nat64.h>
#include <arpa/inet.h>
typedef int (nat64clat_cb_t)(ipfw_nat64clat_cfg *i, const char *name,
uint8_t set);
static int nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set,
int sort);
static void nat64clat_create(const char *name, uint8_t set, int ac, char **av);
static void nat64clat_config(const char *name, uint8_t set, int ac, char **av);
static void nat64clat_destroy(const char *name, uint8_t set);
static void nat64clat_stats(const char *name, uint8_t set);
static void nat64clat_reset_stats(const char *name, uint8_t set);
static int nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name,
uint8_t set);
static int nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name,
uint8_t set);
static struct _s_x nat64cmds[] = {
{ "create", TOK_CREATE },
{ "config", TOK_CONFIG },
{ "destroy", TOK_DESTROY },
{ "list", TOK_LIST },
{ "show", TOK_LIST },
{ "stats", TOK_STATS },
{ NULL, 0 }
};
static struct _s_x nat64statscmds[] = {
{ "reset", TOK_RESET },
{ NULL, 0 }
};
/*
* This one handles all nat64clat-related commands
* ipfw [set N] nat64clat NAME {create | config} ...
* ipfw [set N] nat64clat NAME stats [reset]
* ipfw [set N] nat64clat {NAME | all} destroy
* ipfw [set N] nat64clat {NAME | all} {list | show}
*/
#define nat64clat_check_name table_check_name
void
ipfw_nat64clat_handler(int ac, char *av[])
{
const char *name;
int tcmd;
uint8_t set;
if (co.use_set != 0)
set = co.use_set - 1;
else
set = 0;
ac--; av++;
NEED1("nat64clat needs instance name");
name = *av;
if (nat64clat_check_name(name) != 0) {
if (strcmp(name, "all") == 0)
name = NULL;
else
errx(EX_USAGE, "nat64clat instance name %s is invalid",
name);
}
ac--; av++;
NEED1("nat64clat needs command");
tcmd = get_token(nat64cmds, *av, "nat64clat command");
if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
errx(EX_USAGE, "nat64clat instance name required");
switch (tcmd) {
case TOK_CREATE:
ac--; av++;
nat64clat_create(name, set, ac, av);
break;
case TOK_CONFIG:
ac--; av++;
nat64clat_config(name, set, ac, av);
break;
case TOK_LIST:
nat64clat_foreach(nat64clat_show_cb, name, set, 1);
break;
case TOK_DESTROY:
if (name == NULL)
nat64clat_foreach(nat64clat_destroy_cb, NULL, set, 0);
else
nat64clat_destroy(name, set);
break;
case TOK_STATS:
ac--; av++;
if (ac == 0) {
nat64clat_stats(name, set);
break;
}
tcmd = get_token(nat64statscmds, *av, "stats command");
if (tcmd == TOK_RESET)
nat64clat_reset_stats(name, set);
}
}
static void
nat64clat_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
{
ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
ntlv->head.length = sizeof(ipfw_obj_ntlv);
ntlv->idx = 1;
ntlv->set = set;
strlcpy(ntlv->name, name, sizeof(ntlv->name));
}
static struct _s_x nat64newcmds[] = {
{ "plat_prefix", TOK_PLAT_PREFIX },
{ "clat_prefix", TOK_CLAT_PREFIX },
{ "log", TOK_LOG },
{ "-log", TOK_LOGOFF },
{ "allow_private", TOK_PRIVATE },
{ "-allow_private", TOK_PRIVATEOFF },
{ NULL, 0 }
};
/*
* Creates new nat64clat instance
* ipfw nat64clat <NAME> create clat_prefix <prefix> plat_prefix <prefix>
* Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ]
*/
#define NAT64CLAT_HAS_CLAT_PREFIX 0x01
#define NAT64CLAT_HAS_PLAT_PREFIX 0x02
static void
nat64clat_create(const char *name, uint8_t set, int ac, char *av[])
{
char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64clat_cfg)];
ipfw_nat64clat_cfg *cfg;
ipfw_obj_lheader *olh;
int tcmd, flags;
char *p;
struct in6_addr prefix;
uint8_t plen;
memset(buf, 0, sizeof(buf));
olh = (ipfw_obj_lheader *)buf;
cfg = (ipfw_nat64clat_cfg *)(olh + 1);
/* Some reasonable defaults */
inet_pton(AF_INET6, "64:ff9b::", &cfg->plat_prefix);
cfg->plat_plen = 96;
cfg->set = set;
flags = NAT64CLAT_HAS_PLAT_PREFIX;
while (ac > 0) {
tcmd = get_token(nat64newcmds, *av, "option");
ac--; av++;
switch (tcmd) {
case TOK_PLAT_PREFIX:
case TOK_CLAT_PREFIX:
if (tcmd == TOK_PLAT_PREFIX) {
NEED1("IPv6 plat_prefix required");
} else {
NEED1("IPv6 clat_prefix required");
}
if ((p = strchr(*av, '/')) != NULL)
*p++ = '\0';
if (inet_pton(AF_INET6, *av, &prefix) != 1)
errx(EX_USAGE,
"Bad prefix: %s", *av);
plen = strtol(p, NULL, 10);
if (ipfw_check_nat64prefix(&prefix, plen) != 0)
errx(EX_USAGE,
"Bad prefix length: %s", p);
if (tcmd == TOK_PLAT_PREFIX) {
flags |= NAT64CLAT_HAS_PLAT_PREFIX;
cfg->plat_prefix = prefix;
cfg->plat_plen = plen;
} else {
flags |= NAT64CLAT_HAS_CLAT_PREFIX;
cfg->clat_prefix = prefix;
cfg->clat_plen = plen;
}
ac--; av++;
break;
case TOK_LOG:
cfg->flags |= NAT64_LOG;
break;
case TOK_LOGOFF:
cfg->flags &= ~NAT64_LOG;
break;
case TOK_PRIVATE:
cfg->flags |= NAT64_ALLOW_PRIVATE;
break;
case TOK_PRIVATEOFF:
cfg->flags &= ~NAT64_ALLOW_PRIVATE;
break;
}
}
/* Check validness */
if ((flags & NAT64CLAT_HAS_PLAT_PREFIX) != NAT64CLAT_HAS_PLAT_PREFIX)
errx(EX_USAGE, "plat_prefix required");
if ((flags & NAT64CLAT_HAS_CLAT_PREFIX) != NAT64CLAT_HAS_CLAT_PREFIX)
errx(EX_USAGE, "clat_prefix required");
olh->count = 1;
olh->objsize = sizeof(*cfg);
olh->size = sizeof(buf);
strlcpy(cfg->name, name, sizeof(cfg->name));
if (do_set3(IP_FW_NAT64CLAT_CREATE, &olh->opheader, sizeof(buf)) != 0)
err(EX_OSERR, "nat64clat instance creation failed");
}
/*
* Configures existing nat64clat instance
* ipfw nat64clat <NAME> config <options>
* Request: [ ipfw_obj_header ipfw_nat64clat_cfg ]
*/
static void
nat64clat_config(const char *name, uint8_t set, int ac, char **av)
{
char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64clat_cfg)];
ipfw_nat64clat_cfg *cfg;
ipfw_obj_header *oh;
char *opt;
char *p;
size_t sz;
int tcmd;
struct in6_addr prefix;
uint8_t plen;
if (ac == 0)
errx(EX_USAGE, "config options required");
memset(&buf, 0, sizeof(buf));
oh = (ipfw_obj_header *)buf;
cfg = (ipfw_nat64clat_cfg *)(oh + 1);
sz = sizeof(buf);
nat64clat_fill_ntlv(&oh->ntlv, name, set);
if (do_get3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, &sz) != 0)
err(EX_OSERR, "failed to get config for instance %s", name);
while (ac > 0) {
tcmd = get_token(nat64newcmds, *av, "option");
opt = *av;
ac--; av++;
switch (tcmd) {
case TOK_PLAT_PREFIX:
case TOK_CLAT_PREFIX:
if (tcmd == TOK_PLAT_PREFIX) {
NEED1("IPv6 plat_prefix required");
} else {
NEED1("IPv6 clat_prefix required");
}
if ((p = strchr(*av, '/')) != NULL)
*p++ = '\0';
if (inet_pton(AF_INET6, *av, &prefix) != 1)
errx(EX_USAGE,
"Bad prefix: %s", *av);
plen = strtol(p, NULL, 10);
if (ipfw_check_nat64prefix(&prefix, plen) != 0)
errx(EX_USAGE,
"Bad prefix length: %s", p);
if (tcmd == TOK_PLAT_PREFIX) {
cfg->plat_prefix = prefix;
cfg->plat_plen = plen;
} else {
cfg->clat_prefix = prefix;
cfg->clat_plen = plen;
}
ac--; av++;
break;
case TOK_LOG:
cfg->flags |= NAT64_LOG;
break;
case TOK_LOGOFF:
cfg->flags &= ~NAT64_LOG;
break;
case TOK_PRIVATE:
cfg->flags |= NAT64_ALLOW_PRIVATE;
break;
case TOK_PRIVATEOFF:
cfg->flags &= ~NAT64_ALLOW_PRIVATE;
break;
default:
errx(EX_USAGE, "Can't change %s option", opt);
}
}
if (do_set3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, sizeof(buf)) != 0)
err(EX_OSERR, "nat64clat instance configuration failed");
}
/*
* Destroys nat64clat instance.
* Request: [ ipfw_obj_header ]
*/
static void
nat64clat_destroy(const char *name, uint8_t set)
{
ipfw_obj_header oh;
memset(&oh, 0, sizeof(oh));
nat64clat_fill_ntlv(&oh.ntlv, name, set);
if (do_set3(IP_FW_NAT64CLAT_DESTROY, &oh.opheader, sizeof(oh)) != 0)
err(EX_OSERR, "failed to destroy nat instance %s", name);
}
/*
* Get nat64clat instance statistics.
* Request: [ ipfw_obj_header ]
* Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
*/
static int
nat64clat_get_stats(const char *name, uint8_t set,
struct ipfw_nat64clat_stats *stats)
{
ipfw_obj_header *oh;
ipfw_obj_ctlv *oc;
size_t sz;
sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
oh = calloc(1, sz);
nat64clat_fill_ntlv(&oh->ntlv, name, set);
if (do_get3(IP_FW_NAT64CLAT_STATS, &oh->opheader, &sz) == 0) {
oc = (ipfw_obj_ctlv *)(oh + 1);
memcpy(stats, oc + 1, sizeof(*stats));
free(oh);
return (0);
}
free(oh);
return (-1);
}
static void
nat64clat_stats(const char *name, uint8_t set)
{
struct ipfw_nat64clat_stats stats;
if (nat64clat_get_stats(name, set, &stats) != 0)
err(EX_OSERR, "Error retrieving stats");
if (co.use_set != 0 || set != 0)
printf("set %u ", set);
printf("nat64clat %s\n", name);
printf("\t%ju packets translated from IPv6 to IPv4\n",
(uintmax_t)stats.opcnt64);
printf("\t%ju packets translated from IPv4 to IPv6\n",
(uintmax_t)stats.opcnt46);
printf("\t%ju IPv6 fragments created\n",
(uintmax_t)stats.ofrags);
printf("\t%ju IPv4 fragments received\n",
(uintmax_t)stats.ifrags);
printf("\t%ju output packets dropped due to no bufs, etc.\n",
(uintmax_t)stats.oerrors);
printf("\t%ju output packets discarded due to no IPv4 route\n",
(uintmax_t)stats.noroute4);
printf("\t%ju output packets discarded due to no IPv6 route\n",
(uintmax_t)stats.noroute6);
printf("\t%ju packets discarded due to unsupported protocol\n",
(uintmax_t)stats.noproto);
printf("\t%ju packets discarded due to memory allocation problems\n",
(uintmax_t)stats.nomem);
printf("\t%ju packets discarded due to some errors\n",
(uintmax_t)stats.dropped);
}
/*
* Reset nat64clat instance statistics specified by @oh->ntlv.
* Request: [ ipfw_obj_header ]
*/
static void
nat64clat_reset_stats(const char *name, uint8_t set)
{
ipfw_obj_header oh;
memset(&oh, 0, sizeof(oh));
nat64clat_fill_ntlv(&oh.ntlv, name, set);
if (do_set3(IP_FW_NAT64CLAT_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
err(EX_OSERR, "failed to reset stats for instance %s", name);
}
static int
nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set)
{
char plat_buf[INET6_ADDRSTRLEN], clat_buf[INET6_ADDRSTRLEN];
if (name != NULL && strcmp(cfg->name, name) != 0)
return (ESRCH);
if (co.use_set != 0 && cfg->set != set)
return (ESRCH);
if (co.use_set != 0 || cfg->set != 0)
printf("set %u ", cfg->set);
inet_ntop(AF_INET6, &cfg->clat_prefix, clat_buf, sizeof(clat_buf));
inet_ntop(AF_INET6, &cfg->plat_prefix, plat_buf, sizeof(plat_buf));
printf("nat64clat %s clat_prefix %s/%u plat_prefix %s/%u",
cfg->name, clat_buf, cfg->clat_plen, plat_buf, cfg->plat_plen);
if (cfg->flags & NAT64_LOG)
printf(" log");
if (cfg->flags & NAT64_ALLOW_PRIVATE)
printf(" allow_private");
printf("\n");
return (0);
}
static int
nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set)
{
if (co.use_set != 0 && cfg->set != set)
return (ESRCH);
nat64clat_destroy(cfg->name, cfg->set);
return (0);
}
/*
* Compare nat64clat instances names.
* Honor number comparison.
*/
static int
nat64name_cmp(const void *a, const void *b)
{
ipfw_nat64clat_cfg *ca, *cb;
ca = (ipfw_nat64clat_cfg *)a;
cb = (ipfw_nat64clat_cfg *)b;
if (ca->set > cb->set)
return (1);
else if (ca->set < cb->set)
return (-1);
return (stringnum_cmp(ca->name, cb->name));
}
/*
* Retrieves nat64clat instance list from kernel,
* optionally sorts it and calls requested function for each instance.
*
* Request: [ ipfw_obj_lheader ]
* Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ]
*/
static int
nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set, int sort)
{
ipfw_obj_lheader *olh;
ipfw_nat64clat_cfg *cfg;
size_t sz;
int i, error;
/* Start with reasonable default */
sz = sizeof(*olh) + 16 * sizeof(*cfg);
for (;;) {
if ((olh = calloc(1, sz)) == NULL)
return (ENOMEM);
olh->size = sz;
if (do_get3(IP_FW_NAT64CLAT_LIST, &olh->opheader, &sz) != 0) {
sz = olh->size;
free(olh);
if (errno != ENOMEM)
return (errno);
continue;
}
if (sort != 0)
qsort(olh + 1, olh->count, olh->objsize,
nat64name_cmp);
cfg = (ipfw_nat64clat_cfg *)(olh + 1);
for (i = 0; i < olh->count; i++) {
error = f(cfg, name, set); /* Ignore errors for now */
cfg = (ipfw_nat64clat_cfg *)((caddr_t)cfg +
olh->objsize);
}
free(olh);
break;
}
return (0);
}

View File

@ -4393,6 +4393,10 @@ netpfil/ipfw/ip_fw_iface.c optional inet ipfirewall
netpfil/ipfw/ip_fw_nat.c optional inet ipfirewall_nat
netpfil/ipfw/nat64/ip_fw_nat64.c optional inet inet6 ipfirewall \
ipfirewall_nat64
netpfil/ipfw/nat64/nat64clat.c optional inet inet6 ipfirewall \
ipfirewall_nat64
netpfil/ipfw/nat64/nat64clat_control.c optional inet inet6 ipfirewall \
ipfirewall_nat64
netpfil/ipfw/nat64/nat64lsn.c optional inet inet6 ipfirewall \
ipfirewall_nat64
netpfil/ipfw/nat64/nat64lsn_control.c optional inet inet6 ipfirewall \

View File

@ -4,6 +4,7 @@
KMOD= ipfw_nat64
SRCS= ip_fw_nat64.c nat64_translate.c
SRCS+= nat64clat.c nat64clat_control.c
SRCS+= nat64lsn.c nat64lsn_control.c
SRCS+= nat64stl.c nat64stl_control.c

View File

@ -134,6 +134,13 @@ typedef struct _ip_fw3_opheader {
#define IP_FW_NPTV6_STATS 154 /* Get NPTv6 instance statistics */
#define IP_FW_NPTV6_RESET_STATS 155 /* Reset NPTv6 instance statistics */
#define IP_FW_NAT64CLAT_CREATE 160 /* Create clat NAT64 instance */
#define IP_FW_NAT64CLAT_DESTROY 161 /* Destroy clat NAT64 instance */
#define IP_FW_NAT64CLAT_CONFIG 162 /* Modify clat NAT64 instance */
#define IP_FW_NAT64CLAT_LIST 163 /* List clat NAT64 instances */
#define IP_FW_NAT64CLAT_STATS 164 /* Get NAT64CLAT instance statistics */
#define IP_FW_NAT64CLAT_RESET_STATS 165 /* Reset NAT64CLAT instance statistics */
/*
* The kernel representation of ipfw rules is made of a list of
* 'instructions' (for all practical purposes equivalent to BPF

View File

@ -45,6 +45,19 @@ struct ipfw_nat64stl_stats {
uint64_t dropped; /* dropped due to some errors */
};
struct ipfw_nat64clat_stats {
uint64_t opcnt64; /* 6to4 of packets translated */
uint64_t opcnt46; /* 4to6 of packets translated */
uint64_t ofrags; /* number of fragments generated */
uint64_t ifrags; /* number of fragments received */
uint64_t oerrors; /* number of output errors */
uint64_t noroute4;
uint64_t noroute6;
uint64_t noproto; /* Protocol not supported */
uint64_t nomem; /* mbuf allocation failed */
uint64_t dropped; /* dropped due to some errors */
};
struct ipfw_nat64lsn_stats {
uint64_t opcnt64; /* 6to4 of packets translated */
uint64_t opcnt46; /* 4to6 of packets translated */
@ -95,6 +108,17 @@ typedef struct _ipfw_nat64stl_cfg {
uint32_t flags;
} ipfw_nat64stl_cfg;
typedef struct _ipfw_nat64clat_cfg {
char name[64]; /* NAT name */
struct in6_addr plat_prefix; /* NAT64 (PLAT) prefix */
struct in6_addr clat_prefix; /* Client (CLAT) prefix */
uint8_t plat_plen; /* PLAT Prefix length */
uint8_t clat_plen; /* CLAT Prefix length */
uint8_t set; /* Named instance set [0..31] */
uint8_t spare;
uint32_t flags;
} ipfw_nat64clat_cfg;
/*
* NAT64LSN default configuration values
*/

View File

@ -86,9 +86,15 @@ vnet_ipfw_nat64_init(const void *arg __unused)
error = nat64stl_init(ch, first);
if (error != 0)
return (error);
error = nat64clat_init(ch, first);
if (error != 0) {
nat64stl_uninit(ch, first);
return (error);
}
error = nat64lsn_init(ch, first);
if (error != 0) {
nat64stl_uninit(ch, first);
nat64clat_uninit(ch, first);
return (error);
}
return (0);
@ -103,6 +109,7 @@ vnet_ipfw_nat64_uninit(const void *arg __unused)
ch = &V_layer3_chain;
last = IS_DEFAULT_VNET(curvnet) ? 1: 0;
nat64stl_uninit(ch, last);
nat64clat_uninit(ch, last);
nat64lsn_uninit(ch, last);
return (0);
}

View File

@ -54,5 +54,7 @@ int nat64stl_init(struct ip_fw_chain *ch, int first);
void nat64stl_uninit(struct ip_fw_chain *ch, int last);
int nat64lsn_init(struct ip_fw_chain *ch, int first);
void nat64lsn_uninit(struct ip_fw_chain *ch, int last);
int nat64clat_init(struct ip_fw_chain *ch, int first);
void nat64clat_uninit(struct ip_fw_chain *ch, int last);
#endif /* _IP_FW_NAT64_H_ */

View File

@ -0,0 +1,255 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Yandex LLC
* Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
* Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
*
* 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/kernel.h>
#include <sys/lock.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/rmlock.h>
#include <sys/rwlock.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_pflog.h>
#include <net/pfil.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet6/ip_fw_nat64.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include <netpfil/pf/pf.h>
#include "nat64clat.h"
#define NAT64_LOOKUP(chain, cmd) \
(struct nat64clat_cfg *)SRV_OBJECT((chain), (cmd)->arg1)
static void
nat64clat_log(struct pfloghdr *plog, struct mbuf *m, sa_family_t family,
uint32_t kidx)
{
static uint32_t pktid = 0;
memset(plog, 0, sizeof(*plog));
plog->length = PFLOG_REAL_HDRLEN;
plog->af = family;
plog->action = PF_NAT;
plog->dir = PF_IN;
plog->rulenr = htonl(kidx);
pktid++;
plog->subrulenr = htonl(pktid);
plog->ruleset[0] = '\0';
strlcpy(plog->ifname, "NAT64CLAT", sizeof(plog->ifname));
ipfw_bpf_mtap2(plog, PFLOG_HDRLEN, m);
}
static int
nat64clat_handle_ip4(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
struct mbuf *m)
{
struct pfloghdr loghdr, *logdata;
struct in6_addr saddr, daddr;
struct ip *ip;
ip = mtod(m, struct ip*);
/* source address for CLAT may be private with no harm */
if (nat64_check_ip4(ip->ip_src.s_addr) != 0 ||
nat64_check_ip4(ip->ip_dst.s_addr) != 0 ||
nat64_check_private_ip4(&cfg->base, ip->ip_dst.s_addr) != 0)
return (NAT64SKIP);
memcpy(&saddr, &cfg->base.clat_prefix, sizeof(saddr));
nat64_embed_ip4(&saddr, cfg->base.clat_plen, ip->ip_src.s_addr);
memcpy(&daddr, &cfg->base.plat_prefix, sizeof(daddr));
nat64_embed_ip4(&daddr, cfg->base.plat_plen, ip->ip_dst.s_addr);
if (cfg->base.flags & NAT64_LOG) {
logdata = &loghdr;
nat64clat_log(logdata, m, AF_INET, cfg->no.kidx);
} else
logdata = NULL;
return (nat64_do_handle_ip4(m, &saddr, &daddr, 0, &cfg->base,
logdata));
}
static int
nat64clat_handle_ip6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
struct mbuf *m)
{
struct pfloghdr loghdr, *logdata;
struct ip6_hdr *ip6;
uint32_t aaddr;
/*
* NOTE: we expect ipfw_chk() did m_pullup() up to upper level
* protocol's headers. Also we skip some checks, that ip6_input(),
* ip6_forward(), ip6_fastfwd() and ipfw_chk() already did.
*/
ip6 = mtod(m, struct ip6_hdr *);
/* Check ip6_dst matches configured prefix */
if (memcmp(&ip6->ip6_dst, &cfg->base.clat_prefix,
cfg->base.clat_plen / 8) != 0)
return (NAT64SKIP);
/* Check ip6_src matches configured prefix */
if (memcmp(&ip6->ip6_src, &cfg->base.plat_prefix,
cfg->base.plat_plen / 8) != 0)
return (NAT64SKIP);
if (cfg->base.flags & NAT64_LOG) {
logdata = &loghdr;
nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx);
} else
logdata = NULL;
aaddr = nat64_extract_ip4(&ip6->ip6_src, cfg->base.plat_plen);
return (nat64_do_handle_ip6(m, aaddr, 0, &cfg->base, logdata));
}
static int
nat64clat_handle_icmp6(struct ip_fw_chain *chain, struct nat64clat_cfg *cfg,
struct mbuf *m)
{
struct pfloghdr loghdr, *logdata;
struct nat64_counters *stats;
struct ip6_hdr *ip6i;
struct icmp6_hdr *icmp6;
uint32_t daddr;
int hlen, proto;
hlen = 0;
stats = &cfg->base.stats;
proto = nat64_getlasthdr(m, &hlen);
if (proto != IPPROTO_ICMPV6) {
NAT64STAT_INC(stats, dropped);
return (NAT64MFREE);
}
icmp6 = mtodo(m, hlen);
switch (icmp6->icmp6_type) {
case ICMP6_DST_UNREACH:
case ICMP6_PACKET_TOO_BIG:
case ICMP6_TIME_EXCEED_TRANSIT:
case ICMP6_PARAM_PROB:
break;
default:
NAT64STAT_INC(stats, dropped);
return (NAT64MFREE);
}
hlen += sizeof(struct icmp6_hdr);
if (m->m_pkthdr.len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN) {
NAT64STAT_INC(stats, dropped);
return (NAT64MFREE);
}
if (m->m_len < hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN)
m = m_pullup(m, hlen + sizeof(struct ip6_hdr) + ICMP_MINLEN);
if (m == NULL) {
NAT64STAT_INC(stats, nomem);
return (NAT64RETURN);
}
/*
* Use destination address from inner IPv6 header to determine
* IPv4 mapped address.
*/
ip6i = mtodo(m, hlen);
daddr = nat64_extract_ip4(&ip6i->ip6_dst, cfg->base.clat_plen);
if (daddr == 0) {
NAT64STAT_INC(stats, dropped);
return (NAT64MFREE);
}
if (cfg->base.flags & NAT64_LOG) {
logdata = &loghdr;
nat64clat_log(logdata, m, AF_INET6, cfg->no.kidx);
} else
logdata = NULL;
return (nat64_handle_icmp6(m, 0, daddr, 0, &cfg->base, logdata));
}
int
ipfw_nat64clat(struct ip_fw_chain *chain, struct ip_fw_args *args,
ipfw_insn *cmd, int *done)
{
ipfw_insn *icmd;
struct nat64clat_cfg *cfg;
int ret;
IPFW_RLOCK_ASSERT(chain);
*done = 0; /* try next rule if not matched */
icmd = cmd + 1;
if (cmd->opcode != O_EXTERNAL_ACTION ||
cmd->arg1 != V_nat64clat_eid ||
icmd->opcode != O_EXTERNAL_INSTANCE ||
(cfg = NAT64_LOOKUP(chain, icmd)) == NULL)
return (0);
switch (args->f_id.addr_type) {
case 4:
ret = nat64clat_handle_ip4(chain, cfg, args->m);
break;
case 6:
ret = nat64clat_handle_ip6(chain, cfg, args->m);
break;
default:
return (0);
}
if (ret == NAT64SKIP) {
/*
* In case when packet is ICMPv6 message from an intermediate
* router, the source address of message will not match the
* addresses from configured prefixes.
*/
if (args->f_id.proto != IPPROTO_ICMPV6)
return (0);
ret = nat64clat_handle_icmp6(chain, cfg, args->m);
}
if (ret == NAT64SKIP)
return (0);
*done = 1; /* terminate the search */
if (ret == NAT64MFREE)
m_freem(args->m);
args->m = NULL;
return (IP_FW_NAT64);
}

View File

@ -0,0 +1,54 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Yandex LLC
* Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
* Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _IP_FW_NAT64CLAT_H_
#define _IP_FW_NAT64CLAT_H_
#include "ip_fw_nat64.h"
#include "nat64_translate.h"
struct nat64clat_cfg {
struct named_object no;
struct nat64_config base;
#define NAT64CLAT_FLAGSMASK \
(NAT64_LOG | NAT64_ALLOW_PRIVATE) /* flags to pass to userland */
char name[64];
};
VNET_DECLARE(uint16_t, nat64clat_eid);
#define V_nat64clat_eid VNET(nat64clat_eid)
#define IPFW_TLV_NAT64CLAT_NAME IPFW_TLV_EACTION_NAME(V_nat64clat_eid)
int ipfw_nat64clat(struct ip_fw_chain *chain, struct ip_fw_args *args,
ipfw_insn *cmd, int *done);
#endif

View File

@ -0,0 +1,614 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Yandex LLC
* Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
* Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
*
* 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/sockopt.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netinet6/in6_var.h>
#include <netinet6/ip6_var.h>
#include <netinet6/ip_fw_nat64.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include "nat64clat.h"
VNET_DEFINE(uint16_t, nat64clat_eid) = 0;
static struct nat64clat_cfg *nat64clat_alloc_config(const char *name,
uint8_t set);
static void nat64clat_free_config(struct nat64clat_cfg *cfg);
static struct nat64clat_cfg *nat64clat_find(struct namedobj_instance *ni,
const char *name, uint8_t set);
static struct nat64clat_cfg *
nat64clat_alloc_config(const char *name, uint8_t set)
{
struct nat64clat_cfg *cfg;
cfg = malloc(sizeof(struct nat64clat_cfg), M_IPFW, M_WAITOK | M_ZERO);
COUNTER_ARRAY_ALLOC(cfg->base.stats.cnt, NAT64STATS, M_WAITOK);
cfg->no.name = cfg->name;
cfg->no.etlv = IPFW_TLV_NAT64CLAT_NAME;
cfg->no.set = set;
strlcpy(cfg->name, name, sizeof(cfg->name));
return (cfg);
}
static void
nat64clat_free_config(struct nat64clat_cfg *cfg)
{
COUNTER_ARRAY_FREE(cfg->base.stats.cnt, NAT64STATS);
free(cfg, M_IPFW);
}
static void
nat64clat_export_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg,
ipfw_nat64clat_cfg *uc)
{
uc->plat_prefix = cfg->base.plat_prefix;
uc->plat_plen = cfg->base.plat_plen;
uc->clat_prefix = cfg->base.clat_prefix;
uc->clat_plen = cfg->base.clat_plen;
uc->flags = cfg->base.flags & NAT64CLAT_FLAGSMASK;
uc->set = cfg->no.set;
strlcpy(uc->name, cfg->no.name, sizeof(uc->name));
}
struct nat64clat_dump_arg {
struct ip_fw_chain *ch;
struct sockopt_data *sd;
};
static int
export_config_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct nat64clat_dump_arg *da = (struct nat64clat_dump_arg *)arg;
ipfw_nat64clat_cfg *uc;
uc = (ipfw_nat64clat_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc));
nat64clat_export_config(da->ch, (struct nat64clat_cfg *)no, uc);
return (0);
}
static struct nat64clat_cfg *
nat64clat_find(struct namedobj_instance *ni, const char *name, uint8_t set)
{
struct nat64clat_cfg *cfg;
cfg = (struct nat64clat_cfg *)ipfw_objhash_lookup_name_type(ni, set,
IPFW_TLV_NAT64CLAT_NAME, name);
return (cfg);
}
/*
* Creates new consumer-side nat64 translator instance.
* Data layout (v0)(current):
* Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ]
*
* Returns 0 on success
*/
static int
nat64clat_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_lheader *olh;
ipfw_nat64clat_cfg *uc;
struct namedobj_instance *ni;
struct nat64clat_cfg *cfg;
if (sd->valsize != sizeof(*olh) + sizeof(*uc))
return (EINVAL);
olh = (ipfw_obj_lheader *)sd->kbuf;
uc = (ipfw_nat64clat_cfg *)(olh + 1);
if (ipfw_check_object_name_generic(uc->name) != 0)
return (EINVAL);
if (uc->set >= IPFW_MAX_SETS ||
nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0 ||
nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0)
return (EINVAL);
ni = CHAIN_TO_SRV(ch);
IPFW_UH_RLOCK(ch);
if (nat64clat_find(ni, uc->name, uc->set) != NULL) {
IPFW_UH_RUNLOCK(ch);
return (EEXIST);
}
IPFW_UH_RUNLOCK(ch);
cfg = nat64clat_alloc_config(uc->name, uc->set);
cfg->base.plat_prefix = uc->plat_prefix;
cfg->base.plat_plen = uc->plat_plen;
cfg->base.clat_prefix = uc->clat_prefix;
cfg->base.clat_plen = uc->clat_plen;
cfg->base.flags = (uc->flags & NAT64CLAT_FLAGSMASK) |
NAT64_CLATPFX | NAT64_PLATPFX;
if (IN6_IS_ADDR_WKPFX(&cfg->base.plat_prefix))
cfg->base.flags |= NAT64_WKPFX;
IPFW_UH_WLOCK(ch);
if (nat64clat_find(ni, uc->name, uc->set) != NULL) {
IPFW_UH_WUNLOCK(ch);
nat64clat_free_config(cfg);
return (EEXIST);
}
if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) {
IPFW_UH_WUNLOCK(ch);
nat64clat_free_config(cfg);
return (ENOSPC);
}
ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no);
/* Okay, let's link data */
SRV_OBJECT(ch, cfg->no.kidx) = cfg;
IPFW_UH_WUNLOCK(ch);
return (0);
}
/*
* Change existing nat64clat instance configuration.
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ipfw_nat64clat_cfg ]
* Reply: [ ipfw_obj_header ipfw_nat64clat_cfg ]
*
* Returns 0 on success
*/
static int
nat64clat_config(struct ip_fw_chain *ch, ip_fw3_opheader *op,
struct sockopt_data *sd)
{
ipfw_obj_header *oh;
ipfw_nat64clat_cfg *uc;
struct nat64clat_cfg *cfg;
struct namedobj_instance *ni;
uint32_t flags;
if (sd->valsize != sizeof(*oh) + sizeof(*uc))
return (EINVAL);
oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd,
sizeof(*oh) + sizeof(*uc));
uc = (ipfw_nat64clat_cfg *)(oh + 1);
if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 ||
oh->ntlv.set >= IPFW_MAX_SETS)
return (EINVAL);
ni = CHAIN_TO_SRV(ch);
if (sd->sopt->sopt_dir == SOPT_GET) {
IPFW_UH_RLOCK(ch);
cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set);
if (cfg == NULL) {
IPFW_UH_RUNLOCK(ch);
return (ENOENT);
}
nat64clat_export_config(ch, cfg, uc);
IPFW_UH_RUNLOCK(ch);
return (0);
}
IPFW_UH_WLOCK(ch);
cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set);
if (cfg == NULL) {
IPFW_UH_WUNLOCK(ch);
return (ENOENT);
}
/*
* For now allow to change only following values:
* plat_prefix, plat_plen, clat_prefix, clat_plen, flags.
*/
flags = 0;
if (uc->plat_plen != cfg->base.plat_plen ||
!IN6_ARE_ADDR_EQUAL(&uc->plat_prefix, &cfg->base.plat_prefix)) {
if (nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0) {
IPFW_UH_WUNLOCK(ch);
return (EINVAL);
}
flags |= NAT64_PLATPFX;
}
if (uc->clat_plen != cfg->base.clat_plen ||
!IN6_ARE_ADDR_EQUAL(&uc->clat_prefix, &cfg->base.clat_prefix)) {
if (nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0) {
IPFW_UH_WUNLOCK(ch);
return (EINVAL);
}
flags |= NAT64_CLATPFX;
}
if (flags != 0) {
IPFW_WLOCK(ch);
if (flags & NAT64_PLATPFX) {
cfg->base.plat_prefix = uc->plat_prefix;
cfg->base.plat_plen = uc->plat_plen;
}
if (flags & NAT64_CLATPFX) {
cfg->base.clat_prefix = uc->clat_prefix;
cfg->base.clat_plen = uc->clat_plen;
}
IPFW_WUNLOCK(ch);
}
cfg->base.flags &= ~NAT64CLAT_FLAGSMASK;
cfg->base.flags |= uc->flags & NAT64CLAT_FLAGSMASK;
IPFW_UH_WUNLOCK(ch);
return (0);
}
static void
nat64clat_detach_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg)
{
IPFW_UH_WLOCK_ASSERT(ch);
ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no);
ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx);
}
/*
* Destroys nat64 instance.
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ]
*
* Returns 0 on success
*/
static int
nat64clat_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_header *oh;
struct nat64clat_cfg *cfg;
if (sd->valsize != sizeof(*oh))
return (EINVAL);
oh = (ipfw_obj_header *)sd->kbuf;
if (ipfw_check_object_name_generic(oh->ntlv.name) != 0)
return (EINVAL);
IPFW_UH_WLOCK(ch);
cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);
if (cfg == NULL) {
IPFW_UH_WUNLOCK(ch);
return (ENOENT);
}
if (cfg->no.refcnt > 0) {
IPFW_UH_WUNLOCK(ch);
return (EBUSY);
}
ipfw_reset_eaction_instance(ch, V_nat64clat_eid, cfg->no.kidx);
SRV_OBJECT(ch, cfg->no.kidx) = NULL;
nat64clat_detach_config(ch, cfg);
IPFW_UH_WUNLOCK(ch);
nat64clat_free_config(cfg);
return (0);
}
/*
* Lists all nat64clat instances currently available in kernel.
* Data layout (v0)(current):
* Request: [ ipfw_obj_lheader ]
* Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ]
*
* Returns 0 on success
*/
static int
nat64clat_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_lheader *olh;
struct nat64clat_dump_arg da;
/* Check minimum header size */
if (sd->valsize < sizeof(ipfw_obj_lheader))
return (EINVAL);
olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh));
IPFW_UH_RLOCK(ch);
olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch),
IPFW_TLV_NAT64CLAT_NAME);
olh->objsize = sizeof(ipfw_nat64clat_cfg);
olh->size = sizeof(*olh) + olh->count * olh->objsize;
if (sd->valsize < olh->size) {
IPFW_UH_RUNLOCK(ch);
return (ENOMEM);
}
memset(&da, 0, sizeof(da));
da.ch = ch;
da.sd = sd;
ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb,
&da, IPFW_TLV_NAT64CLAT_NAME);
IPFW_UH_RUNLOCK(ch);
return (0);
}
#define __COPY_STAT_FIELD(_cfg, _stats, _field) \
(_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field)
static void
export_stats(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg,
struct ipfw_nat64clat_stats *stats)
{
__COPY_STAT_FIELD(cfg, stats, opcnt64);
__COPY_STAT_FIELD(cfg, stats, opcnt46);
__COPY_STAT_FIELD(cfg, stats, ofrags);
__COPY_STAT_FIELD(cfg, stats, ifrags);
__COPY_STAT_FIELD(cfg, stats, oerrors);
__COPY_STAT_FIELD(cfg, stats, noroute4);
__COPY_STAT_FIELD(cfg, stats, noroute6);
__COPY_STAT_FIELD(cfg, stats, noproto);
__COPY_STAT_FIELD(cfg, stats, nomem);
__COPY_STAT_FIELD(cfg, stats, dropped);
}
/*
* Get nat64clat statistics.
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ]
* Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]]
*
* Returns 0 on success
*/
static int
nat64clat_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op,
struct sockopt_data *sd)
{
struct ipfw_nat64clat_stats stats;
struct nat64clat_cfg *cfg;
ipfw_obj_header *oh;
ipfw_obj_ctlv *ctlv;
size_t sz;
sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats);
if (sd->valsize % sizeof(uint64_t))
return (EINVAL);
if (sd->valsize < sz)
return (ENOMEM);
oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz);
if (oh == NULL)
return (EINVAL);
memset(&stats, 0, sizeof(stats));
IPFW_UH_RLOCK(ch);
cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);
if (cfg == NULL) {
IPFW_UH_RUNLOCK(ch);
return (ENOENT);
}
export_stats(ch, cfg, &stats);
IPFW_UH_RUNLOCK(ch);
ctlv = (ipfw_obj_ctlv *)(oh + 1);
memset(ctlv, 0, sizeof(*ctlv));
ctlv->head.type = IPFW_TLV_COUNTERS;
ctlv->head.length = sz - sizeof(ipfw_obj_header);
ctlv->count = sizeof(stats) / sizeof(uint64_t);
ctlv->objsize = sizeof(uint64_t);
ctlv->version = IPFW_NAT64_VERSION;
memcpy(ctlv + 1, &stats, sizeof(stats));
return (0);
}
/*
* Reset nat64clat statistics.
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ]
*
* Returns 0 on success
*/
static int
nat64clat_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op,
struct sockopt_data *sd)
{
struct nat64clat_cfg *cfg;
ipfw_obj_header *oh;
if (sd->valsize != sizeof(*oh))
return (EINVAL);
oh = (ipfw_obj_header *)sd->kbuf;
if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 ||
oh->ntlv.set >= IPFW_MAX_SETS)
return (EINVAL);
IPFW_UH_WLOCK(ch);
cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);
if (cfg == NULL) {
IPFW_UH_WUNLOCK(ch);
return (ENOENT);
}
COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS);
IPFW_UH_WUNLOCK(ch);
return (0);
}
static struct ipfw_sopt_handler scodes[] = {
{ IP_FW_NAT64CLAT_CREATE, 0, HDIR_SET, nat64clat_create },
{ IP_FW_NAT64CLAT_DESTROY,0, HDIR_SET, nat64clat_destroy },
{ IP_FW_NAT64CLAT_CONFIG, 0, HDIR_BOTH, nat64clat_config },
{ IP_FW_NAT64CLAT_LIST, 0, HDIR_GET, nat64clat_list },
{ IP_FW_NAT64CLAT_STATS, 0, HDIR_GET, nat64clat_stats },
{ IP_FW_NAT64CLAT_RESET_STATS,0, HDIR_SET, nat64clat_reset_stats },
};
static int
nat64clat_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype)
{
ipfw_insn *icmd;
icmd = cmd - 1;
if (icmd->opcode != O_EXTERNAL_ACTION ||
icmd->arg1 != V_nat64clat_eid)
return (1);
*puidx = cmd->arg1;
*ptype = 0;
return (0);
}
static void
nat64clat_update_arg1(ipfw_insn *cmd, uint16_t idx)
{
cmd->arg1 = idx;
}
static int
nat64clat_findbyname(struct ip_fw_chain *ch, struct tid_info *ti,
struct named_object **pno)
{
int err;
err = ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti,
IPFW_TLV_NAT64CLAT_NAME, pno);
return (err);
}
static struct named_object *
nat64clat_findbykidx(struct ip_fw_chain *ch, uint16_t idx)
{
struct namedobj_instance *ni;
struct named_object *no;
IPFW_UH_WLOCK_ASSERT(ch);
ni = CHAIN_TO_SRV(ch);
no = ipfw_objhash_lookup_kidx(ni, idx);
KASSERT(no != NULL, ("NAT with index %d not found", idx));
return (no);
}
static int
nat64clat_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set,
enum ipfw_sets_cmd cmd)
{
return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch), IPFW_TLV_NAT64CLAT_NAME,
set, new_set, cmd));
}
static struct opcode_obj_rewrite opcodes[] = {
{
.opcode = O_EXTERNAL_INSTANCE,
.etlv = IPFW_TLV_EACTION /* just show it isn't table */,
.classifier = nat64clat_classify,
.update = nat64clat_update_arg1,
.find_byname = nat64clat_findbyname,
.find_bykidx = nat64clat_findbykidx,
.manage_sets = nat64clat_manage_sets,
},
};
static int
destroy_config_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct nat64clat_cfg *cfg;
struct ip_fw_chain *ch;
ch = (struct ip_fw_chain *)arg;
cfg = (struct nat64clat_cfg *)SRV_OBJECT(ch, no->kidx);
SRV_OBJECT(ch, no->kidx) = NULL;
nat64clat_detach_config(ch, cfg);
nat64clat_free_config(cfg);
return (0);
}
int
nat64clat_init(struct ip_fw_chain *ch, int first)
{
V_nat64clat_eid = ipfw_add_eaction(ch, ipfw_nat64clat, "nat64clat");
if (V_nat64clat_eid == 0)
return (ENXIO);
IPFW_ADD_SOPT_HANDLER(first, scodes);
IPFW_ADD_OBJ_REWRITER(first, opcodes);
return (0);
}
void
nat64clat_uninit(struct ip_fw_chain *ch, int last)
{
IPFW_DEL_OBJ_REWRITER(last, opcodes);
IPFW_DEL_SOPT_HANDLER(last, scodes);
ipfw_del_eaction(ch, V_nat64clat_eid);
/*
* Since we already have deregistered external action,
* our named objects become unaccessible via rules, because
* all rules were truncated by ipfw_del_eaction().
* So, we can unlink and destroy our named objects without holding
* IPFW_WLOCK().
*/
IPFW_UH_WLOCK(ch);
ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch,
IPFW_TLV_NAT64CLAT_NAME);
V_nat64clat_eid = 0;
IPFW_UH_WUNLOCK(ch);
}