nptv6.c revision 316444
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: stable/11/sbin/ipfw/nptv6.c 316444 2017-04-03 07:30:47Z ae $");
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 (co.use_set != 0)
93		set = 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      { NULL, 0 }
156};
157
158
159static void
160nptv6_parse_prefix(const char *arg, struct in6_addr *prefix, int *len)
161{
162	char *p, *l;
163
164	p = strdup(arg);
165	if (p == NULL)
166		err(EX_OSERR, NULL);
167	if ((l = strchr(p, '/')) != NULL)
168		*l++ = '\0';
169	if (inet_pton(AF_INET6, p, prefix) != 1)
170		errx(EX_USAGE, "Bad prefix: %s", p);
171	if (l != NULL) {
172		*len = (int)strtol(l, &l, 10);
173		if (*l != '\0' || *len <= 0 || *len > 64)
174			errx(EX_USAGE, "Bad prefix length: %s", arg);
175	} else
176		*len = 0;
177	free(p);
178}
179/*
180 * Creates new nptv6 instance
181 * ipfw nptv6 <NAME> create int_prefix <prefix> ext_prefix <prefix>
182 * Request: [ ipfw_obj_lheader ipfw_nptv6_cfg ]
183 */
184#define	NPTV6_HAS_INTPREFIX	0x01
185#define	NPTV6_HAS_EXTPREFIX	0x02
186#define	NPTV6_HAS_PREFIXLEN	0x04
187static void
188nptv6_create(const char *name, uint8_t set, int ac, char *av[])
189{
190	char buf[sizeof(ipfw_obj_lheader) + sizeof(ipfw_nptv6_cfg)];
191	struct in6_addr mask;
192	ipfw_nptv6_cfg *cfg;
193	ipfw_obj_lheader *olh;
194	int tcmd, flags, plen;
195	char *p = "\0";
196
197	plen = 0;
198	memset(buf, 0, sizeof(buf));
199	olh = (ipfw_obj_lheader *)buf;
200	cfg = (ipfw_nptv6_cfg *)(olh + 1);
201	cfg->set = set;
202	flags = 0;
203	while (ac > 0) {
204		tcmd = get_token(nptv6newcmds, *av, "option");
205		ac--; av++;
206
207		switch (tcmd) {
208		case TOK_INTPREFIX:
209			NEED1("IPv6 prefix required");
210			nptv6_parse_prefix(*av, &cfg->internal, &plen);
211			flags |= NPTV6_HAS_INTPREFIX;
212			if (plen > 0)
213				goto check_prefix;
214			ac--; av++;
215			break;
216		case TOK_EXTPREFIX:
217			NEED1("IPv6 prefix required");
218			nptv6_parse_prefix(*av, &cfg->external, &plen);
219			flags |= NPTV6_HAS_EXTPREFIX;
220			if (plen > 0)
221				goto check_prefix;
222			ac--; av++;
223			break;
224		case TOK_PREFIXLEN:
225			NEED1("IPv6 prefix length required");
226			plen = strtol(*av, &p, 10);
227check_prefix:
228			if (*p != '\0' || plen < 8 || plen > 64)
229				errx(EX_USAGE, "wrong prefix length: %s", *av);
230			/* RFC 6296 Sec. 3.1 */
231			if (cfg->plen > 0 && cfg->plen != plen) {
232				warnx("Prefix length mismatch (%d vs %d).  "
233				    "It was extended up to %d",
234				    cfg->plen, plen, MAX(plen, cfg->plen));
235				plen = MAX(plen, cfg->plen);
236			}
237			cfg->plen = plen;
238			flags |= NPTV6_HAS_PREFIXLEN;
239			ac--; av++;
240			break;
241		}
242	}
243
244	/* Check validness */
245	if ((flags & NPTV6_HAS_INTPREFIX) != NPTV6_HAS_INTPREFIX)
246		errx(EX_USAGE, "int_prefix required");
247	if ((flags & NPTV6_HAS_EXTPREFIX) != NPTV6_HAS_EXTPREFIX)
248		errx(EX_USAGE, "ext_prefix required");
249	if ((flags & NPTV6_HAS_PREFIXLEN) != NPTV6_HAS_PREFIXLEN)
250		errx(EX_USAGE, "prefixlen required");
251
252	n2mask(&mask, cfg->plen);
253	APPLY_MASK(&cfg->internal, &mask);
254	APPLY_MASK(&cfg->external, &mask);
255
256	olh->count = 1;
257	olh->objsize = sizeof(*cfg);
258	olh->size = sizeof(buf);
259	strlcpy(cfg->name, name, sizeof(cfg->name));
260	if (do_set3(IP_FW_NPTV6_CREATE, &olh->opheader, sizeof(buf)) != 0)
261		err(EX_OSERR, "nptv6 instance creation failed");
262}
263
264/*
265 * Destroys NPTv6 instance.
266 * Request: [ ipfw_obj_header ]
267 */
268static void
269nptv6_destroy(const char *name, uint8_t set)
270{
271	ipfw_obj_header oh;
272
273	memset(&oh, 0, sizeof(oh));
274	nptv6_fill_ntlv(&oh.ntlv, name, set);
275	if (do_set3(IP_FW_NPTV6_DESTROY, &oh.opheader, sizeof(oh)) != 0)
276		err(EX_OSERR, "failed to destroy nat instance %s", name);
277}
278
279/*
280 * Get NPTv6 instance statistics.
281 * Request: [ ipfw_obj_header ]
282 * Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ] ]
283 */
284static int
285nptv6_get_stats(const char *name, uint8_t set, struct ipfw_nptv6_stats *stats)
286{
287	ipfw_obj_header *oh;
288	ipfw_obj_ctlv *oc;
289	size_t sz;
290
291	sz = sizeof(*oh) + sizeof(*oc) + sizeof(*stats);
292	oh = calloc(1, sz);
293	nptv6_fill_ntlv(&oh->ntlv, name, set);
294	if (do_get3(IP_FW_NPTV6_STATS, &oh->opheader, &sz) == 0) {
295		oc = (ipfw_obj_ctlv *)(oh + 1);
296		memcpy(stats, oc + 1, sizeof(*stats));
297		free(oh);
298		return (0);
299	}
300	free(oh);
301	return (-1);
302}
303
304static void
305nptv6_stats(const char *name, uint8_t set)
306{
307	struct ipfw_nptv6_stats stats;
308
309	if (nptv6_get_stats(name, set, &stats) != 0)
310		err(EX_OSERR, "Error retrieving stats");
311
312	if (co.use_set != 0 || set != 0)
313		printf("set %u ", set);
314	printf("nptv6 %s\n", name);
315	printf("\t%ju packets translated (internal to external)\n",
316	    (uintmax_t)stats.in2ex);
317	printf("\t%ju packets translated (external to internal)\n",
318	    (uintmax_t)stats.ex2in);
319	printf("\t%ju packets dropped due to some error\n",
320	    (uintmax_t)stats.dropped);
321}
322
323/*
324 * Reset NPTv6 instance statistics specified by @oh->ntlv.
325 * Request: [ ipfw_obj_header ]
326 */
327static void
328nptv6_reset_stats(const char *name, uint8_t set)
329{
330	ipfw_obj_header oh;
331
332	memset(&oh, 0, sizeof(oh));
333	nptv6_fill_ntlv(&oh.ntlv, name, set);
334	if (do_set3(IP_FW_NPTV6_RESET_STATS, &oh.opheader, sizeof(oh)) != 0)
335		err(EX_OSERR, "failed to reset stats for instance %s", name);
336}
337
338static int
339nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set)
340{
341	char abuf[INET6_ADDRSTRLEN];
342
343	if (name != NULL && strcmp(cfg->name, name) != 0)
344		return (ESRCH);
345
346	if (co.use_set != 0 && cfg->set != set)
347		return (ESRCH);
348
349	if (co.use_set != 0 || cfg->set != 0)
350		printf("set %u ", cfg->set);
351	inet_ntop(AF_INET6, &cfg->internal, abuf, sizeof(abuf));
352	printf("nptv6 %s int_prefix %s ", cfg->name, abuf);
353	inet_ntop(AF_INET6, &cfg->external, abuf, sizeof(abuf));
354	printf("ext_prefix %s prefixlen %u\n", abuf, cfg->plen);
355	return (0);
356}
357
358static int
359nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set)
360{
361
362	if (co.use_set != 0 && cfg->set != set)
363		return (ESRCH);
364
365	nptv6_destroy(cfg->name, cfg->set);
366	return (0);
367}
368
369
370/*
371 * Compare NPTv6 instances names.
372 * Honor number comparison.
373 */
374static int
375nptv6name_cmp(const void *a, const void *b)
376{
377	ipfw_nptv6_cfg *ca, *cb;
378
379	ca = (ipfw_nptv6_cfg *)a;
380	cb = (ipfw_nptv6_cfg *)b;
381
382	if (ca->set > cb->set)
383		return (1);
384	else if (ca->set < cb->set)
385		return (-1);
386	return (stringnum_cmp(ca->name, cb->name));
387}
388
389/*
390 * Retrieves NPTv6 instance list from kernel,
391 * Request: [ ipfw_obj_lheader ]
392 * Reply: [ ipfw_obj_lheader ipfw_nptv6_cfg x N ]
393 */
394static int
395nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set, int sort)
396{
397	ipfw_obj_lheader *olh;
398	ipfw_nptv6_cfg *cfg;
399	size_t sz;
400	int i, error;
401
402	/* Start with reasonable default */
403	sz = sizeof(*olh) + 16 * sizeof(*cfg);
404	for (;;) {
405		if ((olh = calloc(1, sz)) == NULL)
406			return (ENOMEM);
407
408		olh->size = sz;
409		if (do_get3(IP_FW_NPTV6_LIST, &olh->opheader, &sz) != 0) {
410			sz = olh->size;
411			free(olh);
412			if (errno != ENOMEM)
413				return (errno);
414			continue;
415		}
416
417		if (sort != 0)
418			qsort(olh + 1, olh->count, olh->objsize, nptv6name_cmp);
419
420		cfg = (ipfw_nptv6_cfg *)(olh + 1);
421		for (i = 0; i < olh->count; i++) {
422			error = f(cfg, name, set);
423			cfg = (ipfw_nptv6_cfg *)((caddr_t)cfg + olh->objsize);
424		}
425		free(olh);
426		break;
427	}
428	return (0);
429}
430
431