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