1/*-
2 * Copyright (c) 2010-2020 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * This material is based upon work partially supported by The
6 * NetBSD Foundation under a contract with 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#include <sys/cdefs.h>
31__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.52 2023/08/08 10:36:04 riastradh Exp $");
32
33#include <sys/types.h>
34#include <sys/mman.h>
35#include <sys/stat.h>
36#if !defined(_NPF_STANDALONE)
37#include <sys/ioctl.h>
38#endif
39#include <netinet/in_systm.h>
40#include <netinet/in.h>
41#include <net/if.h>
42
43#include <stdlib.h>
44#include <string.h>
45#include <assert.h>
46#include <unistd.h>
47#include <errno.h>
48#include <err.h>
49
50#include <nv.h>
51#include <dnv.h>
52
53#include <cdbw.h>
54
55#define	_NPF_PRIVATE
56#include "npf.h"
57
58struct nl_rule {
59	nvlist_t *	rule_dict;
60};
61
62struct nl_rproc {
63	nvlist_t *	rproc_dict;
64};
65
66struct nl_table {
67	nvlist_t *	table_dict;
68};
69
70struct nl_alg {
71	nvlist_t *	alg_dict;
72};
73
74struct nl_ext {
75	nvlist_t *	ext_dict;
76};
77
78struct nl_config {
79	nvlist_t *	ncf_dict;
80
81	/* Temporary rule list. */
82	nvlist_t **	ncf_rule_list;
83	unsigned	ncf_rule_count;
84
85	/* Iterators. */
86	unsigned	ncf_reduce[16];
87	unsigned	ncf_nlevel;
88
89	nl_rule_t	ncf_cur_rule;
90	nl_table_t	ncf_cur_table;
91	nl_rproc_t	ncf_cur_rproc;
92};
93
94/*
95 * Various helper routines.
96 */
97
98static bool
99_npf_add_addr(nvlist_t *nvl, const char *name, int af, const npf_addr_t *addr)
100{
101	size_t sz;
102
103	if (af == AF_INET) {
104		sz = sizeof(struct in_addr);
105	} else if (af == AF_INET6) {
106		sz = sizeof(struct in6_addr);
107	} else {
108		return false;
109	}
110	nvlist_add_binary(nvl, name, addr, sz);
111	return nvlist_error(nvl) == 0;
112}
113
114static unsigned
115_npf_get_addr(const nvlist_t *nvl, const char *name, npf_addr_t *addr)
116{
117	const void *d;
118	size_t sz = 0;
119
120	d = nvlist_get_binary(nvl, name, &sz);
121	switch (sz) {
122	case sizeof(struct in_addr):
123	case sizeof(struct in6_addr):
124		memcpy(addr, d, sz);
125		return (unsigned)sz;
126	}
127	return 0;
128}
129
130static bool
131_npf_dataset_lookup(const nvlist_t *dict, const char *dataset,
132    const char *key, const char *name)
133{
134	const nvlist_t * const *items;
135	size_t nitems;
136
137	if (!nvlist_exists_nvlist_array(dict, dataset)) {
138		return false;
139	}
140	items = nvlist_get_nvlist_array(dict, dataset, &nitems);
141	for (unsigned i = 0; i < nitems; i++) {
142		const char *item_name;
143
144		item_name = dnvlist_get_string(items[i], key, NULL);
145		if (item_name && strcmp(item_name, name) == 0) {
146			return true;
147		}
148	}
149	return false;
150}
151
152static const nvlist_t *
153_npf_dataset_getelement(nvlist_t *dict, const char *dataset, unsigned i)
154{
155	const nvlist_t * const *items;
156	size_t nitems;
157
158	if (!nvlist_exists_nvlist_array(dict, dataset)) {
159		return NULL;
160	}
161	items = nvlist_get_nvlist_array(dict, dataset, &nitems);
162	if (i < nitems) {
163		return items[i];
164	}
165	return NULL;
166}
167
168/*
169 * _npf_rules_process: transform the ruleset representing nested rules
170 * with sublists into a single array with skip-to marks.
171 */
172static void
173_npf_rules_process(nl_config_t *ncf, nvlist_t *dict, const char *key)
174{
175	nvlist_t **items;
176	size_t nitems;
177
178	if (!nvlist_exists_nvlist_array(dict, key)) {
179		return;
180	}
181	items = nvlist_take_nvlist_array(dict, key, &nitems);
182	for (unsigned i = 0; i < nitems; i++) {
183		nvlist_t *rule_dict = items[i];
184		size_t len = (ncf->ncf_rule_count + 1) * sizeof(nvlist_t *);
185		void *p = realloc(ncf->ncf_rule_list, len);
186
187		/*
188		 * - Add rule to the transformed array.
189		 * - Process subrules recursively.
190		 * - Add the skip-to position.
191		 */
192		ncf->ncf_rule_list = p;
193		ncf->ncf_rule_list[ncf->ncf_rule_count] = rule_dict;
194		ncf->ncf_rule_count++;
195
196		if (nvlist_exists_nvlist_array(rule_dict, "subrules")) {
197			unsigned idx;
198
199			_npf_rules_process(ncf, rule_dict, "subrules");
200			idx = ncf->ncf_rule_count; // post-recursion index
201			nvlist_add_number(rule_dict, "skip-to", idx);
202		}
203		assert(nvlist_error(rule_dict) == 0);
204	}
205	free(items);
206}
207
208/*
209 * _npf_init_error: initialize the error structure with the message
210 * from the current error number
211 */
212static int
213_npf_init_error(int error, npf_error_t *errinfo)
214{
215	if (error && errinfo) {
216		memset(errinfo, 0, sizeof(*errinfo));
217		errinfo->error_msg = strerror(error);
218	}
219	return error;
220}
221
222/*
223 * _npf_extract_error: check the error number field and extract the
224 * error details into the npf_error_t structure.
225 */
226static int
227_npf_extract_error(nvlist_t *resp, npf_error_t *errinfo)
228{
229	int error;
230
231	error = dnvlist_get_number(resp, "errno", 0);
232	if (error && errinfo) {
233		memset(errinfo, 0, sizeof(npf_error_t));
234
235		errinfo->id = dnvlist_get_number(resp, "id", 0);
236		errinfo->error_msg =
237		    dnvlist_take_string(resp, "error-msg", NULL);
238		errinfo->source_file =
239		    dnvlist_take_string(resp, "source-file", NULL);
240		errinfo->source_line =
241		    dnvlist_take_number(resp, "source-line", 0);
242	}
243	return error;
244}
245
246/*
247 * npf_xfer_fd: transfer the given request and receive a response.
248 *
249 * => Sets the 'operation' key on the 'req' dictionary.
250 * => On success: returns 0 and valid nvlist in 'resp'.
251 * => On failure: returns an error number.
252 */
253static int
254_npf_xfer_fd(int fd, unsigned long cmd, nvlist_t *req, nvlist_t **resp)
255{
256	struct stat st;
257	int kernver;
258
259	/*
260	 * Set the NPF version and operation.
261	 */
262	if (!nvlist_exists(req, "version")) {
263		nvlist_add_number(req, "version", NPF_VERSION);
264	}
265	nvlist_add_number(req, "operation", cmd);
266
267	/*
268	 * Determine the type of file descriptor:
269	 * - If socket, then perform nvlist_send()/nvlist_recv().
270	 * - If a character device, then use ioctl.
271	 */
272	if (fstat(fd, &st) == -1) {
273		goto err;
274	}
275	switch (st.st_mode & S_IFMT) {
276#if !defined(__NetBSD__)
277	case S_IFSOCK:
278		if (nvlist_send(fd, req) == -1) {
279			goto err;
280		}
281		if (resp && (*resp = nvlist_recv(fd, 0)) == NULL) {
282			goto err;
283		}
284		break;
285#endif
286#if !defined(_NPF_STANDALONE)
287	case S_IFBLK:
288	case S_IFCHR:
289		if (ioctl(fd, IOC_NPF_VERSION, &kernver) == -1) {
290			goto err;
291		}
292		if (kernver != NPF_VERSION) {
293			errno = EPROGMISMATCH;
294			goto err;
295		}
296		if (nvlist_xfer_ioctl(fd, cmd, req, resp) == -1) {
297			goto err;
298		}
299		break;
300#else
301		(void)kernver;
302#endif
303	default:
304		errno = ENOTSUP;
305		goto err;
306	}
307	return 0;
308err:
309	return errno ? errno : EIO;
310}
311
312/*
313 * npf_xfer_fd_errno: same as npf_xfer_fd(), but:
314 *
315 * => After successful retrieval of the response, inspects it, extracts
316 *    the 'errno' value (if any) and returns it.
317 * => Destroys the response.
318 */
319static int
320_npf_xfer_fd_errno(int fd, unsigned long cmd, nvlist_t *req)
321{
322	nvlist_t *resp;
323	int error;
324
325	error = _npf_xfer_fd(fd, cmd, req, &resp);
326	if (error) {
327		return error;
328	}
329	error = _npf_extract_error(resp, NULL);
330	nvlist_destroy(resp);
331	return error;
332}
333
334/*
335 * CONFIGURATION INTERFACE.
336 */
337
338nl_config_t *
339npf_config_create(void)
340{
341	nl_config_t *ncf;
342
343	ncf = calloc(1, sizeof(nl_config_t));
344	if (!ncf) {
345		return NULL;
346	}
347	ncf->ncf_dict = nvlist_create(0);
348	nvlist_add_number(ncf->ncf_dict, "version", NPF_VERSION);
349	return ncf;
350}
351
352int
353npf_config_submit(nl_config_t *ncf, int fd, npf_error_t *errinfo)
354{
355	nvlist_t *resp = NULL;
356	int error;
357
358	/* Ensure the config is built. */
359	(void)npf_config_build(ncf);
360
361	error = _npf_xfer_fd(fd, IOC_NPF_LOAD, ncf->ncf_dict, &resp);
362	if (error) {
363		return _npf_init_error(errno, errinfo);
364	}
365	error = _npf_extract_error(resp, errinfo);
366	nvlist_destroy(resp);
367	return error;
368}
369
370nl_config_t *
371npf_config_retrieve(int fd)
372{
373	nl_config_t *ncf;
374	nvlist_t *req, *resp = NULL;
375	int error;
376
377	ncf = calloc(1, sizeof(nl_config_t));
378	if (!ncf) {
379		return NULL;
380	}
381
382	req = nvlist_create(0);
383	error = _npf_xfer_fd(fd, IOC_NPF_SAVE, req, &resp);
384	nvlist_destroy(req);
385
386	if (error || _npf_extract_error(resp, NULL) != 0) {
387		nvlist_destroy(resp);
388		free(ncf);
389		return NULL;
390	}
391	ncf->ncf_dict = resp;
392	return ncf;
393}
394
395void *
396npf_config_export(nl_config_t *ncf, size_t *length)
397{
398	/* Ensure the config is built. */
399	(void)npf_config_build(ncf);
400	return nvlist_pack(ncf->ncf_dict, length);
401}
402
403nl_config_t *
404npf_config_import(const void *blob, size_t len)
405{
406	nl_config_t *ncf;
407
408	ncf = calloc(1, sizeof(nl_config_t));
409	if (!ncf) {
410		return NULL;
411	}
412	ncf->ncf_dict = nvlist_unpack(blob, len, 0);
413	if (!ncf->ncf_dict) {
414		free(ncf);
415		return NULL;
416	}
417	return ncf;
418}
419
420int
421npf_config_flush(int fd)
422{
423	nl_config_t *ncf;
424	npf_error_t errinfo;
425	int error;
426
427	ncf = npf_config_create();
428	if (!ncf) {
429		return ENOMEM;
430	}
431	nvlist_add_bool(ncf->ncf_dict, "flush", true);
432	error = npf_config_submit(ncf, fd, &errinfo);
433	npf_config_destroy(ncf);
434	return error;
435}
436
437bool
438npf_config_active_p(nl_config_t *ncf)
439{
440	return dnvlist_get_bool(ncf->ncf_dict, "active", false);
441}
442
443bool
444npf_config_loaded_p(nl_config_t *ncf)
445{
446	return nvlist_exists_nvlist_array(ncf->ncf_dict, "rules");
447}
448
449const void *
450npf_config_build(nl_config_t *ncf)
451{
452	_npf_rules_process(ncf, ncf->ncf_dict, "__rules");
453	if (ncf->ncf_rule_list) {
454		/* Set the transformed ruleset. */
455		nvlist_move_nvlist_array(ncf->ncf_dict, "rules",
456		    ncf->ncf_rule_list, ncf->ncf_rule_count);
457
458		/* Clear the temporary list. */
459		ncf->ncf_rule_list = NULL;
460		ncf->ncf_rule_count = 0;
461	}
462	assert(nvlist_error(ncf->ncf_dict) == 0);
463	return (void *)ncf->ncf_dict;
464}
465
466void
467npf_config_destroy(nl_config_t *ncf)
468{
469	nvlist_destroy(ncf->ncf_dict);
470	free(ncf);
471}
472
473/*
474 * PARAMETERS.
475 */
476
477int
478npf_param_get(nl_config_t *ncf, const char *name, int *valp)
479{
480	const nvlist_t *params;
481
482	params = dnvlist_get_nvlist(ncf->ncf_dict, "params", NULL);
483	if (params == NULL || !nvlist_exists(params, name)) {
484		return ENOENT;
485	}
486	*valp = (int)dnvlist_get_number(params, name, 0);
487	return 0;
488}
489
490int
491npf_param_set(nl_config_t *ncf, const char *name, int val)
492{
493	nvlist_t *params;
494
495	/* Ensure params dictionary. */
496	if (nvlist_exists(ncf->ncf_dict, "params")) {
497		params = nvlist_take_nvlist(ncf->ncf_dict, "params");
498	} else {
499		params = nvlist_create(0);
500	}
501
502	/*
503	 * If the parameter is already set, then free it first.
504	 * Set the parameter.  Note: values can be negative.
505	 */
506	if (nvlist_exists(params, name)) {
507		nvlist_free_number(params, name);
508	}
509	nvlist_add_number(params, name, (uint64_t)val);
510	nvlist_add_nvlist(ncf->ncf_dict, "params", params);
511	return 0;
512}
513
514const char *
515npf_param_iterate(nl_config_t *ncf, nl_iter_t *iter, int *val, int *defval)
516{
517	void *cookie = (void *)(intptr_t)*iter;
518	const nvlist_t *params, *dparams;
519	const char *name;
520	int type;
521
522	assert(sizeof(nl_iter_t) >= sizeof(void *));
523
524	params = dnvlist_get_nvlist(ncf->ncf_dict, "params", NULL);
525	if (params == NULL) {
526		return NULL;
527	}
528skip:
529	if ((name = nvlist_next(params, &type, &cookie)) == NULL) {
530		*iter = NPF_ITER_BEGIN;
531		return NULL;
532	}
533	if (type != NV_TYPE_NUMBER) {
534		goto skip; // should never happen, though
535	}
536	if (defval) {
537		dparams = dnvlist_get_nvlist(ncf->ncf_dict,
538		    "params-defaults", NULL);
539		if (dparams == NULL) {
540			errno = EINVAL;
541			return NULL;
542		}
543		*defval = (int)nvlist_get_number(dparams, name);
544	}
545
546	*val = (int)nvlist_get_number(params, name);
547	*iter = (intptr_t)cookie;
548	return name;
549}
550
551/*
552 * DYNAMIC RULESET INTERFACE.
553 */
554
555static inline bool
556_npf_nat_ruleset_p(const char *name)
557{
558	return strncmp(name, NPF_RULESET_MAP_PREF,
559	    sizeof(NPF_RULESET_MAP_PREF) - 1) == 0;
560}
561
562int
563npf_ruleset_add(int fd, const char *rname, nl_rule_t *rl, uint64_t *id)
564{
565	const bool natset = _npf_nat_ruleset_p(rname);
566	nvlist_t *rule_nvl = rl->rule_dict, *resp;
567	int error;
568
569	nvlist_add_number(rule_nvl, "attr",
570	    NPF_RULE_DYNAMIC | nvlist_take_number(rule_nvl, "attr"));
571
572	if (natset && !dnvlist_get_bool(rule_nvl, "nat-rule", false)) {
573		errno = EINVAL;
574		return errno;
575	}
576	nvlist_add_string(rule_nvl, "ruleset-name", rname);
577	nvlist_add_bool(rule_nvl, "nat-ruleset", natset);
578	nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_ADD);
579
580	error = _npf_xfer_fd(fd, IOC_NPF_RULE, rule_nvl, &resp);
581	if (error) {
582		return error;
583	}
584	*id = nvlist_get_number(resp, "id");
585	nvlist_destroy(resp);
586	return 0;
587}
588
589int
590npf_ruleset_remove(int fd, const char *rname, uint64_t id)
591{
592	const bool natset = _npf_nat_ruleset_p(rname);
593	nvlist_t *rule_nvl = nvlist_create(0);
594	int error;
595
596	nvlist_add_string(rule_nvl, "ruleset-name", rname);
597	nvlist_add_bool(rule_nvl, "nat-ruleset", natset);
598	nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_REMOVE);
599	nvlist_add_number(rule_nvl, "id", id);
600
601	error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl);
602	nvlist_destroy(rule_nvl);
603	return error;
604}
605
606int
607npf_ruleset_remkey(int fd, const char *rname, const void *key, size_t len)
608{
609	const bool natset = _npf_nat_ruleset_p(rname);
610	nvlist_t *rule_nvl = nvlist_create(0);
611	int error;
612
613	nvlist_add_string(rule_nvl, "ruleset-name", rname);
614	nvlist_add_bool(rule_nvl, "nat-ruleset", natset);
615	nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_REMKEY);
616	nvlist_add_binary(rule_nvl, "key", key, len);
617
618	error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl);
619	nvlist_destroy(rule_nvl);
620	return error;
621}
622
623int
624npf_ruleset_flush(int fd, const char *rname)
625{
626	const bool natset = _npf_nat_ruleset_p(rname);
627	nvlist_t *rule_nvl = nvlist_create(0);
628	int error;
629
630	nvlist_add_string(rule_nvl, "ruleset-name", rname);
631	nvlist_add_bool(rule_nvl, "nat-ruleset", natset);
632	nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_FLUSH);
633
634	error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl);
635	nvlist_destroy(rule_nvl);
636	return error;
637}
638
639/*
640 * NPF EXTENSION INTERFACE.
641 */
642
643nl_ext_t *
644npf_ext_construct(const char *name)
645{
646	nl_ext_t *ext;
647
648	ext = malloc(sizeof(*ext));
649	if (!ext) {
650		return NULL;
651	}
652	ext->ext_dict = nvlist_create(0);
653	nvlist_add_string(ext->ext_dict, "name", name);
654	return ext;
655}
656
657void
658npf_ext_param_u32(nl_ext_t *ext, const char *key, uint32_t val)
659{
660	nvlist_add_number(ext->ext_dict, key, val);
661}
662
663void
664npf_ext_param_bool(nl_ext_t *ext, const char *key, bool val)
665{
666	nvlist_add_bool(ext->ext_dict, key, val);
667}
668
669void
670npf_ext_param_string(nl_ext_t *ext, const char *key, const char *val)
671{
672	nvlist_add_string(ext->ext_dict, key, val);
673}
674
675/*
676 * RULE INTERFACE.
677 */
678
679nl_rule_t *
680npf_rule_create(const char *name, uint32_t attr, const char *ifname)
681{
682	nl_rule_t *rl;
683
684	rl = malloc(sizeof(nl_rule_t));
685	if (!rl) {
686		return NULL;
687	}
688	rl->rule_dict = nvlist_create(0);
689	nvlist_add_number(rl->rule_dict, "attr", attr);
690	if (name) {
691		nvlist_add_string(rl->rule_dict, "name", name);
692	}
693	if (ifname) {
694		nvlist_add_string(rl->rule_dict, "ifname", ifname);
695	}
696	return rl;
697}
698
699int
700npf_rule_setcode(nl_rule_t *rl, int type, const void *code, size_t len)
701{
702	if (type != NPF_CODE_BPF) {
703		return ENOTSUP;
704	}
705	nvlist_add_number(rl->rule_dict, "code-type", (unsigned)type);
706	nvlist_add_binary(rl->rule_dict, "code", code, len);
707	return nvlist_error(rl->rule_dict);
708}
709
710int
711npf_rule_setkey(nl_rule_t *rl, const void *key, size_t len)
712{
713	nvlist_add_binary(rl->rule_dict, "key", key, len);
714	return nvlist_error(rl->rule_dict);
715}
716
717int
718npf_rule_setinfo(nl_rule_t *rl, const void *info, size_t len)
719{
720	nvlist_add_binary(rl->rule_dict, "info", info, len);
721	return nvlist_error(rl->rule_dict);
722}
723
724int
725npf_rule_setprio(nl_rule_t *rl, int pri)
726{
727	nvlist_add_number(rl->rule_dict, "prio", (uint64_t)pri);
728	return nvlist_error(rl->rule_dict);
729}
730
731int
732npf_rule_setproc(nl_rule_t *rl, const char *name)
733{
734	nvlist_add_string(rl->rule_dict, "rproc", name);
735	return nvlist_error(rl->rule_dict);
736}
737
738void *
739npf_rule_export(nl_rule_t *rl, size_t *length)
740{
741	return nvlist_pack(rl->rule_dict, length);
742}
743
744bool
745npf_rule_exists_p(nl_config_t *ncf, const char *name)
746{
747	const char *key = nvlist_exists_nvlist_array(ncf->ncf_dict,
748	    "rules") ? "rules" : "__rules"; // config may not be built yet
749	return _npf_dataset_lookup(ncf->ncf_dict, key, "name", name);
750}
751
752int
753npf_rule_insert(nl_config_t *ncf, nl_rule_t *parent, nl_rule_t *rl)
754{
755	nvlist_t *rule_dict = rl->rule_dict;
756	nvlist_t *target;
757	const char *key;
758
759	if (parent) {
760		/* Subrule of the parent. */
761		target = parent->rule_dict;
762		key = "subrules";
763	} else {
764		/* Global ruleset. */
765		target = ncf->ncf_dict;
766		key = "__rules";
767	}
768	nvlist_append_nvlist_array(target, key, rule_dict);
769	nvlist_destroy(rule_dict);
770	free(rl);
771	return 0;
772}
773
774static nl_rule_t *
775_npf_rule_iterate1(nl_config_t *ncf, const char *key,
776    nl_iter_t *iter, unsigned *level)
777{
778	unsigned i = *iter;
779	const nvlist_t *rule_dict;
780	uint32_t skipto;
781
782	if (i == 0) {
783		/* Initialise the iterator. */
784		ncf->ncf_nlevel = 0;
785		ncf->ncf_reduce[0] = 0;
786	}
787
788	rule_dict = _npf_dataset_getelement(ncf->ncf_dict, key, i);
789	if (!rule_dict) {
790		*iter = NPF_ITER_BEGIN;
791		return NULL;
792	}
793	*iter = i + 1; // next
794	*level = ncf->ncf_nlevel;
795
796	skipto = dnvlist_get_number(rule_dict, "skip-to", 0);
797	if (skipto) {
798		ncf->ncf_nlevel++;
799		ncf->ncf_reduce[ncf->ncf_nlevel] = skipto;
800	}
801	if (ncf->ncf_reduce[ncf->ncf_nlevel] == (i + 1)) {
802		assert(ncf->ncf_nlevel > 0);
803		ncf->ncf_nlevel--;
804	}
805
806	ncf->ncf_cur_rule.rule_dict = __UNCONST(rule_dict); // XXX
807	return &ncf->ncf_cur_rule;
808}
809
810nl_rule_t *
811npf_rule_iterate(nl_config_t *ncf, nl_iter_t *iter, unsigned *level)
812{
813	return _npf_rule_iterate1(ncf, "rules", iter, level);
814}
815
816const char *
817npf_rule_getname(nl_rule_t *rl)
818{
819	return dnvlist_get_string(rl->rule_dict, "name", NULL);
820}
821
822uint32_t
823npf_rule_getattr(nl_rule_t *rl)
824{
825	return dnvlist_get_number(rl->rule_dict, "attr", 0);
826}
827
828const char *
829npf_rule_getinterface(nl_rule_t *rl)
830{
831	return dnvlist_get_string(rl->rule_dict, "ifname", NULL);
832}
833
834const void *
835npf_rule_getinfo(nl_rule_t *rl, size_t *len)
836{
837	return dnvlist_get_binary(rl->rule_dict, "info", len, NULL, 0);
838}
839
840const char *
841npf_rule_getproc(nl_rule_t *rl)
842{
843	return dnvlist_get_string(rl->rule_dict, "rproc", NULL);
844}
845
846uint64_t
847npf_rule_getid(nl_rule_t *rl)
848{
849	return dnvlist_get_number(rl->rule_dict, "id", 0);
850}
851
852const void *
853npf_rule_getcode(nl_rule_t *rl, int *type, size_t *len)
854{
855	*type = (int)dnvlist_get_number(rl->rule_dict, "code-type", 0);
856	return dnvlist_get_binary(rl->rule_dict, "code", len, NULL, 0);
857}
858
859int
860_npf_ruleset_list(int fd, const char *rname, nl_config_t *ncf)
861{
862	const bool natset = _npf_nat_ruleset_p(rname);
863	nvlist_t *req, *resp;
864	int error;
865
866	req = nvlist_create(0);
867	nvlist_add_string(req, "ruleset-name", rname);
868	nvlist_add_bool(req, "nat-ruleset", natset);
869	nvlist_add_number(req, "command", NPF_CMD_RULE_LIST);
870
871	error = _npf_xfer_fd(fd, IOC_NPF_RULE, req, &resp);
872	nvlist_destroy(req);
873	if (error) {
874		return error;
875	}
876
877	if (nvlist_exists_nvlist_array(resp, "rules")) {
878		nvlist_t **rules;
879		size_t n;
880
881		rules = nvlist_take_nvlist_array(resp, "rules", &n);
882		nvlist_move_nvlist_array(ncf->ncf_dict, "rules", rules, n);
883	}
884	nvlist_destroy(resp);
885	return 0;
886}
887
888void
889npf_rule_destroy(nl_rule_t *rl)
890{
891	nvlist_destroy(rl->rule_dict);
892	free(rl);
893}
894
895/*
896 * RULE PROCEDURE INTERFACE.
897 */
898
899nl_rproc_t *
900npf_rproc_create(const char *name)
901{
902	nl_rproc_t *rp;
903
904	rp = malloc(sizeof(nl_rproc_t));
905	if (!rp) {
906		return NULL;
907	}
908	rp->rproc_dict = nvlist_create(0);
909	nvlist_add_string(rp->rproc_dict, "name", name);
910	return rp;
911}
912
913int
914npf_rproc_extcall(nl_rproc_t *rp, nl_ext_t *ext)
915{
916	nvlist_t *rproc_dict = rp->rproc_dict;
917	const char *name = dnvlist_get_string(ext->ext_dict, "name", NULL);
918
919	if (_npf_dataset_lookup(rproc_dict, "extcalls", "name", name)) {
920		return EEXIST;
921	}
922	nvlist_append_nvlist_array(rproc_dict, "extcalls", ext->ext_dict);
923	nvlist_destroy(ext->ext_dict);
924	free(ext);
925	return 0;
926}
927
928bool
929npf_rproc_exists_p(nl_config_t *ncf, const char *name)
930{
931	return _npf_dataset_lookup(ncf->ncf_dict, "rprocs", "name", name);
932}
933
934int
935npf_rproc_insert(nl_config_t *ncf, nl_rproc_t *rp)
936{
937	const char *name;
938
939	name = dnvlist_get_string(rp->rproc_dict, "name", NULL);
940	if (!name) {
941		return EINVAL;
942	}
943	if (npf_rproc_exists_p(ncf, name)) {
944		return EEXIST;
945	}
946	nvlist_append_nvlist_array(ncf->ncf_dict, "rprocs", rp->rproc_dict);
947	nvlist_destroy(rp->rproc_dict);
948	free(rp);
949	return 0;
950}
951
952nl_rproc_t *
953npf_rproc_iterate(nl_config_t *ncf, nl_iter_t *iter)
954{
955	const nvlist_t *rproc_dict;
956	unsigned i = *iter;
957
958	rproc_dict = _npf_dataset_getelement(ncf->ncf_dict, "rprocs", i);
959	if (!rproc_dict) {
960		*iter = NPF_ITER_BEGIN;
961		return NULL;
962	}
963	*iter = i + 1; // next
964	ncf->ncf_cur_rproc.rproc_dict = __UNCONST(rproc_dict); // XXX
965	return &ncf->ncf_cur_rproc;
966}
967
968const char *
969npf_rproc_getname(nl_rproc_t *rp)
970{
971	return dnvlist_get_string(rp->rproc_dict, "name", NULL);
972}
973
974/*
975 * NAT INTERFACE.
976 */
977
978nl_nat_t *
979npf_nat_create(int type, unsigned flags, const char *ifname)
980{
981	nl_rule_t *rl;
982	nvlist_t *rule_dict;
983	uint32_t attr;
984
985	attr = NPF_RULE_PASS | NPF_RULE_FINAL |
986	    (type == NPF_NATOUT ? NPF_RULE_OUT : NPF_RULE_IN);
987
988	/* Create a rule for NAT policy.  Next, will add NAT data. */
989	rl = npf_rule_create(NULL, attr, ifname);
990	if (!rl) {
991		return NULL;
992	}
993	rule_dict = rl->rule_dict;
994
995	/* Translation type and flags. */
996	nvlist_add_number(rule_dict, "type", type);
997	nvlist_add_number(rule_dict, "flags", flags);
998	nvlist_add_bool(rule_dict, "nat-rule", true);
999	return (nl_nat_t *)rl;
1000}
1001
1002int
1003npf_nat_insert(nl_config_t *ncf, nl_nat_t *nt)
1004{
1005	nvlist_append_nvlist_array(ncf->ncf_dict, "nat", nt->rule_dict);
1006	nvlist_destroy(nt->rule_dict);
1007	free(nt);
1008	return 0;
1009}
1010
1011nl_nat_t *
1012npf_nat_iterate(nl_config_t *ncf, nl_iter_t *iter)
1013{
1014	unsigned level;
1015	return _npf_rule_iterate1(ncf, "nat", iter, &level);
1016}
1017
1018int
1019npf_nat_setaddr(nl_nat_t *nt, int af, npf_addr_t *addr, npf_netmask_t mask)
1020{
1021	/* Translation IP and mask. */
1022	if (!_npf_add_addr(nt->rule_dict, "nat-addr", af, addr)) {
1023		return nvlist_error(nt->rule_dict);
1024	}
1025	nvlist_add_number(nt->rule_dict, "nat-mask", (uint32_t)mask);
1026	return nvlist_error(nt->rule_dict);
1027}
1028
1029int
1030npf_nat_setport(nl_nat_t *nt, in_port_t port)
1031{
1032	/* Translation port (for redirect case). */
1033	nvlist_add_number(nt->rule_dict, "nat-port", port);
1034	return nvlist_error(nt->rule_dict);
1035}
1036
1037int
1038npf_nat_settable(nl_nat_t *nt, unsigned tid)
1039{
1040	/*
1041	 * Translation table ID; the address/mask will then serve as a filter.
1042	 */
1043	nvlist_add_number(nt->rule_dict, "nat-table-id", tid);
1044	return nvlist_error(nt->rule_dict);
1045}
1046
1047int
1048npf_nat_setalgo(nl_nat_t *nt, unsigned algo)
1049{
1050	nvlist_add_number(nt->rule_dict, "nat-algo", algo);
1051	return nvlist_error(nt->rule_dict);
1052}
1053
1054int
1055npf_nat_setnpt66(nl_nat_t *nt, uint16_t adj)
1056{
1057	int error;
1058
1059	if ((error = npf_nat_setalgo(nt, NPF_ALGO_NPT66)) != 0) {
1060		return error;
1061	}
1062	nvlist_add_number(nt->rule_dict, "npt66-adj", adj);
1063	return nvlist_error(nt->rule_dict);
1064}
1065
1066int
1067npf_nat_gettype(nl_nat_t *nt)
1068{
1069	return dnvlist_get_number(nt->rule_dict, "type", 0);
1070}
1071
1072unsigned
1073npf_nat_getflags(nl_nat_t *nt)
1074{
1075	return dnvlist_get_number(nt->rule_dict, "flags", 0);
1076}
1077
1078unsigned
1079npf_nat_getalgo(nl_nat_t *nt)
1080{
1081	return dnvlist_get_number(nt->rule_dict, "nat-algo", 0);
1082}
1083
1084const npf_addr_t *
1085npf_nat_getaddr(nl_nat_t *nt, size_t *alen, npf_netmask_t *mask)
1086{
1087	const void *data;
1088
1089	if (nvlist_exists(nt->rule_dict, "nat-addr")) {
1090		data = nvlist_get_binary(nt->rule_dict, "nat-addr", alen);
1091		*mask = nvlist_get_number(nt->rule_dict, "nat-mask");
1092	} else {
1093		data = NULL;
1094		*alen = 0;
1095		*mask = NPF_NO_NETMASK;
1096	}
1097	return data;
1098}
1099
1100in_port_t
1101npf_nat_getport(nl_nat_t *nt)
1102{
1103	return (uint16_t)dnvlist_get_number(nt->rule_dict, "nat-port", 0);
1104}
1105
1106unsigned
1107npf_nat_gettable(nl_nat_t *nt)
1108{
1109	return dnvlist_get_number(nt->rule_dict, "nat-table-id", 0);
1110}
1111
1112/*
1113 * TABLE INTERFACE.
1114 */
1115
1116nl_table_t *
1117npf_table_create(const char *name, unsigned id, int type)
1118{
1119	nl_table_t *tl;
1120
1121	tl = malloc(sizeof(*tl));
1122	if (!tl) {
1123		return NULL;
1124	}
1125	tl->table_dict = nvlist_create(0);
1126	nvlist_add_string(tl->table_dict, "name", name);
1127	nvlist_add_number(tl->table_dict, "id", id);
1128	nvlist_add_number(tl->table_dict, "type", type);
1129	return tl;
1130}
1131
1132int
1133npf_table_add_entry(nl_table_t *tl, int af, const npf_addr_t *addr,
1134    const npf_netmask_t mask)
1135{
1136	nvlist_t *entry;
1137
1138	entry = nvlist_create(0);
1139	if (!entry) {
1140		return ENOMEM;
1141	}
1142	if (!_npf_add_addr(entry, "addr", af, addr)) {
1143		nvlist_destroy(entry);
1144		return EINVAL;
1145	}
1146	nvlist_add_number(entry, "mask", mask);
1147	nvlist_append_nvlist_array(tl->table_dict, "entries", entry);
1148	nvlist_destroy(entry);
1149	return 0;
1150}
1151
1152static inline int
1153_npf_table_build_const(nl_table_t *tl)
1154{
1155	struct cdbw *cdbw;
1156	const nvlist_t * const *entries;
1157	int error = 0, fd = -1;
1158	size_t nitems, len;
1159	void *cdb, *buf;
1160	struct stat sb;
1161	char sfn[32];
1162
1163	if (dnvlist_get_number(tl->table_dict, "type", 0) != NPF_TABLE_CONST) {
1164		return 0;
1165	}
1166
1167	if (!nvlist_exists_nvlist_array(tl->table_dict, "entries")) {
1168		return 0;
1169	}
1170
1171	/*
1172	 * Create a constant database and put all the entries.
1173	 */
1174	if ((cdbw = cdbw_open()) == NULL) {
1175		return errno;
1176	}
1177	entries = nvlist_get_nvlist_array(tl->table_dict, "entries", &nitems);
1178	for (unsigned i = 0; i < nitems; i++) {
1179		const nvlist_t *entry = entries[i];
1180		const npf_addr_t *addr;
1181		size_t alen;
1182
1183		addr = dnvlist_get_binary(entry, "addr", &alen, NULL, 0);
1184		if (addr == NULL || alen == 0 || alen > sizeof(npf_addr_t)) {
1185			error = EINVAL;
1186			goto out;
1187		}
1188		if (cdbw_put(cdbw, addr, alen, addr, alen) == -1) {
1189			error = errno;
1190			goto out;
1191		}
1192	}
1193
1194	/*
1195	 * Write the constant database into a temporary file.
1196	 */
1197	strncpy(sfn, "/tmp/npfcdb.XXXXXX", sizeof(sfn));
1198	sfn[sizeof(sfn) - 1] = '\0';
1199
1200	if ((fd = mkstemp(sfn)) == -1) {
1201		error = errno;
1202		goto out;
1203	}
1204	unlink(sfn);
1205
1206	if (cdbw_output(cdbw, fd, "npf-table-cdb", NULL) == -1) {
1207		error = errno;
1208		goto out;
1209	}
1210	if (fstat(fd, &sb) == -1) {
1211		error = errno;
1212		goto out;
1213	}
1214	len = sb.st_size;
1215
1216	/*
1217	 * Memory-map the database and copy it into a buffer.
1218	 */
1219	buf = malloc(len);
1220	if (!buf) {
1221		error = ENOMEM;
1222		goto out;
1223	}
1224	cdb = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
1225	if (cdb == MAP_FAILED) {
1226		error = errno;
1227		free(buf);
1228		goto out;
1229	}
1230	munmap(cdb, len);
1231
1232	/*
1233	 * Move the data buffer to the nvlist.
1234	 */
1235	nvlist_move_binary(tl->table_dict, "data", buf, len);
1236	error = nvlist_error(tl->table_dict);
1237out:
1238	if (fd != -1) {
1239		close(fd);
1240	}
1241	cdbw_close(cdbw);
1242	return error;
1243}
1244
1245int
1246npf_table_insert(nl_config_t *ncf, nl_table_t *tl)
1247{
1248	const char *name;
1249	int error;
1250
1251	name = dnvlist_get_string(tl->table_dict, "name", NULL);
1252	if (!name) {
1253		return EINVAL;
1254	}
1255	if (_npf_dataset_lookup(ncf->ncf_dict, "tables", "name", name)) {
1256		return EEXIST;
1257	}
1258	if ((error = _npf_table_build_const(tl)) != 0) {
1259		return error;
1260	}
1261	nvlist_append_nvlist_array(ncf->ncf_dict, "tables", tl->table_dict);
1262	nvlist_destroy(tl->table_dict);
1263	free(tl);
1264	return 0;
1265}
1266
1267int
1268npf_table_replace(int fd, nl_table_t *tl, npf_error_t *errinfo)
1269{
1270	nvlist_t *resp = NULL;
1271	int error;
1272
1273	/* Ensure const tables are built. */
1274	if ((error = _npf_table_build_const(tl)) != 0) {
1275		return _npf_init_error(errno, errinfo);
1276	}
1277	error = _npf_xfer_fd(fd, IOC_NPF_TABLE_REPLACE, tl->table_dict, &resp);
1278	if (error) {
1279		assert(resp == NULL);
1280		return _npf_init_error(errno, errinfo);
1281	}
1282	error = _npf_extract_error(resp, errinfo);
1283	nvlist_destroy(resp);
1284	return error;
1285}
1286
1287nl_table_t *
1288npf_table_iterate(nl_config_t *ncf, nl_iter_t *iter)
1289{
1290	const nvlist_t *table_dict;
1291	unsigned i = *iter;
1292
1293	table_dict = _npf_dataset_getelement(ncf->ncf_dict, "tables", i);
1294	if (!table_dict) {
1295		*iter = NPF_ITER_BEGIN;
1296		return NULL;
1297	}
1298	*iter = i + 1; // next
1299	ncf->ncf_cur_table.table_dict = __UNCONST(table_dict); // XXX
1300	return &ncf->ncf_cur_table;
1301}
1302
1303unsigned
1304npf_table_getid(nl_table_t *tl)
1305{
1306	return dnvlist_get_number(tl->table_dict, "id", (unsigned)-1);
1307}
1308
1309const char *
1310npf_table_getname(nl_table_t *tl)
1311{
1312	return dnvlist_get_string(tl->table_dict, "name", NULL);
1313}
1314
1315int
1316npf_table_gettype(nl_table_t *tl)
1317{
1318	return dnvlist_get_number(tl->table_dict, "type", 0);
1319}
1320
1321void
1322npf_table_destroy(nl_table_t *tl)
1323{
1324	nvlist_destroy(tl->table_dict);
1325	free(tl);
1326}
1327
1328/*
1329 * ALG INTERFACE.
1330 */
1331
1332int
1333npf_alg_load(nl_config_t *ncf, const char *name)
1334{
1335	nvlist_t *alg_dict;
1336
1337	if (_npf_dataset_lookup(ncf->ncf_dict, "algs", "name", name)) {
1338		return EEXIST;
1339	}
1340	alg_dict = nvlist_create(0);
1341	nvlist_add_string(alg_dict, "name", name);
1342	nvlist_append_nvlist_array(ncf->ncf_dict, "algs", alg_dict);
1343	nvlist_destroy(alg_dict);
1344	return 0;
1345}
1346
1347/*
1348 * CONNECTION / NAT ENTRY INTERFACE.
1349 */
1350
1351typedef struct {
1352	unsigned	alen;
1353	unsigned	proto;
1354	npf_addr_t	addr[3];
1355	in_port_t	port[3];
1356} npf_connpoint_t;
1357
1358static int
1359_npf_conn_lookup(int fd, const int af, npf_addr_t *addr[2], in_port_t port[2],
1360    unsigned proto, const char *ifname, unsigned di)
1361{
1362	nvlist_t *req = NULL, *resp = NULL, *key_nv;
1363	const nvlist_t *nat;
1364	int error = EINVAL;
1365
1366	/*
1367	 * Setup the connection lookup key.
1368	 */
1369	if ((key_nv = nvlist_create(0)) == NULL) {
1370		return ENOMEM;
1371	}
1372	if (!_npf_add_addr(key_nv, "saddr", af, addr[0])) {
1373		nvlist_destroy(key_nv);
1374		goto out;
1375	}
1376	if (!_npf_add_addr(key_nv, "daddr", af, addr[1])) {
1377		nvlist_destroy(key_nv);
1378		goto out;
1379	}
1380	nvlist_add_number(key_nv, "sport", htons(port[0]));
1381	nvlist_add_number(key_nv, "dport", htons(port[1]));
1382	nvlist_add_number(key_nv, "proto", proto);
1383	if (ifname) {
1384		nvlist_add_string(key_nv, "ifname", ifname);
1385	}
1386	if (di) {
1387		nvlist_add_number(key_nv, "di", di);
1388	}
1389
1390	/*
1391	 * Setup the request.
1392	 */
1393	if ((req = nvlist_create(0)) == NULL) {
1394		error = ENOMEM;
1395		goto out;
1396	}
1397	nvlist_move_nvlist(req, "key", key_nv);
1398
1399	/* Lookup: retrieve the connection entry. */
1400	error = _npf_xfer_fd(fd, IOC_NPF_CONN_LOOKUP, req, &resp);
1401	if (error) {
1402		goto out;
1403	}
1404
1405	/*
1406	 * Get the NAT entry and extract the translated pair.
1407	 */
1408	if ((nat = dnvlist_get_nvlist(resp, "nat", NULL)) == NULL) {
1409		error = ENOENT;
1410		goto out;
1411	}
1412	if (_npf_get_addr(nat, "oaddr", addr[0]) == 0 ||
1413	    _npf_get_addr(nat, "taddr", addr[1]) == 0) {
1414		error = EINVAL;
1415		goto out;
1416	}
1417	port[0] = ntohs(nvlist_get_number(nat, "oport"));
1418	port[1] = ntohs(nvlist_get_number(nat, "tport"));
1419out:
1420	if (resp) {
1421		nvlist_destroy(resp);
1422	}
1423	if (req) {
1424		nvlist_destroy(req);
1425	}
1426	return error;
1427}
1428
1429int
1430npf_nat_lookup(int fd, int af, npf_addr_t *addr[2], in_port_t port[2],
1431    int proto, int di __unused)
1432{
1433	int error;
1434
1435	port[0] = ntohs(port[0]); port[1] = ntohs(port[1]);
1436	error = _npf_conn_lookup(fd, af, addr, port, proto, NULL, 0);
1437	port[0] = htons(port[0]); port[1] = htons(port[1]);
1438	return error;
1439}
1440
1441static bool
1442npf_connkey_handle(const nvlist_t *key_nv, npf_connpoint_t *ep)
1443{
1444	unsigned alen1, alen2;
1445
1446	alen1 = _npf_get_addr(key_nv, "saddr", &ep->addr[0]);
1447	alen2 = _npf_get_addr(key_nv, "daddr", &ep->addr[1]);
1448	if (alen1 == 0 || alen1 != alen2) {
1449		return false;
1450	}
1451	ep->alen = alen1;
1452	ep->port[0] = ntohs(nvlist_get_number(key_nv, "sport"));
1453	ep->port[1] = ntohs(nvlist_get_number(key_nv, "dport"));
1454	ep->proto = nvlist_get_number(key_nv, "proto");
1455	return true;
1456}
1457
1458static void
1459npf_conn_handle(const nvlist_t *conn, npf_conn_func_t func, void *arg)
1460{
1461	const nvlist_t *key_nv, *nat_nv;
1462	const char *ifname;
1463	npf_connpoint_t ep;
1464
1465	memset(&ep, 0, sizeof(npf_connpoint_t));
1466
1467	ifname = dnvlist_get_string(conn, "ifname", NULL);
1468	key_nv = dnvlist_get_nvlist(conn, "forw-key", NULL);
1469	if (!npf_connkey_handle(key_nv, &ep)) {
1470		goto err;
1471	}
1472	if ((nat_nv = dnvlist_get_nvlist(conn, "nat", NULL)) != NULL) {
1473		if (_npf_get_addr(nat_nv, "taddr", &ep.addr[2]) != ep.alen) {
1474			goto err;
1475		}
1476		ep.port[2] = ntohs(nvlist_get_number(nat_nv, "tport"));
1477	}
1478	/*
1479	 * XXX: add 'proto' and 'flow'; perhaps expand and pass the
1480	 * whole to npf_connpoint_t?
1481	 */
1482	(*func)((unsigned)ep.alen, ep.addr, ep.port, ifname, arg);
1483err:
1484	return;
1485}
1486
1487int
1488npf_conn_list(int fd, npf_conn_func_t func, void *arg)
1489{
1490	nl_config_t *ncf;
1491	const nvlist_t * const *conns;
1492	size_t nitems;
1493
1494	ncf = npf_config_retrieve(fd);
1495	if (!ncf) {
1496		return errno;
1497	}
1498	if (!nvlist_exists_nvlist_array(ncf->ncf_dict, "conn-list")) {
1499		return 0;
1500	}
1501	conns = nvlist_get_nvlist_array(ncf->ncf_dict, "conn-list", &nitems);
1502	for (unsigned i = 0; i < nitems; i++) {
1503		const nvlist_t *conn = conns[i];
1504		npf_conn_handle(conn, func, arg);
1505	}
1506	npf_config_destroy(ncf);
1507	return 0;
1508}
1509
1510/*
1511 * MISC.
1512 */
1513
1514void
1515_npf_debug_addif(nl_config_t *ncf, const char *ifname)
1516{
1517	nvlist_t *debug;
1518
1519	/*
1520	 * Initialise the debug dictionary on the first call.
1521	 */
1522	debug = dnvlist_take_nvlist(ncf->ncf_dict, "debug", NULL);
1523	if (debug == NULL) {
1524		debug = nvlist_create(0);
1525	}
1526	if (!_npf_dataset_lookup(debug, "interfaces", "name", ifname)) {
1527		nvlist_t *ifdict = nvlist_create(0);
1528		nvlist_add_string(ifdict, "name", ifname);
1529		nvlist_add_number(ifdict, "index", if_nametoindex(ifname));
1530		nvlist_append_nvlist_array(debug, "interfaces", ifdict);
1531		nvlist_destroy(ifdict);
1532	}
1533	nvlist_move_nvlist(ncf->ncf_dict, "debug", debug);
1534}
1535
1536void
1537_npf_config_dump(nl_config_t *ncf, int fd)
1538{
1539	(void)npf_config_build(ncf);
1540	nvlist_dump(ncf->ncf_dict, fd);
1541}
1542