npf_show.c revision 1.29 1 /*-
2 * Copyright (c) 2013-2019 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to The NetBSD Foundation
6 * by Mindaugas Rasiukevicius.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 /*
31 * NPF configuration printing.
32 *
33 * Each rule having BPF byte-code has a binary description.
34 */
35
36 #include <sys/cdefs.h>
37 __RCSID("$NetBSD: npf_show.c,v 1.29 2019/08/10 22:23:55 rmind Exp $");
38
39 #include <sys/socket.h>
40 #define __FAVOR_BSD
41 #include <netinet/in.h>
42 #include <netinet/tcp.h>
43 #include <net/if.h>
44
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <stdbool.h>
49 #include <inttypes.h>
50 #include <errno.h>
51 #include <err.h>
52
53 #include "npfctl.h"
54
55 #define SEEN_SRC 0x01
56 #define SEEN_DST 0x02
57
58 typedef struct {
59 nl_config_t * conf;
60 FILE * fp;
61 long fpos;
62 u_int flags;
63 uint32_t curmark;
64 unsigned level;
65 } npf_conf_info_t;
66
67 static npf_conf_info_t stdout_ctx;
68
69 static void print_linesep(npf_conf_info_t *);
70
71 void
72 npfctl_show_init(void)
73 {
74 stdout_ctx.fp = stdout;
75 stdout_ctx.fpos = 0;
76 stdout_ctx.flags = 0;
77 stdout_ctx.level = 0;
78 }
79
80 /*
81 * Helper routines to print various pieces of information.
82 */
83
84 static void
85 print_indent(npf_conf_info_t *ctx, unsigned level)
86 {
87 if (level < ctx->level) {
88 /*
89 * Level decrease -- end of the group.
90 * Print the group closing curly bracket.
91 */
92 fputs("}\n", ctx->fp);
93 }
94 if (level == 0) {
95 /*
96 * Group level -- separate groups by a trailing new line.
97 */
98 print_linesep(ctx);
99 }
100 ctx->level = level;
101
102 while (level--) {
103 fprintf(ctx->fp, "\t");
104 }
105 }
106
107 static void
108 print_linesep(npf_conf_info_t *ctx)
109 {
110 if (ftell(ctx->fp) != ctx->fpos) {
111 fputs("\n", ctx->fp);
112 ctx->fpos = ftell(ctx->fp);
113 }
114 }
115
116 static size_t
117 tcpflags2string(char *buf, u_int tfl)
118 {
119 u_int i = 0;
120
121 if (tfl & TH_FIN) buf[i++] = 'F';
122 if (tfl & TH_SYN) buf[i++] = 'S';
123 if (tfl & TH_RST) buf[i++] = 'R';
124 if (tfl & TH_PUSH) buf[i++] = 'P';
125 if (tfl & TH_ACK) buf[i++] = 'A';
126 if (tfl & TH_URG) buf[i++] = 'U';
127 if (tfl & TH_ECE) buf[i++] = 'E';
128 if (tfl & TH_CWR) buf[i++] = 'C';
129 buf[i] = '\0';
130 return i;
131 }
132
133 static char *
134 print_family(npf_conf_info_t *ctx __unused, const uint32_t *words)
135 {
136 const int af = words[0];
137
138 switch (af) {
139 case AF_INET:
140 return estrdup("inet4");
141 case AF_INET6:
142 return estrdup("inet6");
143 default:
144 errx(EXIT_FAILURE, "invalid byte-code mark (family)");
145 }
146 return NULL;
147 }
148
149 static char *
150 print_address(npf_conf_info_t *ctx __unused, const uint32_t *words)
151 {
152 const int af = *words++;
153 const u_int mask = *words++;
154 const npf_addr_t *addr;
155 int alen = 0;
156
157 switch (af) {
158 case AF_INET:
159 alen = 4;
160 break;
161 case AF_INET6:
162 alen = 16;
163 break;
164 default:
165 errx(EXIT_FAILURE, "invalid byte-code mark (address)");
166 }
167 addr = (const npf_addr_t *)words;
168 return npfctl_print_addrmask(alen, "%a", addr, mask);
169 }
170
171 static char *
172 print_number(npf_conf_info_t *ctx __unused, const uint32_t *words)
173 {
174 char *p;
175 easprintf(&p, "%u", words[0]);
176 return p;
177 }
178
179 static char *
180 print_table(npf_conf_info_t *ctx, const uint32_t *words)
181 {
182 const unsigned tid = words[0];
183 const char *tname;
184 char *s = NULL;
185 bool ifaddr;
186
187 tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
188 easprintf(&s, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
189 return s;
190 }
191
192 static char *
193 print_proto(npf_conf_info_t *ctx, const uint32_t *words)
194 {
195 switch (words[0]) {
196 case IPPROTO_TCP:
197 return estrdup("tcp");
198 case IPPROTO_UDP:
199 return estrdup("udp");
200 case IPPROTO_ICMP:
201 return estrdup("icmp");
202 case IPPROTO_ICMPV6:
203 return estrdup("ipv6-icmp");
204 }
205 return print_number(ctx, words);
206 }
207
208 static char *
209 print_tcpflags(npf_conf_info_t *ctx __unused, const uint32_t *words)
210 {
211 const u_int tf = words[0], tf_mask = words[1];
212 char buf[16];
213
214 size_t n = tcpflags2string(buf, tf);
215 if (tf != tf_mask) {
216 buf[n++] = '/';
217 tcpflags2string(buf + n, tf_mask);
218 }
219 return estrdup(buf);
220 }
221
222 static char *
223 print_pbarrier(npf_conf_info_t *ctx, const uint32_t *words __unused)
224 {
225 if (ctx->curmark == BM_SRC_PORTS && (ctx->flags & SEEN_SRC) == 0) {
226 ctx->flags |= SEEN_SRC;
227 return estrdup("from any");
228 }
229 if (ctx->curmark == BM_DST_PORTS && (ctx->flags & SEEN_DST) == 0) {
230 ctx->flags |= SEEN_DST;
231 return estrdup("to any");
232 }
233 return NULL;
234 }
235
236 static char *
237 print_portrange(npf_conf_info_t *ctx __unused, const uint32_t *words)
238 {
239 u_int fport = words[0], tport = words[1];
240 char *p;
241
242 if (fport != tport) {
243 easprintf(&p, "%u-%u", fport, tport);
244 } else {
245 easprintf(&p, "%u", fport);
246 }
247 return p;
248 }
249
250 /*
251 * The main keyword mapping tables defining the syntax:
252 * - Mapping of rule attributes (flags) to the keywords.
253 * - Mapping of the byte-code marks to the keywords.
254 */
255
256 #define F(name) __CONCAT(NPF_RULE_, name)
257 #define STATEFUL_ALL (NPF_RULE_STATEFUL | NPF_RULE_GSTATEFUL)
258 #define NAME_AT 2
259
260 static const struct attr_keyword_mapent {
261 uint32_t mask;
262 uint32_t flags;
263 const char * val;
264 } attr_keyword_map[] = {
265 { F(GROUP)|F(DYNAMIC), F(GROUP), "group" },
266 { F(DYNAMIC), F(DYNAMIC), "ruleset" },
267 { F(GROUP)|F(PASS), 0, "block" },
268 { F(GROUP)|F(PASS), F(PASS), "pass" },
269 { F(RETRST)|F(RETICMP), F(RETRST)|F(RETICMP), "return" },
270 { F(RETRST)|F(RETICMP), F(RETRST), "return-rst" },
271 { F(RETRST)|F(RETICMP), F(RETICMP), "return-icmp" },
272 { STATEFUL_ALL, F(STATEFUL), "stateful" },
273 { STATEFUL_ALL, STATEFUL_ALL, "stateful-all" },
274 { F(DIMASK), F(IN), "in" },
275 { F(DIMASK), F(OUT), "out" },
276 { F(FINAL), F(FINAL), "final" },
277 };
278
279 static const struct mark_keyword_mapent {
280 u_int mark;
281 const char * token;
282 const char * sep;
283 u_int set_flags;
284 char * (*printfn)(npf_conf_info_t *, const uint32_t *);
285 u_int fwords;
286 } mark_keyword_map[] = {
287 { BM_IPVER, "family %s", NULL, 0, print_family, 1 },
288 { BM_PROTO, "proto %s", ", ", 0, print_proto, 1 },
289 { BM_TCPFL, "flags %s", NULL, 0, print_tcpflags, 2 },
290 { BM_ICMP_TYPE, "icmp-type %s", NULL, 0, print_number, 1 },
291 { BM_ICMP_CODE, "code %s", NULL, 0, print_number, 1 },
292
293 { BM_SRC_CIDR, "from %s", ", ", SEEN_SRC, print_address, 6 },
294 { BM_SRC_TABLE, "from %s", ", ", SEEN_SRC, print_table, 1 },
295 { BM_SRC_PORTS, "%s", NULL, 0, print_pbarrier, 2 },
296 { BM_SRC_PORTS, "port %s", ", ", 0, print_portrange,2 },
297
298 { BM_DST_CIDR, "to %s", ", ", SEEN_DST, print_address, 6 },
299 { BM_DST_TABLE, "to %s", ", ", SEEN_DST, print_table, 1 },
300 { BM_DST_PORTS, "%s", NULL, 0, print_pbarrier, 2 },
301 { BM_DST_PORTS, "port %s", ", ", 0, print_portrange,2 },
302 };
303
304 static const char * __attribute__((format_arg(2)))
305 verified_fmt(const char *fmt, const char *t __unused)
306 {
307 return fmt;
308 }
309
310 static char *
311 scan_marks(npf_conf_info_t *ctx, const struct mark_keyword_mapent *mk,
312 const uint32_t *marks, size_t mlen)
313 {
314 char buf[2048], *vals[256], *p;
315 size_t nvals = 0;
316
317 /* Scan for the marks and extract the values. */
318 mlen /= sizeof(uint32_t);
319 while (mlen > 2) {
320 const uint32_t m = *marks++;
321 const u_int nwords = *marks++;
322
323 if ((mlen -= 2) < nwords) {
324 errx(EXIT_FAILURE, "byte-code marking inconsistency");
325 }
326 if (m == mk->mark) {
327 char *val;
328
329 /* Set the current mark and the flags. */
330 ctx->flags |= mk->set_flags;
331 ctx->curmark = m;
332
333 /* Value is processed by the print function. */
334 assert(mk->fwords == nwords);
335 if ((val = mk->printfn(ctx, marks)) != NULL) {
336 vals[nvals++] = val;
337 }
338 }
339 marks += nwords;
340 mlen -= nwords;
341 }
342 if (nvals == 0) {
343 return NULL;
344 }
345 assert(nvals == 1 || mk->sep != NULL);
346
347 /*
348 * Join all the values and print. Add curly brackets if there
349 * is more than value and it can be a set.
350 */
351 if (!join(buf, sizeof(buf), nvals, vals, mk->sep ? mk->sep : "")) {
352 errx(EXIT_FAILURE, "out of memory while parsing the rule");
353 }
354 easprintf(&p, nvals > 1 ? "{ %s }" : "%s", buf);
355
356 for (u_int i = 0; i < nvals; i++) {
357 free(vals[i]);
358 }
359 return p;
360 }
361
362 static void
363 npfctl_print_id(npf_conf_info_t *ctx, nl_rule_t *rl)
364 {
365 uint64_t id = id = npf_rule_getid(rl);
366 fprintf(ctx->fp, "# id=\"%" PRIx64 "\" ", id);
367 }
368
369 static void
370 npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl)
371 {
372 const void *marks;
373 size_t mlen, len;
374 const void *code;
375 int type;
376
377 marks = npf_rule_getinfo(rl, &mlen);
378 if (!marks && (code = npf_rule_getcode(rl, &type, &len)) != NULL) {
379 /*
380 * No marks, but the byte-code is present. This must
381 * have been filled by libpcap(3) or possibly an unknown
382 * to us byte-code.
383 */
384 fprintf(ctx->fp, "%s ", type == NPF_CODE_BPF ?
385 "pcap-filter \"...\"" : "unrecognized-bytecode");
386 return;
387 }
388 ctx->flags = 0;
389
390 /*
391 * BPF filter criteria described by the byte-code marks.
392 */
393 for (u_int i = 0; i < __arraycount(mark_keyword_map); i++) {
394 const struct mark_keyword_mapent *mk = &mark_keyword_map[i];
395 char *val;
396
397 if ((val = scan_marks(ctx, mk, marks, mlen)) != NULL) {
398 fprintf(ctx->fp, verified_fmt(mk->token, "%s"), val);
399 fputs(" ", ctx->fp);
400 free(val);
401 }
402 }
403 if (!mlen) {
404 fputs("all ", ctx->fp);
405 }
406 }
407
408 static void
409 npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl)
410 {
411 const uint32_t attr = npf_rule_getattr(rl);
412 const char *rproc, *ifname, *name;
413
414 /* Rule attributes/flags. */
415 for (u_int i = 0; i < __arraycount(attr_keyword_map); i++) {
416 const struct attr_keyword_mapent *ak = &attr_keyword_map[i];
417
418 if (i == NAME_AT && (name = npf_rule_getname(rl)) != NULL) {
419 fprintf(ctx->fp, "\"%s\" ", name);
420 }
421 if ((attr & ak->mask) == ak->flags) {
422 fprintf(ctx->fp, "%s ", ak->val);
423 }
424 }
425 if ((ifname = npf_rule_getinterface(rl)) != NULL) {
426 fprintf(ctx->fp, "on %s ", ifname);
427 }
428 if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_OUT) && !ifname) {
429 /* The default group is a special case. */
430 fprintf(ctx->fp, "default ");
431 }
432 if ((attr & NPF_DYNAMIC_GROUP) == NPF_RULE_GROUP) {
433 /* Group; done. */
434 fprintf(ctx->fp, "{ ");
435 goto out;
436 }
437
438 /* Print filter criteria. */
439 npfctl_print_filter(ctx, rl);
440
441 /* Rule procedure. */
442 if ((rproc = npf_rule_getproc(rl)) != NULL) {
443 fprintf(ctx->fp, "apply \"%s\" ", rproc);
444 }
445 out:
446 npfctl_print_id(ctx, rl);
447 fputs("\n", ctx->fp);
448 }
449
450 static void
451 npfctl_print_nat(npf_conf_info_t *ctx, nl_nat_t *nt)
452 {
453 const unsigned dynamic_natset = NPF_RULE_GROUP | NPF_RULE_DYNAMIC;
454 nl_rule_t *rl = (nl_nat_t *)nt;
455 const char *ifname, *algo, *seg1, *seg2, *arrow;
456 const npf_addr_t *addr;
457 npf_netmask_t mask;
458 in_port_t port;
459 size_t alen;
460 unsigned flags;
461 char *seg;
462
463 /* Get flags and the interface. */
464 flags = npf_nat_getflags(nt);
465 ifname = npf_rule_getinterface(rl);
466 assert(ifname != NULL);
467
468 if ((npf_rule_getattr(rl) & dynamic_natset) == dynamic_natset) {
469 const char *name = npf_rule_getname(rl);
470 fprintf(ctx->fp, "map ruleset \"%s\" on %s\n", name, ifname);
471 return;
472 }
473
474 /* Get the translation address or table (and port, if used). */
475 addr = npf_nat_getaddr(nt, &alen, &mask);
476 if (addr) {
477 seg = npfctl_print_addrmask(alen, "%a", addr, mask);
478 } else {
479 const unsigned tid = npf_nat_gettable(nt);
480 const char *tname;
481 bool ifaddr;
482
483 tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
484 easprintf(&seg, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
485 }
486
487 if ((port = npf_nat_getport(nt)) != 0) {
488 char *p;
489 easprintf(&p, "%s port %u", seg, ntohs(port));
490 free(seg), seg = p;
491 }
492 seg1 = seg2 = "any";
493
494 /* Get the NAT type and determine the translation segment. */
495 switch (npf_nat_gettype(nt)) {
496 case NPF_NATIN:
497 arrow = "<-";
498 seg1 = seg;
499 break;
500 case NPF_NATOUT:
501 arrow = "->";
502 seg2 = seg;
503 break;
504 default:
505 abort();
506 }
507
508 /* NAT algorithm. */
509 switch (npf_nat_getalgo(nt)) {
510 case NPF_ALGO_NETMAP:
511 algo = "algo netmap ";
512 break;
513 case NPF_ALGO_IPHASH:
514 algo = "algo ip-hash ";
515 break;
516 case NPF_ALGO_RR:
517 algo = "algo round-robin ";
518 break;
519 case NPF_ALGO_NPT66:
520 algo = "algo npt66";
521 break;
522 default:
523 algo = "";
524 break;
525 }
526
527 /* FIXME also handle "any" */
528
529 /* Print out the NAT policy with the filter criteria. */
530 fprintf(ctx->fp, "map %s %s %s%s%s %s %s pass ",
531 ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic",
532 algo, (flags & NPF_NAT_PORTS) ? "" : "no-ports ",
533 seg1, arrow, seg2);
534 npfctl_print_filter(ctx, rl);
535 npfctl_print_id(ctx, rl);
536 fputs("\n", ctx->fp);
537 free(seg);
538 }
539
540 static void
541 npfctl_print_table(npf_conf_info_t *ctx, nl_table_t *tl)
542 {
543 const char *name = npf_table_getname(tl);
544 const unsigned type = npf_table_gettype(tl);
545 const char *table_types[] = {
546 [NPF_TABLE_IPSET] = "ipset",
547 [NPF_TABLE_LPM] = "lpm",
548 [NPF_TABLE_CONST] = "const",
549 };
550
551 if (name[0] == '.') {
552 /* Internal tables use dot and are hidden. */
553 return;
554 }
555 assert(type < __arraycount(table_types));
556 fprintf(ctx->fp, "table <%s> type %s\n", name, table_types[type]);
557 }
558
559 int
560 npfctl_config_show(int fd)
561 {
562 npf_conf_info_t *ctx = &stdout_ctx;
563 nl_config_t *ncf;
564 bool loaded;
565
566 if (fd) {
567 ncf = npf_config_retrieve(fd);
568 if (ncf == NULL) {
569 return errno;
570 }
571 loaded = npf_config_loaded_p(ncf);
572 fprintf(ctx->fp, "# filtering:\t%s\n# config:\t%s\n",
573 npf_config_active_p(ncf) ? "active" : "inactive",
574 loaded ? "loaded" : "empty");
575 print_linesep(ctx);
576 } else {
577 ncf = npfctl_config_ref();
578 (void)npf_config_build(ncf);
579 loaded = true;
580 }
581 ctx->conf = ncf;
582
583 if (loaded) {
584 nl_rule_t *rl;
585 nl_rproc_t *rp;
586 nl_nat_t *nt;
587 nl_table_t *tl;
588 nl_iter_t i;
589 unsigned level;
590
591 i = NPF_ITER_BEGIN;
592 while ((tl = npf_table_iterate(ncf, &i)) != NULL) {
593 npfctl_print_table(ctx, tl);
594 }
595 print_linesep(ctx);
596
597 i = NPF_ITER_BEGIN;
598 while ((rp = npf_rproc_iterate(ncf, &i)) != NULL) {
599 const char *rpname = npf_rproc_getname(rp);
600 fprintf(ctx->fp, "procedure \"%s\"\n", rpname);
601 }
602 print_linesep(ctx);
603
604 i = NPF_ITER_BEGIN;
605 while ((nt = npf_nat_iterate(ncf, &i)) != NULL) {
606 npfctl_print_nat(ctx, nt);
607 }
608 print_linesep(ctx);
609
610 i = NPF_ITER_BEGIN;
611 while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
612 print_indent(ctx, level);
613 npfctl_print_rule(ctx, rl);
614 }
615 print_indent(ctx, 0);
616 }
617 npf_config_destroy(ncf);
618 return 0;
619 }
620
621 int
622 npfctl_ruleset_show(int fd, const char *ruleset_name)
623 {
624 npf_conf_info_t *ctx = &stdout_ctx;
625 nl_config_t *ncf;
626 nl_rule_t *rl;
627 unsigned level;
628 nl_iter_t i;
629 int error;
630
631 ncf = npf_config_create();
632 ctx->conf = ncf;
633
634 if ((error = _npf_ruleset_list(fd, ruleset_name, ncf)) != 0) {
635 return error;
636 }
637 i = NPF_ITER_BEGIN;
638 while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
639 npfctl_print_rule(ctx, rl);
640 }
641 npf_config_destroy(ncf);
642 return error;
643 }
644