1 /* $NetBSD: npfctl.c,v 1.47 2016/06/29 21:40:20 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2009-2014 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.47 2016/06/29 21:40:20 christos Exp $"); 34 35 #include <sys/ioctl.h> 36 #include <sys/stat.h> 37 #include <sys/types.h> 38 #include <sys/module.h> 39 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <err.h> 44 #include <fcntl.h> 45 #include <unistd.h> 46 #include <errno.h> 47 #include <sha1.h> 48 49 #include "npfctl.h" 50 51 extern void npf_yyparse_string(const char *); 52 53 enum { 54 NPFCTL_START, 55 NPFCTL_STOP, 56 NPFCTL_RELOAD, 57 NPFCTL_SHOWCONF, 58 NPFCTL_FLUSH, 59 NPFCTL_VALIDATE, 60 NPFCTL_TABLE, 61 NPFCTL_RULE, 62 NPFCTL_STATS, 63 NPFCTL_SAVE, 64 NPFCTL_LOAD, 65 }; 66 67 static const struct operations_s { 68 const char * cmd; 69 int action; 70 } operations[] = { 71 /* Start, stop, reload */ 72 { "start", NPFCTL_START }, 73 { "stop", NPFCTL_STOP }, 74 { "reload", NPFCTL_RELOAD }, 75 { "show", NPFCTL_SHOWCONF, }, 76 { "flush", NPFCTL_FLUSH }, 77 { "valid", NPFCTL_VALIDATE }, 78 /* Table */ 79 { "table", NPFCTL_TABLE }, 80 /* Rule */ 81 { "rule", NPFCTL_RULE }, 82 /* Stats */ 83 { "stats", NPFCTL_STATS }, 84 /* Full state save/load */ 85 { "save", NPFCTL_SAVE }, 86 { "load", NPFCTL_LOAD }, 87 /* --- */ 88 { NULL, 0 } 89 }; 90 91 bool 92 join(char *buf, size_t buflen, int count, char **args, const char *sep) 93 { 94 const u_int seplen = strlen(sep); 95 char *s = buf, *p = NULL; 96 97 for (int i = 0; i < count; i++) { 98 size_t len; 99 100 p = stpncpy(s, args[i], buflen); 101 len = p - s + seplen; 102 if (len >= buflen) { 103 return false; 104 } 105 buflen -= len; 106 strcpy(p, sep); 107 s = p + seplen; 108 } 109 *p = '\0'; 110 return true; 111 } 112 113 __dead static void 114 usage(void) 115 { 116 const char *progname = getprogname(); 117 118 fprintf(stderr, 119 "Usage:\t%s start | stop | flush | show | stats\n", 120 progname); 121 fprintf(stderr, 122 "\t%s validate | reload [<rule-file>]\n", 123 progname); 124 fprintf(stderr, 125 "\t%s rule \"rule-name\" { add | rem } <rule-syntax>\n", 126 progname); 127 fprintf(stderr, 128 "\t%s rule \"rule-name\" rem-id <rule-id>\n", 129 progname); 130 fprintf(stderr, 131 "\t%s rule \"rule-name\" { list | flush }\n", 132 progname); 133 fprintf(stderr, 134 "\t%s table <tid> { add | rem | test } <address/mask>\n", 135 progname); 136 fprintf(stderr, 137 "\t%s table <tid> { list | flush }\n", 138 progname); 139 fprintf(stderr, 140 "\t%s save | load\n", 141 progname); 142 exit(EXIT_FAILURE); 143 } 144 145 static int 146 npfctl_print_stats(int fd) 147 { 148 static const struct stats_s { 149 /* Note: -1 indicates a new section. */ 150 int index; 151 const char * name; 152 } stats[] = { 153 { -1, "Packets passed" }, 154 { NPF_STAT_PASS_DEFAULT, "default pass" }, 155 { NPF_STAT_PASS_RULESET, "ruleset pass" }, 156 { NPF_STAT_PASS_CONN, "state pass" }, 157 158 { -1, "Packets blocked" }, 159 { NPF_STAT_BLOCK_DEFAULT, "default block" }, 160 { NPF_STAT_BLOCK_RULESET, "ruleset block" }, 161 162 { -1, "State and NAT entries" }, 163 { NPF_STAT_CONN_CREATE, "state allocations"}, 164 { NPF_STAT_CONN_DESTROY, "state destructions"}, 165 { NPF_STAT_NAT_CREATE, "NAT entry allocations" }, 166 { NPF_STAT_NAT_DESTROY, "NAT entry destructions"}, 167 168 { -1, "Network buffers" }, 169 { NPF_STAT_NBUF_NONCONTIG, "non-contiguous cases" }, 170 { NPF_STAT_NBUF_CONTIG_FAIL, "contig alloc failures" }, 171 172 { -1, "Invalid packet state cases" }, 173 { NPF_STAT_INVALID_STATE, "cases in total" }, 174 { NPF_STAT_INVALID_STATE_TCP1, "TCP case I" }, 175 { NPF_STAT_INVALID_STATE_TCP2, "TCP case II" }, 176 { NPF_STAT_INVALID_STATE_TCP3, "TCP case III" }, 177 178 { -1, "Packet race cases" }, 179 { NPF_STAT_RACE_NAT, "NAT association race" }, 180 { NPF_STAT_RACE_CONN, "duplicate state race" }, 181 182 { -1, "Fragmentation" }, 183 { NPF_STAT_FRAGMENTS, "fragments" }, 184 { NPF_STAT_REASSEMBLY, "reassembled" }, 185 { NPF_STAT_REASSFAIL, "failed reassembly" }, 186 187 { -1, "Other" }, 188 { NPF_STAT_ERROR, "unexpected errors" }, 189 }; 190 uint64_t *st = ecalloc(1, NPF_STATS_SIZE); 191 192 if (ioctl(fd, IOC_NPF_STATS, &st) != 0) { 193 err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)"); 194 } 195 196 for (unsigned i = 0; i < __arraycount(stats); i++) { 197 const char *sname = stats[i].name; 198 int sidx = stats[i].index; 199 200 if (sidx == -1) { 201 printf("%s:\n", sname); 202 } else { 203 printf("\t%"PRIu64" %s\n", st[sidx], sname); 204 } 205 } 206 207 free(st); 208 return 0; 209 } 210 211 void 212 npfctl_print_error(const nl_error_t *ne) 213 { 214 const char *srcfile = ne->ne_source_file; 215 216 if (srcfile) { 217 warnx("source %s line %d", srcfile, ne->ne_source_line); 218 } 219 if (ne->ne_id) { 220 warnx("object: %d", ne->ne_id); 221 } 222 } 223 224 char * 225 npfctl_print_addrmask(int alen, const npf_addr_t *addr, npf_netmask_t mask) 226 { 227 struct sockaddr_storage ss; 228 char *buf = ecalloc(1, 64); 229 int len; 230 231 switch (alen) { 232 case 4: { 233 struct sockaddr_in *sin = (void *)&ss; 234 sin->sin_len = sizeof(*sin); 235 sin->sin_family = AF_INET; 236 sin->sin_port = 0; 237 memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr)); 238 break; 239 } 240 case 16: { 241 struct sockaddr_in6 *sin6 = (void *)&ss; 242 sin6->sin6_len = sizeof(*sin6); 243 sin6->sin6_family = AF_INET6; 244 sin6->sin6_port = 0; 245 sin6->sin6_scope_id = 0; 246 memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr)); 247 break; 248 } 249 default: 250 assert(false); 251 } 252 len = sockaddr_snprintf(buf, 64, "%a", (struct sockaddr *)&ss); 253 if (mask && mask != NPF_NO_NETMASK) { 254 snprintf(&buf[len], 64 - len, "/%u", mask); 255 } 256 return buf; 257 } 258 259 __dead static void 260 npfctl_table(int fd, int argc, char **argv) 261 { 262 static const struct tblops_s { 263 const char * cmd; 264 int action; 265 } tblops[] = { 266 { "add", NPF_CMD_TABLE_ADD }, 267 { "rem", NPF_CMD_TABLE_REMOVE }, 268 { "del", NPF_CMD_TABLE_REMOVE }, 269 { "test", NPF_CMD_TABLE_LOOKUP }, 270 { "list", NPF_CMD_TABLE_LIST }, 271 { "flush", NPF_CMD_TABLE_FLUSH }, 272 { NULL, 0 } 273 }; 274 npf_ioctl_table_t nct; 275 fam_addr_mask_t fam; 276 size_t buflen = 512; 277 char *cmd, *arg; 278 int n, alen; 279 280 /* Default action is list. */ 281 memset(&nct, 0, sizeof(npf_ioctl_table_t)); 282 nct.nct_name = argv[0]; 283 cmd = argv[1]; 284 285 for (n = 0; tblops[n].cmd != NULL; n++) { 286 if (strcmp(cmd, tblops[n].cmd) != 0) { 287 continue; 288 } 289 nct.nct_cmd = tblops[n].action; 290 break; 291 } 292 if (tblops[n].cmd == NULL) { 293 errx(EXIT_FAILURE, "invalid command '%s'", cmd); 294 } 295 296 switch (nct.nct_cmd) { 297 case NPF_CMD_TABLE_LIST: 298 case NPF_CMD_TABLE_FLUSH: 299 arg = NULL; 300 break; 301 default: 302 if (argc < 3) { 303 usage(); 304 } 305 arg = argv[2]; 306 } 307 308 again: 309 switch (nct.nct_cmd) { 310 case NPF_CMD_TABLE_LIST: 311 nct.nct_data.buf.buf = ecalloc(1, buflen); 312 nct.nct_data.buf.len = buflen; 313 break; 314 case NPF_CMD_TABLE_FLUSH: 315 break; 316 default: 317 if (!npfctl_parse_cidr(arg, &fam, &alen)) { 318 errx(EXIT_FAILURE, "invalid CIDR '%s'", arg); 319 } 320 nct.nct_data.ent.alen = alen; 321 memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, alen); 322 nct.nct_data.ent.mask = fam.fam_mask; 323 } 324 325 if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) { 326 errno = 0; 327 } 328 switch (errno) { 329 case 0: 330 break; 331 case EEXIST: 332 errx(EXIT_FAILURE, "entry already exists or is conflicting"); 333 case ENOENT: 334 errx(EXIT_FAILURE, "no matching entry was not found"); 335 case EINVAL: 336 errx(EXIT_FAILURE, "invalid address, mask or table ID"); 337 case ENOMEM: 338 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) { 339 /* XXX */ 340 free(nct.nct_data.buf.buf); 341 buflen <<= 1; 342 goto again; 343 } 344 /* FALLTHROUGH */ 345 default: 346 err(EXIT_FAILURE, "ioctl(IOC_NPF_TABLE)"); 347 } 348 349 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) { 350 npf_ioctl_ent_t *ent = nct.nct_data.buf.buf; 351 char *buf; 352 353 while (nct.nct_data.buf.len--) { 354 if (!ent->alen) 355 break; 356 buf = npfctl_print_addrmask(ent->alen, 357 &ent->addr, ent->mask); 358 puts(buf); 359 ent++; 360 } 361 free(nct.nct_data.buf.buf); 362 } else { 363 printf("%s: %s\n", getprogname(), 364 nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ? 365 "matching entry found" : "success"); 366 } 367 exit(EXIT_SUCCESS); 368 } 369 370 static nl_rule_t * 371 npfctl_parse_rule(int argc, char **argv) 372 { 373 char rule_string[1024]; 374 nl_rule_t *rl; 375 376 /* Get the rule string and parse it. */ 377 if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) { 378 errx(EXIT_FAILURE, "command too long"); 379 } 380 npfctl_parse_string(rule_string); 381 if ((rl = npfctl_rule_ref()) == NULL) { 382 errx(EXIT_FAILURE, "could not parse the rule"); 383 } 384 return rl; 385 } 386 387 static void 388 SHA1(const uint8_t *d, unsigned int n, uint8_t *md) 389 { 390 SHA1_CTX c; 391 392 SHA1Init(&c); 393 SHA1Update(&c, d, n); 394 SHA1Final(md, &c); 395 memset(&c, 0, sizeof(c)); 396 } 397 398 static void 399 npfctl_generate_key(nl_rule_t *rl, void *key) 400 { 401 void *meta; 402 size_t len; 403 404 if ((meta = npf_rule_export(rl, &len)) == NULL) { 405 errx(EXIT_FAILURE, "error generating rule key"); 406 } 407 __CTASSERT(NPF_RULE_MAXKEYLEN >= SHA1_DIGEST_LENGTH); 408 memset(key, 0, NPF_RULE_MAXKEYLEN); 409 SHA1(meta, (unsigned int)len, key); 410 free(meta); 411 } 412 413 __dead static void 414 npfctl_rule(int fd, int argc, char **argv) 415 { 416 static const struct ruleops_s { 417 const char * cmd; 418 int action; 419 bool extra_arg; 420 } ruleops[] = { 421 { "add", NPF_CMD_RULE_ADD, true }, 422 { "rem", NPF_CMD_RULE_REMKEY, true }, 423 { "del", NPF_CMD_RULE_REMKEY, true }, 424 { "rem-id", NPF_CMD_RULE_REMOVE, true }, 425 { "list", NPF_CMD_RULE_LIST, false }, 426 { "flush", NPF_CMD_RULE_FLUSH, false }, 427 { NULL, 0, 0 } 428 }; 429 uint8_t key[NPF_RULE_MAXKEYLEN]; 430 const char *ruleset_name = argv[0]; 431 const char *cmd = argv[1]; 432 int error, action = 0; 433 uint64_t rule_id; 434 bool extra_arg; 435 nl_rule_t *rl; 436 437 for (int n = 0; ruleops[n].cmd != NULL; n++) { 438 if (strcmp(cmd, ruleops[n].cmd) == 0) { 439 action = ruleops[n].action; 440 extra_arg = ruleops[n].extra_arg; 441 break; 442 } 443 } 444 argc -= 2; 445 argv += 2; 446 447 if (!action || (extra_arg && argc == 0)) { 448 usage(); 449 } 450 451 switch (action) { 452 case NPF_CMD_RULE_ADD: 453 rl = npfctl_parse_rule(argc, argv); 454 npfctl_generate_key(rl, key); 455 npf_rule_setkey(rl, key, sizeof(key)); 456 error = npf_ruleset_add(fd, ruleset_name, rl, &rule_id); 457 break; 458 case NPF_CMD_RULE_REMKEY: 459 rl = npfctl_parse_rule(argc, argv); 460 npfctl_generate_key(rl, key); 461 error = npf_ruleset_remkey(fd, ruleset_name, key, sizeof(key)); 462 break; 463 case NPF_CMD_RULE_REMOVE: 464 rule_id = strtoull(argv[0], NULL, 16); 465 error = npf_ruleset_remove(fd, ruleset_name, rule_id); 466 break; 467 case NPF_CMD_RULE_LIST: 468 error = npfctl_ruleset_show(fd, ruleset_name); 469 break; 470 case NPF_CMD_RULE_FLUSH: 471 error = npf_ruleset_flush(fd, ruleset_name); 472 break; 473 default: 474 assert(false); 475 } 476 477 switch (error) { 478 case 0: 479 /* Success. */ 480 break; 481 case ESRCH: 482 errx(EXIT_FAILURE, "ruleset \"%s\" not found", ruleset_name); 483 case ENOENT: 484 errx(EXIT_FAILURE, "rule was not found"); 485 default: 486 errx(EXIT_FAILURE, "rule operation: %s", strerror(error)); 487 } 488 if (action == NPF_CMD_RULE_ADD) { 489 printf("OK %" PRIx64 "\n", rule_id); 490 } 491 exit(EXIT_SUCCESS); 492 } 493 494 static bool bpfjit = true; 495 496 void 497 npfctl_bpfjit(bool onoff) 498 { 499 bpfjit = onoff; 500 } 501 502 static void 503 npfctl_preload_bpfjit(void) 504 { 505 modctl_load_t args = { 506 .ml_filename = "bpfjit", 507 .ml_flags = MODCTL_NO_PROP, 508 .ml_props = NULL, 509 .ml_propslen = 0 510 }; 511 512 if (!bpfjit) 513 return; 514 515 if (modctl(MODCTL_LOAD, &args) != 0 && errno != EEXIST) { 516 static const char *p = "; performance will be degraded"; 517 if (errno == ENOENT) 518 warnx("the bpfjit module seems to be missing%s", p); 519 else 520 warn("error loading the bpfjit module%s", p); 521 warnx("To disable this warning `set bpf.jit off' in " 522 "/etc/npf.conf"); 523 } 524 } 525 526 static int 527 npfctl_save(int fd) 528 { 529 nl_config_t *ncf; 530 bool active, loaded; 531 int error; 532 533 ncf = npf_config_retrieve(fd, &active, &loaded); 534 if (ncf == NULL) { 535 return errno; 536 } 537 error = npf_config_export(ncf, NPF_DB_PATH); 538 npf_config_destroy(ncf); 539 return error; 540 } 541 542 static int 543 npfctl_load(int fd) 544 { 545 nl_config_t *ncf; 546 int error; 547 548 ncf = npf_config_import(NPF_DB_PATH); 549 if (ncf == NULL) { 550 return errno; 551 } 552 errno = error = npf_config_submit(ncf, fd); 553 if (error) { 554 nl_error_t ne; 555 _npf_config_error(ncf, &ne); 556 npfctl_print_error(&ne); 557 } 558 npf_config_destroy(ncf); 559 return error; 560 } 561 562 static void 563 npfctl(int action, int argc, char **argv) 564 { 565 int fd, ver, boolval, ret = 0; 566 567 fd = open(NPF_DEV_PATH, O_RDONLY); 568 if (fd == -1) { 569 err(EXIT_FAILURE, "cannot open '%s'", NPF_DEV_PATH); 570 } 571 if (ioctl(fd, IOC_NPF_VERSION, &ver) == -1) { 572 err(EXIT_FAILURE, "ioctl(IOC_NPF_VERSION)"); 573 } 574 if (ver != NPF_VERSION) { 575 errx(EXIT_FAILURE, 576 "incompatible NPF interface version (%d, kernel %d)\n" 577 "Hint: update userland?", NPF_VERSION, ver); 578 } 579 580 const char *fun = ""; 581 switch (action) { 582 case NPFCTL_START: 583 boolval = true; 584 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval); 585 fun = "ioctl(IOC_NPF_SWITCH)"; 586 break; 587 case NPFCTL_STOP: 588 boolval = false; 589 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval); 590 fun = "ioctl(IOC_NPF_SWITCH)"; 591 break; 592 case NPFCTL_RELOAD: 593 npfctl_config_init(false); 594 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]); 595 npfctl_preload_bpfjit(); 596 errno = ret = npfctl_config_send(fd, NULL); 597 fun = "npfctl_config_send"; 598 break; 599 case NPFCTL_SHOWCONF: 600 ret = npfctl_config_show(fd); 601 fun = "npfctl_config_show"; 602 break; 603 case NPFCTL_FLUSH: 604 ret = npf_config_flush(fd); 605 fun = "npf_config_flush"; 606 break; 607 case NPFCTL_VALIDATE: 608 npfctl_config_init(false); 609 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]); 610 ret = npfctl_config_show(0); 611 fun = "npfctl_config_show"; 612 break; 613 case NPFCTL_TABLE: 614 if ((argc -= 2) < 2) { 615 usage(); 616 } 617 argv += 2; 618 npfctl_table(fd, argc, argv); 619 break; 620 case NPFCTL_RULE: 621 if ((argc -= 2) < 2) { 622 usage(); 623 } 624 argv += 2; 625 npfctl_rule(fd, argc, argv); 626 break; 627 case NPFCTL_LOAD: 628 npfctl_preload_bpfjit(); 629 ret = npfctl_load(fd); 630 fun = "npfctl_config_load"; 631 break; 632 case NPFCTL_SAVE: 633 fd = npfctl_save(fd); 634 fun = "npfctl_config_save"; 635 break; 636 case NPFCTL_STATS: 637 ret = npfctl_print_stats(fd); 638 fun = "npfctl_print_stats"; 639 break; 640 } 641 if (ret) { 642 err(EXIT_FAILURE, "%s", fun); 643 } 644 close(fd); 645 } 646 647 int 648 main(int argc, char **argv) 649 { 650 char *cmd; 651 652 if (argc < 2) { 653 usage(); 654 } 655 cmd = argv[1]; 656 657 if (strcmp(cmd, "debug") == 0) { 658 const char *cfg = argc > 2 ? argv[2] : "/etc/npf.conf"; 659 const char *out = argc > 3 ? argv[3] : "/tmp/npf.plist"; 660 661 npfctl_config_init(true); 662 npfctl_parse_file(cfg); 663 npfctl_config_send(0, out); 664 return EXIT_SUCCESS; 665 } 666 667 /* Find and call the subroutine. */ 668 for (int n = 0; operations[n].cmd != NULL; n++) { 669 const char *opcmd = operations[n].cmd; 670 if (strncmp(cmd, opcmd, strlen(opcmd)) != 0) 671 continue; 672 npfctl(operations[n].action, argc, argv); 673 return EXIT_SUCCESS; 674 } 675 usage(); 676 } 677