diff options
author | ae <ae@FreeBSD.org> | 2017-04-03 08:50:54 +0000 |
---|---|---|
committer | ae <ae@FreeBSD.org> | 2017-04-03 08:50:54 +0000 |
commit | 1f5f6e71571eeaab683b65e91887222f4a6ece97 (patch) | |
tree | ff473621e6678586ebca72423c488eb34dc474e8 /sbin/ipfw | |
parent | ae659ef435156d011821295969f63c5f4dcfb1d2 (diff) | |
download | FreeBSD-src-1f5f6e71571eeaab683b65e91887222f4a6ece97.zip FreeBSD-src-1f5f6e71571eeaab683b65e91887222f4a6ece97.tar.gz |
MFC r304041:
Move logging via BPF support into separate file.
* make interface cloner VNET-aware;
* simplify cloner code and use if_clone_simple();
* migrate LOGIF_LOCK() to rmlock;
* add ipfw_bpf_mtap2() function to pass mbuf to BPF;
* introduce new additional ipfwlog0 pseudo interface. It differs from
ipfw0 by DLT type used in bpfattach. This interface is intended to
used by ipfw modules to dump packets with additional info attached.
Currently pflog format is used. ipfw_bpf_mtap2() function uses second
argument to determine which interface use for dumping. If dlen is equal
to ETHER_HDR_LEN it uses old ipfw0 interface, if dlen is equal to
PFLOG_HDRLEN - ipfwlog0 will be used.
Obtained from: Yandex LLC
Sponsored by: Yandex LLC
MFC r304043:
Add three helper function to manage tables from external modules.
ipfw_objhash_lookup_table_kidx does lookup kernel index of table;
ipfw_ref_table/ipfw_unref_table takes and releases reference to table.
Obtained from: Yandex LLC
Sponsored by: Yandex LLC
MFC r304046, 304108:
Add ipfw_nat64 module that implements stateless and stateful NAT64.
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
MFC r304048:
Replace __noinline with special debug macro NAT64NOINLINE.
MFC r304061:
Use %ju to print unsigned 64-bit value.
MFC r304076:
Make statistics nat64lsn, nat64stl an nptv6 output netstat-like:
"@value @description" and fix build due to -Wformat errors.
MFC r304378 (by bz):
Try to fix gcc compilation errors (which are right).
nat64_getlasthdr() returns an int, which can be -1 in case of error,
storing the result in an uint8_t and then comparing to < 0 is not
helpful. Do what is done in the rest of the code and make proto an
int here as well.
MFC r309187:
Fix ICMPv6 Time Exceeded error message translation.
MFC r314718:
Use new ipfw_lookup_table() in the nat64 too.
MFC r315204,315233:
Use memset with structure size.
Diffstat (limited to 'sbin/ipfw')
-rw-r--r-- | sbin/ipfw/Makefile | 2 | ||||
-rw-r--r-- | sbin/ipfw/ipfw.8 | 223 | ||||
-rw-r--r-- | sbin/ipfw/ipfw2.c | 2 | ||||
-rw-r--r-- | sbin/ipfw/ipfw2.h | 28 | ||||
-rw-r--r-- | sbin/ipfw/main.c | 4 | ||||
-rw-r--r-- | sbin/ipfw/nat64lsn.c | 881 | ||||
-rw-r--r-- | sbin/ipfw/nat64stl.c | 535 | ||||
-rw-r--r-- | sbin/ipfw/tables.c | 8 |
8 files changed, 1676 insertions, 7 deletions
diff --git a/sbin/ipfw/Makefile b/sbin/ipfw/Makefile index 54809b2..06ffb48 100644 --- a/sbin/ipfw/Makefile +++ b/sbin/ipfw/Makefile @@ -5,7 +5,7 @@ PACKAGE=ipfw PROG= ipfw SRCS= ipfw2.c dummynet.c ipv6.c main.c nat.c tables.c -SRCS+= nptv6.c +SRCS+= nat64lsn.c nat64stl.c nptv6.c WARNS?= 2 .if ${MK_PF} != "no" diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 0317026..f7c3a78 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -113,6 +113,37 @@ in-kernel NAT. .Oc .Oc .Ar pathname +.Ss STATEFUL IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION +.Nm +.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm create Ar create-options +.Nm +.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm config Ar config-options +.Nm +.Oo Cm set Ar N Oc Cm nat64lsn +.Brq Ar name | all +.Brq Cm list | show +.Op Cm states +.Nm +.Oo Cm set Ar N Oc Cm nat64lsn +.Brq Ar name | all +.Cm destroy +.Nm +.Oo Cm set Ar N Oc Cm nat64lsn Ar name Cm stats Op Cm reset +.Ss STATELESS IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION +.Nm +.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm create Ar create-options +.Nm +.Oo Cm set Ar N Oc Cm nat64stl Ar name Cm config Ar config-options +.Nm +.Oo Cm set Ar N Oc Cm nat64stl +.Brq Ar name | all +.Brq Cm list | show +.Nm +.Oo Cm set Ar N Oc Cm nat64stl +.Brq Ar name | all +.Cm destroy +.Nm +.Oo Cm set Ar N Oc Cm nat64stl 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 @@ -837,6 +868,16 @@ nat instance see the .Sx NETWORK ADDRESS TRANSLATION (NAT) Section for further information. +.It Cm nat64lsn Ar name +Pass packet to a stateful 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 nat64stl Ar name +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 nptv6 Ar name Pass packet to a NPTv6 instance (for IPv6-to-IPv6 network prefix translation): see the @@ -2944,9 +2985,189 @@ instances. See .Sx SYSCTL VARIABLES for more info. +.Sh IPv6/IPv4 NETWORK ADDRESS AND PROTOCOL TRANSLATION +.Nm +supports in-kernel IPv6/IPv4 network address and protocol translation. +Stateful NAT64 translation allows IPv6-only clients to contact IPv4 servers +using unicast TCP, UDP or ICMP protocols. +One or more IPv4 addresses assigned to a stateful NAT64 translator are shared +among serveral IPv6-only clients. +When stateful NAT64 is used in conjunction with DNS64, no changes are usually +required in the IPv6 client or the IPv4 server. +The kernel module +.Cm ipfw_nat64 +should be loaded or kernel should have +.Cm options IPFIREWALL_NAT64 +to be able use stateful NAT64 translator. +.Pp +Stateful NAT64 uses a bunch of memory for several types of objects. +When IPv6 client initiates connection, NAT64 translator creates a host entry +in the states table. +Each host entry has a number of ports group entries allocated on demand. +Ports group entries contains connection state entries. +There are several options to control limits and lifetime for these objects. +.Pp +NAT64 translator follows RFC7915 when does ICMPv6/ICMP translation, +unsupported message types will be silently dropped. +IPv6 needs several ICMPv6 message types to be explicitly allowed for correct +operation. +Make sure that ND6 neighbor solicitation (ICMPv6 type 135) and neighbor +advertisement (ICMPv6 type 136) messages will not be handled by translation +rules. +.Pp +After translation NAT64 translator sends packets through corresponding netisr +queue. +Thus translator host should be configured as IPv4 and IPv6 router. +.Pp +Currently both stateful and stateless NAT64 translators use Well-Known IPv6 +Prefix +.Ar 64:ff9b::/96 +to represent IPv4 addresses in the IPv6 address. +Thus DNS64 service and routing should be configured to use Well-Known IPv6 +Prefix. +.Pp +The stateful NAT64 configuration command is the following: +.Bd -ragged -offset indent +.Bk -words +.Cm nat64lsn +.Ar name +.Cm create +.Ar create-options +.Ek +.Ed +.Pp +The following parameters can be configured: +.Bl -tag -width indent +.It Cm prefix4 Ar ipv4_prefix/mask +The IPv4 prefix with mask defines the pool of IPv4 addresses used as +source address after translation. +Stateful NAT64 module translates IPv6 source address of client to one +IPv4 address from this pool. +Note that incoming IPv4 packets that don't have corresponding state entry +in the states table will be dropped by translator. +Make sure that translation rules handle packets, destined to configured prefix. +.It Cm max_ports Ar number +Maximum number of ports reserved for upper level protocols to one IPv6 client. +All reserved ports are divided into chunks between supported protocols. +The number of connections from one IPv6 client is limited by this option. +Note that closed TCP connections still remain in the list of connections until +.Cm tcp_close_age +interval will not expire. +Default value is +.Ar 2048 . +.It Cm host_del_age Ar seconds +The number of seconds until the host entry for a IPv6 client will be deleted +and all its resources will be released due to inactivity. +Default value is +.Ar 3600 . +.It Cm pg_del_age Ar seconds +The number of seconds until a ports group with unused state entries will +be released. +Default value is +.Ar 900 . +.It Cm tcp_syn_age Ar seconds +The number of seconds while a state entry for TCP connection with only SYN +sent will be kept. +If TCP connection establishing will not be finished, +state entry will be deleted. +Default value is +.Ar 10 . +.It Cm tcp_est_age Ar seconds +The number of seconds while a state entry for established TCP connection +will be kept. +Default value is +.Ar 7200 . +.It Cm tcp_close_age Ar seconds +The number of seconds while a state entry for closed TCP connection +will be kept. +Keeping state entries for closed connections is needed, because IPv4 servers +typically keep closed connections in a TIME_WAIT state for a several minutes. +Since translator's IPv4 addresses are shared among all IPv6 clients, +new connections from the same addresses and ports may be rejected by server, +because these connections are still in a TIME_WAIT state. +Keeping them in translator's state table protects from such rejects. +Default value is +.Ar 180 . +.It Cm udp_age Ar seconds +The number of seconds while translator keeps state entry in a waiting for +reply to the sent UDP datagram. +Default value is +.Ar 120 . +.It Cm icmp_age Ar seconds +The number of seconds while translator keeps state entry in a waiting for +reply to the sent ICMP message. +Default value is +.Ar 60 . +.It Cm log +Turn on logging of all handled packets via BPF through +.Ar ipfwlog0 +interface. +.Ar ipfwlog0 +is a pseudo interface and can be created after a boot manually with +.Cm ifconfig +command. +Note that it has different purpose than +.Ar ipfw0 +interface. +Translators sends to BPF an additional information with each packet. +With +.Cm tcpdump +you are able to see each handled packet before and after translation. +.It Cm -log +Turn off logging of all handled packets via BPF. +.El +.Pp +To inspect a states table of stateful NAT64 the following command can be used: +.Bd -ragged -offset indent +.Bk -words +.Cm nat64lsn +.Ar name +.Cm show Cm states +.Ek +.Ed +.Pp +.Pp +Stateless NAT64 translator doesn't use a states table for translation +and converts IPv4 addresses to IPv6 and vice versa solely based on the +mappings taken from configured lookup tables. +Since a states table doesn't used by stateless translator, +it can be configured to pass IPv4 clients to IPv6-only servers. +.Pp +The stateless NAT64 configuration command is the following: +.Bd -ragged -offset indent +.Bk -words +.Cm nat64stl +.Ar name +.Cm create +.Ar create-options +.Ek +.Ed +.Pp +The following parameters can be configured: +.Bl -tag -width indent +.It Cm table4 Ar table46 +The lookup table +.Ar table46 +contains mapping how IPv4 addresses should be translated to IPv6 addresses. +.It Cm table6 Ar table64 +The lookup table +.Ar table64 +contains mapping how IPv6 addresses should be translated to IPv4 addresses. +.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. +.El +.Pp +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. .Sh IPv6-to-IPv6 NETWORK PREFIX TRANSLATION (NPTv6) .Nm -support in-kernel IPv6-to-IPv6 network prefix translation as described +supports in-kernel IPv6-to-IPv6 network prefix translation as described in RFC6296. The kernel module .Cm ipfw_nptv6 diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index c5533b4..b26d7f0 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -235,6 +235,8 @@ static struct _s_x ether_types[] = { }; static struct _s_x rule_eactions[] = { + { "nat64lsn", TOK_NAT64LSN }, + { "nat64stl", TOK_NAT64STL }, { "nptv6", TOK_NPTV6 }, { NULL, 0 } /* terminator */ }; diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 1033ab8..0b63e3b 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -254,7 +254,30 @@ enum tokens { TOK_UNLOCK, TOK_VLIST, TOK_OLIST, + + /* NAT64 tokens */ + TOK_NAT64STL, + TOK_NAT64LSN, TOK_STATS, + TOK_STATES, + TOK_CONFIG, + TOK_TABLE4, + TOK_TABLE6, + TOK_PREFIX4, + TOK_PREFIX6, + TOK_AGG_LEN, + TOK_AGG_COUNT, + TOK_MAX_PORTS, + TOK_JMAXLEN, + TOK_PORT_RANGE, + TOK_HOST_DEL_AGE, + TOK_PG_DEL_AGE, + TOK_TCP_SYN_AGE, + TOK_TCP_CLOSE_AGE, + TOK_TCP_EST_AGE, + TOK_UDP_AGE, + TOK_ICMP_AGE, + TOK_LOGOFF, /* NPTv6 tokens */ TOK_NPTV6, @@ -347,6 +370,8 @@ 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_nat64lsn_handler(int ac, char *av[]); +void ipfw_nat64stl_handler(int ac, char *av[]); void ipfw_nptv6_handler(int ac, char *av[]); int ipfw_check_object_name(const char *name); @@ -389,7 +414,10 @@ void fill_table(struct _ipfw_insn *cmd, char *av, uint8_t opcode, /* tables.c */ struct _ipfw_obj_ctlv; +struct _ipfw_obj_ntlv; int table_check_name(const char *tablename); void ipfw_list_ta(int ac, char *av[]); void ipfw_list_values(int ac, char *av[]); +void table_fill_ntlv(struct _ipfw_obj_ntlv *ntlv, const char *name, + uint8_t set, uint16_t uidx); diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c index cb4168f..b7ff07a 100644 --- a/sbin/ipfw/main.c +++ b/sbin/ipfw/main.c @@ -425,6 +425,10 @@ ipfw_main(int oldac, char **oldav) if (co.use_set || try_next) { if (_substrcmp(*av, "delete") == 0) ipfw_delete(av); + else if (!strncmp(*av, "nat64stl", strlen(*av))) + ipfw_nat64stl_handler(ac, av); + else if (!strncmp(*av, "nat64lsn", strlen(*av))) + ipfw_nat64lsn_handler(ac, av); else if (!strncmp(*av, "nptv6", strlen(*av))) ipfw_nptv6_handler(ac, av); else if (_substrcmp(*av, "flush") == 0) diff --git a/sbin/ipfw/nat64lsn.c b/sbin/ipfw/nat64lsn.c new file mode 100644 index 0000000..7fd3c77 --- /dev/null +++ b/sbin/ipfw/nat64lsn.c @@ -0,0 +1,881 @@ +/*- + * 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 <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> + +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); +} + +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"); + + if (co.use_set != 0 || set != 0) + printf("set %u ", set); + printf("nat64lsn %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); + printf("\t%ju packets not matched with IPv4 prefix\n", + (uintmax_t)stats.nomatch4); + + printf("\t%ju mbufs queued for post processing\n", + (uintmax_t)stats.jreinjected); + printf("\t%ju times the job queue was processed\n", + (uintmax_t)stats.jcalls); + printf("\t%ju job requests queued\n", + (uintmax_t)stats.jrequests); + printf("\t%ju job requests queue limit reached\n", + (uintmax_t)stats.jmaxlen); + printf("\t%ju job requests failed due to memory allocation problems\n", + (uintmax_t)stats.jnomem); + + printf("\t%ju hosts allocated\n", (uintmax_t)stats.hostcount); + printf("\t%ju hosts requested\n", (uintmax_t)stats.jhostsreq); + printf("\t%ju host requests failed\n", (uintmax_t)stats.jhostfails); + + printf("\t%ju portgroups requested\n", (uintmax_t)stats.jportreq); + printf("\t%ju portgroups allocated\n", (uintmax_t)stats.spgcreated); + printf("\t%ju portgroups deleted\n", (uintmax_t)stats.spgdeleted); + printf("\t%ju portgroup requests failed\n", + (uintmax_t)stats.jportfails); + printf("\t%ju portgroups allocated for TCP\n", + (uintmax_t)stats.tcpchunks); + printf("\t%ju portgroups allocated for UDP\n", + (uintmax_t)stats.udpchunks); + printf("\t%ju portgroups allocated for ICMP\n", + (uintmax_t)stats.icmpchunks); + + printf("\t%ju states created\n", (uintmax_t)stats.screated); + printf("\t%ju states deleted\n", (uintmax_t)stats.sdeleted); +} + +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); +} + diff --git a/sbin/ipfw/nat64stl.c b/sbin/ipfw/nat64stl.c new file mode 100644 index 0000000..6cd936c --- /dev/null +++ b/sbin/ipfw/nat64stl.c @@ -0,0 +1,535 @@ +/*- + * Copyright (c) 2015-2016 Yandex LLC + * 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 <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> + +static int nat64stl_check_prefix(struct in6_addr *prefix, int length); +typedef int (nat64stl_cb_t)(ipfw_nat64stl_cfg *i, const char *name, + uint8_t set); +static int nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set, + int sort); + +static void nat64stl_create(const char *name, uint8_t set, int ac, char **av); +static void nat64stl_config(const char *name, uint8_t set, int ac, char **av); +static void nat64stl_destroy(const char *name, uint8_t set); +static void nat64stl_stats(const char *name, uint8_t set); +static void nat64stl_reset_stats(const char *name, uint8_t set); +static int nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name, + uint8_t set); +static int nat64stl_destroy_cb(ipfw_nat64stl_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 } +}; + +#define IPV6_ADDR_INT32_WKPFX htonl(0x64ff9b) +#define IN6_IS_ADDR_WKPFX(a) \ + ((a)->__u6_addr.__u6_addr32[0] == IPV6_ADDR_INT32_WKPFX && \ + (a)->__u6_addr.__u6_addr32[1] == 0 && \ + (a)->__u6_addr.__u6_addr32[2] == 0) +static int +nat64stl_check_prefix(struct in6_addr *prefix, int length) +{ + + if (IN6_IS_ADDR_WKPFX(prefix) && length == 96) + return (0); +#if 0 + switch (length) { + case 32: + case 40: + case 48: + case 56: + case 64: + /* Well-known prefix has 96 prefix length */ + if (IN6_IS_ADDR_WKPFX(prefix)) + return (1); + /* FALLTHROUGH */ + case 96: + /* Bits 64 to 71 must be set to zero */ + if (prefix->__u6_addr.__u6_addr8[8] != 0) + return (1); + /* XXX: looks incorrect */ + if (IN6_IS_ADDR_MULTICAST(prefix) || + IN6_IS_ADDR_UNSPECIFIED(prefix) || + IN6_IS_ADDR_LOOPBACK(prefix)) + return (1); + return (0); + } +#endif + return (1); +} + +static struct _s_x nat64statscmds[] = { + { "reset", TOK_RESET }, + { NULL, 0 } +}; + +/* + * This one handles all nat64stl-related commands + * ipfw [set N] nat64stl NAME {create | config} ... + * ipfw [set N] nat64stl NAME stats [reset] + * ipfw [set N] nat64stl {NAME | all} destroy + * ipfw [set N] nat64stl {NAME | all} {list | show} + */ +#define nat64stl_check_name table_check_name +void +ipfw_nat64stl_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("nat64stl needs instance name"); + name = *av; + if (nat64stl_check_name(name) != 0) { + if (strcmp(name, "all") == 0) + name = NULL; + else + errx(EX_USAGE, "nat64stl instance name %s is invalid", + name); + } + ac--; av++; + NEED1("nat64stl needs command"); + + tcmd = get_token(nat64cmds, *av, "nat64stl command"); + if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST) + errx(EX_USAGE, "nat64stl instance name required"); + switch (tcmd) { + case TOK_CREATE: + ac--; av++; + nat64stl_create(name, set, ac, av); + break; + case TOK_CONFIG: + ac--; av++; + nat64stl_config(name, set, ac, av); + break; + case TOK_LIST: + nat64stl_foreach(nat64stl_show_cb, name, set, 1); + break; + case TOK_DESTROY: + if (name == NULL) + nat64stl_foreach(nat64stl_destroy_cb, NULL, set, 0); + else + nat64stl_destroy(name, set); + break; + case TOK_STATS: + ac--; av++; + if (ac == 0) { + nat64stl_stats(name, set); + break; + } + tcmd = get_token(nat64statscmds, *av, "stats command"); + if (tcmd == TOK_RESET) + nat64stl_reset_stats(name, set); + } +} + + +static void +nat64stl_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[] = { + { "table4", TOK_TABLE4 }, + { "table6", TOK_TABLE6 }, + { "prefix6", TOK_PREFIX6 }, + { "log", TOK_LOG }, + { "-log", TOK_LOGOFF }, + { NULL, 0 } +}; + +/* + * Creates new nat64stl instance + * ipfw nat64stl <NAME> create table4 <name> table6 <name> [ prefix6 <prefix>] + * Request: [ ipfw_obj_lheader ipfw_nat64stl_cfg ] + */ +#define NAT64STL_HAS_TABLE4 0x01 +#define NAT64STL_HAS_TABLE6 0x02 +#define NAT64STL_HAS_PREFIX6 0x04 +static void +nat64stl_create(const char *name, uint8_t set, int ac, char *av[]) +{ + char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64stl_cfg)]; + ipfw_nat64stl_cfg *cfg; + ipfw_obj_lheader *olh; + int tcmd, flags; + char *p; + + memset(buf, 0, sizeof(buf)); + olh = (ipfw_obj_lheader *)buf; + cfg = (ipfw_nat64stl_cfg *)(olh + 1); + + /* Some reasonable defaults */ + inet_pton(AF_INET6, "64:ff9b::", &cfg->prefix6); + cfg->plen6 = 96; + cfg->set = set; + flags = NAT64STL_HAS_PREFIX6; + while (ac > 0) { + tcmd = get_token(nat64newcmds, *av, "option"); + ac--; av++; + + switch (tcmd) { + case TOK_TABLE4: + NEED1("table name required"); + table_fill_ntlv(&cfg->ntlv4, *av, set, 4); + flags |= NAT64STL_HAS_TABLE4; + ac--; av++; + break; + case TOK_TABLE6: + NEED1("table name required"); + table_fill_ntlv(&cfg->ntlv6, *av, set, 6); + flags |= NAT64STL_HAS_TABLE6; + ac--; av++; + break; + case TOK_PREFIX6: + NEED1("IPv6 prefix6 required"); + if ((p = strchr(*av, '/')) != NULL) + *p++ = '\0'; + if (inet_pton(AF_INET6, *av, &cfg->prefix6) != 1) + errx(EX_USAGE, + "Bad prefix: %s", *av); + cfg->plen6 = strtol(p, NULL, 10); + if (nat64stl_check_prefix(&cfg->prefix6, + cfg->plen6) != 0) + errx(EX_USAGE, + "Bad prefix length: %s", p); + flags |= NAT64STL_HAS_PREFIX6; + ac--; av++; + break; + case TOK_LOG: + cfg->flags |= NAT64_LOG; + break; + case TOK_LOGOFF: + cfg->flags &= ~NAT64_LOG; + break; + } + } + + /* Check validness */ + if ((flags & NAT64STL_HAS_TABLE4) != NAT64STL_HAS_TABLE4) + errx(EX_USAGE, "table4 required"); + if ((flags & NAT64STL_HAS_TABLE6) != NAT64STL_HAS_TABLE6) + errx(EX_USAGE, "table6 required"); + if ((flags & NAT64STL_HAS_PREFIX6) != NAT64STL_HAS_PREFIX6) + errx(EX_USAGE, "prefix6 required"); + + olh->count = 1; + olh->objsize = sizeof(*cfg); + olh->size = sizeof(buf); + strlcpy(cfg->name, name, sizeof(cfg->name)); + if (do_set3(IP_FW_NAT64STL_CREATE, &olh->opheader, sizeof(buf)) != 0) + err(EX_OSERR, "nat64stl instance creation failed"); +} + +/* + * Configures existing nat64stl instance + * ipfw nat64stl <NAME> config <options> + * Request: [ ipfw_obj_header ipfw_nat64stl_cfg ] + */ +static void +nat64stl_config(const char *name, uint8_t set, int ac, char **av) +{ + char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64stl_cfg)]; + ipfw_nat64stl_cfg *cfg; + ipfw_obj_header *oh; + char *opt; + size_t sz; + int tcmd; + + if (ac == 0) + errx(EX_USAGE, "config options required"); + memset(&buf, 0, sizeof(buf)); + oh = (ipfw_obj_header *)buf; + cfg = (ipfw_nat64stl_cfg *)(oh + 1); + sz = sizeof(buf); + + nat64stl_fill_ntlv(&oh->ntlv, name, set); + if (do_get3(IP_FW_NAT64STL_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) { +#if 0 + case TOK_TABLE4: + NEED1("table name required"); + table_fill_ntlv(&cfg->ntlv4, *av, set, 4); + ac--; av++; + break; + case TOK_TABLE6: + NEED1("table name required"); + table_fill_ntlv(&cfg->ntlv6, *av, set, 6); + ac--; av++; + break; +#endif + 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_NAT64STL_CONFIG, &oh->opheader, sizeof(buf)) != 0) + err(EX_OSERR, "nat64stl instance configuration failed"); +} + +/* + * Destroys nat64stl instance. + * Request: [ ipfw_obj_header ] + */ +static void +nat64stl_destroy(const char *name, uint8_t set) +{ + ipfw_obj_header oh; + + memset(&oh, 0, sizeof(oh)); + nat64stl_fill_ntlv(&oh.ntlv, name, set); + if (do_set3(IP_FW_NAT64STL_DESTROY, &oh.opheader, sizeof(oh)) != 0) + err(EX_OSERR, "failed to destroy nat instance %s", name); +} + +/* + * Get nat64stl instance statistics. + * Request: [ ipfw_obj_header ] + * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ] + */ +static int +nat64stl_get_stats(const char *name, uint8_t set, + struct ipfw_nat64stl_stats *stats) +{ + ipfw_obj_header *oh; + ipfw_obj_ctlv *oc; + size_t sz; + + sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats); + oh = calloc(1, sz); + nat64stl_fill_ntlv(&oh->ntlv, name, set); + if (do_get3(IP_FW_NAT64STL_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 +nat64stl_stats(const char *name, uint8_t set) +{ + struct ipfw_nat64stl_stats stats; + + if (nat64stl_get_stats(name, set, &stats) != 0) + err(EX_OSERR, "Error retrieving stats"); + + if (co.use_set != 0 || set != 0) + printf("set %u ", set); + printf("nat64stl %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 nat64stl instance statistics specified by @oh->ntlv. + * Request: [ ipfw_obj_header ] + */ +static void +nat64stl_reset_stats(const char *name, uint8_t set) +{ + ipfw_obj_header oh; + + memset(&oh, 0, sizeof(oh)); + nat64stl_fill_ntlv(&oh.ntlv, name, set); + if (do_set3(IP_FW_NAT64STL_RESET_STATS, &oh.opheader, sizeof(oh)) != 0) + err(EX_OSERR, "failed to reset stats for instance %s", name); +} + +static int +nat64stl_show_cb(ipfw_nat64stl_cfg *cfg, const char *name, uint8_t set) +{ + + 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); + printf("nat64stl %s table4 %s table6 %s", + cfg->name, cfg->ntlv4.name, cfg->ntlv6.name); + if (cfg->flags & NAT64_LOG) + printf(" log"); + printf("\n"); + return (0); +} + +static int +nat64stl_destroy_cb(ipfw_nat64stl_cfg *cfg, const char *name, uint8_t set) +{ + + if (co.use_set != 0 && cfg->set != set) + return (ESRCH); + + nat64stl_destroy(cfg->name, cfg->set); + return (0); +} + + +/* + * Compare nat64stl instances names. + * Honor number comparison. + */ +static int +nat64name_cmp(const void *a, const void *b) +{ + ipfw_nat64stl_cfg *ca, *cb; + + ca = (ipfw_nat64stl_cfg *)a; + cb = (ipfw_nat64stl_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 nat64stl instance list from kernel, + * optionally sorts it and calls requested function for each instance. + * + * Request: [ ipfw_obj_lheader ] + * Reply: [ ipfw_obj_lheader ipfw_nat64stl_cfg x N ] + */ +static int +nat64stl_foreach(nat64stl_cb_t *f, const char *name, uint8_t set, int sort) +{ + ipfw_obj_lheader *olh; + ipfw_nat64stl_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_NAT64STL_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_nat64stl_cfg *)(olh + 1); + for (i = 0; i < olh->count; i++) { + error = f(cfg, name, set); /* Ignore errors for now */ + cfg = (ipfw_nat64stl_cfg *)((caddr_t)cfg + + olh->objsize); + } + free(olh); + break; + } + return (0); +} + diff --git a/sbin/ipfw/tables.c b/sbin/ipfw/tables.c index 5b0dd95..6f41de3 100644 --- a/sbin/ipfw/tables.c +++ b/sbin/ipfw/tables.c @@ -53,8 +53,6 @@ static void table_lock(ipfw_obj_header *oh, int lock); static int table_swap(ipfw_obj_header *oh, char *second); static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i); static int table_show_info(ipfw_xtable_info *i, void *arg); -static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, - uint32_t set, uint16_t uidx); static int table_flush_one(ipfw_xtable_info *i, void *arg); static int table_show_one(ipfw_xtable_info *i, void *arg); @@ -155,7 +153,7 @@ ipfw_table_handler(int ac, char *av[]) ipfw_xtable_info i; ipfw_obj_header oh; char *tablename; - uint32_t set; + uint8_t set; void *arg; memset(&oh, 0, sizeof(oh)); @@ -292,8 +290,8 @@ ipfw_table_handler(int ac, char *av[]) } } -static void -table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint32_t set, +void +table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set, uint16_t uidx) { |