Home | History | Annotate | Line # | Download | only in npfctl
npf_show.c revision 1.35
      1 /*-
      2  * Copyright (c) 2013-2020 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.35 2025/06/01 00:33:51 joe 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_PROTO	0x01
     56 
     57 typedef struct {
     58 	char **		values;
     59 	unsigned	count;
     60 } elem_list_t;
     61 
     62 enum {
     63 	LIST_PROTO = 0, LIST_SADDR, LIST_DADDR, LIST_SPORT, LIST_DPORT,
     64 	LIST_COUNT,
     65 };
     66 
     67 typedef struct {
     68 	nl_config_t *	conf;
     69 	bool		validating;
     70 
     71 	FILE *		fp;
     72 	long		fpos;
     73 	long		fposln;
     74 	int		glevel;
     75 
     76 	unsigned	flags;
     77 	uint32_t	curmark;
     78 	uint64_t	seen_marks;
     79 	elem_list_t	list[LIST_COUNT];
     80 
     81 } npf_conf_info_t;
     82 
     83 static void	print_linesep(npf_conf_info_t *);
     84 
     85 static npf_conf_info_t *
     86 npfctl_show_init(void)
     87 {
     88 	static npf_conf_info_t stdout_ctx;
     89 	memset(&stdout_ctx, 0, sizeof(npf_conf_info_t));
     90 	stdout_ctx.glevel = -1;
     91 	stdout_ctx.fp = stdout;
     92 	return &stdout_ctx;
     93 }
     94 
     95 static void
     96 list_push(elem_list_t *list, char *val)
     97 {
     98 	const unsigned n = list->count;
     99 	char **values;
    100 
    101 	if ((values = calloc(n + 1, sizeof(char *))) == NULL) {
    102 		err(EXIT_FAILURE, "calloc");
    103 	}
    104 	for (unsigned i = 0; i < n; i++) {
    105 		values[i] = list->values[i];
    106 	}
    107 	values[n] = val;
    108 	free(list->values);
    109 	list->values = values;
    110 	list->count++;
    111 }
    112 
    113 static char *
    114 list_join_free(elem_list_t *list, const bool use_br, const char *sep)
    115 {
    116 	char *s, buf[2048];
    117 
    118 	if (!join(buf, sizeof(buf), list->count, list->values, sep)) {
    119 		errx(EXIT_FAILURE, "out of memory while parsing the rule");
    120 	}
    121 	easprintf(&s, (use_br && list->count > 1) ? "{ %s }" : "%s", buf);
    122 	for (unsigned i = 0; i < list->count; i++) {
    123 		free(list->values[i]);
    124 	}
    125 	free(list->values);
    126 	list->values = NULL;
    127 	list->count = 0;
    128 	return s;
    129 }
    130 
    131 /*
    132  * Helper routines to print various pieces of information.
    133  */
    134 
    135 static void
    136 print_indent(npf_conf_info_t *ctx, unsigned level)
    137 {
    138 	if (ctx->glevel >= 0 && level <= (unsigned)ctx->glevel) {
    139 		/*
    140 		 * Level decrease -- end of the group.
    141 		 * Print the group closing curly bracket.
    142 		 */
    143 		ctx->fpos += fprintf(ctx->fp, "}\n\n");
    144 		ctx->glevel = -1;
    145 	}
    146 	while (level--) {
    147 		ctx->fpos += fprintf(ctx->fp, "\t");
    148 	}
    149 }
    150 
    151 static void
    152 print_linesep(npf_conf_info_t *ctx)
    153 {
    154 	if (ctx->fpos != ctx->fposln) {
    155 		ctx->fpos += fprintf(ctx->fp, "\n");
    156 		ctx->fposln = ctx->fpos;
    157 	}
    158 }
    159 
    160 static size_t
    161 tcpflags2string(char *buf, unsigned tfl)
    162 {
    163 	unsigned i = 0;
    164 
    165 	if (tfl & TH_FIN)	buf[i++] = 'F';
    166 	if (tfl & TH_SYN)	buf[i++] = 'S';
    167 	if (tfl & TH_RST)	buf[i++] = 'R';
    168 	if (tfl & TH_PUSH)	buf[i++] = 'P';
    169 	if (tfl & TH_ACK)	buf[i++] = 'A';
    170 	if (tfl & TH_URG)	buf[i++] = 'U';
    171 	if (tfl & TH_ECE)	buf[i++] = 'E';
    172 	if (tfl & TH_CWR)	buf[i++] = 'W';
    173 	buf[i] = '\0';
    174 	return i;
    175 }
    176 
    177 static char *
    178 print_family(npf_conf_info_t *ctx __unused, const uint32_t *words)
    179 {
    180 	const int af = words[0];
    181 
    182 	switch (af) {
    183 	case AF_INET:
    184 		return estrdup("inet4");
    185 	case AF_INET6:
    186 		return estrdup("inet6");
    187 	default:
    188 		errx(EXIT_FAILURE, "invalid byte-code mark (family)");
    189 	}
    190 	return NULL;
    191 }
    192 
    193 static char *
    194 print_address(npf_conf_info_t *ctx __unused, const uint32_t *words)
    195 {
    196 	const int af = *words++;
    197 	const unsigned mask = *words++;
    198 	const npf_addr_t *addr;
    199 	int alen = 0;
    200 
    201 	switch (af) {
    202 	case AF_INET:
    203 		alen = 4;
    204 		break;
    205 	case AF_INET6:
    206 		alen = 16;
    207 		break;
    208 	default:
    209 		errx(EXIT_FAILURE, "invalid byte-code mark (address)");
    210 	}
    211 	addr = (const npf_addr_t *)words;
    212 	return npfctl_print_addrmask(alen, "%a", addr, mask);
    213 }
    214 
    215 static char *
    216 print_number(npf_conf_info_t *ctx __unused, const uint32_t *words)
    217 {
    218 	char *p;
    219 	easprintf(&p, "%u", words[0]);
    220 	return p;
    221 }
    222 
    223 static char *
    224 print_table(npf_conf_info_t *ctx, const uint32_t *words)
    225 {
    226 	const unsigned tid = words[0];
    227 	const char *tname;
    228 	char *s = NULL;
    229 	bool ifaddr;
    230 
    231 	tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
    232 	easprintf(&s, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
    233 	return s;
    234 }
    235 
    236 static char *
    237 print_proto(npf_conf_info_t *ctx, const uint32_t *words)
    238 {
    239 	ctx->flags |= SEEN_PROTO;
    240 	switch (words[0]) {
    241 	case IPPROTO_TCP:
    242 		return estrdup("tcp");
    243 	case IPPROTO_UDP:
    244 		return estrdup("udp");
    245 	case IPPROTO_ICMP:
    246 		return estrdup("icmp");
    247 	case IPPROTO_ICMPV6:
    248 		return estrdup("ipv6-icmp");
    249 	}
    250 	return print_number(ctx, words);
    251 }
    252 
    253 static char *
    254 print_tcpflags(npf_conf_info_t *ctx __unused, const uint32_t *words)
    255 {
    256 	const unsigned tf = words[0], tf_mask = words[1];
    257 	char buf[32];
    258 	size_t n;
    259 
    260 	if ((ctx->flags & SEEN_PROTO) == 0) {
    261 		/*
    262 		 * Note: the TCP flag matching might be without 'proto tcp'
    263 		 * when using a plain 'stateful' rule.  In such case, just
    264 		 * skip showing of the flags as they are implicit.
    265 		 */
    266 		return NULL;
    267 	}
    268 	n = tcpflags2string(buf, tf);
    269 	if (tf != tf_mask) {
    270 		buf[n++] = '/';
    271 		tcpflags2string(buf + n, tf_mask);
    272 	}
    273 	return estrdup(buf);
    274 }
    275 
    276 static char *
    277 print_portrange(npf_conf_info_t *ctx __unused, const uint32_t *words)
    278 {
    279 	unsigned fport = words[0], tport = words[1];
    280 	char *p;
    281 
    282 	if (fport != tport) {
    283 		easprintf(&p, "%u-%u", fport, tport);
    284 	} else {
    285 		easprintf(&p, "%u", fport);
    286 	}
    287 	return p;
    288 }
    289 
    290 /*
    291  * The main keyword mapping tables defining the syntax:
    292  * - Mapping of rule attributes (flags) to the keywords.
    293  * - Mapping of the byte-code marks to the keywords.
    294  */
    295 
    296 #define	F(name)		__CONCAT(NPF_RULE_, name)
    297 #define	STATEFUL_ALL	(NPF_RULE_STATEFUL | NPF_RULE_GSTATEFUL)
    298 #define	NAME_AT		2
    299 
    300 static const struct attr_keyword_mapent {
    301 	uint32_t	mask;
    302 	uint32_t	flags;
    303 	const char *	val;
    304 } attr_keyword_map[] = {
    305 	{ F(GROUP)|F(DYNAMIC),	F(GROUP),		"group"		},
    306 	{ F(GROUP)|F(DYNAMIC),	F(GROUP)|F(DYNAMIC),	"ruleset"	},
    307 	{ F(GROUP)|F(PASS),	0,			"block"		},
    308 	{ F(GROUP)|F(PASS),	F(PASS),		"pass"		},
    309 	{ F(RETRST)|F(RETICMP),	F(RETRST)|F(RETICMP),	"return"	},
    310 	{ F(RETRST)|F(RETICMP),	F(RETRST),		"return-rst"	},
    311 	{ F(RETRST)|F(RETICMP),	F(RETICMP),		"return-icmp"	},
    312 	{ STATEFUL_ALL,		F(STATEFUL),		"stateful"	},
    313 	{ STATEFUL_ALL,		STATEFUL_ALL,		"stateful-all"	},
    314 	{ F(DIMASK),		F(IN),			"in"		},
    315 	{ F(DIMASK),		F(OUT),			"out"		},
    316 	{ F(FINAL),		F(FINAL),		"final"		},
    317 };
    318 
    319 static const struct mark_keyword_mapent {
    320 	unsigned	mark;
    321 	const char *	format;
    322 	int		list_id;
    323 	char *		(*printfn)(npf_conf_info_t *, const uint32_t *);
    324 	unsigned	fwords;
    325 } mark_keyword_map[] = {
    326 	{ BM_IPVER,	"family %s",	LIST_PROTO,	print_family,	1 },
    327 	{ BM_PROTO,	"proto %s",	LIST_PROTO,	print_proto,	1 },
    328 	{ BM_TCPFL,	"flags %s",	LIST_PROTO,	print_tcpflags,	2 },
    329 	{ BM_ICMP_TYPE,	"icmp-type %s",	LIST_PROTO,	print_number,	1 },
    330 	{ BM_ICMP_CODE,	"code %s",	LIST_PROTO,	print_number,	1 },
    331 
    332 	{ BM_SRC_NEG,	NULL,		-1,		NULL,		0 },
    333 	{ BM_SRC_CIDR,	NULL,		LIST_SADDR,	print_address,	6 },
    334 	{ BM_SRC_TABLE,	NULL,		LIST_SADDR,	print_table,	1 },
    335 	{ BM_SRC_PORTS,	NULL,		LIST_SPORT,	print_portrange,2 },
    336 
    337 	{ BM_DST_NEG,	NULL,		-1,		NULL,		0 },
    338 	{ BM_DST_CIDR,	NULL,		LIST_DADDR,	print_address,	6 },
    339 	{ BM_DST_TABLE,	NULL,		LIST_DADDR,	print_table,	1 },
    340 	{ BM_DST_PORTS,	NULL,		LIST_DPORT,	print_portrange,2 },
    341 };
    342 
    343 static const char * __attribute__((format_arg(2)))
    344 verified_fmt(const char *fmt, const char *t __unused)
    345 {
    346 	return fmt;
    347 }
    348 
    349 static void
    350 scan_marks(npf_conf_info_t *ctx, const struct mark_keyword_mapent *mk,
    351     const uint32_t *marks, size_t mlen)
    352 {
    353 	elem_list_t sublist, *target_list;
    354 
    355 	/*
    356 	 * If format is used for this mark, then collect multiple elements
    357 	 * in into the list, merge and re-push the set into the target list.
    358 	 *
    359 	 * Currently, this is applicable only for 'proto { tcp, udp }'.
    360 	 */
    361 	memset(&sublist, 0, sizeof(elem_list_t));
    362 	target_list = mk->format ? &sublist : &ctx->list[mk->list_id];
    363 
    364 	/* Scan for the marks and extract the values. */
    365 	mlen /= sizeof(uint32_t);
    366 	while (mlen > 2) {
    367 		const uint32_t m = *marks++;
    368 		const unsigned nwords = *marks++;
    369 
    370 		if ((mlen -= 2) < nwords) {
    371 			errx(EXIT_FAILURE, "byte-code marking inconsistency");
    372 		}
    373 		if (m == mk->mark) {
    374 			/*
    375 			 * Set the current mark and note it as seen.
    376 			 * Value is processed by the print function,
    377 			 * otherwise we just need to note the mark.
    378 			 */
    379 			ctx->curmark = m;
    380 			assert(BM_COUNT < (sizeof(uint64_t) * CHAR_BIT));
    381 			ctx->seen_marks |= UINT64_C(1) << m;
    382 			assert(mk->fwords == nwords);
    383 
    384 			if (mk->printfn) {
    385 				char *val;
    386 
    387 				if ((val = mk->printfn(ctx, marks)) != NULL) {
    388 					list_push(target_list, val);
    389 				}
    390 			}
    391 		}
    392 		marks += nwords;
    393 		mlen -= nwords;
    394 	}
    395 
    396 	if (sublist.count) {
    397 		char *val, *elements;
    398 
    399 		elements = list_join_free(&sublist, true, ", ");
    400 		easprintf(&val, verified_fmt(mk->format, "%s"), elements );
    401 		list_push(&ctx->list[mk->list_id], val);
    402 		free(elements);
    403 	}
    404 }
    405 
    406 static void
    407 npfctl_print_id(npf_conf_info_t *ctx, nl_rule_t *rl)
    408 {
    409 	const uint64_t id = npf_rule_getid(rl);
    410 
    411 	if (id) {
    412 		ctx->fpos += fprintf(ctx->fp, "# id=\"%" PRIx64 "\" ", id);
    413 	}
    414 }
    415 
    416 static void
    417 npfctl_print_filter_generic(npf_conf_info_t *ctx)
    418 {
    419 	elem_list_t *list = &ctx->list[LIST_PROTO];
    420 
    421 	if (list->count) {
    422 		char *elements = list_join_free(list, false, " ");
    423 		ctx->fpos += fprintf(ctx->fp, "%s ", elements);
    424 		free(elements);
    425 	}
    426 }
    427 
    428 static bool
    429 npfctl_print_filter_seg(npf_conf_info_t *ctx, unsigned which)
    430 {
    431 	static const struct {
    432 		const char *	keyword;
    433 		unsigned	alist;
    434 		unsigned	plist;
    435 		unsigned	negbm;
    436 	} refs[] = {
    437 		[NPF_SRC] = {
    438 			.keyword	= "from",
    439 			.alist		= LIST_SADDR,
    440 			.plist		= LIST_SPORT,
    441 			.negbm		= UINT64_C(1) << BM_SRC_NEG,
    442 		},
    443 		[NPF_DST] = {
    444 			.keyword	= "to",
    445 			.alist		= LIST_DADDR,
    446 			.plist		= LIST_DPORT,
    447 			.negbm		= UINT64_C(1) << BM_DST_NEG,
    448 		}
    449 	};
    450 	const char *neg = !!(ctx->seen_marks & refs[which].negbm) ? "! " : "";
    451 	const char *kwd = refs[which].keyword;
    452 	bool seen_filter = false;
    453 	elem_list_t *list;
    454 	char *elements;
    455 
    456 	list = &ctx->list[refs[which].alist];
    457 	if (list->count != 0) {
    458 		seen_filter = true;
    459 		elements = list_join_free(list, true, ", ");
    460 		ctx->fpos += fprintf(ctx->fp, "%s %s%s ", kwd, neg, elements);
    461 		free(elements);
    462 	}
    463 
    464 	list = &ctx->list[refs[which].plist];
    465 	if (list->count != 0) {
    466 		if (!seen_filter) {
    467 			ctx->fpos += fprintf(ctx->fp, "%s any ", kwd);
    468 			seen_filter = true;
    469 		}
    470 		elements = list_join_free(list, true, ", ");
    471 		ctx->fpos += fprintf(ctx->fp, "port %s ", elements);
    472 		free(elements);
    473 	}
    474 	return seen_filter;
    475 }
    476 
    477 static bool
    478 npfctl_print_filter(npf_conf_info_t *ctx, nl_rule_t *rl)
    479 {
    480 	const void *marks;
    481 	size_t mlen, len;
    482 	const void *code;
    483 	bool seenf = false;
    484 	int type;
    485 
    486 	marks = npf_rule_getinfo(rl, &mlen);
    487 	if (!marks && (code = npf_rule_getcode(rl, &type, &len)) != NULL) {
    488 		/*
    489 		 * No marks, but the byte-code is present.  This must
    490 		 * have been filled by libpcap(3) or possibly an unknown
    491 		 * to us byte-code.
    492 		 */
    493 		ctx->fpos += fprintf(ctx->fp, "%s ", type == NPF_CODE_BPF ?
    494 		    "pcap-filter \"...\"" : "unrecognized-bytecode");
    495 		return true;
    496 	}
    497 	ctx->flags = 0;
    498 
    499 	/*
    500 	 * BPF filter criteria described by the byte-code marks.
    501 	 */
    502 	ctx->seen_marks = 0;
    503 	for (unsigned i = 0; i < __arraycount(mark_keyword_map); i++) {
    504 		const struct mark_keyword_mapent *mk = &mark_keyword_map[i];
    505 		scan_marks(ctx, mk, marks, mlen);
    506 	}
    507 	npfctl_print_filter_generic(ctx);
    508 	seenf |= npfctl_print_filter_seg(ctx, NPF_SRC);
    509 	seenf |= npfctl_print_filter_seg(ctx, NPF_DST);
    510 	return seenf;
    511 }
    512 
    513 static char *
    514 print_guid(char *buf, struct r_id id, int size)
    515 {
    516 	if (id.op == NPF_OP_XRG) {
    517 		snprintf(buf, size, "%u <> %u", id.id[0], id.id[1]);
    518 	} else if (id.op == NPF_OP_IRG) {
    519 		snprintf(buf, size, "%u >< %u", id.id[0], id.id[1]);
    520 	} else if (id.op == NPF_OP_EQ ) {
    521 		snprintf(buf, size, "%u", id.id[0]);
    522 	} else if (id.op == NPF_OP_NE) {
    523 		snprintf(buf, size, "!= %u", id.id[0]);
    524 	} else if (id.op == NPF_OP_LE) {
    525 		snprintf(buf, size, "<= %u", id.id[0]);
    526 	} else if (id.op == NPF_OP_LT) {
    527 		snprintf(buf, size, "< %u", id.id[0]);
    528 	} else if (id.op == NPF_OP_GE) {
    529 		snprintf(buf, size, ">= %u", id.id[0]);
    530 	} else if (id.op == NPF_OP_GT) {
    531 		snprintf(buf, size, "> %u", id.id[0]);
    532 	} else {
    533 		return NULL;
    534 	}
    535 	return buf;
    536 }
    537 #define BUF_SIZE	40
    538 static void
    539 npfctl_print_rule(npf_conf_info_t *ctx, nl_rule_t *rl, unsigned level)
    540 {
    541 	const uint32_t attr = npf_rule_getattr(rl);
    542 	const char *rproc, *ifname, *name;
    543 	bool dyn_ruleset;
    544 	struct r_id rid;
    545 	char buf[BUF_SIZE];
    546 
    547 	/* Rule attributes/flags. */
    548 	for (unsigned i = 0; i < __arraycount(attr_keyword_map); i++) {
    549 		const struct attr_keyword_mapent *ak = &attr_keyword_map[i];
    550 
    551 		if (i == NAME_AT && (name = npf_rule_getname(rl)) != NULL) {
    552 			ctx->fpos += fprintf(ctx->fp, "\"%s\" ", name);
    553 		}
    554 		if ((attr & ak->mask) == ak->flags) {
    555 			ctx->fpos += fprintf(ctx->fp, "%s ", ak->val);
    556 		}
    557 	}
    558 	if ((ifname = npf_rule_getinterface(rl)) != NULL) {
    559 		ctx->fpos += fprintf(ctx->fp, "on %s ", ifname);
    560 	}
    561 	if (attr == (NPF_RULE_GROUP | NPF_RULE_IN | NPF_RULE_OUT) && !ifname) {
    562 		/* The default group is a special case. */
    563 		ctx->fpos += fprintf(ctx->fp, "default ");
    564 	}
    565 	if ((attr & NPF_DYNAMIC_GROUP) == NPF_RULE_GROUP) {
    566 		/* Group; done. */
    567 		ctx->fpos += fprintf(ctx->fp, "{ ");
    568 		ctx->glevel = level;
    569 		goto out;
    570 	}
    571 
    572 	/* Print filter criteria. */
    573 	dyn_ruleset = (attr & NPF_DYNAMIC_GROUP) == NPF_DYNAMIC_GROUP;
    574 	if (!npfctl_print_filter(ctx, rl) && !dyn_ruleset) {
    575 		ctx->fpos += fprintf(ctx->fp, "all ");
    576 	}
    577 
    578 	if (!npf_rule_getrid(&rid, rl, "r_user")) {
    579 		ctx->fpos += fprintf(ctx->fp, "user %s ", print_guid(buf, rid, BUF_SIZE));
    580 	}
    581 
    582 	if (!npf_rule_getrid(&rid, rl, "r_group")) {
    583 		ctx->fpos += fprintf(ctx->fp, "group %s ", print_guid(buf, rid, BUF_SIZE));
    584 	}
    585 
    586 	/* Rule procedure. */
    587 	if ((rproc = npf_rule_getproc(rl)) != NULL) {
    588 		ctx->fpos += fprintf(ctx->fp, "apply \"%s\" ", rproc);
    589 	}
    590 out:
    591 	npfctl_print_id(ctx, rl);
    592 	ctx->fpos += fprintf(ctx->fp, "\n");
    593 }
    594 
    595 static void
    596 npfctl_print_nat(npf_conf_info_t *ctx, nl_nat_t *nt)
    597 {
    598 	const unsigned dynamic_natset = NPF_RULE_GROUP | NPF_RULE_DYNAMIC;
    599 	nl_rule_t *rl = (nl_nat_t *)nt;
    600 	const char *ifname, *algo, *seg1, *seg2, *arrow;
    601 	const npf_addr_t *addr;
    602 	npf_netmask_t mask;
    603 	in_port_t port;
    604 	size_t alen;
    605 	unsigned flags;
    606 	char *seg;
    607 
    608 	/* Get flags and the interface. */
    609 	flags = npf_nat_getflags(nt);
    610 	ifname = npf_rule_getinterface(rl);
    611 	assert(ifname != NULL);
    612 
    613 	if ((npf_rule_getattr(rl) & dynamic_natset) == dynamic_natset) {
    614 		const char *name = npf_rule_getname(rl);
    615 		ctx->fpos += fprintf(ctx->fp,
    616 		    "map ruleset \"%s\" on %s\n", name, ifname);
    617 		return;
    618 	}
    619 
    620 	/* Get the translation address or table (and port, if used). */
    621 	addr = npf_nat_getaddr(nt, &alen, &mask);
    622 	if (addr) {
    623 		seg = npfctl_print_addrmask(alen, "%a", addr, mask);
    624 	} else {
    625 		const unsigned tid = npf_nat_gettable(nt);
    626 		const char *tname;
    627 		bool ifaddr;
    628 
    629 		tname = npfctl_table_getname(ctx->conf, tid, &ifaddr);
    630 		easprintf(&seg, ifaddr ? "ifaddrs(%s)" : "<%s>", tname);
    631 	}
    632 
    633 	if ((port = npf_nat_getport(nt)) != 0) {
    634 		char *p;
    635 		easprintf(&p, "%s port %u", seg, ntohs(port));
    636 		free(seg), seg = p;
    637 	}
    638 	seg1 = seg2 = "any";
    639 
    640 	/* Get the NAT type and determine the translation segment. */
    641 	switch (npf_nat_gettype(nt)) {
    642 	case NPF_NATIN:
    643 		arrow = "<-";
    644 		seg1 = seg;
    645 		break;
    646 	case NPF_NATOUT:
    647 		arrow = "->";
    648 		seg2 = seg;
    649 		break;
    650 	default:
    651 		abort();
    652 	}
    653 
    654 	/* NAT algorithm. */
    655 	switch (npf_nat_getalgo(nt)) {
    656 	case NPF_ALGO_NETMAP:
    657 		algo = "algo netmap ";
    658 		break;
    659 	case NPF_ALGO_IPHASH:
    660 		algo = "algo ip-hash ";
    661 		break;
    662 	case NPF_ALGO_RR:
    663 		algo = "algo round-robin ";
    664 		break;
    665 	case NPF_ALGO_NPT66:
    666 		algo = "algo npt66 ";
    667 		break;
    668 	default:
    669 		algo = "";
    670 		break;
    671 	}
    672 
    673 	/* XXX also handle "any" */
    674 
    675 	/* Print out the NAT policy with the filter criteria. */
    676 	ctx->fpos += fprintf(ctx->fp, "map %s %s %s%s%s %s %s pass ",
    677 	    ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic",
    678 	    algo, (flags & NPF_NAT_PORTS) ? "" : "no-ports ",
    679 	    seg1, arrow, seg2);
    680 	npfctl_print_filter(ctx, rl);
    681 	npfctl_print_id(ctx, rl);
    682 	ctx->fpos += fprintf(ctx->fp, "\n");
    683 	free(seg);
    684 }
    685 
    686 static void
    687 npfctl_print_table(npf_conf_info_t *ctx, nl_table_t *tl)
    688 {
    689 	const char *name = npf_table_getname(tl);
    690 	const unsigned type = npf_table_gettype(tl);
    691 	const char *table_types[] = {
    692 		[NPF_TABLE_IPSET]	= "ipset",
    693 		[NPF_TABLE_LPM]		= "lpm",
    694 		[NPF_TABLE_CONST]	= "const",
    695 	};
    696 
    697 	if (name[0] == '.') {
    698 		/* Internal tables use dot and are hidden. */
    699 		return;
    700 	}
    701 	assert(type < __arraycount(table_types));
    702 	ctx->fpos += fprintf(ctx->fp,
    703 	    "table <%s> type %s\n", name, table_types[type]);
    704 }
    705 
    706 static void
    707 npfctl_print_params(npf_conf_info_t *ctx, nl_config_t *ncf)
    708 {
    709 	nl_iter_t i = NPF_ITER_BEGIN;
    710 	int val, defval, *dval;
    711 	const char *name;
    712 
    713 	dval = ctx->validating ? NULL : &defval;
    714 	while ((name = npf_param_iterate(ncf, &i, &val, dval)) != NULL) {
    715 		if (dval && val == *dval) {
    716 			continue;
    717 		}
    718 		ctx->fpos += fprintf(ctx->fp, "set %s %d\n", name, val);
    719 	}
    720 	print_linesep(ctx);
    721 }
    722 
    723 int
    724 npfctl_config_show(int fd)
    725 {
    726 	npf_conf_info_t *ctx = npfctl_show_init();
    727 	nl_config_t *ncf;
    728 	bool loaded;
    729 
    730 	if (fd) {
    731 		ncf = npf_config_retrieve(fd);
    732 		if (ncf == NULL) {
    733 			return errno;
    734 		}
    735 		loaded = npf_config_loaded_p(ncf);
    736 		ctx->validating = false;
    737 		ctx->fpos += fprintf(ctx->fp,
    738 		    "# filtering:\t%s\n# config:\t%s\n",
    739 		    npf_config_active_p(ncf) ? "active" : "inactive",
    740 		    loaded ? "loaded" : "empty");
    741 		print_linesep(ctx);
    742 	} else {
    743 		ncf = npfctl_config_ref();
    744 		npfctl_config_build();
    745 		ctx->validating = true;
    746 		loaded = true;
    747 	}
    748 	ctx->conf = ncf;
    749 
    750 	if (loaded) {
    751 		nl_rule_t *rl;
    752 		nl_rproc_t *rp;
    753 		nl_nat_t *nt;
    754 		nl_table_t *tl;
    755 		nl_iter_t i;
    756 		unsigned level;
    757 
    758 		npfctl_print_params(ctx, ncf);
    759 
    760 		i = NPF_ITER_BEGIN;
    761 		while ((tl = npf_table_iterate(ncf, &i)) != NULL) {
    762 			npfctl_print_table(ctx, tl);
    763 		}
    764 		print_linesep(ctx);
    765 
    766 		i = NPF_ITER_BEGIN;
    767 		while ((rp = npf_rproc_iterate(ncf, &i)) != NULL) {
    768 			const char *rpname = npf_rproc_getname(rp);
    769 			ctx->fpos += fprintf(ctx->fp,
    770 			    "procedure \"%s\"\n", rpname);
    771 		}
    772 		print_linesep(ctx);
    773 
    774 		i = NPF_ITER_BEGIN;
    775 		while ((nt = npf_nat_iterate(ncf, &i)) != NULL) {
    776 			npfctl_print_nat(ctx, nt);
    777 		}
    778 		print_linesep(ctx);
    779 
    780 		i = NPF_ITER_BEGIN;
    781 		while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
    782 			print_indent(ctx, level);
    783 			npfctl_print_rule(ctx, rl, level);
    784 		}
    785 		print_indent(ctx, 0);
    786 	}
    787 	npf_config_destroy(ncf);
    788 	return 0;
    789 }
    790 
    791 int
    792 npfctl_ruleset_show(int fd, const char *ruleset_name)
    793 {
    794 	npf_conf_info_t *ctx = npfctl_show_init();
    795 	nl_config_t *ncf;
    796 	nl_rule_t *rl;
    797 	unsigned level;
    798 	nl_iter_t i;
    799 	int error;
    800 
    801 	ncf = npf_config_create();
    802 	ctx->conf = ncf;
    803 
    804 	if ((error = _npf_ruleset_list(fd, ruleset_name, ncf)) != 0) {
    805 		return error;
    806 	}
    807 	i = NPF_ITER_BEGIN;
    808 	while ((rl = npf_rule_iterate(ncf, &i, &level)) != NULL) {
    809 		npfctl_print_rule(ctx, rl, 0);
    810 	}
    811 	npf_config_destroy(ncf);
    812 	return error;
    813 }
    814