1 /*-
2 * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This material is based upon work partially supported by The
6 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
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 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: npfctl.c,v 1.65 2021/07/14 09:15:01 christos Exp $");
32
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/socket.h>
36 #include <sys/mman.h>
37 #include <sys/un.h>
38 #ifdef __NetBSD__
39 #include <sys/module.h>
40 #endif
41
42 #include <stdio.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <fcntl.h>
47 #include <errno.h>
48 #include <err.h>
49
50 #include "npfctl.h"
51
52 enum {
53 NPFCTL_START,
54 NPFCTL_STOP,
55 NPFCTL_RELOAD,
56 NPFCTL_SHOWCONF,
57 NPFCTL_FLUSH,
58 NPFCTL_VALIDATE,
59 NPFCTL_TABLE,
60 NPFCTL_RULE,
61 NPFCTL_STATS,
62 NPFCTL_SAVE,
63 NPFCTL_LOAD,
64 NPFCTL_DEBUG,
65 NPFCTL_CONN_LIST,
66 };
67
68 bool
join(char * buf,size_t buflen,int count,char ** args,const char * sep)69 join(char *buf, size_t buflen, int count, char **args, const char *sep)
70 {
71 const unsigned seplen = strlen(sep);
72 char *s = buf, *p = NULL;
73
74 for (int i = 0; i < count; i++) {
75 size_t len;
76
77 p = stpncpy(s, args[i], buflen);
78 len = p - s + seplen;
79 if (len >= buflen) {
80 return false;
81 }
82 buflen -= len;
83 strcpy(p, sep);
84 s = p + seplen;
85 }
86 *p = '\0';
87 return true;
88 }
89
90 __dead void
usage(void)91 usage(void)
92 {
93 const char *progname = getprogname();
94
95 fprintf(stderr,
96 "Usage:\t%s start | stop | flush | show | stats\n",
97 progname);
98 fprintf(stderr,
99 "\t%s validate | reload [<rule-file>]\n",
100 progname);
101 fprintf(stderr,
102 "\t%s rule \"rule-name\" { add | rem } <rule-syntax>\n",
103 progname);
104 fprintf(stderr,
105 "\t%s rule \"rule-name\" rem-id <rule-id>\n",
106 progname);
107 fprintf(stderr,
108 "\t%s rule \"rule-name\" { list | flush }\n",
109 progname);
110 fprintf(stderr,
111 "\t%s table \"table-name\" { add | rem | test } <address/mask>\n",
112 progname);
113 fprintf(stderr,
114 "\t%s table \"table-name\" { list | flush }\n",
115 progname);
116 fprintf(stderr,
117 "\t%s table \"table-name\" replace [-n \"name\"]"
118 " [-t <type>] <table-file>\n",
119 progname);
120 fprintf(stderr,
121 "\t%s save | load\n",
122 progname);
123 fprintf(stderr,
124 "\t%s list [-46hNnw] [-i <ifname>]\n",
125 progname);
126 fprintf(stderr,
127 "\t%s debug { -a | -b <binary-config> | -c <config> } "
128 "[ -o <outfile> ]\n",
129 progname);
130 exit(EXIT_FAILURE);
131 }
132
133 static int
npfctl_print_stats(int fd)134 npfctl_print_stats(int fd)
135 {
136 static const struct stats_s {
137 /* Note: -1 indicates a new section. */
138 int index;
139 const char * name;
140 } stats[] = {
141 { -1, "Packets passed" },
142 { NPF_STAT_PASS_DEFAULT, "default pass" },
143 { NPF_STAT_PASS_RULESET, "ruleset pass" },
144 { NPF_STAT_PASS_CONN, "state pass" },
145
146 { -1, "Packets blocked" },
147 { NPF_STAT_BLOCK_DEFAULT, "default block" },
148 { NPF_STAT_BLOCK_RULESET, "ruleset block" },
149
150 { -1, "State and NAT entries" },
151 { NPF_STAT_CONN_CREATE, "state allocations"},
152 { NPF_STAT_CONN_DESTROY, "state destructions"},
153 { NPF_STAT_NAT_CREATE, "NAT entry allocations" },
154 { NPF_STAT_NAT_DESTROY, "NAT entry destructions"},
155
156 { -1, "Network buffers" },
157 { NPF_STAT_NBUF_NONCONTIG, "non-contiguous cases" },
158 { NPF_STAT_NBUF_CONTIG_FAIL, "contig alloc failures" },
159
160 { -1, "Invalid packet state cases" },
161 { NPF_STAT_INVALID_STATE, "cases in total" },
162 { NPF_STAT_INVALID_STATE_TCP1, "TCP case I" },
163 { NPF_STAT_INVALID_STATE_TCP2, "TCP case II" },
164 { NPF_STAT_INVALID_STATE_TCP3, "TCP case III" },
165
166 { -1, "Packet race cases" },
167 { NPF_STAT_RACE_NAT, "NAT association race" },
168 { NPF_STAT_RACE_CONN, "duplicate state race" },
169
170 { -1, "Fragmentation" },
171 { NPF_STAT_FRAGMENTS, "fragments" },
172 { NPF_STAT_REASSEMBLY, "reassembled" },
173 { NPF_STAT_REASSFAIL, "failed reassembly" },
174
175 { -1, "Other" },
176 { NPF_STAT_ERROR, "unexpected errors" },
177 };
178 uint64_t *st = ecalloc(1, NPF_STATS_SIZE);
179
180 if (ioctl(fd, IOC_NPF_STATS, &st) != 0) {
181 err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)");
182 }
183
184 for (unsigned i = 0; i < __arraycount(stats); i++) {
185 const char *sname = stats[i].name;
186 int sidx = stats[i].index;
187
188 if (sidx == -1) {
189 printf("%s:\n", sname);
190 } else {
191 printf("\t%"PRIu64" %s\n", st[sidx], sname);
192 }
193 }
194
195 free(st);
196 return 0;
197 }
198
199 void
npfctl_print_error(const npf_error_t * ne)200 npfctl_print_error(const npf_error_t *ne)
201 {
202 const char *srcfile = ne->source_file;
203
204 if (ne->error_msg) {
205 errx(EXIT_FAILURE, "%s", ne->error_msg);
206 }
207 if (srcfile) {
208 warnx("source %s line %d", srcfile, ne->source_line);
209 }
210 if (ne->id) {
211 warnx("object: %" PRIi64, ne->id);
212 }
213 }
214
215 char *
npfctl_print_addrmask(int alen,const char * fmt,const npf_addr_t * addr,npf_netmask_t mask)216 npfctl_print_addrmask(int alen, const char *fmt, const npf_addr_t *addr,
217 npf_netmask_t mask)
218 {
219 const unsigned buflen = 256;
220 char *buf = ecalloc(1, buflen);
221 struct sockaddr_storage ss;
222
223 memset(&ss, 0, sizeof(ss));
224
225 switch (alen) {
226 case 4: {
227 struct sockaddr_in *sin = (void *)&ss;
228 sin->sin_family = AF_INET;
229 memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
230 break;
231 }
232 case 16: {
233 struct sockaddr_in6 *sin6 = (void *)&ss;
234 sin6->sin6_family = AF_INET6;
235 memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
236 break;
237 }
238 default:
239 abort();
240 }
241 sockaddr_snprintf(buf, buflen, fmt, (const void *)&ss);
242 if (mask && mask != NPF_NO_NETMASK) {
243 const unsigned len = strlen(buf);
244 snprintf(&buf[len], buflen - len, "/%u", mask);
245 }
246 return buf;
247 }
248
249 bool
npfctl_addr_iszero(const npf_addr_t * addr)250 npfctl_addr_iszero(const npf_addr_t *addr)
251 {
252 static const npf_addr_t zero; /* must be static */
253 return memcmp(addr, &zero, sizeof(npf_addr_t)) == 0;
254 }
255
256 static bool bpfjit = true;
257
258 void
npfctl_bpfjit(bool onoff)259 npfctl_bpfjit(bool onoff)
260 {
261 bpfjit = onoff;
262 }
263
264 static void
npfctl_preload_bpfjit(void)265 npfctl_preload_bpfjit(void)
266 {
267 #ifdef __NetBSD__
268 modctl_load_t args = {
269 .ml_filename = "bpfjit",
270 .ml_flags = MODCTL_NO_PROP,
271 .ml_props = NULL,
272 .ml_propslen = 0
273 };
274
275 if (!bpfjit)
276 return;
277
278 if (modctl(MODCTL_LOAD, &args) != 0 && errno != EEXIST) {
279 static const char *p = "; performance will be degraded";
280 if (errno == ENOENT)
281 warnx("the bpfjit module seems to be missing%s", p);
282 else
283 warn("error loading the bpfjit module%s", p);
284 warnx("To disable this warning `set bpf.jit off' in "
285 "/etc/npf.conf");
286 }
287 #endif
288 }
289
290 static nl_config_t *
npfctl_import(const char * path)291 npfctl_import(const char *path)
292 {
293 nl_config_t *ncf;
294 struct stat sb;
295 size_t blen;
296 void *blob;
297 int fd;
298
299 /*
300 * The file may change while reading - we are not handling this,
301 * just leaving this responsibility for the caller.
302 */
303 if ((fd = open(path, O_RDONLY)) == -1) {
304 err(EXIT_FAILURE, "open: '%s'", path);
305 }
306 if (fstat(fd, &sb) == -1) {
307 err(EXIT_FAILURE, "stat: '%s'", path);
308 }
309 if ((blen = sb.st_size) == 0) {
310 errx(EXIT_FAILURE,
311 "the binary configuration file '%s' is empty", path);
312 }
313 blob = mmap(NULL, blen, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
314 if (blob == MAP_FAILED) {
315 err(EXIT_FAILURE, "mmap: '%s'", path);
316 }
317 ncf = npf_config_import(blob, blen);
318 munmap(blob, blen);
319 return ncf;
320 }
321
322 static int
npfctl_load(int fd)323 npfctl_load(int fd)
324 {
325 nl_config_t *ncf;
326 npf_error_t errinfo;
327
328 /*
329 * Import the configuration, submit it and destroy.
330 */
331 ncf = npfctl_import(NPF_DB_PATH);
332 if (ncf == NULL) {
333 err(EXIT_FAILURE, "npf_config_import: '%s'", NPF_DB_PATH);
334 }
335 if ((errno = npf_config_submit(ncf, fd, &errinfo)) != 0) {
336 npfctl_print_error(&errinfo);
337 }
338 npf_config_destroy(ncf);
339 return errno;
340 }
341
342 static int
npfctl_open_dev(const char * path)343 npfctl_open_dev(const char *path)
344 {
345 struct stat st;
346 int fd;
347
348 if (lstat(path, &st) == -1) {
349 err(EXIT_FAILURE, "fstat: '%s'", path);
350 }
351 if ((st.st_mode & S_IFMT) == S_IFSOCK) {
352 struct sockaddr_un addr;
353
354 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
355 err(EXIT_FAILURE, "socket");
356 }
357 memset(&addr, 0, sizeof(addr));
358 addr.sun_family = AF_UNIX;
359 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
360
361 if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
362 err(EXIT_FAILURE, "connect: '%s'", path);
363 }
364 } else {
365 if ((fd = open(path, O_RDONLY)) == -1) {
366 err(EXIT_FAILURE, "open: '%s'", path);
367 }
368 }
369 return fd;
370 }
371
372 static void
npfctl_debug(int argc,char ** argv)373 npfctl_debug(int argc, char **argv)
374 {
375 const char *conf = NULL, *bconf = NULL, *outfile = NULL;
376 bool use_active = false;
377 nl_config_t *ncf = NULL;
378 int fd, c, optcount;
379
380 argc--;
381 argv++;
382
383 npfctl_config_init(true);
384 while ((c = getopt(argc, argv, "ab:c:o:")) != -1) {
385 switch (c) {
386 case 'a':
387 use_active = true;
388 break;
389 case 'b':
390 bconf = optarg;
391 break;
392 case 'c':
393 conf = optarg;
394 break;
395 case 'o':
396 outfile = optarg;
397 break;
398 default:
399 usage();
400 }
401 }
402
403 /*
404 * Options -a, -b and -c are mutually exclusive, so allow only one.
405 * If no options were specified, then set the defaults.
406 */
407 optcount = (int)!!use_active + (int)!!conf + (int)!!bconf;
408 if (optcount != 1) {
409 if (optcount > 1) {
410 usage();
411 }
412 conf = NPF_CONF_PATH;
413 outfile = outfile ? outfile : "npf.nvlist";
414 }
415
416 if (use_active) {
417 puts("Loading the active configuration");
418 fd = npfctl_open_dev(NPF_DEV_PATH);
419 if ((ncf = npf_config_retrieve(fd)) == NULL) {
420 err(EXIT_FAILURE, "npf_config_retrieve: '%s'",
421 NPF_DEV_PATH);
422 }
423 }
424
425 if (conf) {
426 printf("Loading %s\n", conf);
427 npfctl_parse_file(conf);
428 npfctl_config_build();
429 ncf = npfctl_config_ref();
430 }
431
432 if (bconf) {
433 printf("Importing %s\n", bconf);
434 ncf = npfctl_import(bconf);
435 }
436
437 printf("Configuration:\n\n");
438 _npf_config_dump(ncf, STDOUT_FILENO);
439 if (outfile) {
440 printf("\nSaving binary to %s\n", outfile);
441 npfctl_config_save(ncf, outfile);
442 }
443 npf_config_destroy(ncf);
444 }
445
446 static void
npfctl(int action,int argc,char ** argv)447 npfctl(int action, int argc, char **argv)
448 {
449 int fd, boolval, ret = 0;
450 const char *fun = "";
451 nl_config_t *ncf;
452
453 switch (action) {
454 case NPFCTL_VALIDATE:
455 case NPFCTL_DEBUG:
456 fd = 0;
457 break;
458 default:
459 fd = npfctl_open_dev(NPF_DEV_PATH);
460 }
461
462 switch (action) {
463 case NPFCTL_START:
464 boolval = true;
465 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
466 fun = "ioctl(IOC_NPF_SWITCH)";
467 break;
468 case NPFCTL_STOP:
469 boolval = false;
470 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
471 fun = "ioctl(IOC_NPF_SWITCH)";
472 break;
473 case NPFCTL_RELOAD:
474 npfctl_config_init(false);
475 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
476 npfctl_preload_bpfjit();
477 errno = ret = npfctl_config_send(fd);
478 fun = "npfctl_config_send";
479 break;
480 case NPFCTL_SHOWCONF:
481 ret = npfctl_config_show(fd);
482 fun = "npfctl_config_show";
483 break;
484 case NPFCTL_FLUSH:
485 ret = npf_config_flush(fd);
486 fun = "npf_config_flush";
487 break;
488 case NPFCTL_TABLE:
489 if ((argc -= 2) < 2) {
490 usage();
491 }
492 argv += 2;
493 if (strcmp(argv[1], "replace") == 0) {
494 npfctl_table_replace(fd, argc, argv);
495 } else {
496 npfctl_table(fd, argc, argv);
497 }
498 break;
499 case NPFCTL_RULE:
500 if ((argc -= 2) < 2) {
501 usage();
502 }
503 argv += 2;
504 npfctl_rule(fd, argc, argv);
505 break;
506 case NPFCTL_LOAD:
507 npfctl_preload_bpfjit();
508 ret = npfctl_load(fd);
509 fun = "npfctl_config_load";
510 break;
511 case NPFCTL_SAVE:
512 ncf = npf_config_retrieve(fd);
513 if (ncf) {
514 npfctl_config_save(ncf,
515 argc > 2 ? argv[2] : NPF_DB_PATH);
516 npf_config_destroy(ncf);
517 } else {
518 ret = errno;
519 }
520 fun = "npfctl_config_save";
521 break;
522 case NPFCTL_STATS:
523 ret = npfctl_print_stats(fd);
524 fun = "npfctl_print_stats";
525 break;
526 case NPFCTL_CONN_LIST:
527 ret = npfctl_conn_list(fd, argc, argv);
528 fun = "npfctl_conn_list";
529 break;
530 case NPFCTL_VALIDATE:
531 npfctl_config_init(false);
532 npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
533 ret = npfctl_config_show(0);
534 fun = "npfctl_config_show";
535 break;
536 case NPFCTL_DEBUG:
537 npfctl_debug(argc, argv);
538 break;
539 }
540 if (ret) {
541 err(EXIT_FAILURE, "%s", fun);
542 }
543 if (fd) {
544 close(fd);
545 }
546 }
547
548 int
main(int argc,char ** argv)549 main(int argc, char **argv)
550 {
551 static const struct operations_s {
552 const char * cmd;
553 int action;
554 } operations[] = {
555 /* Start, stop, reload */
556 { "start", NPFCTL_START },
557 { "stop", NPFCTL_STOP },
558 { "reload", NPFCTL_RELOAD },
559 { "show", NPFCTL_SHOWCONF, },
560 { "flush", NPFCTL_FLUSH },
561 /* Table */
562 { "table", NPFCTL_TABLE },
563 /* Rule */
564 { "rule", NPFCTL_RULE },
565 /* Stats */
566 { "stats", NPFCTL_STATS },
567 /* Full state save/load */
568 { "save", NPFCTL_SAVE },
569 { "load", NPFCTL_LOAD },
570 { "list", NPFCTL_CONN_LIST },
571 /* Misc. */
572 { "valid", NPFCTL_VALIDATE },
573 { "debug", NPFCTL_DEBUG },
574 /* --- */
575 { NULL, 0 }
576 };
577 char *cmd;
578
579 if (argc < 2) {
580 usage();
581 }
582 cmd = argv[1];
583
584 /* Find and call the subroutine. */
585 for (int n = 0; operations[n].cmd != NULL; n++) {
586 const char *opcmd = operations[n].cmd;
587
588 if (strncmp(cmd, opcmd, strlen(opcmd)) != 0) {
589 continue;
590 }
591 npfctl(operations[n].action, argc, argv);
592 return EXIT_SUCCESS;
593 }
594 usage();
595 }
596