Home | History | Annotate | Line # | Download | only in npfctl
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