1/*-
2 * Copyright (c) 2016 Yandex LLC
3 * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31#include <sys/param.h>
32#include <sys/socket.h>
33
34#include "ipfw2.h"
35
36#include <ctype.h>
37#include <err.h>
38#include <errno.h>
39#include <inttypes.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_nptv6.h>
49#include <arpa/inet.h>
50
51
52typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set);
53static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set,
54    int sort);
55
56static void nptv6_create(const char *name, uint8_t set, int ac, char **av);
57static void nptv6_destroy(const char *name, uint8_t set);
58static void nptv6_stats(const char *name, uint8_t set);
59static void nptv6_reset_stats(const char *name, uint8_t set);
60static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
61static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
62
63static struct _s_x nptv6cmds[] = {
64      { "create",	TOK_CREATE },
65      { "destroy",	TOK_DESTROY },
66      { "list",		TOK_LIST },
67      { "show",		TOK_LIST },
68      { "stats",	TOK_STATS },
69      { NULL, 0 }
70};
71
72static struct _s_x nptv6statscmds[] = {
73      { "reset",	TOK_RESET },
74      { NULL, 0 }
75};
76
77/*
78 * This one handles all NPTv6-related commands
79 *	ipfw [set N] nptv6 NAME {create | config} ...
80 *	ipfw [set N] nptv6 NAME stats [reset]
81 *	ipfw [set N] nptv6 {NAME | all} destroy
82 *	ipfw [set N] nptv6 {NAME | all} {list | show}
83 */
84#define	nptv6_check_name	table_check_name
85void
86ipfw_nptv6_handler(int ac, char *av[])
87{
88	const char *name;
89	int tcmd;
90	uint8_t set;
91
92	if (g_co.use_set != 0)
93		set = g_co.use_set - 1;
94	else
95		set = 0;
96	ac--; av++;
97
98	NEED1("nptv6 needs instance name");
99	name = *av;
100	if (nptv6_check_name(name) != 0) {
101		if (strcmp(name, "all") == 0) {
102			name = NULL;
103		} else
104			errx(EX_USAGE, "nptv6 instance name %s is invalid",
105			    name);
106	}
107	ac--; av++;
108	NEED1("nptv6 needs command");
109
110	tcmd = get_token(nptv6cmds, *av, "nptv6 command");
111	if (name == NULL && tcmd != TOK_DESTROY && tcmd != TOK_LIST)
112		errx(EX_USAGE, "nptv6 instance name required");
113	switch (tcmd) {
114	case TOK_CREATE:
115		ac--; av++;
116		nptv6_create(name, set, ac, av);
117		break;
118	case TOK_LIST:
119		nptv6_foreach(nptv6_show_cb, name, set, 1);
120		break;
121	case TOK_DESTROY:
122		if (name == NULL)
123			nptv6_foreach(nptv6_destroy_cb, NULL, set, 0);
124		else
125			nptv6_destroy(name, set);
126		break;
127	case TOK_STATS:
128		ac--; av++;
129		if (ac == 0) {
130			nptv6_stats(name, set);
131			break;
132		}
133		tcmd = get_token(nptv6statscmds, *av, "stats command");
134		if (tcmd == TOK_RESET)
135			nptv6_reset_stats(name, set);
136	}
137}
138
139
140static void
141nptv6_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint8_t set)
142{
143
144	ntlv->head.type = IPFW_TLV_EACTION_NAME(1); /* it doesn't matter */
145	ntlv->head.length = sizeof(ipfw_obj_ntlv);
146	ntlv->idx = 1;
147	ntlv->set = set;
148	strlcpy(ntlv->name, name, sizeof(ntlv->name));
149}
150
151static struct _s_x nptv6newcmds[] = {
152      { "int_prefix",	TOK_INTPREFIX },
153      { "ext_prefix",	TOK_EXTPREFIX },
154      { "prefixlen",	TOK_PREFIXLEN },
155      { "ext_if",	TOK_EXTIF },
156      { NULL, 0 }
157};
158
159
160static void
161nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len)
162{
163	char *p, *l;
164
165	p = strdup(arg);
166	if (p == NULL)
167		err(EX_OSERR, NULL);
168	if ((l = strchr(p, '/')) != NULL)
169		*l++ = '\0';
170	if (inet_pton(AF_INET6, p, prefix) != 1)
171		errx(EX_USAGE, "Bad prefix: %s", p);
172	if (l != NULL) {
173		*len = (int)strtol(l, &l, 10);
174		if (*l != '\0' || *len <= 0 || *len > 64)
175			errx(EX_USAGE, "Bad prefix length: %s", arg);
176	} else
177		*len = 0;
178	free(p);
179}
180/*
181 * Creates new nptv6 instance
182 * ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <prefix>
183 * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ]
184 */
185#define	NPTV6_HAS_INTPREFIX	0x01
186#define	NPTV6_HAS_EXTPREFIX	0x02
187#define	NPTV6_HAS_PREFIXLEN	0x04
188static void
189nptv6_create(const char *name, uint8_t set, int ac, char *av[])
190{
191	char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)];
192	struct in6_addr mask;
193	ipfw_nptv6_cfg *cfg;
194	ipfw_obj_lheader *olh;
195	int tcmd, flags, plen;
196	char *p;
197
198	plen = 0;
199	memset(buf, 0, sizeof(buf));
200	olh = (ipfw_obj_lheader *)buf;
201	cfg = (ipfw_nptv6_cfg *)(olh + 1);
202	cfg->set = set;
203	flags = 0;
204	while (ac > 0) {
205		tcmd = get_token(nptv6newcmds, *av, "option");
206		ac--; av++;
207
208		switch (tcmd) {
209		case TOK_INTPREFIX:
210			NEED1("IPv6 prefix required");
211			nptv6_parse_prefix(*av, &cfg->internal, &plen);
212			flags |= NPTV6_HAS_INTPREFIX;
213			if (plen > 0)
214				goto check_prefix;
215			ac--; av++;
216			break;
217		case TOK_EXTPREFIX:
218			if (flags & NPTV6_HAS_EXTPREFIX)
219				errx(EX_USAGE,
220				    "Only one ext_prefix or ext_if allowed");
221			NEED1("IPv6 prefix required");
222			nptv6_parse_prefix(*av, &cfg->external, &plen);
223			flags |= NPTV6_HAS_EXTPREFIX;
224			if (plen > 0)
225				goto check_prefix;
226			ac--; av++;
227			break;
228		case TOK_EXTIF:
229			if (flags & NPTV6_HAS_EXTPREFIX)
230				errx(EX_USAGE,
231				    "Only one ext_prefix or ext_if allowed");
232			NEED1("Interface name required");
233			if (strlen(*av) >= sizeof(cfg->if_name))
234				errx(EX_USAGE, "Invalid interface name");
235			flags |= NPTV6_HAS_EXTPREFIX;
236			cfg->flags |= NPTV6_DYNAMIC_PREFIX;
237			strncpy(cfg->if_name, *av, sizeof(cfg->if_name));
238			ac--; av++;
239			break;
240		case TOK_PREFIXLEN:
241			NEED1("IPv6 prefix length required");
242			plen = strtol(*av, &p, 10);
243check_prefix:
244			if (*p != '\0' || plen < 8 || plen > 64)
245				errx(EX_USAGE, "wrong prefix length: %s", *av);
246			/* RFC 6296 Sec. 3.1 */
247			if (cfg->plen > 0 && cfg->plen != plen) {
248				warnx("Prefix length mismatch (%d vs %d).  "
249				    "It was extended up to %d",
250				    cfg->plen, plen, MAX(plen, cfg->plen));
251				plen = MAX(plen, cfg->plen);
252			}
253			cfg->plen = plen;
254			flags |= NPTV6_HAS_PREFIXLEN;
255			ac--; av++;
256			break;
257		}
258	}
259
260	/* Check validness */
261	if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX)
262		errx(EX_USAGE, "int_prefix required");
263	if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX)
264		errx(EX_USAGE, "ext_prefix or ext_if required");
265	if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN)
266		errx(EX_USAGE, "prefixlen required");
267
268	n2mask(&mask, cfg->plen);
269	APPLY_MASK(&cfg->internal, &mask);
270	if ((cfg->flags & NPTV6_DYNAMIC_PREFIX) == 0)
271		APPLY_MASK(&cfg->external, &mask);
272
273	olh->count = 1;
274	olh->objsize = sizeof(*cfg);
275	olh->size = sizeof(buf);
276	strlcpy(cfg->name, name, sizeof(cfg->name));
277	if (do_set3(IP_FW_NPTV6_CREATE, &olh->opheader, sizeof(buf)) != 0)
278		err(EX_OSERR, "nptv6 instance creation failed");
279}
280
281/*
282 * Destroys NPTv6 instance.
283 * Request: [ ipfw_obj_header ]
284 */
285static void
286nptv6_destroy(const char *name, uint8_t set)
287{
288	ipfw_obj_header oh;
289
290	memset(&oh, 0, sizeof(oh));
291	nptv6_fill_ntlv(&oh.ntlv, name, set);
292	if (do_set3(IP_FW_NPTV6_DESTROY, &oh.opheader, sizeof(oh)) != 0)
293		err(EX_OSERR, "failed to destroy nat instance %s", name);
294}
295
296/*
297 * Get NPTv6 instance statistics.
298 * Request: [ ipfw_obj_header ]
299 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
300 */
301static int
302nptv6_get_stats(const char *name, uint8_t set, struct ipfw_nptv6_stats *stats)
303{
304	ipfw_obj_header *oh;
305	ipfw_obj_ctlv *oc;
306	size_t sz;
307
308	sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
309	oh = calloc(1, sz);
310	nptv6_fill_ntlv(&oh->ntlv, name, set);
311	if (do_get3(IP_FW_NPTV6_STATS, &oh->opheader, &sz) == 0) {
312		oc = (ipfw_obj_ctlv *)(oh + 1);
313		memcpy(stats, oc + 1, sizeof(*stats));
314		free(oh);
315		return (0);
316	}
317	free(oh);
318	return (-1);
319}
320
321static void
322nptv6_stats(const char *name, uint8_t set)
323{
324	struct ipfw_nptv6_stats stats;
325
326	if (nptv6_get_stats(name, set, &stats) != 0)
327		err(EX_OSERR, "Error retrieving stats");
328
329	if (g_co.use_set != 0 || set != 0)
330		printf("set %u ", set);
331	printf("nptv6 %s\n", name);
332	printf("\t%ju packets translated (internal to external)\n",
333	    (uintmax_t)stats.in2ex);
334	printf("\t%ju packets translated (external to internal)\n",
335	    (uintmax_t)stats.ex2in);
336	printf("\t%ju packets dropped due to some error\n",
337	    (uintmax_t)stats.dropped);
338}
339
340/*
341 * Reset NPTv6 instance statistics specified by @oh->ntlv.
342 * Request: [ ipfw_obj_header ]
343 */
344static void
345nptv6_reset_stats(const char *name, uint8_t set)
346{
347	ipfw_obj_header oh;
348
349	memset(&oh, 0, sizeof(oh));
350	nptv6_fill_ntlv(&oh.ntlv, name, set);
351	if (do_set3(IP_FW_NPTV6_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
352		err(EX_OSERR, "failed to reset stats for instance %s", name);
353}
354
355static int
356nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set)
357{
358	char abuf[INET6_ADDRSTRLEN];
359
360	if (name != NULL && strcmp(cfg->name, name) != 0)
361		return (ESRCH);
362
363	if (g_co.use_set != 0 && cfg->set != set)
364		return (ESRCH);
365
366	if (g_co.use_set != 0 || cfg->set != 0)
367		printf("set %u ", cfg->set);
368	inet_ntop(AF_INET6, &cfg->internal, abuf, sizeof(abuf));
369	printf("nptv6 %s int_prefix %s ", cfg->name, abuf);
370	if (cfg->flags & NPTV6_DYNAMIC_PREFIX)
371		printf("ext_if %s ", cfg->if_name);
372	else {
373		inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf));
374		printf("ext_prefix %s ", abuf);
375	}
376	printf("prefixlen %u\n", cfg->plen);
377	return (0);
378}
379
380static int
381nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name __unused, uint8_t set)
382{
383
384	if (g_co.use_set != 0 && cfg->set != set)
385		return (ESRCH);
386
387	nptv6_destroy(cfg->name, cfg->set);
388	return (0);
389}
390
391
392/*
393 * Compare NPTv6 instances names.
394 * Honor number comparison.
395 */
396static int
397nptv6name_cmp(const void *a, const void *b)
398{
399	const ipfw_nptv6_cfg *ca, *cb;
400
401	ca = (const ipfw_nptv6_cfg *)a;
402	cb = (const ipfw_nptv6_cfg *)b;
403
404	if (ca->set > cb->set)
405		return (1);
406	else if (ca->set < cb->set)
407		return (-1);
408	return (stringnum_cmp(ca->name, cb->name));
409}
410
411/*
412 * Retrieves NPTv6 instance list from kernel,
413 * Request: [ ipfw_obj_lheader ]
414 * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ]
415 */
416static int
417nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, int sort)
418{
419	ipfw_obj_lheader *olh;
420	ipfw_nptv6_cfg *cfg;
421	size_t sz;
422	uint32_t i;
423	int error;
424
425	/* Start with reasonable default */
426	sz = sizeof(*olh) + 16 * sizeof(*cfg);
427	for (;;) {
428		if ((olh = calloc(1, sz)) == NULL)
429			return (ENOMEM);
430
431		olh->size = sz;
432		if (do_get3(IP_FW_NPTV6_LIST, &olh->opheader, &sz) != 0) {
433			sz = olh->size;
434			free(olh);
435			if (errno != ENOMEM)
436				return (errno);
437			continue;
438		}
439
440		if (sort != 0)
441			qsort(olh + 1, olh->count, olh->objsize, nptv6name_cmp);
442
443		cfg = (ipfw_nptv6_cfg *)(olh + 1);
444		for (i = 0; i < olh->count; i++) {
445			error = f(cfg, name, set);
446			cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize);
447		}
448		free(olh);
449		break;
450	}
451	return (0);
452}
453
454