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