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