npfctl.c revision 1.42.2.2 1 /* $NetBSD: npfctl.c,v 1.42.2.2 2014/12/29 17:31:47 martin 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.42.2.2 2014/12/29 17:31:47 martin 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_preload_bpfjit();
584 npfctl_config_init(false);
585 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
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