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 52enum { 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 68bool 69join(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 91usage(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 133static int 134npfctl_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 199void 200npfctl_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 215char * 216npfctl_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 249bool 250npfctl_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 256static bool bpfjit = true; 257 258void 259npfctl_bpfjit(bool onoff) 260{ 261 bpfjit = onoff; 262} 263 264static void 265npfctl_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 290static nl_config_t * 291npfctl_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 322static int 323npfctl_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 342static int 343npfctl_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 372static void 373npfctl_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 446static void 447npfctl(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 548int 549main(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