npfctl.c revision 1.60.2.3 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.60.2.3 2020/06/20 15:46:48 martin 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
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
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
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
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 *
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
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
259 npfctl_bpfjit(bool onoff)
260 {
261 bpfjit = onoff;
262 }
263
264 static 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 *
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, "could not open `%s'", path);
305 }
306 if (fstat(fd, &sb) == -1) {
307 err(EXIT_FAILURE, "stat");
308 }
309 if ((blen = sb.st_size) == 0) {
310 err(EXIT_FAILURE, "the binary configuration file is empty");
311 }
312 blob = mmap(NULL, blen, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
313 if (blob == MAP_FAILED) {
314 err(EXIT_FAILURE, "mmap");
315 }
316 ncf = npf_config_import(blob, blen);
317 munmap(blob, blen);
318 return ncf;
319 }
320
321 static int
322 npfctl_load(int fd)
323 {
324 nl_config_t *ncf;
325 npf_error_t errinfo;
326
327 /*
328 * Import the configuration, submit it and destroy.
329 */
330 ncf = npfctl_import(NPF_DB_PATH);
331 if (ncf == NULL) {
332 err(EXIT_FAILURE, "npf_config_import");
333 }
334 if ((errno = npf_config_submit(ncf, fd, &errinfo)) != 0) {
335 npfctl_print_error(&errinfo);
336 }
337 npf_config_destroy(ncf);
338 return errno;
339 }
340
341 static int
342 npfctl_open_dev(const char *path)
343 {
344 struct stat st;
345 int fd;
346
347 if (lstat(path, &st) == -1) {
348 err(EXIT_FAILURE, "fstat");
349 }
350 if ((st.st_mode & S_IFMT) == S_IFSOCK) {
351 struct sockaddr_un addr;
352
353 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
354 err(EXIT_FAILURE, "socket");
355 }
356 memset(&addr, 0, sizeof(addr));
357 addr.sun_family = AF_UNIX;
358 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
359
360 if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
361 err(EXIT_FAILURE, "connect");
362 }
363 } else {
364 if ((fd = open(path, O_RDONLY)) == -1) {
365 err(EXIT_FAILURE, "cannot open '%s'", path);
366 }
367 }
368 return fd;
369 }
370
371 static void
372 npfctl_debug(int argc, char **argv)
373 {
374 const char *conf = NULL, *bconf = NULL, *outfile = NULL;
375 bool use_active = false;
376 nl_config_t *ncf = NULL;
377 int fd, c, optcount;
378
379 argc--;
380 argv++;
381
382 npfctl_config_init(true);
383 while ((c = getopt(argc, argv, "ab:c:o:")) != -1) {
384 switch (c) {
385 case 'a':
386 use_active = true;
387 break;
388 case 'b':
389 bconf = optarg;
390 break;
391 case 'c':
392 conf = optarg;
393 break;
394 case 'o':
395 outfile = optarg;
396 break;
397 default:
398 usage();
399 }
400 }
401
402 /*
403 * Options -a, -b and -c are mutually exclusive, so allow only one.
404 * If no options were specified, then set the defaults.
405 */
406 optcount = (int)!!use_active + (int)!!conf + (int)!!bconf;
407 if (optcount != 1) {
408 if (optcount > 1) {
409 usage();
410 }
411 conf = NPF_CONF_PATH;
412 outfile = outfile ? outfile : "npf.nvlist";
413 }
414
415 if (use_active) {
416 puts("Loading the active configuration");
417 fd = npfctl_open_dev(NPF_DEV_PATH);
418 if ((ncf = npf_config_retrieve(fd)) == NULL) {
419 err(EXIT_FAILURE, "npf_config_retrieve");
420 }
421 }
422
423 if (conf) {
424 printf("Loading %s\n", conf);
425 npfctl_parse_file(conf);
426 npfctl_config_build();
427 ncf = npfctl_config_ref();
428 }
429
430 if (bconf) {
431 printf("Importing %s\n", bconf);
432 ncf = npfctl_import(bconf);
433 }
434
435 printf("Configuration:\n\n");
436 _npf_config_dump(ncf, STDOUT_FILENO);
437 if (outfile) {
438 printf("\nSaving binary to %s\n", outfile);
439 npfctl_config_save(ncf, outfile);
440 }
441 npf_config_destroy(ncf);
442 }
443
444 static void
445 npfctl(int action, int argc, char **argv)
446 {
447 int fd, boolval, ret = 0;
448 const char *fun = "";
449 nl_config_t *ncf;
450
451 switch (action) {
452 case NPFCTL_VALIDATE:
453 case NPFCTL_DEBUG:
454 fd = 0;
455 break;
456 default:
457 fd = npfctl_open_dev(NPF_DEV_PATH);
458 }
459
460 switch (action) {
461 case NPFCTL_START:
462 boolval = true;
463 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
464 fun = "ioctl(IOC_NPF_SWITCH)";
465 break;
466 case NPFCTL_STOP:
467 boolval = false;
468 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
469 fun = "ioctl(IOC_NPF_SWITCH)";
470 break;
471 case NPFCTL_RELOAD:
472 npfctl_config_init(false);
473 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
474 npfctl_preload_bpfjit();
475 errno = ret = npfctl_config_send(fd);
476 fun = "npfctl_config_send";
477 break;
478 case NPFCTL_SHOWCONF:
479 ret = npfctl_config_show(fd);
480 fun = "npfctl_config_show";
481 break;
482 case NPFCTL_FLUSH:
483 ret = npf_config_flush(fd);
484 fun = "npf_config_flush";
485 break;
486 case NPFCTL_TABLE:
487 if ((argc -= 2) < 2) {
488 usage();
489 }
490 argv += 2;
491 if (strcmp(argv[1], "replace") == 0) {
492 npfctl_table_replace(fd, argc, argv);
493 } else {
494 npfctl_table(fd, argc, argv);
495 }
496 break;
497 case NPFCTL_RULE:
498 if ((argc -= 2) < 2) {
499 usage();
500 }
501 argv += 2;
502 npfctl_rule(fd, argc, argv);
503 break;
504 case NPFCTL_LOAD:
505 npfctl_preload_bpfjit();
506 ret = npfctl_load(fd);
507 fun = "npfctl_config_load";
508 break;
509 case NPFCTL_SAVE:
510 ncf = npf_config_retrieve(fd);
511 if (ncf) {
512 npfctl_config_save(ncf,
513 argc > 2 ? argv[2] : NPF_DB_PATH);
514 npf_config_destroy(ncf);
515 } else {
516 ret = errno;
517 }
518 fun = "npfctl_config_save";
519 break;
520 case NPFCTL_STATS:
521 ret = npfctl_print_stats(fd);
522 fun = "npfctl_print_stats";
523 break;
524 case NPFCTL_CONN_LIST:
525 ret = npfctl_conn_list(fd, argc, argv);
526 fun = "npfctl_conn_list";
527 break;
528 case NPFCTL_VALIDATE:
529 npfctl_config_init(false);
530 npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
531 ret = npfctl_config_show(0);
532 fun = "npfctl_config_show";
533 break;
534 case NPFCTL_DEBUG:
535 npfctl_debug(argc, argv);
536 break;
537 }
538 if (ret) {
539 err(EXIT_FAILURE, "%s", fun);
540 }
541 if (fd) {
542 close(fd);
543 }
544 }
545
546 int
547 main(int argc, char **argv)
548 {
549 static const struct operations_s {
550 const char * cmd;
551 int action;
552 } operations[] = {
553 /* Start, stop, reload */
554 { "start", NPFCTL_START },
555 { "stop", NPFCTL_STOP },
556 { "reload", NPFCTL_RELOAD },
557 { "show", NPFCTL_SHOWCONF, },
558 { "flush", NPFCTL_FLUSH },
559 /* Table */
560 { "table", NPFCTL_TABLE },
561 /* Rule */
562 { "rule", NPFCTL_RULE },
563 /* Stats */
564 { "stats", NPFCTL_STATS },
565 /* Full state save/load */
566 { "save", NPFCTL_SAVE },
567 { "load", NPFCTL_LOAD },
568 { "list", NPFCTL_CONN_LIST },
569 /* Misc. */
570 { "valid", NPFCTL_VALIDATE },
571 { "debug", NPFCTL_DEBUG },
572 /* --- */
573 { NULL, 0 }
574 };
575 char *cmd;
576
577 if (argc < 2) {
578 usage();
579 }
580 cmd = argv[1];
581
582 /* Find and call the subroutine. */
583 for (int n = 0; operations[n].cmd != NULL; n++) {
584 const char *opcmd = operations[n].cmd;
585
586 if (strncmp(cmd, opcmd, strlen(opcmd)) != 0) {
587 continue;
588 }
589 npfctl(operations[n].action, argc, argv);
590 return EXIT_SUCCESS;
591 }
592 usage();
593 }
594