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