npfctl.c revision 1.67 1 /*-
2 * Copyright (c) 2009-2025 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.67 2025/07/01 19:55:16 joe 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_ETHER_STAT_PASS, "ether pass" },
143 { NPF_STAT_PASS_DEFAULT, "default pass" },
144 { NPF_STAT_PASS_RULESET, "ruleset pass" },
145 { NPF_STAT_PASS_CONN, "state pass" },
146
147 { -1, "Packets blocked" },
148 { NPF_ETHER_STAT_BLOCK, "ether block" },
149 { NPF_STAT_BLOCK_DEFAULT, "default block" },
150 { NPF_STAT_BLOCK_RULESET, "ruleset block" },
151
152 { -1, "State and NAT entries" },
153 { NPF_STAT_CONN_CREATE, "state allocations"},
154 { NPF_STAT_CONN_DESTROY, "state destructions"},
155 { NPF_STAT_NAT_CREATE, "NAT entry allocations" },
156 { NPF_STAT_NAT_DESTROY, "NAT entry destructions"},
157
158 { -1, "Network buffers" },
159 { NPF_STAT_NBUF_NONCONTIG, "non-contiguous cases" },
160 { NPF_STAT_NBUF_CONTIG_FAIL, "contig alloc failures" },
161
162 { -1, "Invalid packet state cases" },
163 { NPF_STAT_INVALID_STATE, "cases in total" },
164 { NPF_STAT_INVALID_STATE_TCP1, "TCP case I" },
165 { NPF_STAT_INVALID_STATE_TCP2, "TCP case II" },
166 { NPF_STAT_INVALID_STATE_TCP3, "TCP case III" },
167
168 { -1, "Packet race cases" },
169 { NPF_STAT_RACE_NAT, "NAT association race" },
170 { NPF_STAT_RACE_CONN, "duplicate state race" },
171
172 { -1, "Fragmentation" },
173 { NPF_STAT_FRAGMENTS, "fragments" },
174 { NPF_STAT_REASSEMBLY, "reassembled" },
175 { NPF_STAT_REASSFAIL, "failed reassembly" },
176
177 { -1, "Other" },
178 { NPF_STAT_ERROR, "unexpected errors" },
179 };
180 uint64_t *st = ecalloc(1, NPF_STATS_SIZE);
181
182 if (ioctl(fd, IOC_NPF_STATS, &st) != 0) {
183 err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)");
184 }
185
186 for (unsigned i = 0; i < __arraycount(stats); i++) {
187 const char *sname = stats[i].name;
188 int sidx = stats[i].index;
189
190 if (sidx == -1) {
191 printf("%s:\n", sname);
192 } else {
193 printf("\t%"PRIu64" %s\n", st[sidx], sname);
194 }
195 }
196
197 free(st);
198 return 0;
199 }
200
201 void
202 npfctl_print_error(const npf_error_t *ne)
203 {
204 const char *srcfile = ne->source_file;
205
206 if (ne->error_msg) {
207 errx(EXIT_FAILURE, "%s", ne->error_msg);
208 }
209 if (srcfile) {
210 warnx("source %s line %d", srcfile, ne->source_line);
211 }
212 if (ne->id) {
213 warnx("object: %" PRIi64, ne->id);
214 }
215 }
216
217 char *
218 npfctl_print_addrmask(int alen, const char *fmt, const npf_addr_t *addr,
219 npf_netmask_t mask)
220 {
221 const unsigned buflen = 256;
222 char *buf = ecalloc(1, buflen);
223 struct sockaddr_storage ss;
224
225 memset(&ss, 0, sizeof(ss));
226
227 switch (alen) {
228 case 4: {
229 struct sockaddr_in *sin = (void *)&ss;
230 sin->sin_family = AF_INET;
231 memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
232 break;
233 }
234 case 16: {
235 struct sockaddr_in6 *sin6 = (void *)&ss;
236 sin6->sin6_family = AF_INET6;
237 memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
238 break;
239 }
240 default:
241 abort();
242 }
243 sockaddr_snprintf(buf, buflen, fmt, (const void *)&ss);
244 if (mask && mask != NPF_NO_NETMASK) {
245 const unsigned len = strlen(buf);
246 snprintf(&buf[len], buflen - len, "/%u", mask);
247 }
248 return buf;
249 }
250
251 bool
252 npfctl_addr_iszero(const npf_addr_t *addr)
253 {
254 static const npf_addr_t zero; /* must be static */
255 return memcmp(addr, &zero, sizeof(npf_addr_t)) == 0;
256 }
257
258 static bool bpfjit = true;
259
260 void
261 npfctl_bpfjit(bool onoff)
262 {
263 bpfjit = onoff;
264 }
265
266 static void
267 npfctl_preload_bpfjit(void)
268 {
269 #ifdef __NetBSD__
270 modctl_load_t args = {
271 .ml_filename = "bpfjit",
272 .ml_flags = MODCTL_NO_PROP,
273 .ml_props = NULL,
274 .ml_propslen = 0
275 };
276
277 if (!bpfjit)
278 return;
279
280 if (modctl(MODCTL_LOAD, &args) != 0 && errno != EEXIST) {
281 static const char *p = "; performance will be degraded";
282 if (errno == ENOENT)
283 warnx("the bpfjit module seems to be missing%s", p);
284 else
285 warn("error loading the bpfjit module%s", p);
286 warnx("To disable this warning `set bpf.jit off' in "
287 "/etc/npf.conf");
288 }
289 #endif
290 }
291
292 static nl_config_t *
293 npfctl_import(const char *path)
294 {
295 nl_config_t *ncf;
296 struct stat sb;
297 size_t blen;
298 void *blob;
299 int fd;
300
301 /*
302 * The file may change while reading - we are not handling this,
303 * just leaving this responsibility for the caller.
304 */
305 if ((fd = open(path, O_RDONLY)) == -1) {
306 err(EXIT_FAILURE, "open: '%s'", path);
307 }
308 if (fstat(fd, &sb) == -1) {
309 err(EXIT_FAILURE, "stat: '%s'", path);
310 }
311 if ((blen = sb.st_size) == 0) {
312 errx(EXIT_FAILURE,
313 "the binary configuration file '%s' is empty", path);
314 }
315 blob = mmap(NULL, blen, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
316 if (blob == MAP_FAILED) {
317 err(EXIT_FAILURE, "mmap: '%s'", path);
318 }
319 ncf = npf_config_import(blob, blen);
320 munmap(blob, blen);
321 return ncf;
322 }
323
324 static int
325 npfctl_load(int fd)
326 {
327 nl_config_t *ncf;
328 npf_error_t errinfo;
329
330 /*
331 * Import the configuration, submit it and destroy.
332 */
333 ncf = npfctl_import(NPF_DB_PATH);
334 if (ncf == NULL) {
335 err(EXIT_FAILURE, "npf_config_import: '%s'", NPF_DB_PATH);
336 }
337 if ((errno = npf_config_submit(ncf, fd, &errinfo)) != 0) {
338 npfctl_print_error(&errinfo);
339 }
340 npf_config_destroy(ncf);
341 return errno;
342 }
343
344 static int
345 npfctl_open_dev(const char *path)
346 {
347 struct stat st;
348 int fd;
349
350 if (lstat(path, &st) == -1) {
351 err(EXIT_FAILURE, "fstat: '%s'", path);
352 }
353 if ((st.st_mode & S_IFMT) == S_IFSOCK) {
354 struct sockaddr_un addr;
355
356 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
357 err(EXIT_FAILURE, "socket");
358 }
359 memset(&addr, 0, sizeof(addr));
360 addr.sun_family = AF_UNIX;
361 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
362
363 if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
364 err(EXIT_FAILURE, "connect: '%s'", path);
365 }
366 } else {
367 if ((fd = open(path, O_RDONLY)) == -1) {
368 err(EXIT_FAILURE, "open: '%s'", path);
369 }
370 }
371 return fd;
372 }
373
374 static void
375 npfctl_debug(int argc, char **argv)
376 {
377 const char *conf = NULL, *bconf = NULL, *outfile = NULL;
378 bool use_active = false;
379 nl_config_t *ncf = NULL;
380 int fd, c, optcount;
381
382 argc--;
383 argv++;
384
385 npfctl_config_init(true);
386 while ((c = getopt(argc, argv, "ab:c:o:")) != -1) {
387 switch (c) {
388 case 'a':
389 use_active = true;
390 break;
391 case 'b':
392 bconf = optarg;
393 break;
394 case 'c':
395 conf = optarg;
396 break;
397 case 'o':
398 outfile = optarg;
399 break;
400 default:
401 usage();
402 }
403 }
404
405 /*
406 * Options -a, -b and -c are mutually exclusive, so allow only one.
407 * If no options were specified, then set the defaults.
408 */
409 optcount = (int)!!use_active + (int)!!conf + (int)!!bconf;
410 if (optcount != 1) {
411 if (optcount > 1) {
412 usage();
413 }
414 conf = NPF_CONF_PATH;
415 outfile = outfile ? outfile : "npf.nvlist";
416 }
417
418 if (use_active) {
419 puts("Loading the active configuration");
420 fd = npfctl_open_dev(NPF_DEV_PATH);
421 if ((ncf = npf_config_retrieve(fd)) == NULL) {
422 err(EXIT_FAILURE, "npf_config_retrieve: '%s'",
423 NPF_DEV_PATH);
424 }
425 }
426
427 if (conf) {
428 printf("Loading %s\n", conf);
429 npfctl_parse_file(conf);
430 npfctl_config_build();
431 ncf = npfctl_config_ref();
432 }
433
434 if (bconf) {
435 printf("Importing %s\n", bconf);
436 ncf = npfctl_import(bconf);
437 }
438
439 printf("Configuration:\n\n");
440 _npf_config_dump(ncf, STDOUT_FILENO);
441 if (outfile) {
442 printf("\nSaving binary to %s\n", outfile);
443 npfctl_config_save(ncf, outfile);
444 }
445 npf_config_destroy(ncf);
446 }
447
448 static void
449 npfctl(int action, int argc, char **argv)
450 {
451 int fd, boolval, ret = 0;
452 const char *fun = "";
453 nl_config_t *ncf;
454
455 switch (action) {
456 case NPFCTL_VALIDATE:
457 case NPFCTL_DEBUG:
458 fd = 0;
459 break;
460 default:
461 fd = npfctl_open_dev(NPF_DEV_PATH);
462 }
463
464 switch (action) {
465 case NPFCTL_START:
466 boolval = true;
467 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
468 fun = "ioctl(IOC_NPF_SWITCH)";
469 break;
470 case NPFCTL_STOP:
471 boolval = false;
472 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
473 fun = "ioctl(IOC_NPF_SWITCH)";
474 break;
475 case NPFCTL_RELOAD:
476 npfctl_config_init(false);
477 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
478 npfctl_preload_bpfjit();
479 errno = ret = npfctl_config_send(fd);
480 fun = "npfctl_config_send";
481 break;
482 case NPFCTL_SHOWCONF:
483 ret = npfctl_config_show(fd);
484 fun = "npfctl_config_show";
485 break;
486 case NPFCTL_FLUSH:
487 ret = npf_config_flush(fd);
488 fun = "npf_config_flush";
489 break;
490 case NPFCTL_TABLE:
491 if ((argc -= 2) < 2) {
492 usage();
493 }
494 argv += 2;
495 if (strcmp(argv[1], "replace") == 0) {
496 npfctl_table_replace(fd, argc, argv);
497 } else {
498 npfctl_table(fd, argc, argv);
499 }
500 break;
501 case NPFCTL_RULE:
502 if ((argc -= 2) < 2) {
503 usage();
504 }
505 argv += 2;
506 npfctl_rule(fd, argc, argv);
507 break;
508 case NPFCTL_LOAD:
509 npfctl_preload_bpfjit();
510 ret = npfctl_load(fd);
511 fun = "npfctl_config_load";
512 break;
513 case NPFCTL_SAVE:
514 ncf = npf_config_retrieve(fd);
515 if (ncf) {
516 npfctl_config_save(ncf,
517 argc > 2 ? argv[2] : NPF_DB_PATH);
518 npf_config_destroy(ncf);
519 } else {
520 ret = errno;
521 }
522 fun = "npfctl_config_save";
523 break;
524 case NPFCTL_STATS:
525 ret = npfctl_print_stats(fd);
526 fun = "npfctl_print_stats";
527 break;
528 case NPFCTL_CONN_LIST:
529 ret = npfctl_conn_list(fd, argc, argv);
530 fun = "npfctl_conn_list";
531 break;
532 case NPFCTL_VALIDATE:
533 npfctl_config_init(false);
534 npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
535 ret = npfctl_config_show(0);
536 fun = "npfctl_config_show";
537 break;
538 case NPFCTL_DEBUG:
539 npfctl_debug(argc, argv);
540 break;
541 }
542 if (ret) {
543 err(EXIT_FAILURE, "%s", fun);
544 }
545 if (fd) {
546 close(fd);
547 }
548 }
549
550 int
551 main(int argc, char **argv)
552 {
553 static const struct operations_s {
554 const char * cmd;
555 int action;
556 } operations[] = {
557 /* Start, stop, reload */
558 { "start", NPFCTL_START },
559 { "stop", NPFCTL_STOP },
560 { "reload", NPFCTL_RELOAD },
561 { "show", NPFCTL_SHOWCONF, },
562 { "flush", NPFCTL_FLUSH },
563 /* Table */
564 { "table", NPFCTL_TABLE },
565 /* Rule */
566 { "rule", NPFCTL_RULE },
567 /* Stats */
568 { "stats", NPFCTL_STATS },
569 /* Full state save/load */
570 { "save", NPFCTL_SAVE },
571 { "load", NPFCTL_LOAD },
572 { "list", NPFCTL_CONN_LIST },
573 /* Misc. */
574 { "valid", NPFCTL_VALIDATE },
575 { "debug", NPFCTL_DEBUG },
576 /* --- */
577 { NULL, 0 }
578 };
579 char *cmd;
580
581 if (argc < 2) {
582 usage();
583 }
584 cmd = argv[1];
585
586 /* Find and call the subroutine. */
587 for (int n = 0; operations[n].cmd != NULL; n++) {
588 const char *opcmd = operations[n].cmd;
589
590 if (strncmp(cmd, opcmd, strlen(opcmd)) != 0) {
591 continue;
592 }
593 npfctl(operations[n].action, argc, argv);
594 return EXIT_SUCCESS;
595 }
596 usage();
597 }
598