1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Yandex LLC
5 * Copyright (c) 2019 Andrey V. Elsukov <ae@FreeBSD.org>
6 * Copyright (c) 2019 Boris N. Lytochkin <lytboris@gmail.com>
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/types.h>
31#include <sys/socket.h>
32
33#include "ipfw2.h"
34
35#include <ctype.h>
36#include <err.h>
37#include <errno.h>
38#include <inttypes.h>
39#include <netdb.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <sysexits.h>
44
45#include <net/if.h>
46#include <netinet/in.h>
47#include <netinet/ip_fw.h>
48#include <netinet6/ip_fw_nat64.h>
49#include <arpa/inet.h>
50
51typedef int (nat64clat_cb_t)(ipfw_nat64clat_cfg *i, const char *name,
52    uint8_t set);
53static int nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set,
54    int sort);
55
56static void nat64clat_create(const char *name, uint8_t set, int ac, char **av);
57static void nat64clat_config(const char *name, uint8_t set, int ac, char **av);
58static void nat64clat_destroy(const char *name, uint8_t set);
59static void nat64clat_stats(const char *name, uint8_t set);
60static void nat64clat_reset_stats(const char *name, uint8_t set);
61static int nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name,
62    uint8_t set);
63static int nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name,
64    uint8_t set);
65
66static struct _s_x nat64cmds[] = {
67      { "create",	TOK_CREATE },
68      { "config",	TOK_CONFIG },
69      { "destroy",	TOK_DESTROY },
70      { "list",		TOK_LIST },
71      { "show",		TOK_LIST },
72      { "stats",	TOK_STATS },
73      { NULL, 0 }
74};
75
76static struct _s_x nat64statscmds[] = {
77      { "reset",	TOK_RESET },
78      { NULL, 0 }
79};
80
81/*
82 * This one handles all nat64clat-related commands
83 *	ipfw [set N] nat64clat NAME {create | config} ...
84 *	ipfw [set N] nat64clat NAME stats [reset]
85 *	ipfw [set N] nat64clat {NAME | all} destroy
86 *	ipfw [set N] nat64clat {NAME | all} {list | show}
87 */
88#define	nat64clat_check_name	table_check_name
89void
90ipfw_nat64clat_handler(int ac, char *av[])
91{
92	const char *name;
93	int tcmd;
94	uint8_t set;
95
96	if (g_co.use_set != 0)
97		set = g_co.use_set - 1;
98	else
99		set = 0;
100	ac--; av++;
101
102	NEED1("nat64clat needs instance name");
103	name = *av;
104	if (nat64clat_check_name(name) != 0) {
105		if (strcmp(name, "all") == 0)
106			name = NULL;
107		else
108			errx(EX_USAGE, "nat64clat instance name %s is invalid",
109			    name);
110	}
111	ac--; av++;
112	NEED1("nat64clat needs command");
113
114	tcmd = get_token(nat64cmds, *av, "nat64clat command");
115	if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
116		errx(EX_USAGE, "nat64clat instance name required");
117	switch (tcmd) {
118	case TOK_CREATE:
119		ac--; av++;
120		nat64clat_create(name, set, ac, av);
121		break;
122	case TOK_CONFIG:
123		ac--; av++;
124		nat64clat_config(name, set, ac, av);
125		break;
126	case TOK_LIST:
127		nat64clat_foreach(nat64clat_show_cb, name, set, 1);
128		break;
129	case TOK_DESTROY:
130		if (name == NULL)
131			nat64clat_foreach(nat64clat_destroy_cb, NULL, set, 0);
132		else
133			nat64clat_destroy(name, set);
134		break;
135	case TOK_STATS:
136		ac--; av++;
137		if (ac == 0) {
138			nat64clat_stats(name, set);
139			break;
140		}
141		tcmd = get_token(nat64statscmds, *av, "stats command");
142		if (tcmd == TOK_RESET)
143			nat64clat_reset_stats(name, set);
144	}
145}
146
147
148static void
149nat64clat_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
150{
151
152	ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
153	ntlv->head.length = sizeof(ipfw_obj_ntlv);
154	ntlv->idx = 1;
155	ntlv->set = set;
156	strlcpy(ntlv->name, name, sizeof(ntlv->name));
157}
158
159static struct _s_x nat64newcmds[] = {
160      { "plat_prefix",	TOK_PLAT_PREFIX },
161      { "clat_prefix",	TOK_CLAT_PREFIX },
162      { "log",		TOK_LOG },
163      { "-log",		TOK_LOGOFF },
164      { "allow_private", TOK_PRIVATE },
165      { "-allow_private", TOK_PRIVATEOFF },
166      { NULL, 0 }
167};
168
169/*
170 * Creates new nat64clat instance
171 * ipfw nat64clat <NAME> create clat_prefix <prefix> plat_prefix <prefix>
172 * Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ]
173 */
174#define	NAT64CLAT_HAS_CLAT_PREFIX	0x01
175#define	NAT64CLAT_HAS_PLAT_PREFIX	0x02
176static void
177nat64clat_create(const char *name, uint8_t set, int ac, char *av[])
178{
179	char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nat64clat_cfg)];
180	ipfw_nat64clat_cfg *cfg;
181	ipfw_obj_lheader *olh;
182	int tcmd, flags;
183	char *p;
184	struct in6_addr prefix;
185	uint8_t plen;
186
187	memset(buf, 0, sizeof(buf));
188	olh = (ipfw_obj_lheader *)buf;
189	cfg = (ipfw_nat64clat_cfg *)(olh + 1);
190
191	/* Some reasonable defaults */
192	inet_pton(AF_INET6, "64:ff9b::", &cfg->plat_prefix);
193	cfg->plat_plen = 96;
194	cfg->set = set;
195	flags = NAT64CLAT_HAS_PLAT_PREFIX;
196	while (ac > 0) {
197		tcmd = get_token(nat64newcmds, *av, "option");
198		ac--; av++;
199
200		switch (tcmd) {
201		case TOK_PLAT_PREFIX:
202		case TOK_CLAT_PREFIX:
203			if (tcmd == TOK_PLAT_PREFIX) {
204				NEED1("IPv6 plat_prefix required");
205			} else {
206				NEED1("IPv6 clat_prefix required");
207			}
208
209			if ((p = strchr(*av, '/')) != NULL)
210				*p++ = '\0';
211			if (inet_pton(AF_INET6, *av, &prefix) != 1)
212				errx(EX_USAGE,
213				    "Bad prefix: %s", *av);
214			plen = strtol(p, NULL, 10);
215			if (ipfw_check_nat64prefix(&prefix, plen) != 0)
216				errx(EX_USAGE,
217				    "Bad prefix length: %s", p);
218			if (tcmd == TOK_PLAT_PREFIX) {
219				flags |= NAT64CLAT_HAS_PLAT_PREFIX;
220				cfg->plat_prefix = prefix;
221				cfg->plat_plen = plen;
222			} else {
223				flags |= NAT64CLAT_HAS_CLAT_PREFIX;
224				cfg->clat_prefix = prefix;
225				cfg->clat_plen = plen;
226			}
227			ac--; av++;
228			break;
229		case TOK_LOG:
230			cfg->flags |= NAT64_LOG;
231			break;
232		case TOK_LOGOFF:
233			cfg->flags &= ~NAT64_LOG;
234			break;
235		case TOK_PRIVATE:
236			cfg->flags |= NAT64_ALLOW_PRIVATE;
237			break;
238		case TOK_PRIVATEOFF:
239			cfg->flags &= ~NAT64_ALLOW_PRIVATE;
240			break;
241		}
242	}
243
244	/* Check validness */
245	if ((flags & NAT64CLAT_HAS_PLAT_PREFIX) != NAT64CLAT_HAS_PLAT_PREFIX)
246		errx(EX_USAGE, "plat_prefix required");
247	if ((flags & NAT64CLAT_HAS_CLAT_PREFIX) != NAT64CLAT_HAS_CLAT_PREFIX)
248		errx(EX_USAGE, "clat_prefix required");
249
250	olh->count = 1;
251	olh->objsize = sizeof(*cfg);
252	olh->size = sizeof(buf);
253	strlcpy(cfg->name, name, sizeof(cfg->name));
254	if (do_set3(IP_FW_NAT64CLAT_CREATE, &olh->opheader, sizeof(buf)) != 0)
255		err(EX_OSERR, "nat64clat instance creation failed");
256}
257
258/*
259 * Configures existing nat64clat instance
260 * ipfw nat64clat <NAME> config <options>
261 * Request: [ ipfw_obj_header ipfw_nat64clat_cfg ]
262 */
263static void
264nat64clat_config(const char *name, uint8_t set, int ac, char **av)
265{
266	char buf[sizeof(ipfw_obj_header) + sizeof(ipfw_nat64clat_cfg)];
267	ipfw_nat64clat_cfg *cfg;
268	ipfw_obj_header *oh;
269	char *opt;
270	char *p;
271	size_t sz;
272	int tcmd;
273	struct in6_addr prefix;
274	uint8_t plen;
275
276	if (ac == 0)
277		errx(EX_USAGE, "config options required");
278	memset(&buf, 0, sizeof(buf));
279	oh = (ipfw_obj_header *)buf;
280	cfg = (ipfw_nat64clat_cfg *)(oh + 1);
281	sz = sizeof(buf);
282
283	nat64clat_fill_ntlv(&oh->ntlv, name, set);
284	if (do_get3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, &sz) != 0)
285		err(EX_OSERR, "failed to get config for instance %s", name);
286
287	while (ac > 0) {
288		tcmd = get_token(nat64newcmds, *av, "option");
289		opt = *av;
290		ac--; av++;
291
292		switch (tcmd) {
293		case TOK_PLAT_PREFIX:
294		case TOK_CLAT_PREFIX:
295			if (tcmd == TOK_PLAT_PREFIX) {
296				NEED1("IPv6 plat_prefix required");
297			} else {
298				NEED1("IPv6 clat_prefix required");
299			}
300
301			if ((p = strchr(*av, '/')) != NULL)
302				*p++ = '\0';
303			else
304				errx(EX_USAGE,
305				    "Prefix length required: %s", *av);
306			if (inet_pton(AF_INET6, *av, &prefix) != 1)
307				errx(EX_USAGE,
308				    "Bad prefix: %s", *av);
309			plen = strtol(p, NULL, 10);
310			if (ipfw_check_nat64prefix(&prefix, plen) != 0)
311				errx(EX_USAGE,
312				    "Bad prefix length: %s", p);
313			if (tcmd == TOK_PLAT_PREFIX) {
314				cfg->plat_prefix = prefix;
315				cfg->plat_plen = plen;
316			} else {
317				cfg->clat_prefix = prefix;
318				cfg->clat_plen = plen;
319			}
320			ac--; av++;
321			break;
322		case TOK_LOG:
323			cfg->flags |= NAT64_LOG;
324			break;
325		case TOK_LOGOFF:
326			cfg->flags &= ~NAT64_LOG;
327			break;
328		case TOK_PRIVATE:
329			cfg->flags |= NAT64_ALLOW_PRIVATE;
330			break;
331		case TOK_PRIVATEOFF:
332			cfg->flags &= ~NAT64_ALLOW_PRIVATE;
333			break;
334		default:
335			errx(EX_USAGE, "Can't change %s option", opt);
336		}
337	}
338
339	if (do_set3(IP_FW_NAT64CLAT_CONFIG, &oh->opheader, sizeof(buf)) != 0)
340		err(EX_OSERR, "nat64clat instance configuration failed");
341}
342
343/*
344 * Destroys nat64clat instance.
345 * Request: [ ipfw_obj_header ]
346 */
347static void
348nat64clat_destroy(const char *name, uint8_t set)
349{
350	ipfw_obj_header oh;
351
352	memset(&oh, 0, sizeof(oh));
353	nat64clat_fill_ntlv(&oh.ntlv, name, set);
354	if (do_set3(IP_FW_NAT64CLAT_DESTROY, &oh.opheader, sizeof(oh)) != 0)
355		err(EX_OSERR, "failed to destroy nat instance %s", name);
356}
357
358/*
359 * Get nat64clat instance statistics.
360 * Request: [ ipfw_obj_header ]
361 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
362 */
363static int
364nat64clat_get_stats(const char *name, uint8_t set,
365    struct ipfw_nat64clat_stats *stats)
366{
367	ipfw_obj_header *oh;
368	ipfw_obj_ctlv *oc;
369	size_t sz;
370
371	sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
372	oh = calloc(1, sz);
373	nat64clat_fill_ntlv(&oh->ntlv, name, set);
374	if (do_get3(IP_FW_NAT64CLAT_STATS, &oh->opheader, &sz) == 0) {
375		oc = (ipfw_obj_ctlv *)(oh + 1);
376		memcpy(stats, oc + 1, sizeof(*stats));
377		free(oh);
378		return (0);
379	}
380	free(oh);
381	return (-1);
382}
383
384static void
385nat64clat_stats(const char *name, uint8_t set)
386{
387	struct ipfw_nat64clat_stats stats;
388
389	if (nat64clat_get_stats(name, set, &stats) != 0)
390		err(EX_OSERR, "Error retrieving stats");
391
392	if (g_co.use_set != 0 || set != 0)
393		printf("set %u ", set);
394	printf("nat64clat %s\n", name);
395
396	printf("\t%ju packets translated from IPv6 to IPv4\n",
397	    (uintmax_t)stats.opcnt64);
398	printf("\t%ju packets translated from IPv4 to IPv6\n",
399	    (uintmax_t)stats.opcnt46);
400	printf("\t%ju IPv6 fragments created\n",
401	    (uintmax_t)stats.ofrags);
402	printf("\t%ju IPv4 fragments received\n",
403	    (uintmax_t)stats.ifrags);
404	printf("\t%ju output packets dropped due to no bufs, etc.\n",
405	    (uintmax_t)stats.oerrors);
406	printf("\t%ju output packets discarded due to no IPv4 route\n",
407	    (uintmax_t)stats.noroute4);
408	printf("\t%ju output packets discarded due to no IPv6 route\n",
409	    (uintmax_t)stats.noroute6);
410	printf("\t%ju packets discarded due to unsupported protocol\n",
411	    (uintmax_t)stats.noproto);
412	printf("\t%ju packets discarded due to memory allocation problems\n",
413	    (uintmax_t)stats.nomem);
414	printf("\t%ju packets discarded due to some errors\n",
415	    (uintmax_t)stats.dropped);
416}
417
418/*
419 * Reset nat64clat instance statistics specified by @oh->ntlv.
420 * Request: [ ipfw_obj_header ]
421 */
422static void
423nat64clat_reset_stats(const char *name, uint8_t set)
424{
425	ipfw_obj_header oh;
426
427	memset(&oh, 0, sizeof(oh));
428	nat64clat_fill_ntlv(&oh.ntlv, name, set);
429	if (do_set3(IP_FW_NAT64CLAT_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
430		err(EX_OSERR, "failed to reset stats for instance %s", name);
431}
432
433static int
434nat64clat_show_cb(ipfw_nat64clat_cfg *cfg, const char *name, uint8_t set)
435{
436	char plat_buf[INET6_ADDRSTRLEN], clat_buf[INET6_ADDRSTRLEN];
437
438	if (name != NULL && strcmp(cfg->name, name) != 0)
439		return (ESRCH);
440
441	if (g_co.use_set != 0 && cfg->set != set)
442		return (ESRCH);
443
444	if (g_co.use_set != 0 || cfg->set != 0)
445		printf("set %u ", cfg->set);
446
447	inet_ntop(AF_INET6, &cfg->clat_prefix, clat_buf, sizeof(clat_buf));
448	inet_ntop(AF_INET6, &cfg->plat_prefix, plat_buf, sizeof(plat_buf));
449	printf("nat64clat %s clat_prefix %s/%u plat_prefix %s/%u",
450	    cfg->name, clat_buf, cfg->clat_plen, plat_buf, cfg->plat_plen);
451	if (cfg->flags & NAT64_LOG)
452		printf(" log");
453	if (cfg->flags & NAT64_ALLOW_PRIVATE)
454		printf(" allow_private");
455	printf("\n");
456	return (0);
457}
458
459static int
460nat64clat_destroy_cb(ipfw_nat64clat_cfg *cfg, const char *name __unused,
461    uint8_t set)
462{
463
464	if (g_co.use_set != 0 && cfg->set != set)
465		return (ESRCH);
466
467	nat64clat_destroy(cfg->name, cfg->set);
468	return (0);
469}
470
471
472/*
473 * Compare nat64clat instances names.
474 * Honor number comparison.
475 */
476static int
477nat64name_cmp(const void *a, const void *b)
478{
479	const ipfw_nat64clat_cfg *ca, *cb;
480
481	ca = (const ipfw_nat64clat_cfg *)a;
482	cb = (const ipfw_nat64clat_cfg *)b;
483
484	if (ca->set > cb->set)
485		return (1);
486	else if (ca->set < cb->set)
487		return (-1);
488	return (stringnum_cmp(ca->name, cb->name));
489}
490
491/*
492 * Retrieves nat64clat instance list from kernel,
493 * optionally sorts it and calls requested function for each instance.
494 *
495 * Request: [ ipfw_obj_lheader ]
496 * Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ]
497 */
498static int
499nat64clat_foreach(nat64clat_cb_t *f, const char *name, uint8_t set, int sort)
500{
501	ipfw_obj_lheader *olh;
502	ipfw_nat64clat_cfg *cfg;
503	size_t sz;
504	uint32_t i;
505
506	/* Start with reasonable default */
507	sz = sizeof(*olh) + 16 * sizeof(*cfg);
508	for (;;) {
509		if ((olh = calloc(1, sz)) == NULL)
510			return (ENOMEM);
511
512		olh->size = sz;
513		if (do_get3(IP_FW_NAT64CLAT_LIST, &olh->opheader, &sz) != 0) {
514			sz = olh->size;
515			free(olh);
516			if (errno != ENOMEM)
517				return (errno);
518			continue;
519		}
520
521		if (sort != 0)
522			qsort(olh + 1, olh->count, olh->objsize,
523			    nat64name_cmp);
524
525		cfg = (ipfw_nat64clat_cfg *)(olh + 1);
526		for (i = 0; i < olh->count; i++) {
527			(void)f(cfg, name, set); /* Ignore errors for now */
528			cfg = (ipfw_nat64clat_cfg *)((caddr_t)cfg +
529			    olh->objsize);
530		}
531		free(olh);
532		break;
533	}
534	return (0);
535}
536
537