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