npfctl.c revision 1.38 1 /* $NetBSD: npfctl.c,v 1.38 2013/09/19 01:04:45 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.38 2013/09/19 01:04:45 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 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 sess-load | sess-save\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_SESSION, "session pass" },
157
158 { -1, "Packets blocked" },
159 { NPF_STAT_BLOCK_DEFAULT, "default block" },
160 { NPF_STAT_BLOCK_RULESET, "ruleset block" },
161
162 { -1, "Session and NAT entries" },
163 { NPF_STAT_SESSION_CREATE, "session allocations" },
164 { NPF_STAT_SESSION_DESTROY, "session 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_SESSION, "duplicate session 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 static const char *ncode_errors[] = {
215 [-NPF_ERR_OPCODE] = "invalid instruction",
216 [-NPF_ERR_JUMP] = "invalid jump",
217 [-NPF_ERR_REG] = "invalid register",
218 [-NPF_ERR_INVAL] = "invalid argument value",
219 [-NPF_ERR_RANGE] = "processing out of range"
220 };
221 const int nc_err = ne->ne_ncode_error;
222 const char *srcfile = ne->ne_source_file;
223
224 if (srcfile) {
225 warnx("source %s line %d", srcfile, ne->ne_source_line);
226 }
227 if (nc_err) {
228 warnx("n-code error (%d): %s at offset 0x%x",
229 nc_err, ncode_errors[-nc_err], ne->ne_ncode_errat);
230 }
231 if (ne->ne_id) {
232 warnx("object: %d", ne->ne_id);
233 }
234 }
235
236 char *
237 npfctl_print_addrmask(int alen, const npf_addr_t *addr, npf_netmask_t mask)
238 {
239 struct sockaddr_storage ss;
240 char *buf = ecalloc(1, 64);
241 int len;
242
243 switch (alen) {
244 case 4: {
245 struct sockaddr_in *sin = (void *)&ss;
246 sin->sin_len = sizeof(*sin);
247 sin->sin_family = AF_INET;
248 sin->sin_port = 0;
249 memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
250 break;
251 }
252 case 16: {
253 struct sockaddr_in6 *sin6 = (void *)&ss;
254 sin6->sin6_len = sizeof(*sin6);
255 sin6->sin6_family = AF_INET6;
256 sin6->sin6_port = 0;
257 sin6->sin6_scope_id = 0;
258 memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
259 break;
260 }
261 default:
262 assert(false);
263 }
264 len = sockaddr_snprintf(buf, 64, "%a", (struct sockaddr *)&ss);
265 if (mask && mask != NPF_NO_NETMASK) {
266 snprintf(&buf[len], 64 - len, "/%u", mask);
267 }
268 return buf;
269 }
270
271 __dead static void
272 npfctl_table(int fd, int argc, char **argv)
273 {
274 static const struct tblops_s {
275 const char * cmd;
276 int action;
277 } tblops[] = {
278 { "add", NPF_CMD_TABLE_ADD },
279 { "rem", NPF_CMD_TABLE_REMOVE },
280 { "del", NPF_CMD_TABLE_REMOVE },
281 { "test", NPF_CMD_TABLE_LOOKUP },
282 { "list", NPF_CMD_TABLE_LIST },
283 { "flush", NPF_CMD_TABLE_FLUSH },
284 { NULL, 0 }
285 };
286 npf_ioctl_table_t nct;
287 fam_addr_mask_t fam;
288 size_t buflen = 512;
289 char *cmd, *arg = NULL; /* XXX gcc */
290 int n, alen;
291
292 /* Default action is list. */
293 memset(&nct, 0, sizeof(npf_ioctl_table_t));
294 nct.nct_tid = atoi(argv[0]);
295 cmd = argv[1];
296
297 for (n = 0; tblops[n].cmd != NULL; n++) {
298 if (strcmp(cmd, tblops[n].cmd) != 0) {
299 continue;
300 }
301 nct.nct_cmd = tblops[n].action;
302 break;
303 }
304 if (tblops[n].cmd == NULL) {
305 errx(EXIT_FAILURE, "invalid command '%s'", cmd);
306 }
307
308 switch (nct.nct_cmd) {
309 case NPF_CMD_TABLE_LIST:
310 case NPF_CMD_TABLE_FLUSH:
311 break;
312 default:
313 if (argc < 3) {
314 usage();
315 }
316 arg = argv[2];
317 }
318
319 again:
320 switch (nct.nct_cmd) {
321 case NPF_CMD_TABLE_LIST:
322 nct.nct_data.buf.buf = ecalloc(1, buflen);
323 nct.nct_data.buf.len = buflen;
324 break;
325 case NPF_CMD_TABLE_FLUSH:
326 break;
327 default:
328 if (!npfctl_parse_cidr(arg, &fam, &alen)) {
329 errx(EXIT_FAILURE, "invalid CIDR '%s'", arg);
330 }
331 nct.nct_data.ent.alen = alen;
332 memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, alen);
333 nct.nct_data.ent.mask = fam.fam_mask;
334 }
335
336 if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) {
337 errno = 0;
338 }
339 switch (errno) {
340 case 0:
341 break;
342 case EEXIST:
343 errx(EXIT_FAILURE, "entry already exists or is conflicting");
344 case ENOENT:
345 errx(EXIT_FAILURE, "no matching entry was not found");
346 case EINVAL:
347 errx(EXIT_FAILURE, "invalid address, mask or table ID");
348 case ENOMEM:
349 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
350 /* XXX */
351 free(nct.nct_data.buf.buf);
352 buflen <<= 1;
353 goto again;
354 }
355 /* FALLTHROUGH */
356 default:
357 err(EXIT_FAILURE, "ioctl(IOC_NPF_TABLE)");
358 }
359
360 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
361 npf_ioctl_ent_t *ent = nct.nct_data.buf.buf;
362 char *buf;
363
364 while (nct.nct_data.buf.len--) {
365 if (!ent->alen)
366 break;
367 buf = npfctl_print_addrmask(ent->alen,
368 &ent->addr, ent->mask);
369 puts(buf);
370 ent++;
371 }
372 free(nct.nct_data.buf.buf);
373 } else {
374 printf("%s: %s\n", getprogname(),
375 nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ?
376 "matching entry found" : "success");
377 }
378 exit(EXIT_SUCCESS);
379 }
380
381 static nl_rule_t *
382 npfctl_parse_rule(int argc, char **argv)
383 {
384 char rule_string[1024];
385 nl_rule_t *rl;
386
387 /* Get the rule string and parse it. */
388 if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) {
389 errx(EXIT_FAILURE, "command too long");
390 }
391 npfctl_parse_string(rule_string);
392 if ((rl = npfctl_rule_ref()) == NULL) {
393 errx(EXIT_FAILURE, "could not parse the rule");
394 }
395 return rl;
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 >= SHA_DIGEST_LENGTH);
408 memset(key, 0, NPF_RULE_MAXKEYLEN);
409 SHA1(meta, 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 void
495 npfctl(int action, int argc, char **argv)
496 {
497 int fd, ver, boolval, ret = 0;
498
499 fd = open(NPF_DEV_PATH, O_RDONLY);
500 if (fd == -1) {
501 err(EXIT_FAILURE, "cannot open '%s'", NPF_DEV_PATH);
502 }
503 if (ioctl(fd, IOC_NPF_VERSION, &ver) == -1) {
504 err(EXIT_FAILURE, "ioctl(IOC_NPF_VERSION)");
505 }
506 if (ver != NPF_VERSION) {
507 errx(EXIT_FAILURE,
508 "incompatible NPF interface version (%d, kernel %d)\n"
509 "Hint: update userland?", NPF_VERSION, ver);
510 }
511
512 const char *fun = "";
513 switch (action) {
514 case NPFCTL_START:
515 boolval = true;
516 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
517 fun = "ioctl(IOC_NPF_SWITCH)";
518 break;
519 case NPFCTL_STOP:
520 boolval = false;
521 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
522 fun = "ioctl(IOC_NPF_SWITCH)";
523 break;
524 case NPFCTL_RELOAD:
525 npfctl_config_init(false);
526 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
527 errno = ret = npfctl_config_send(fd, NULL);
528 fun = "npfctl_config_send";
529 break;
530 case NPFCTL_SHOWCONF:
531 ret = npfctl_config_show(fd);
532 fun = "npfctl_config_show";
533 break;
534 case NPFCTL_FLUSH:
535 ret = npf_config_flush(fd);
536 fun = "npf_config_flush";
537 break;
538 case NPFCTL_VALIDATE:
539 npfctl_config_init(false);
540 npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
541 ret = npfctl_config_show(0);
542 fun = "npfctl_config_show";
543 break;
544 case NPFCTL_TABLE:
545 if ((argc -= 2) < 2) {
546 usage();
547 }
548 argv += 2;
549 npfctl_table(fd, argc, argv);
550 break;
551 case NPFCTL_RULE:
552 if ((argc -= 2) < 2) {
553 usage();
554 }
555 argv += 2;
556 npfctl_rule(fd, argc, argv);
557 break;
558 case NPFCTL_STATS:
559 ret = npfctl_print_stats(fd);
560 fun = "npfctl_print_stats";
561 break;
562 case NPFCTL_SESSIONS_SAVE:
563 if (npf_sessions_recv(fd, NPF_SESSDB_PATH) != 0) {
564 errx(EXIT_FAILURE, "could not save sessions to '%s'",
565 NPF_SESSDB_PATH);
566 }
567 break;
568 case NPFCTL_SESSIONS_LOAD:
569 if (npf_sessions_send(fd, NPF_SESSDB_PATH) != 0) {
570 errx(EXIT_FAILURE, "no sessions loaded from '%s'",
571 NPF_SESSDB_PATH);
572 }
573 break;
574 }
575 if (ret) {
576 err(EXIT_FAILURE, "%s", fun);
577 }
578 close(fd);
579 }
580
581 int
582 main(int argc, char **argv)
583 {
584 char *cmd;
585
586 if (argc < 2) {
587 usage();
588 }
589 cmd = argv[1];
590
591 if (strcmp(cmd, "debug") == 0) {
592 const char *cfg = argc > 2 ? argv[2] : "/etc/npf.conf";
593 const char *out = argc > 3 ? argv[3] : "/tmp/npf.plist";
594
595 npfctl_config_init(true);
596 npfctl_parse_file(cfg);
597 npfctl_config_send(0, out);
598 return EXIT_SUCCESS;
599 }
600
601 /* Find and call the subroutine. */
602 for (int n = 0; operations[n].cmd != NULL; n++) {
603 const char *opcmd = operations[n].cmd;
604 if (strncmp(cmd, opcmd, strlen(opcmd)) != 0)
605 continue;
606 npfctl(operations[n].action, argc, argv);
607 return EXIT_SUCCESS;
608 }
609 usage();
610 }
611