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
52 typedef int (nptv6_cb_t)(ipfw_nptv6_cfg *i, const char *name, uint8_t set);
53 static int nptv6_foreach(nptv6_cb_t *f, const char *name, uint8_t set,
54 int sort);
55
56 static void nptv6_create(const char *name, uint8_t set, int ac, char **av);
57 static void nptv6_destroy(const char *name, uint8_t set);
58 static void nptv6_stats(const char *name, uint8_t set);
59 static void nptv6_reset_stats(const char *name, uint8_t set);
60 static int nptv6_show_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
61 static int nptv6_destroy_cb(ipfw_nptv6_cfg *cfg, const char *name, uint8_t set);
62
63 static 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
72 static 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
85 void
ipfw_nptv6_handler(int ac,char * av[])86 ipfw_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
140 static void
nptv6_fill_ntlv(ipfw_obj_ntlv * ntlv,const char * name,uint8_t set)141 nptv6_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
151 static struct _s_x nptv6newcmds[] = {
152 { "int_prefix", TOK_INTPREFIX },
153 { "ext_prefix", TOK_EXTPREFIX },
154 { "prefixlen", TOK_PREFIXLEN },
155 { NULL, 0 }
156 };
157
158
159 static void
nptv6_parse_prefix(const char * arg,struct in6_addr * prefix,int * len)160 nptv6_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
187 static void
nptv6_create(const char * name,uint8_t set,int ac,char * av[])188 nptv6_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);
227 check_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 */
268 static void
nptv6_destroy(const char * name,uint8_t set)269 nptv6_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 */
284 static int
nptv6_get_stats(const char * name,uint8_t set,struct ipfw_nptv6_stats * stats)285 nptv6_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
304 static void
nptv6_stats(const char * name,uint8_t set)305 nptv6_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 */
327 static void
nptv6_reset_stats(const char * name,uint8_t set)328 nptv6_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
338 static int
nptv6_show_cb(ipfw_nptv6_cfg * cfg,const char * name,uint8_t set)339 nptv6_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
358 static int
nptv6_destroy_cb(ipfw_nptv6_cfg * cfg,const char * name,uint8_t set)359 nptv6_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 */
374 static int
nptv6name_cmp(const void * a,const void * b)375 nptv6name_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 */
394 static int
nptv6_foreach(nptv6_cb_t * f,const char * name,uint8_t set,int sort)395 nptv6_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