xref: /netbsd-src/usr.sbin/npf/npfctl/npf_show.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: npf_show.c,v 1.25 2017/12/10 22:04:41 rmind Exp $	*/
2 
3 /*-
4  * Copyright (c) 2013 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by 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 /*
33  * NPF configuration printing.
34  *
35  * Each rule having BPF byte-code has a binary description.
36  */
37 
38 #include <sys/cdefs.h>
39 __RCSID("$NetBSD: npf_show.c,v 1.25 2017/12/10 22:04:41 rmind Exp $");
40 
41 #include <sys/socket.h>
42 #define	__FAVOR_BSD
43 #include <netinet/in.h>
44 #include <netinet/tcp.h>
45 #include <net/if.h>
46 
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <stdbool.h>
51 #include <inttypes.h>
52 #include <errno.h>
53 #include <err.h>
54 
55 #include "npfctl.h"
56 
57 #define	SEEN_SRC	0x01
58 #define	SEEN_DST	0x02
59 
60 typedef struct {
61 	nl_config_t *	conf;
62 	FILE *		fp;
63 	long		fpos;
64 	u_int		flags;
65 	uint32_t	curmark;
66 } npf_conf_info_t;
67 
68 static npf_conf_info_t	stdout_ctx;
69 
70 static void	print_indent(npf_conf_info_t *, u_int);
71 static void	print_linesep(npf_conf_info_t *);
72 
73 void
74 npfctl_show_init(void)
75 {
76 	stdout_ctx.fp = stdout;
77 	stdout_ctx.fpos = 0;
78 	stdout_ctx.flags = 0;
79 }
80 
81 /*
82  * Helper routines to print various pieces of information.
83  */
84 
85 static void
86 print_indent(npf_conf_info_t *ctx, u_int level)
87 {
88 	if (level == 0) { /* XXX */
89 		print_linesep(ctx);
90 	}
91 	while (level--)
92 		fprintf(ctx->fp, "\t");
93 }
94 
95 static void
96 print_linesep(npf_conf_info_t *ctx)
97 {
98 	if (ftell(ctx->fp) != ctx->fpos) {
99 		fputs("\n", ctx->fp);
100 		ctx->fpos = ftell(ctx->fp);
101 	}
102 }
103 
104 static size_t
105 tcpflags2string(char *buf, u_int tfl)
106 {
107 	u_int i = 0;
108 
109 	if (tfl & TH_FIN)	buf[i++] = 'F';
110 	if (tfl & TH_SYN)	buf[i++] = 'S';
111 	if (tfl & TH_RST)	buf[i++] = 'R';
112 	if (tfl & TH_PUSH)	buf[i++] = 'P';
113 	if (tfl & TH_ACK)	buf[i++] = 'A';
114 	if (tfl & TH_URG)	buf[i++] = 'U';
115 	if (tfl & TH_ECE)	buf[i++] = 'E';
116 	if (tfl & TH_CWR)	buf[i++] = 'C';
117 	buf[i] = '\0';
118 	return i;
119 }
120 
121 static char *
122 print_family(npf_conf_info_t *ctx, const uint32_t *words)
123 {
124 	const int af = words[0];
125 
126 	switch (af) {
127 	case AF_INET:
128 		return estrdup("inet4");
129 	case AF_INET6:
130 		return estrdup("inet6");
131 	default:
132 		errx(EXIT_FAILURE, "invalid byte-code mark (family)");
133 	}
134 	return NULL;
135 }
136 
137 static char *
138 print_address(npf_conf_info_t *ctx, const uint32_t *words)
139 {
140 	const int af = *words++;
141 	const u_int mask = *words++;
142 	const npf_addr_t *addr;
143 	int alen = 0;
144 
145 	switch (af) {
146 	case AF_INET:
147 		alen = 4;
148 		break;
149 	case AF_INET6:
150 		alen = 16;
151 		break;
152 	default:
153 		errx(EXIT_FAILURE, "invalid byte-code mark (address)");
154 	}
155 	addr = (const npf_addr_t *)words;
156 	return npfctl_print_addrmask(alen, "%a", addr, mask);
157 }
158 
159 static char *
160 print_number(npf_conf_info_t *ctx, const uint32_t *words)
161 {
162 	char *p;
163 	easprintf(&p, "%u", words[0]);
164 	return p;
165 }
166 
167 static char *
168 print_table(npf_conf_info_t *ctx, const uint32_t *words)
169 {
170 	unsigned tid = words[0];
171 	nl_table_t *tl;
172 	char *p = NULL;
173 
174 	/* XXX: Iterating all as we need to rewind for the next call. */
175 	while ((tl = npf_table_iterate(ctx->conf)) != NULL) {
176 		if (!p && npf_table_getid(tl) == tid) {
177 			easprintf(&p, "%s", npf_table_getname(tl));
178 		}
179 	}
180 	assert(p != NULL);
181 	return p;
182 }
183 
184 static char *
185 print_proto(npf_conf_info_t *ctx, const uint32_t *words)
186 {
187 	switch (words[0]) {
188 	case IPPROTO_TCP:
189 		return estrdup("tcp");
190 	case IPPROTO_UDP:
191 		return estrdup("udp");
192 	case IPPROTO_ICMP:
193 		return estrdup("icmp");
194 	case IPPROTO_ICMPV6:
195 		return estrdup("ipv6-icmp");
196 	}
197 	return print_number(ctx, words);
198 }
199 
200 static char *
201 print_tcpflags(npf_conf_info_t *ctx, const uint32_t *words)
202 {
203 	const u_int tf = words[0], tf_mask = words[1];
204 	char buf[16];
205 
206 	size_t n = tcpflags2string(buf, tf);
207 	if (tf != tf_mask) {
208 		buf[n++] = '/';
209 		tcpflags2string(buf + n, tf_mask);
210 	}
211 	return estrdup(buf);
212 }
213 
214 static char *
215 print_portrange(npf_conf_info_t *ctx, const uint32_t *words)
216 {
217 	u_int fport = words[0], tport = words[1];
218 	const char *any_str = "";
219 	char *p;
220 
221 	if (ctx->curmark == BM_SRC_PORTS && (ctx->flags & SEEN_SRC) == 0)
222 		any_str = "from any ";
223 	if (ctx->curmark == BM_DST_PORTS && (ctx->flags & SEEN_DST) == 0)
224 		any_str = "to any ";
225 
226 	if (fport != tport) {
227 		easprintf(&p, "%sport %u:%u", any_str, fport, tport);
228 	} else {
229 		easprintf(&p, "%sport %u", any_str, fport);
230 	}
231 	return p;
232 }
233 
234 /*
235  * The main keyword mapping tables defining the syntax:
236  * - Mapping of rule attributes (flags) to the keywords.
237  * - Mapping of the byte-code marks to the keywords.
238  */
239 
240 #define	F(name)		__CONCAT(NPF_RULE_, name)
241 #define	STATEFUL_ENDS	(NPF_RULE_STATEFUL | NPF_RULE_MULTIENDS)
242 #define	NAME_AT		2
243 
244 static const struct attr_keyword_mapent {
245 	uint32_t	mask;
246 	uint32_t	flags;
247 	const char *	val;
248 } attr_keyword_map[] = {
249 	{ F(GROUP)|F(DYNAMIC),	F(GROUP),		"group"		},
250 	{ F(DYNAMIC),		F(DYNAMIC),		"ruleset"	},
251 	{ F(GROUP)|F(PASS),	0,			"block"		},
252 	{ F(GROUP)|F(PASS),	F(PASS),		"pass"		},
253 	{ F(RETRST)|F(RETICMP),	F(RETRST)|F(RETICMP),	"return"	},
254 	{ F(RETRST)|F(RETICMP),	F(RETRST),		"return-rst"	},
255 	{ F(RETRST)|F(RETICMP),	F(RETICMP),		"return-icmp"	},
256 	{ STATEFUL_ENDS,	F(STATEFUL),		"stateful"	},
257 	{ STATEFUL_ENDS,	STATEFUL_ENDS,		"stateful-ends"	},
258 	{ F(DIMASK),		F(IN),			"in"		},
259 	{ F(DIMASK),		F(OUT),			"out"		},
260 	{ F(FINAL),		F(FINAL),		"final"		},
261 };
262 
263 static const struct mark_keyword_mapent {
264 	u_int		mark;
265 	const char *	token;
266 	const char *	sep;
267 	u_int		set_flags;
268 	char *		(*printfn)(npf_conf_info_t *, const uint32_t *);
269 	u_int		fwords;
270 } mark_keyword_map[] = {
271 	{ BM_IPVER,	"family %s",	NULL, 0,	print_family,	1 },
272 	{ BM_PROTO,	"proto %s",	", ", 0,	print_proto,	1 },
273 	{ BM_TCPFL,	"flags %s",	NULL, 0,	print_tcpflags,	2 },
274 	{ BM_ICMP_TYPE,	"icmp-type %s",	NULL, 0,	print_number,	1 },
275 	{ BM_ICMP_CODE,	"code %s",	NULL, 0,	print_number,	1 },
276 
277 	{ BM_SRC_CIDR,	"from %s",	", ", SEEN_SRC,	print_address,	6 },
278 	{ BM_SRC_TABLE,	"from <%s>",	NULL, SEEN_SRC,	print_table,	1 },
279 	{ BM_SRC_PORTS,	"%s",		", ", 0,	print_portrange,2 },
280 
281 	{ BM_DST_CIDR,	"to %s",	", ", SEEN_DST,	print_address,	6 },
282 	{ BM_DST_TABLE,	"to <%s>",	NULL, SEEN_DST,	print_table,	1 },
283 	{ BM_DST_PORTS,	"%s",		", ", 0,	print_portrange,2 },
284 };
285 
286 static const char * __attribute__((format_arg(2)))
287 verified_fmt(const char *fmt, const char *t __unused)
288 {
289 	return fmt;
290 }
291 
292 static char *
293 scan_marks(npf_conf_info_t *ctx, const struct mark_keyword_mapent *mk,
294     const uint32_t *marks, size_t mlen)
295 {
296 	char buf[2048], *vals[256], *p;
297 	size_t nvals = 0;
298 
299 	/* Scan for the marks and extract the values. */
300 	mlen /= sizeof(uint32_t);
301 	while (mlen > 2) {
302 		const uint32_t m = *marks++;
303 		const u_int nwords = *marks++;
304 
305 		if ((mlen -= 2) < nwords) {
306 			errx(EXIT_FAILURE, "byte-code marking inconsistency");
307 		}
308 		if (m == mk->mark) {
309 			/* Set the current mark and the flags. */
310 			ctx->flags |= mk->set_flags;
311 			ctx->curmark = m;
312 
313 			/* Value is processed by the print function. */
314 			assert(mk->fwords == nwords);
315 			vals[nvals++] = mk->printfn(ctx, marks);
316 		}
317 		marks += nwords;
318 		mlen -= nwords;
319 	}
320 	if (nvals == 0) {
321 		return NULL;
322 	}
323 	assert(nvals == 1 || mk->sep != NULL);
324 
325 	/*
326 	 * Join all the values and print.  Add curly brackets if there
327 	 * is more than value and it can be a set.
328 	 */
329 	if (!join(buf, sizeof(buf), nvals, vals, mk->sep ? mk->sep : "")) {
330 		errx(EXIT_FAILURE, "out of memory while parsing the rule");
331 	}
332 	easprintf(&p, nvals > 1 ? "{ %s }" : "%s", buf);
333 
334 	for (u_int i = 0; i < nvals; i++) {
335 		free(vals[i]);
336 	}
337 	return p;
338 }
339 
340 static void
341 npfctl_print_id(npf_conf_info_t *ctx, nl_rule_t *rl)
342 {
343 	uint64_t id = id = npf_rule_getid(rl);
344 	fprintf(ctx->fp, "# id=\"%" PRIx64 "\" ", id);
345 }
346 
347 static void
348 npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl)
349 {
350 	const void *marks;
351 	size_t mlen, len;
352 	const void *code;
353 	int type;
354 
355 	marks = npf_rule_getinfo(rl, &mlen);
356 	if (!marks && (code = npf_rule_getcode(rl, &type, &len)) != NULL) {
357 		/*
358 		 * No marks, but the byte-code is present.  This must
359 		 * have been filled by libpcap(3) or possibly an unknown
360 		 * to us byte-code.
361 		 */
362 		fprintf(ctx->fp, "%s ", type == NPF_CODE_BPF ?
363 		    "pcap-filter \"...\"" : "unrecognized-bytecode");
364 		return;
365 	}
366 	ctx->flags = 0;
367 
368 	/*
369 	 * BPF filter criteria described by the byte-code marks.
370 	 */
371 	for (u_int i = 0; i < __arraycount(mark_keyword_map); i++) {
372 		const struct mark_keyword_mapent *mk = &mark_keyword_map[i];
373 		char *val;
374 
375 		if ((val = scan_marks(ctx, mk, marks, mlen)) != NULL) {
376 			fprintf(ctx->fp, verified_fmt(mk->token, "%s"), val);
377 			fputs(" ", ctx->fp);
378 			free(val);
379 		}
380 	}
381 	if (!mlen) {
382 		fputs("all ", ctx->fp);
383 	}
384 }
385 
386 static void
387 npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl)
388 {
389 	const uint32_t attr = npf_rule_getattr(rl);
390 	const char *rproc, *ifname, *name;
391 
392 	/* Rule attributes/flags. */
393 	for (u_int i = 0; i < __arraycount(attr_keyword_map); i++) {
394 		const struct attr_keyword_mapent *ak = &attr_keyword_map[i];
395 
396 		if (i == NAME_AT && (name = npf_rule_getname(rl)) != NULL) {
397 			fprintf(ctx->fp, "\"%s\" ", name);
398 		}
399 		if ((attr & ak->mask) == ak->flags) {
400 			fprintf(ctx->fp, "%s ", ak->val);
401 		}
402 	}
403 	if ((ifname = npf_rule_getinterface(rl)) != NULL) {
404 		fprintf(ctx->fp, "on %s ", ifname);
405 	}
406 
407 	if ((attr & NPF_DYNAMIC_GROUP) == NPF_RULE_GROUP) {
408 		/* Group; done. */
409 		goto out;
410 	}
411 
412 	/* Print filter criteria. */
413 	npfctl_print_filter(ctx, rl);
414 
415 	/* Rule procedure. */
416 	if ((rproc = npf_rule_getproc(rl)) != NULL) {
417 		fprintf(ctx->fp, "apply \"%s\" ", rproc);
418 	}
419 
420 out:
421 	npfctl_print_id(ctx, rl);
422 	fputs("\n", ctx->fp);
423 }
424 
425 static void
426 npfctl_print_nat(npf_conf_info_t *ctx, nl_nat_t *nt)
427 {
428 	nl_rule_t *rl = (nl_nat_t *)nt;
429 	const char *ifname, *seg1, *seg2, *arrow;
430 	npf_addr_t addr;
431 	in_port_t port;
432 	size_t alen;
433 	u_int flags;
434 	char *seg;
435 
436 	/* Get the interface. */
437 	ifname = npf_rule_getinterface(rl);
438 	assert(ifname != NULL);
439 
440 	/* Get the translation address (and port, if used). */
441 	npf_nat_getmap(nt, &addr, &alen, &port);
442 	seg = npfctl_print_addrmask(alen, "%a", &addr, NPF_NO_NETMASK);
443 	if (port) {
444 		char *p;
445 		easprintf(&p, "%s port %u", seg, ntohs(port));
446 		free(seg), seg = p;
447 	}
448 	seg1 = seg2 = "any";
449 
450 	/* Get the NAT type and determine the translation segment. */
451 	switch (npf_nat_gettype(nt)) {
452 	case NPF_NATIN:
453 		arrow = "<-";
454 		seg1 = seg;
455 		break;
456 	case NPF_NATOUT:
457 		arrow = "->";
458 		seg2 = seg;
459 		break;
460 	default:
461 		abort();
462 	}
463 	flags = npf_nat_getflags(nt);
464 
465 	/* Print out the NAT policy with the filter criteria. */
466 	fprintf(ctx->fp, "map %s %s %s%s%s %s %s pass ",
467 	    ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic",
468 	    "" /* XXX algo, */,
469 	    (flags & NPF_NAT_PORTS) ? "" : "no-ports ",
470 	    seg1, arrow, seg2);
471 	npfctl_print_filter(ctx, rl);
472 	npfctl_print_id(ctx, rl);
473 	fputs("\n", ctx->fp);
474 	free(seg);
475 }
476 
477 static void
478 npfctl_print_table(npf_conf_info_t *ctx, nl_table_t *tl)
479 {
480 	const char *name = npf_table_getname(tl);
481 	const unsigned type = npf_table_gettype(tl);
482 	const char *table_types[] = {
483 		[NPF_TABLE_HASH] = "hash",
484 		[NPF_TABLE_TREE] = "tree",
485 		[NPF_TABLE_CDB]  = "cdb",
486 	};
487 
488 	if (name[0] == '.') {
489 		/* Internal tables use dot and are hidden. */
490 		return;
491 	}
492 	assert(type < __arraycount(table_types));
493 	fprintf(ctx->fp, "table <%s> type %s\n", name, table_types[type]);
494 }
495 
496 int
497 npfctl_config_show(int fd)
498 {
499 	npf_conf_info_t *ctx = &stdout_ctx;
500 	nl_config_t *ncf;
501 	bool loaded;
502 
503 	if (fd) {
504 		ncf = npf_config_retrieve(fd);
505 		if (ncf == NULL) {
506 			return errno;
507 		}
508 		loaded = npf_config_loaded_p(ncf);
509 		fprintf(ctx->fp, "# filtering:\t%s\n# config:\t%s\n",
510 		    npf_config_active_p(ncf) ? "active" : "inactive",
511 		    loaded ? "loaded" : "empty");
512 		print_linesep(ctx);
513 	} else {
514 		ncf = npfctl_config_ref();
515 		(void)npf_config_build(ncf);
516 		loaded = true;
517 	}
518 	ctx->conf = ncf;
519 
520 	if (loaded) {
521 		nl_rule_t *rl;
522 		nl_rproc_t *rp;
523 		nl_nat_t *nt;
524 		nl_table_t *tl;
525 		u_int level;
526 
527 		while ((tl = npf_table_iterate(ncf)) != NULL) {
528 			npfctl_print_table(ctx, tl);
529 		}
530 		print_linesep(ctx);
531 
532 		while ((rp = npf_rproc_iterate(ncf)) != NULL) {
533 			const char *rpname = npf_rproc_getname(rp);
534 			fprintf(ctx->fp, "procedure \"%s\"\n", rpname);
535 		}
536 		print_linesep(ctx);
537 
538 		while ((nt = npf_nat_iterate(ncf)) != NULL) {
539 			npfctl_print_nat(ctx, nt);
540 		}
541 		print_linesep(ctx);
542 
543 		while ((rl = npf_rule_iterate(ncf, &level)) != NULL) {
544 			print_indent(ctx, level);
545 			npfctl_print_rule(ctx, rl);
546 		}
547 		print_linesep(ctx);
548 	}
549 	npf_config_destroy(ncf);
550 	return 0;
551 }
552 
553 int
554 npfctl_ruleset_show(int fd, const char *ruleset_name)
555 {
556 	npf_conf_info_t *ctx = &stdout_ctx;
557 	nl_config_t *ncf;
558 	nl_rule_t *rl;
559 	u_int level;
560 	int error;
561 
562 	ncf = npf_config_create();
563 	ctx->conf = ncf;
564 
565 	if ((error = _npf_ruleset_list(fd, ruleset_name, ncf)) != 0) {
566 		return error;
567 	}
568 	while ((rl = npf_rule_iterate(ncf, &level)) != NULL) {
569 		npfctl_print_rule(ctx, rl);
570 	}
571 	npf_config_destroy(ncf);
572 	return error;
573 }
574