The module works together with ipfw(4) and implemented as its external action module. Stateless NAT64 registers external action with name nat64stl. This keyword should be used to create NAT64 instance and to address this instance in rules. Stateless NAT64 uses two lookup tables with mapped IPv4->IPv6 and IPv6->IPv4 addresses to perform translation. A configuration of instance should looks like this: 1. Create lookup tables: # ipfw table T46 create type addr valtype ipv6 # ipfw table T64 create type addr valtype ipv4 2. Fill T46 and T64 tables. 3. Add rule to allow neighbor solicitation and advertisement: # ipfw add allow icmp6 from any to any icmp6types 135,136 4. Create NAT64 instance: # ipfw nat64stl NAT create table4 T46 table6 T64 5. Add rules that matches the traffic: # ipfw add nat64stl NAT ip from any to table(T46) # ipfw add nat64stl NAT ip from table(T64) to 64:ff9b::/96 6. Configure DNS64 for IPv6 clients and add route to 64:ff9b::/96 via NAT64 host. Stateful NAT64 registers external action with name nat64lsn. The only one option required to create nat64lsn instance - prefix4. It defines the pool of IPv4 addresses used for translation. A configuration of instance should looks like this: 1. Add rule to allow neighbor solicitation and advertisement: # ipfw add allow icmp6 from any to any icmp6types 135,136 2. Create NAT64 instance: # ipfw nat64lsn NAT create prefix4 A.B.C.D/28 3. Add rules that matches the traffic: # ipfw add nat64lsn NAT ip from any to A.B.C.D/28 # ipfw add nat64lsn NAT ip6 from any to 64:ff9b::/96 4. Configure DNS64 for IPv6 clients and add route to 64:ff9b::/96 via NAT64 host. Obtained from: Yandex LLC Relnotes: yes Sponsored by: Yandex LLC Differential Revision: https://reviews.freebsd.org/D6434
855 lines
22 KiB
C
855 lines
22 KiB
C
/*-
|
|
* Copyright (c) 2015-2016 Yandex LLC
|
|
* Copyright (c) 2015-2016 Alexander V. Chernikov <melifaro@FreeBSD.org>
|
|
* Copyright (c) 2015-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/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include "ipfw2.h"
|
|
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.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>
|
|
|
|
static void nat64lsn_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name,
|
|
uint8_t set);
|
|
typedef int (nat64lsn_cb_t)(ipfw_nat64lsn_cfg *cfg, const char *name,
|
|
uint8_t set);
|
|
static int nat64lsn_foreach(nat64lsn_cb_t *f, const char *name, uint8_t set,
|
|
int sort);
|
|
|
|
static void nat64lsn_create(const char *name, uint8_t set, int ac, char **av);
|
|
static void nat64lsn_config(const char *name, uint8_t set, int ac, char **av);
|
|
static void nat64lsn_destroy(const char *name, uint8_t set);
|
|
static void nat64lsn_stats(const char *name, uint8_t set);
|
|
static void nat64lsn_reset_stats(const char *name, uint8_t set);
|
|
static int nat64lsn_show_cb(ipfw_nat64lsn_cfg *cfg, const char *name,
|
|
uint8_t set);
|
|
static int nat64lsn_destroy_cb(ipfw_nat64lsn_cfg *cfg, const char *name,
|
|
uint8_t set);
|
|
static int nat64lsn_states_cb(ipfw_nat64lsn_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 uint64_t
|
|
nat64lsn_print_states(void *buf)
|
|
{
|
|
char s[INET6_ADDRSTRLEN], a[INET_ADDRSTRLEN], f[INET_ADDRSTRLEN];
|
|
char sflags[4], *sf, *proto;
|
|
ipfw_obj_header *oh;
|
|
ipfw_obj_data *od;
|
|
ipfw_nat64lsn_stg *stg;
|
|
ipfw_nat64lsn_state *ste;
|
|
uint64_t next_idx;
|
|
int i, sz;
|
|
|
|
oh = (ipfw_obj_header *)buf;
|
|
od = (ipfw_obj_data *)(oh + 1);
|
|
stg = (ipfw_nat64lsn_stg *)(od + 1);
|
|
sz = od->head.length - sizeof(*od);
|
|
next_idx = 0;
|
|
while (sz > 0 && next_idx != 0xFF) {
|
|
next_idx = stg->next_idx;
|
|
sz -= sizeof(*stg);
|
|
if (stg->count == 0) {
|
|
stg++;
|
|
continue;
|
|
}
|
|
switch (stg->proto) {
|
|
case IPPROTO_TCP:
|
|
proto = "TCP";
|
|
break;
|
|
case IPPROTO_UDP:
|
|
proto = "UDP";
|
|
break;
|
|
case IPPROTO_ICMPV6:
|
|
proto = "ICMPv6";
|
|
break;
|
|
}
|
|
inet_ntop(AF_INET6, &stg->host6, s, sizeof(s));
|
|
inet_ntop(AF_INET, &stg->alias4, a, sizeof(a));
|
|
ste = (ipfw_nat64lsn_state *)(stg + 1);
|
|
for (i = 0; i < stg->count && sz > 0; i++) {
|
|
sf = sflags;
|
|
inet_ntop(AF_INET, &ste->daddr, f, sizeof(f));
|
|
if (stg->proto == IPPROTO_TCP) {
|
|
if (ste->flags & 0x02)
|
|
*sf++ = 'S';
|
|
if (ste->flags & 0x04)
|
|
*sf++ = 'E';
|
|
if (ste->flags & 0x01)
|
|
*sf++ = 'F';
|
|
}
|
|
*sf = '\0';
|
|
switch (stg->proto) {
|
|
case IPPROTO_TCP:
|
|
case IPPROTO_UDP:
|
|
printf("%s:%d\t%s:%d\t%s\t%s\t%d\t%s:%d\n",
|
|
s, ste->sport, a, ste->aport, proto,
|
|
sflags, ste->idle, f, ste->dport);
|
|
break;
|
|
case IPPROTO_ICMPV6:
|
|
printf("%s\t%s\t%s\t\t%d\t%s\n",
|
|
s, a, proto, ste->idle, f);
|
|
break;
|
|
default:
|
|
printf("%s\t%s\t%d\t\t%d\t%s\n",
|
|
s, a, stg->proto, ste->idle, f);
|
|
}
|
|
ste++;
|
|
sz -= sizeof(*ste);
|
|
}
|
|
stg = (ipfw_nat64lsn_stg *)ste;
|
|
}
|
|
return (next_idx);
|
|
}
|
|
|
|
static int
|
|
nat64lsn_states_cb(ipfw_nat64lsn_cfg *cfg, const char *name, uint8_t set)
|
|
{
|
|
ipfw_obj_header *oh;
|
|
ipfw_obj_data *od;
|
|
void *buf;
|
|
uint64_t next_idx;
|
|
size_t sz;
|
|
|
|
if (name != NULL && strcmp(cfg->name, name) != 0)
|
|
return (ESRCH);
|
|
|
|
if (set != 0 && cfg->set != set)
|
|
return (ESRCH);
|
|
|
|
next_idx = 0;
|
|
sz = 4096;
|
|
if ((buf = calloc(1, sz)) == NULL)
|
|
err(EX_OSERR, NULL);
|
|
do {
|
|
oh = (ipfw_obj_header *)buf;
|
|
od = (ipfw_obj_data *)(oh + 1);
|
|
nat64lsn_fill_ntlv(&oh->ntlv, cfg->name, set);
|
|
od->head.type = IPFW_TLV_OBJDATA;
|
|
od->head.length = sizeof(*od) + sizeof(next_idx);
|
|
*((uint64_t *)(od + 1)) = next_idx;
|
|
if (do_get3(IP_FW_NAT64LSN_LIST_STATES, &oh->opheader, &sz))
|
|
err(EX_OSERR, "Error reading nat64lsn states");
|
|
next_idx = nat64lsn_print_states(buf);
|
|
sz = 4096;
|
|
memset(buf, 0, sz);
|
|
} while (next_idx != 0xFF);
|
|
|
|
free(buf);
|
|
return (0);
|
|
}
|
|
|
|
static struct _s_x nat64statscmds[] = {
|
|
{ "reset", TOK_RESET },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static void
|
|
ipfw_nat64lsn_stats_handler(const char *name, uint8_t set, int ac, char *av[])
|
|
{
|
|
int tcmd;
|
|
|
|
if (ac == 0) {
|
|
nat64lsn_stats(name, set);
|
|
return;
|
|
}
|
|
NEED1("nat64lsn stats needs command");
|
|
tcmd = get_token(nat64statscmds, *av, "nat64lsn stats command");
|
|
switch (tcmd) {
|
|
case TOK_RESET:
|
|
nat64lsn_reset_stats(name, set);
|
|
}
|
|
}
|
|
|
|
static struct _s_x nat64listcmds[] = {
|
|
{ "states", TOK_STATES },
|
|
{ "config", TOK_CONFIG },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static void
|
|
ipfw_nat64lsn_list_handler(const char *name, uint8_t set, int ac, char *av[])
|
|
{
|
|
int tcmd;
|
|
|
|
if (ac == 0) {
|
|
nat64lsn_foreach(nat64lsn_show_cb, name, set, 1);
|
|
return;
|
|
}
|
|
NEED1("nat64lsn list needs command");
|
|
tcmd = get_token(nat64listcmds, *av, "nat64lsn list command");
|
|
switch (tcmd) {
|
|
case TOK_STATES:
|
|
nat64lsn_foreach(nat64lsn_states_cb, name, set, 1);
|
|
break;
|
|
case TOK_CONFIG:
|
|
nat64lsn_foreach(nat64lsn_show_cb, name, set, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This one handles all nat64lsn-related commands
|
|
* ipfw [set N] nat64lsn NAME {create | config} ...
|
|
* ipfw [set N] nat64lsn NAME stats
|
|
* ipfw [set N] nat64lsn {NAME | all} destroy
|
|
* ipfw [set N] nat64lsn {NAME | all} {list | show} [config | states]
|
|
*/
|
|
#define nat64lsn_check_name table_check_name
|
|
void
|
|
ipfw_nat64lsn_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("nat64lsn needs instance name");
|
|
name = *av;
|
|
if (nat64lsn_check_name(name) != 0) {
|
|
if (strcmp(name, "all") == 0)
|
|
name = NULL;
|
|
else
|
|
errx(EX_USAGE, "nat64lsn instance name %s is invalid",
|
|
name);
|
|
}
|
|
ac--; av++;
|
|
NEED1("nat64lsn needs command");
|
|
|
|
tcmd = get_token(nat64cmds, *av, "nat64lsn command");
|
|
if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
|
|
errx(EX_USAGE, "nat64lsn instance name required");
|
|
switch (tcmd) {
|
|
case TOK_CREATE:
|
|
ac--; av++;
|
|
nat64lsn_create(name, set, ac, av);
|
|
break;
|
|
case TOK_CONFIG:
|
|
ac--; av++;
|
|
nat64lsn_config(name, set, ac, av);
|
|
break;
|
|
case TOK_LIST:
|
|
ac--; av++;
|
|
ipfw_nat64lsn_list_handler(name, set, ac, av);
|
|
break;
|
|
case TOK_DESTROY:
|
|
if (name == NULL)
|
|
nat64lsn_foreach(nat64lsn_destroy_cb, NULL, set, 0);
|
|
else
|
|
nat64lsn_destroy(name, set);
|
|
break;
|
|
case TOK_STATS:
|
|
ac--; av++;
|
|
ipfw_nat64lsn_stats_handler(name, set, ac, av);
|
|
}
|
|
}
|
|
|
|
static void
|
|
nat64lsn_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 void
|
|
nat64lsn_apply_mask(int af, void *prefix, uint16_t plen)
|
|
{
|
|
struct in6_addr mask6, *p6;
|
|
struct in_addr mask4, *p4;
|
|
|
|
if (af == AF_INET) {
|
|
p4 = (struct in_addr *)prefix;
|
|
mask4.s_addr = htonl(~((1 << (32 - plen)) - 1));
|
|
p4->s_addr &= mask4.s_addr;
|
|
} else if (af == AF_INET6) {
|
|
p6 = (struct in6_addr *)prefix;
|
|
n2mask(&mask6, plen);
|
|
APPLY_MASK(p6, &mask6);
|
|
}
|
|
}
|
|
|
|
static void
|
|
nat64lsn_parse_prefix(const char *arg, int af, void *prefix, uint16_t *plen)
|
|
{
|
|
char *p, *l;
|
|
|
|
p = strdup(arg);
|
|
if (p == NULL)
|
|
err(EX_OSERR, NULL);
|
|
if ((l = strchr(p, '/')) != NULL)
|
|
*l++ = '\0';
|
|
if (l == NULL)
|
|
errx(EX_USAGE, "Prefix length required");
|
|
if (inet_pton(af, p, prefix) != 1)
|
|
errx(EX_USAGE, "Bad prefix: %s", p);
|
|
*plen = (uint16_t)strtol(l, &l, 10);
|
|
if (*l != '\0' || *plen == 0 || (af == AF_INET && *plen > 32) ||
|
|
(af == AF_INET6 && *plen > 96))
|
|
errx(EX_USAGE, "Bad prefix length: %s", arg);
|
|
nat64lsn_apply_mask(af, prefix, *plen);
|
|
free(p);
|
|
}
|
|
|
|
static uint32_t
|
|
nat64lsn_parse_int(const char *arg, const char *desc)
|
|
{
|
|
char *p;
|
|
uint32_t val;
|
|
|
|
val = (uint32_t)strtol(arg, &p, 10);
|
|
if (*p != '\0')
|
|
errx(EX_USAGE, "Invalid %s value: %s\n", desc, arg);
|
|
return (val);
|
|
}
|
|
|
|
static struct _s_x nat64newcmds[] = {
|
|
{ "prefix6", TOK_PREFIX6 },
|
|
{ "agg_len", TOK_AGG_LEN }, /* not yet */
|
|
{ "agg_count", TOK_AGG_COUNT }, /* not yet */
|
|
{ "port_range", TOK_PORT_RANGE }, /* not yet */
|
|
{ "jmaxlen", TOK_JMAXLEN },
|
|
{ "prefix4", TOK_PREFIX4 },
|
|
{ "max_ports", TOK_MAX_PORTS },
|
|
{ "host_del_age", TOK_HOST_DEL_AGE },
|
|
{ "pg_del_age", TOK_PG_DEL_AGE },
|
|
{ "tcp_syn_age", TOK_TCP_SYN_AGE },
|
|
{ "tcp_close_age",TOK_TCP_CLOSE_AGE },
|
|
{ "tcp_est_age", TOK_TCP_EST_AGE },
|
|
{ "udp_age", TOK_UDP_AGE },
|
|
{ "icmp_age", TOK_ICMP_AGE },
|
|
{ "log", TOK_LOG },
|
|
{ "-log", TOK_LOGOFF },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Creates new nat64lsn instance
|
|
* ipfw nat64lsn <NAME> create
|
|
* [ max_ports <N> ]
|
|
* Request: [ ipfw_obj_lheader ipfw_nat64lsn_cfg ]
|
|
*/
|
|
#define NAT64LSN_HAS_PREFIX4 0x01
|
|
#define NAT64LSN_HAS_PREFIX6 0x02
|
|
static void
|
|
nat64lsn_create(const char *name, uint8_t set, int ac, char **av)
|
|
{
|
|
char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64lsn_cfg)];
|
|
ipfw_nat64lsn_cfg *cfg;
|
|
ipfw_obj_lheader *olh;
|
|
int tcmd, flags;
|
|
char *opt;
|
|
|
|
memset(&buf, 0, sizeof(buf));
|
|
olh = (ipfw_obj_lheader *)buf;
|
|
cfg = (ipfw_nat64lsn_cfg *)(olh + 1);
|
|
|
|
/* Some reasonable defaults */
|
|
inet_pton(AF_INET6, "64:ff9b::", &cfg->prefix6);
|
|
cfg->plen6 = 96;
|
|
cfg->set = set;
|
|
cfg->max_ports = NAT64LSN_MAX_PORTS;
|
|
cfg->jmaxlen = NAT64LSN_JMAXLEN;
|
|
cfg->nh_delete_delay = NAT64LSN_HOST_AGE;
|
|
cfg->pg_delete_delay = NAT64LSN_PG_AGE;
|
|
cfg->st_syn_ttl = NAT64LSN_TCP_SYN_AGE;
|
|
cfg->st_estab_ttl = NAT64LSN_TCP_EST_AGE;
|
|
cfg->st_close_ttl = NAT64LSN_TCP_FIN_AGE;
|
|
cfg->st_udp_ttl = NAT64LSN_UDP_AGE;
|
|
cfg->st_icmp_ttl = NAT64LSN_ICMP_AGE;
|
|
flags = NAT64LSN_HAS_PREFIX6;
|
|
while (ac > 0) {
|
|
tcmd = get_token(nat64newcmds, *av, "option");
|
|
opt = *av;
|
|
ac--; av++;
|
|
|
|
switch (tcmd) {
|
|
case TOK_PREFIX4:
|
|
NEED1("IPv4 prefix required");
|
|
nat64lsn_parse_prefix(*av, AF_INET, &cfg->prefix4,
|
|
&cfg->plen4);
|
|
flags |= NAT64LSN_HAS_PREFIX4;
|
|
ac--; av++;
|
|
break;
|
|
#if 0
|
|
case TOK_PREFIX6:
|
|
NEED1("IPv6 prefix required");
|
|
nat64lsn_parse_prefix(*av, AF_INET6, &cfg->prefix6,
|
|
&cfg->plen6);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_AGG_LEN:
|
|
NEED1("Aggregation prefix len required");
|
|
cfg->agg_prefix_len = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_AGG_COUNT:
|
|
NEED1("Max per-prefix count required");
|
|
cfg->agg_prefix_max = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_PORT_RANGE:
|
|
NEED1("port range x[:y] required");
|
|
if ((p = strchr(*av, ':')) == NULL)
|
|
cfg->min_port = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
else {
|
|
*p++ = '\0';
|
|
cfg->min_port = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
cfg->max_port = (uint16_t)nat64lsn_parse_int(
|
|
p, opt);
|
|
}
|
|
ac--; av++;
|
|
break;
|
|
case TOK_JMAXLEN:
|
|
NEED1("job queue length required");
|
|
cfg->jmaxlen = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
#endif
|
|
case TOK_MAX_PORTS:
|
|
NEED1("Max per-user ports required");
|
|
cfg->max_ports = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_HOST_DEL_AGE:
|
|
NEED1("host delete delay required");
|
|
cfg->nh_delete_delay = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_PG_DEL_AGE:
|
|
NEED1("portgroup delete delay required");
|
|
cfg->pg_delete_delay = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_SYN_AGE:
|
|
NEED1("tcp syn age required");
|
|
cfg->st_syn_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_CLOSE_AGE:
|
|
NEED1("tcp close age required");
|
|
cfg->st_close_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_EST_AGE:
|
|
NEED1("tcp est age required");
|
|
cfg->st_estab_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_UDP_AGE:
|
|
NEED1("udp age required");
|
|
cfg->st_udp_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_ICMP_AGE:
|
|
NEED1("icmp age required");
|
|
cfg->st_icmp_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_LOG:
|
|
cfg->flags |= NAT64_LOG;
|
|
break;
|
|
case TOK_LOGOFF:
|
|
cfg->flags &= ~NAT64_LOG;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check validness */
|
|
if ((flags & NAT64LSN_HAS_PREFIX4) != NAT64LSN_HAS_PREFIX4)
|
|
errx(EX_USAGE, "prefix4 required");
|
|
|
|
olh->count = 1;
|
|
olh->objsize = sizeof(*cfg);
|
|
olh->size = sizeof(buf);
|
|
strlcpy(cfg->name, name, sizeof(cfg->name));
|
|
if (do_set3(IP_FW_NAT64LSN_CREATE, &olh->opheader, sizeof(buf)) != 0)
|
|
err(EX_OSERR, "nat64lsn instance creation failed");
|
|
}
|
|
|
|
/*
|
|
* Configures existing nat64lsn instance
|
|
* ipfw nat64lsn <NAME> config <options>
|
|
* Request: [ ipfw_obj_header ipfw_nat64lsn_cfg ]
|
|
*/
|
|
static void
|
|
nat64lsn_config(const char *name, uint8_t set, int ac, char **av)
|
|
{
|
|
char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64lsn_cfg)];
|
|
ipfw_nat64lsn_cfg *cfg;
|
|
ipfw_obj_header *oh;
|
|
size_t sz;
|
|
char *opt;
|
|
int tcmd;
|
|
|
|
if (ac == 0)
|
|
errx(EX_USAGE, "config options required");
|
|
memset(&buf, 0, sizeof(buf));
|
|
oh = (ipfw_obj_header *)buf;
|
|
cfg = (ipfw_nat64lsn_cfg *)(oh + 1);
|
|
sz = sizeof(buf);
|
|
|
|
nat64lsn_fill_ntlv(&oh->ntlv, name, set);
|
|
if (do_get3(IP_FW_NAT64LSN_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_MAX_PORTS:
|
|
NEED1("Max per-user ports required");
|
|
cfg->max_ports = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_JMAXLEN:
|
|
NEED1("job queue length required");
|
|
cfg->jmaxlen = nat64lsn_parse_int(*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_HOST_DEL_AGE:
|
|
NEED1("host delete delay required");
|
|
cfg->nh_delete_delay = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_PG_DEL_AGE:
|
|
NEED1("portgroup delete delay required");
|
|
cfg->pg_delete_delay = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_SYN_AGE:
|
|
NEED1("tcp syn age required");
|
|
cfg->st_syn_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_CLOSE_AGE:
|
|
NEED1("tcp close age required");
|
|
cfg->st_close_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_TCP_EST_AGE:
|
|
NEED1("tcp est age required");
|
|
cfg->st_estab_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_UDP_AGE:
|
|
NEED1("udp age required");
|
|
cfg->st_udp_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_ICMP_AGE:
|
|
NEED1("icmp age required");
|
|
cfg->st_icmp_ttl = (uint16_t)nat64lsn_parse_int(
|
|
*av, opt);
|
|
ac--; av++;
|
|
break;
|
|
case TOK_LOG:
|
|
cfg->flags |= NAT64_LOG;
|
|
break;
|
|
case TOK_LOGOFF:
|
|
cfg->flags &= ~NAT64_LOG;
|
|
break;
|
|
default:
|
|
errx(EX_USAGE, "Can't change %s option", opt);
|
|
}
|
|
}
|
|
|
|
if (do_set3(IP_FW_NAT64LSN_CONFIG, &oh->opheader, sizeof(buf)) != 0)
|
|
err(EX_OSERR, "nat64lsn instance configuration failed");
|
|
}
|
|
|
|
/*
|
|
* Reset nat64lsn instance statistics specified by @oh->ntlv.
|
|
* Request: [ ipfw_obj_header ]
|
|
*/
|
|
static void
|
|
nat64lsn_reset_stats(const char *name, uint8_t set)
|
|
{
|
|
ipfw_obj_header oh;
|
|
|
|
memset(&oh, 0, sizeof(oh));
|
|
nat64lsn_fill_ntlv(&oh.ntlv, name, set);
|
|
if (do_set3(IP_FW_NAT64LSN_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
|
|
err(EX_OSERR, "failed to reset stats for instance %s", name);
|
|
}
|
|
|
|
/*
|
|
* Destroys nat64lsn instance specified by @oh->ntlv.
|
|
* Request: [ ipfw_obj_header ]
|
|
*/
|
|
static void
|
|
nat64lsn_destroy(const char *name, uint8_t set)
|
|
{
|
|
ipfw_obj_header oh;
|
|
|
|
memset(&oh, 0, sizeof(oh));
|
|
nat64lsn_fill_ntlv(&oh.ntlv, name, set);
|
|
if (do_set3(IP_FW_NAT64LSN_DESTROY, &oh.opheader, sizeof(oh)) != 0)
|
|
err(EX_OSERR, "failed to destroy nat instance %s", name);
|
|
}
|
|
|
|
/*
|
|
* Get nat64lsn instance statistics.
|
|
* Request: [ ipfw_obj_header ]
|
|
* Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
|
|
*/
|
|
static int
|
|
nat64lsn_get_stats(const char *name, uint8_t set,
|
|
struct ipfw_nat64lsn_stats *stats)
|
|
{
|
|
ipfw_obj_header *oh;
|
|
ipfw_obj_ctlv *oc;
|
|
size_t sz;
|
|
|
|
sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
|
|
oh = calloc(1, sz);
|
|
nat64lsn_fill_ntlv(&oh->ntlv, name, set);
|
|
if (do_get3(IP_FW_NAT64LSN_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);
|
|
}
|
|
|
|
#define _P_STAT(_s, _f) printf("%8s:\t%lu\n", #_f, _s._f)
|
|
static void
|
|
nat64lsn_stats(const char *name, uint8_t set)
|
|
{
|
|
struct ipfw_nat64lsn_stats stats;
|
|
|
|
if (nat64lsn_get_stats(name, set, &stats) != 0)
|
|
err(EX_OSERR, "Error retrieving stats");
|
|
|
|
_P_STAT(stats, opcnt64);
|
|
_P_STAT(stats, opcnt46);
|
|
_P_STAT(stats, ofrags);
|
|
_P_STAT(stats, ifrags);
|
|
_P_STAT(stats, oerrors);
|
|
_P_STAT(stats, noroute4);
|
|
_P_STAT(stats, noroute6);
|
|
_P_STAT(stats, noproto);
|
|
_P_STAT(stats, nomem);
|
|
_P_STAT(stats, dropped);
|
|
|
|
_P_STAT(stats, hostcount);
|
|
_P_STAT(stats, tcpchunks);
|
|
_P_STAT(stats, udpchunks);
|
|
_P_STAT(stats, icmpchunks);
|
|
_P_STAT(stats, jcalls);
|
|
_P_STAT(stats, jrequests);
|
|
_P_STAT(stats, jhostsreq);
|
|
_P_STAT(stats, jportreq);
|
|
_P_STAT(stats, jhostfails);
|
|
_P_STAT(stats, jportfails);
|
|
_P_STAT(stats, jreinjected);
|
|
_P_STAT(stats, jmaxlen);
|
|
_P_STAT(stats, jnomem);
|
|
_P_STAT(stats, screated);
|
|
_P_STAT(stats, sdeleted);
|
|
_P_STAT(stats, spgcreated);
|
|
_P_STAT(stats, spgdeleted);
|
|
}
|
|
|
|
static int
|
|
nat64lsn_show_cb(ipfw_nat64lsn_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_INET, &cfg->prefix4, abuf, sizeof(abuf));
|
|
printf("nat64lsn %s prefix4 %s/%u ", cfg->name, abuf, cfg->plen4);
|
|
#if 0
|
|
inet_ntop(AF_INET6, &cfg->prefix6, abuf, sizeof(abuf));
|
|
printf("prefix6 %s/%u", abuf, cfg->plen6);
|
|
printf("agg_len %u agg_count %u ", cfg->agg_prefix_len,
|
|
cfg->agg_prefix_max);
|
|
if (cfg->min_port != NAT64LSN_PORT_MIN ||
|
|
cfg->max_port != NAT64LSN_PORT_MAX)
|
|
printf(" port_range %u:%u", cfg->min_port, cfg->max_port);
|
|
if (cfg->jmaxlen != NAT64LSN_JMAXLEN)
|
|
printf(" jmaxlen %u ", cfg->jmaxlen);
|
|
#endif
|
|
if (cfg->max_ports != NAT64LSN_MAX_PORTS)
|
|
printf(" max_ports %u", cfg->max_ports);
|
|
if (cfg->nh_delete_delay != NAT64LSN_HOST_AGE)
|
|
printf(" host_del_age %u", cfg->nh_delete_delay);
|
|
if (cfg->pg_delete_delay != NAT64LSN_PG_AGE)
|
|
printf(" pg_del_age %u ", cfg->pg_delete_delay);
|
|
if (cfg->st_syn_ttl != NAT64LSN_TCP_SYN_AGE)
|
|
printf(" tcp_syn_age %u", cfg->st_syn_ttl);
|
|
if (cfg->st_close_ttl != NAT64LSN_TCP_FIN_AGE)
|
|
printf(" tcp_close_age %u", cfg->st_close_ttl);
|
|
if (cfg->st_estab_ttl != NAT64LSN_TCP_EST_AGE)
|
|
printf(" tcp_est_age %u", cfg->st_estab_ttl);
|
|
if (cfg->st_udp_ttl != NAT64LSN_UDP_AGE)
|
|
printf(" udp_age %u", cfg->st_udp_ttl);
|
|
if (cfg->st_icmp_ttl != NAT64LSN_ICMP_AGE)
|
|
printf(" icmp_age %u", cfg->st_icmp_ttl);
|
|
if (cfg->flags & NAT64_LOG)
|
|
printf(" log");
|
|
printf("\n");
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
nat64lsn_destroy_cb(ipfw_nat64lsn_cfg *cfg, const char *name, uint8_t set)
|
|
{
|
|
|
|
if (co.use_set != 0 && cfg->set != set)
|
|
return (ESRCH);
|
|
|
|
nat64lsn_destroy(cfg->name, cfg->set);
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Compare nat64lsn instances names.
|
|
* Honor number comparison.
|
|
*/
|
|
static int
|
|
nat64name_cmp(const void *a, const void *b)
|
|
{
|
|
ipfw_nat64lsn_cfg *ca, *cb;
|
|
|
|
ca = (ipfw_nat64lsn_cfg *)a;
|
|
cb = (ipfw_nat64lsn_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 nat64lsn instance list from kernel,
|
|
* optionally sorts it and calls requested function for each instance.
|
|
*
|
|
* Request: [ ipfw_obj_lheader ]
|
|
* Reply: [ ipfw_obj_lheader ipfw_nat64lsn_cfg x N ]
|
|
*/
|
|
static int
|
|
nat64lsn_foreach(nat64lsn_cb_t *f, const char *name, uint8_t set, int sort)
|
|
{
|
|
ipfw_obj_lheader *olh;
|
|
ipfw_nat64lsn_cfg *cfg;
|
|
size_t sz;
|
|
int i, error;
|
|
|
|
/* Start with reasonable default */
|
|
sz = sizeof(*olh) + 16 * sizeof(ipfw_nat64lsn_cfg);
|
|
|
|
for (;;) {
|
|
if ((olh = calloc(1, sz)) == NULL)
|
|
return (ENOMEM);
|
|
|
|
olh->size = sz;
|
|
if (do_get3(IP_FW_NAT64LSN_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_nat64lsn_cfg *)(olh + 1);
|
|
for (i = 0; i < olh->count; i++) {
|
|
error = f(cfg, name, set); /* Ignore errors for now */
|
|
cfg = (ipfw_nat64lsn_cfg *)((caddr_t)cfg +
|
|
olh->objsize);
|
|
}
|
|
free(olh);
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|
|
|