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