xref: /netbsd-src/sys/net/npf/npf_ctl.c (revision e6c7e151de239c49d2e38720a061ed9d1fa99309)
1 /*-
2  * Copyright (c) 2009-2019 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 /*
31  * NPF device control.
32  *
33  * Implementation of (re)loading, construction of tables and rules.
34  * NPF nvlist(3) consumer.
35  */
36 
37 #ifdef _KERNEL
38 #include <sys/cdefs.h>
39 __KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.59 2019/09/30 00:37:11 rmind Exp $");
40 
41 #include <sys/param.h>
42 #include <sys/conf.h>
43 #include <sys/kmem.h>
44 #include <net/bpf.h>
45 #endif
46 
47 #include "npf_impl.h"
48 #include "npf_conn.h"
49 
50 #define	NPF_IOCTL_DATA_LIMIT	(4 * 1024 * 1024)
51 
52 #define	NPF_ERR_DEBUG(e) \
53 	nvlist_add_string((e), "source-file", __FILE__); \
54 	nvlist_add_number((e), "source-line", __LINE__);
55 
56 static int
57 npf_nvlist_copyin(npf_t *npf, void *data, nvlist_t **nvl)
58 {
59 	int error = 0;
60 
61 	if (npf->mbufops == NULL) {
62 		error = nvlist_copyin(data, nvl, NPF_IOCTL_DATA_LIMIT);
63 	} else {
64 		*nvl = (nvlist_t *)data;
65 	}
66 	return error;
67 }
68 
69 static int
70 npf_nvlist_copyout(npf_t *npf, void *data, nvlist_t *nvl)
71 {
72 	int error = 0;
73 
74 	if (npf->mbufops == NULL) {
75 		error = nvlist_copyout(data, nvl);
76 	}
77 	nvlist_destroy(nvl);
78 	return error;
79 }
80 
81 static int __noinline
82 npf_mk_params(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict, bool set)
83 {
84 	const nvlist_t *params;
85 	int type, error, val;
86 	const char *name;
87 	void *cookie;
88 
89 	params = dnvlist_get_nvlist(npf_dict, "params", NULL);
90 	if (params == NULL) {
91 		return 0;
92 	}
93 	cookie = NULL;
94 	while ((name = nvlist_next(params, &type, &cookie)) != NULL) {
95 		if (type != NV_TYPE_NUMBER) {
96 			NPF_ERR_DEBUG(errdict);
97 			return EINVAL;
98 		}
99 		val = (int)nvlist_get_number(params, name);
100 		if (set) {
101 			/* Actually set the parameter. */
102 			error = npfk_param_set(npf, name, val);
103 			KASSERT(error == 0);
104 			continue;
105 		}
106 
107 		/* Validate the parameter and its value. */
108 		error = npf_param_check(npf, name, val);
109 		if (__predict_true(error == 0)) {
110 			continue;
111 		}
112 		if (error == ENOENT) {
113 			nvlist_add_stringf(errdict, "error-msg",
114 			    "invalid parameter `%s`", name);
115 		}
116 		if (error == EINVAL) {
117 			nvlist_add_stringf(errdict, "error-msg",
118 			    "invalid parameter `%s` value %d", name, val);
119 		}
120 		return error;
121 	}
122 	return 0;
123 }
124 
125 static int __noinline
126 npf_mk_table_entries(npf_table_t *t, const nvlist_t *table, nvlist_t *errdict)
127 {
128 	const nvlist_t * const *entries;
129 	size_t nitems;
130 	int error = 0;
131 
132 	if (!nvlist_exists_nvlist_array(table, "entries")) {
133 		return 0;
134 	}
135 	entries = nvlist_get_nvlist_array(table, "entries", &nitems);
136 	for (unsigned i = 0; i < nitems; i++) {
137 		const nvlist_t *entry = entries[i];
138 		const npf_addr_t *addr;
139 		npf_netmask_t mask;
140 		size_t alen;
141 
142 		/* Get address and mask.  Add a table entry. */
143 		addr = dnvlist_get_binary(entry, "addr", &alen, NULL, 0);
144 		mask = dnvlist_get_number(entry, "mask", NPF_NO_NETMASK);
145 		if (addr == NULL || alen == 0) {
146 			NPF_ERR_DEBUG(errdict);
147 			error = EINVAL;
148 			break;
149 		}
150 		error = npf_table_insert(t, alen, addr, mask);
151 		if (error) {
152 			NPF_ERR_DEBUG(errdict);
153 			break;
154 		}
155 	}
156 	return error;
157 }
158 
159 /*
160  * npf_mk_table: create a table from provided nvlist.
161  */
162 static int __noinline
163 npf_mk_table(npf_t *npf, const nvlist_t *tbl_dict, nvlist_t *errdict,
164     npf_tableset_t *tblset, npf_table_t **tblp, bool replacing)
165 {
166 	npf_table_t *t;
167 	const char *name;
168 	const void *blob;
169 	uint64_t tid;
170 	size_t size;
171 	int type;
172 	int error = 0;
173 
174 	KASSERT(tblp != NULL);
175 
176 	/* Table name, ID and type.  Validate them. */
177 	name = dnvlist_get_string(tbl_dict, "name", NULL);
178 	if (!name) {
179 		NPF_ERR_DEBUG(errdict);
180 		error = EINVAL;
181 		goto out;
182 	}
183 	tid = dnvlist_get_number(tbl_dict, "id", UINT64_MAX);
184 	type = dnvlist_get_number(tbl_dict, "type", UINT64_MAX);
185 	error = npf_table_check(tblset, name, tid, type, replacing);
186 	if (error) {
187 		NPF_ERR_DEBUG(errdict);
188 		goto out;
189 	}
190 
191 	/* Get the entries or binary data. */
192 	blob = dnvlist_get_binary(tbl_dict, "data", &size, NULL, 0);
193 	if (type == NPF_TABLE_CONST && (blob == NULL || size == 0)) {
194 		NPF_ERR_DEBUG(errdict);
195 		error = EINVAL;
196 		goto out;
197 	}
198 
199 	t = npf_table_create(name, (unsigned)tid, type, blob, size);
200 	if (t == NULL) {
201 		NPF_ERR_DEBUG(errdict);
202 		error = ENOMEM;
203 		goto out;
204 	}
205 
206 	if ((error = npf_mk_table_entries(t, tbl_dict, errdict)) != 0) {
207 		npf_table_destroy(t);
208 		goto out;
209 	}
210 
211 	*tblp = t;
212 out:
213 	return error;
214 }
215 
216 static int __noinline
217 npf_mk_tables(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
218     npf_config_t *nc)
219 {
220 	const nvlist_t * const *tables;
221 	npf_tableset_t *tblset;
222 	size_t nitems;
223 	int error = 0;
224 
225 	if (nvlist_exists_nvlist_array(npf_dict, "tables")) {
226 		tables = nvlist_get_nvlist_array(npf_dict, "tables", &nitems);
227 		if (nitems > NPF_MAX_TABLES) {
228 			NPF_ERR_DEBUG(errdict);
229 			return E2BIG;
230 		}
231 	} else {
232 		tables = NULL;
233 		nitems = 0;
234 	}
235 	tblset = npf_tableset_create(nitems);
236 	for (unsigned i = 0; i < nitems; i++) {
237 		const nvlist_t *table = tables[i];
238 		npf_table_t *t;
239 
240 		error = npf_mk_table(npf, table, errdict, tblset, &t, 0);
241 		if (error) {
242 			break;
243 		}
244 
245 		error = npf_tableset_insert(tblset, t);
246 		KASSERT(error == 0);
247 	}
248 	nc->tableset = tblset;
249 	return error;
250 }
251 
252 static npf_rproc_t *
253 npf_mk_singlerproc(npf_t *npf, const nvlist_t *rproc, nvlist_t *errdict)
254 {
255 	const nvlist_t * const *extcalls;
256 	size_t nitems;
257 	npf_rproc_t *rp;
258 
259 	if (!nvlist_exists_nvlist_array(rproc, "extcalls")) {
260 		NPF_ERR_DEBUG(errdict);
261 		return NULL;
262 	}
263 	rp = npf_rproc_create(rproc);
264 	if (rp == NULL) {
265 		NPF_ERR_DEBUG(errdict);
266 		return NULL;
267 	}
268 	extcalls = nvlist_get_nvlist_array(rproc, "extcalls", &nitems);
269 	for (unsigned i = 0; i < nitems; i++) {
270 		const nvlist_t *extcall = extcalls[i];
271 		const char *name;
272 
273 		name = dnvlist_get_string(extcall, "name", NULL);
274 		if (!name || npf_ext_construct(npf, name, rp, extcall)) {
275 			NPF_ERR_DEBUG(errdict);
276 			npf_rproc_release(rp);
277 			rp = NULL;
278 			break;
279 		}
280 	}
281 	return rp;
282 }
283 
284 static int __noinline
285 npf_mk_rprocs(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
286     npf_config_t *nc)
287 {
288 	const nvlist_t * const *rprocs;
289 	npf_rprocset_t *rpset;
290 	size_t nitems;
291 	int error = 0;
292 
293 	if (nvlist_exists_nvlist_array(npf_dict, "rprocs")) {
294 		rprocs = nvlist_get_nvlist_array(npf_dict, "rprocs", &nitems);
295 		if (nitems > NPF_MAX_RPROCS) {
296 			NPF_ERR_DEBUG(errdict);
297 			return E2BIG;
298 		}
299 	} else {
300 		rprocs = NULL;
301 		nitems = 0;
302 	}
303 	rpset = npf_rprocset_create();
304 	for (unsigned i = 0; i < nitems; i++) {
305 		const nvlist_t *rproc = rprocs[i];
306 		npf_rproc_t *rp;
307 
308 		if ((rp = npf_mk_singlerproc(npf, rproc, errdict)) == NULL) {
309 			error = EINVAL;
310 			break;
311 		}
312 		npf_rprocset_insert(rpset, rp);
313 	}
314 	nc->rule_procs = rpset;
315 	return error;
316 }
317 
318 static int __noinline
319 npf_mk_algs(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict)
320 {
321 	const nvlist_t * const *algs;
322 	size_t nitems;
323 
324 	if (nvlist_exists_nvlist_array(npf_dict, "algs")) {
325 		algs = nvlist_get_nvlist_array(npf_dict, "algs", &nitems);
326 	} else {
327 		algs = NULL;
328 		nitems = 0;
329 	}
330 	for (unsigned i = 0; i < nitems; i++) {
331 		const nvlist_t *alg = algs[i];
332 		const char *name;
333 
334 		name = dnvlist_get_string(alg, "name", NULL);
335 		if (!name) {
336 			NPF_ERR_DEBUG(errdict);
337 			return EINVAL;
338 		}
339 		if (!npf_alg_construct(npf, name)) {
340 			NPF_ERR_DEBUG(errdict);
341 			return EINVAL;
342 		}
343 	}
344 	return 0;
345 }
346 
347 static int __noinline
348 npf_mk_singlerule(npf_t *npf, const nvlist_t *rule, npf_rprocset_t *rpset,
349     npf_rule_t **rlret, nvlist_t *errdict)
350 {
351 	npf_rule_t *rl;
352 	const char *rname;
353 	const void *code;
354 	size_t clen;
355 	int error = 0;
356 
357 	if ((rl = npf_rule_alloc(npf, rule)) == NULL) {
358 		NPF_ERR_DEBUG(errdict);
359 		return EINVAL;
360 	}
361 
362 	/* Assign the rule procedure, if any. */
363 	if ((rname = dnvlist_get_string(rule, "rproc", NULL)) != NULL) {
364 		npf_rproc_t *rp;
365 
366 		if (rpset == NULL) {
367 			NPF_ERR_DEBUG(errdict);
368 			error = EINVAL;
369 			goto err;
370 		}
371 		if ((rp = npf_rprocset_lookup(rpset, rname)) == NULL) {
372 			NPF_ERR_DEBUG(errdict);
373 			error = EINVAL;
374 			goto err;
375 		}
376 		npf_rule_setrproc(rl, rp);
377 	}
378 
379 	/* Filter byte-code (binary data). */
380 	code = dnvlist_get_binary(rule, "code", &clen, NULL, 0);
381 	if (code) {
382 		void *bc;
383 		int type;
384 
385 		type = dnvlist_get_number(rule, "code-type", UINT64_MAX);
386 		if (type != NPF_CODE_BPF) {
387 			NPF_ERR_DEBUG(errdict);
388 			error = ENOTSUP;
389 			goto err;
390 		}
391 		if (clen == 0) {
392 			NPF_ERR_DEBUG(errdict);
393 			error = EINVAL;
394 			goto err;
395 		}
396 		if (!npf_bpf_validate(code, clen)) {
397 			NPF_ERR_DEBUG(errdict);
398 			error = EINVAL;
399 			goto err;
400 		}
401 		bc = kmem_alloc(clen, KM_SLEEP);
402 		memcpy(bc, code, clen); // XXX: use nvlist_take
403 		npf_rule_setcode(rl, type, bc, clen);
404 	}
405 
406 	*rlret = rl;
407 	return 0;
408 err:
409 	nvlist_add_number(errdict, "id", dnvlist_get_number(rule, "prio", 0));
410 	npf_rule_free(rl);
411 	return error;
412 }
413 
414 static int __noinline
415 npf_mk_rules(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
416     npf_config_t *nc)
417 {
418 	const nvlist_t * const *rules;
419 	npf_ruleset_t *rlset;
420 	size_t nitems;
421 	int error = 0;
422 
423 	if (nvlist_exists_nvlist_array(npf_dict, "rules")) {
424 		rules = nvlist_get_nvlist_array(npf_dict, "rules", &nitems);
425 		if (nitems > NPF_MAX_RULES) {
426 			NPF_ERR_DEBUG(errdict);
427 			return E2BIG;
428 		}
429 	} else {
430 		rules = NULL;
431 		nitems = 0;
432 	}
433 	rlset = npf_ruleset_create(nitems);
434 	for (unsigned i = 0; i < nitems; i++) {
435 		const nvlist_t *rule = rules[i];
436 		npf_rule_t *rl = NULL;
437 		const char *name;
438 
439 		error = npf_mk_singlerule(npf, rule, nc->rule_procs, &rl,
440 		    errdict);
441 		if (error) {
442 			break;
443 		}
444 		name = dnvlist_get_string(rule, "name", NULL);
445 		if (name && npf_ruleset_lookup(rlset, name)) {
446 			NPF_ERR_DEBUG(errdict);
447 			npf_rule_free(rl);
448 			error = EEXIST;
449 			break;
450 		}
451 		npf_ruleset_insert(rlset, rl);
452 	}
453 	nc->ruleset = rlset;
454 	return error;
455 }
456 
457 static int __noinline
458 npf_mk_singlenat(npf_t *npf, const nvlist_t *nat, npf_ruleset_t *ntset,
459     npf_tableset_t *tblset, nvlist_t *errdict, npf_rule_t **rlp)
460 {
461 	npf_rule_t *rl = NULL;
462 	npf_natpolicy_t *np;
463 	int error;
464 
465 	/*
466 	 * NAT rules are standard rules, plus the translation policy.
467 	 * We first construct the rule structure.
468 	 */
469 	error = npf_mk_singlerule(npf, nat, NULL, &rl, errdict);
470 	if (error) {
471 		return error;
472 	}
473 	KASSERT(rl != NULL);
474 	*rlp = rl;
475 
476 	/* If this rule is named, then it is a group with NAT policies. */
477 	if (dnvlist_get_string(nat, "name", NULL)) {
478 		return 0;
479 	}
480 
481 	/* Check the table ID. */
482 	if (nvlist_exists_number(nat, "nat-table-id")) {
483 		unsigned tid = nvlist_get_number(nat, "nat-table-id");
484 
485 		if (!npf_tableset_getbyid(tblset, tid)) {
486 			NPF_ERR_DEBUG(errdict);
487 			error = EINVAL;
488 			goto out;
489 		}
490 	}
491 
492 	/* Allocate a new NAT policy and assign it to the rule. */
493 	np = npf_nat_newpolicy(npf, nat, ntset);
494 	if (np == NULL) {
495 		NPF_ERR_DEBUG(errdict);
496 		error = ENOMEM;
497 		goto out;
498 	}
499 	npf_rule_setnat(rl, np);
500 out:
501 	if (error) {
502 		npf_rule_free(rl);
503 	}
504 	return error;
505 }
506 
507 static int __noinline
508 npf_mk_natlist(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
509     npf_config_t *nc)
510 {
511 	const nvlist_t * const *nat_rules;
512 	npf_ruleset_t *ntset;
513 	size_t nitems;
514 	int error = 0;
515 
516 	/*
517 	 * NAT policies must be an array, but enforce a limit.
518 	 */
519 	if (nvlist_exists_nvlist_array(npf_dict, "nat")) {
520 		nat_rules = nvlist_get_nvlist_array(npf_dict, "nat", &nitems);
521 		if (nitems > NPF_MAX_RULES) {
522 			NPF_ERR_DEBUG(errdict);
523 			return E2BIG;
524 		}
525 	} else {
526 		nat_rules = NULL;
527 		nitems = 0;
528 	}
529 	ntset = npf_ruleset_create(nitems);
530 	for (unsigned i = 0; i < nitems; i++) {
531 		const nvlist_t *nat = nat_rules[i];
532 		npf_rule_t *rl = NULL;
533 
534 		error = npf_mk_singlenat(npf, nat, ntset, nc->tableset,
535 		    errdict, &rl);
536 		if (error) {
537 			break;
538 		}
539 		npf_ruleset_insert(ntset, rl);
540 	}
541 	nc->nat_ruleset = ntset;
542 	return error;
543 }
544 
545 /*
546  * npf_mk_connlist: import a list of connections and load them.
547  */
548 static int __noinline
549 npf_mk_connlist(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
550     npf_config_t *nc, npf_conndb_t **conndb)
551 {
552 	const nvlist_t * const *conns;
553 	npf_conndb_t *cd;
554 	size_t nitems;
555 	int error = 0;
556 
557 	if (!nvlist_exists_nvlist_array(npf_dict, "conn-list")) {
558 		*conndb = NULL;
559 		return 0;
560 	}
561 	cd = npf_conndb_create();
562 	conns = nvlist_get_nvlist_array(npf_dict, "conn-list", &nitems);
563 	for (unsigned i = 0; i < nitems; i++) {
564 		const nvlist_t *conn = conns[i];
565 
566 		/* Construct and insert the connection. */
567 		error = npf_conn_import(npf, cd, conn, nc->nat_ruleset);
568 		if (error) {
569 			NPF_ERR_DEBUG(errdict);
570 			break;
571 		}
572 	}
573 	if (error) {
574 		npf_conndb_gc(npf, cd, true, false);
575 		npf_conndb_destroy(cd);
576 	} else {
577 		*conndb = cd;
578 	}
579 	return error;
580 }
581 
582 /*
583  * npfctl_load_nvlist: store passed data i.e. the update settings, create
584  * the passed tables, rules, etc and atomically activate all them.
585  */
586 static int
587 npfctl_load_nvlist(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict)
588 {
589 	npf_config_t *nc;
590 	npf_conndb_t *conndb = NULL;
591 	uint64_t ver;
592 	bool flush;
593 	int error;
594 
595 	nc = npf_config_create();
596 	ver = dnvlist_get_number(npf_dict, "version", UINT64_MAX);
597 	if (ver != NPF_VERSION) {
598 		error = EPROGMISMATCH;
599 		goto fail;
600 	}
601 	error = npf_mk_params(npf, npf_dict, errdict, false /* validate */);
602 	if (error) {
603 		goto fail;
604 	}
605 	error = npf_mk_algs(npf, npf_dict, errdict);
606 	if (error) {
607 		goto fail;
608 	}
609 	error = npf_mk_tables(npf, npf_dict, errdict, nc);
610 	if (error) {
611 		goto fail;
612 	}
613 	error = npf_mk_rprocs(npf, npf_dict, errdict, nc);
614 	if (error) {
615 		goto fail;
616 	}
617 	error = npf_mk_natlist(npf, npf_dict, errdict, nc);
618 	if (error) {
619 		goto fail;
620 	}
621 	error = npf_mk_rules(npf, npf_dict, errdict, nc);
622 	if (error) {
623 		goto fail;
624 	}
625 	error = npf_mk_connlist(npf, npf_dict, errdict, nc, &conndb);
626 	if (error) {
627 		goto fail;
628 	}
629 
630 	flush = dnvlist_get_bool(npf_dict, "flush", false);
631 	nc->default_pass = flush;
632 
633 	/*
634 	 * Finally - perform the load.
635 	 */
636 	npf_config_load(npf, nc, conndb, flush);
637 	npf_mk_params(npf, npf_dict, errdict, true /* set the params */);
638 
639 	/* Done.  Since data is consumed now, we shall not destroy it. */
640 	nc = NULL;
641 fail:
642 	if (nc) {
643 		npf_config_destroy(nc);
644 	}
645 	nvlist_destroy(npf_dict);
646 	return error;
647 }
648 
649 int
650 npfctl_load(npf_t *npf, u_long cmd, void *data)
651 {
652 	nvlist_t *request, *response;
653 	int error;
654 
655 	/*
656 	 * Retrieve the configuration and check the version.
657 	 * Construct a response with error reporting.
658 	 */
659 	error = npf_nvlist_copyin(npf, data, &request);
660 	if (error) {
661 		return error;
662 	}
663 	response = nvlist_create(0);
664 	error = npfctl_load_nvlist(npf, request, response);
665 	nvlist_add_number(response, "errno", error);
666 	return npf_nvlist_copyout(npf, data, response);
667 }
668 
669 /*
670  * npfctl_save: export the config dictionary as it was submitted,
671  * including the current snapshot of the connections.  Additionally,
672  * indicate whether the ruleset is currently active.
673  */
674 int
675 npfctl_save(npf_t *npf, u_long cmd, void *data)
676 {
677 	npf_config_t *nc;
678 	nvlist_t *npf_dict;
679 	int error;
680 
681 	npf_dict = nvlist_create(0);
682 	nvlist_add_number(npf_dict, "version", NPF_VERSION);
683 
684 	/*
685 	 * Serialise the whole NPF config, including connections.
686 	 */
687 	nc = npf_config_enter(npf);
688 	error = npf_conndb_export(npf, npf_dict);
689 	if (error) {
690 		goto out;
691 	}
692 	error = npf_ruleset_export(npf, nc->ruleset, "rules", npf_dict);
693 	if (error) {
694 		goto out;
695 	}
696 	error = npf_ruleset_export(npf, nc->nat_ruleset, "nat", npf_dict);
697 	if (error) {
698 		goto out;
699 	}
700 	error = npf_tableset_export(npf, nc->tableset, npf_dict);
701 	if (error) {
702 		goto out;
703 	}
704 	error = npf_rprocset_export(nc->rule_procs, npf_dict);
705 	if (error) {
706 		goto out;
707 	}
708 	error = npf_alg_export(npf, npf_dict);
709 	if (error) {
710 		goto out;
711 	}
712 	nvlist_add_bool(npf_dict, "active", npf_active_p());
713 	error = npf_nvlist_copyout(npf, data, npf_dict);
714 	npf_dict = NULL;
715 out:
716 	npf_config_exit(npf);
717 	if (npf_dict) {
718 		nvlist_destroy(npf_dict);
719 	}
720 	return error;
721 }
722 
723 /*
724  * npfctl_table_replace_nvlist: atomically replace a table's contents
725  * with the passed table data.
726  */
727 static int __noinline
728 npfctl_table_replace_nvlist(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict)
729 {
730 	npf_table_t *tbl, *gc_tbl = NULL;
731 	npf_config_t *nc;
732 	int error = 0;
733 
734 	nc = npf_config_enter(npf);
735 	error = npf_mk_table(npf, npf_dict, errdict, nc->tableset, &tbl, true);
736 	if (error) {
737 		goto err;
738 	}
739 	gc_tbl = npf_tableset_swap(nc->tableset, tbl);
740 	if (gc_tbl == NULL) {
741 		error = EINVAL;
742 		gc_tbl = tbl;
743 		goto err;
744 	}
745 	npf_config_sync(npf);
746 err:
747 	npf_config_exit(npf);
748 	if (gc_tbl) {
749 		npf_table_destroy(gc_tbl);
750 	}
751 	return error;
752 }
753 
754 int
755 npfctl_table_replace(npf_t *npf, u_long cmd, void *data)
756 {
757 	nvlist_t *request, *response;
758 	int error;
759 
760 	/*
761 	 * Retrieve the configuration and check the version.
762 	 * Construct a response with error reporting.
763 	 */
764 	error = npf_nvlist_copyin(npf, data, &request);
765 	if (error) {
766 		return error;
767 	}
768 	response = nvlist_create(0);
769 	error = npfctl_table_replace_nvlist(npf, request, response);
770 	nvlist_add_number(response, "errno", error);
771 	error = npf_nvlist_copyout(npf, data, response);
772 	nvlist_destroy(request);
773 	return error;
774 }
775 
776 /*
777  * npfctl_conn_lookup: lookup a connection in the list of connections
778  */
779 int
780 npfctl_conn_lookup(npf_t *npf, u_long cmd, void *data)
781 {
782 	nvlist_t *conn_data, *conn_result;
783 	int error;
784 
785 	error = npf_nvlist_copyin(npf, data, &conn_data);
786 	if (error) {
787 		return error;
788 	}
789 	error = npf_conn_find(npf, conn_data, &conn_result);
790 	if (error) {
791 		goto out;
792 	}
793 	error = npf_nvlist_copyout(npf, data, conn_result);
794 out:
795 	nvlist_destroy(conn_data);
796 	return error;
797 }
798 
799 /*
800  * npfctl_rule: add or remove dynamic rules in the specified ruleset.
801  */
802 int
803 npfctl_rule(npf_t *npf, u_long cmd, void *data)
804 {
805 	nvlist_t *npf_rule, *retdict = NULL;
806 	npf_ruleset_t *rlset;
807 	npf_rule_t *rl = NULL;
808 	const char *ruleset_name;
809 	npf_config_t *nc;
810 	uint32_t rcmd;
811 	int error = 0;
812 	bool natset;
813 
814 	error = npf_nvlist_copyin(npf, data, &npf_rule);
815 	if (error) {
816 		return error;
817 	}
818 	rcmd = dnvlist_get_number(npf_rule, "command", 0);
819 	natset = dnvlist_get_bool(npf_rule, "nat-ruleset", false);
820 	ruleset_name = dnvlist_get_string(npf_rule, "ruleset-name", NULL);
821 	if (!ruleset_name) {
822 		error = EINVAL;
823 		goto out;
824 	}
825 
826 	nc = npf_config_enter(npf);
827 	rlset = natset ? nc->nat_ruleset : nc->ruleset;
828 	switch (rcmd) {
829 	case NPF_CMD_RULE_ADD: {
830 		retdict = nvlist_create(0);
831 		if (natset) {
832 			/*
833 			 * Translation rule.
834 			 */
835 			error = npf_mk_singlenat(npf, npf_rule, rlset,
836 			    nc->tableset, retdict, &rl);
837 		} else {
838 			/*
839 			 * Standard rule.
840 			 */
841 			error = npf_mk_singlerule(npf, npf_rule, NULL,
842 			    &rl, retdict);
843 		}
844 		if (error) {
845 			npf_config_exit(npf);
846 			goto out;
847 		}
848 		if ((error = npf_ruleset_add(rlset, ruleset_name, rl)) == 0) {
849 			/* Success. */
850 			uint64_t id = npf_rule_getid(rl);
851 			nvlist_add_number(retdict, "id", id);
852 			rl = NULL;
853 		}
854 		break;
855 	}
856 	case NPF_CMD_RULE_REMOVE: {
857 		uint64_t id = dnvlist_get_number(npf_rule, "id", UINT64_MAX);
858 		error = npf_ruleset_remove(rlset, ruleset_name, id);
859 		break;
860 	}
861 	case NPF_CMD_RULE_REMKEY: {
862 		const void *key;
863 		size_t len;
864 
865 		key = dnvlist_get_binary(npf_rule, "key", &len, NULL, 0);
866 		if (len == 0 || len > NPF_RULE_MAXKEYLEN) {
867 			error = EINVAL;
868 			break;
869 		}
870 		error = npf_ruleset_remkey(rlset, ruleset_name, key, len);
871 		break;
872 	}
873 	case NPF_CMD_RULE_LIST: {
874 		retdict = npf_ruleset_list(npf, rlset, ruleset_name);
875 		if (!retdict) {
876 			error = ESRCH;
877 		}
878 		break;
879 	}
880 	case NPF_CMD_RULE_FLUSH: {
881 		error = npf_ruleset_flush(rlset, ruleset_name);
882 		break;
883 	}
884 	default:
885 		error = EINVAL;
886 		break;
887 	}
888 
889 	/* Destroy any removed rules. */
890 	if (!error && rcmd != NPF_CMD_RULE_ADD && rcmd != NPF_CMD_RULE_LIST) {
891 		npf_config_sync(npf);
892 		npf_ruleset_gc(rlset);
893 	}
894 	npf_config_exit(npf);
895 
896 	if (rl) {
897 		KASSERT(error);
898 		npf_rule_free(rl);
899 	}
900 out:
901 	if (retdict && npf_nvlist_copyout(npf, data, retdict) != 0) {
902 		error = EFAULT; // copyout failure
903 	}
904 	nvlist_destroy(npf_rule);
905 	return error;
906 }
907 
908 /*
909  * npfctl_table: add, remove or query entries in the specified table.
910  *
911  * For maximum performance, the interface is using plain structures.
912  */
913 int
914 npfctl_table(npf_t *npf, void *data)
915 {
916 	const npf_ioctl_table_t *nct = data;
917 	char tname[NPF_TABLE_MAXNAMELEN];
918 	npf_config_t *nc;
919 	npf_table_t *t;
920 	int error;
921 
922 	error = copyinstr(nct->nct_name, tname, sizeof(tname), NULL);
923 	if (error) {
924 		return error;
925 	}
926 
927 	nc = npf_config_enter(npf);
928 	if ((t = npf_tableset_getbyname(nc->tableset, tname)) == NULL) {
929 		npf_config_exit(npf);
930 		return EINVAL;
931 	}
932 
933 	switch (nct->nct_cmd) {
934 	case NPF_CMD_TABLE_LOOKUP:
935 		error = npf_table_lookup(t, nct->nct_data.ent.alen,
936 		    &nct->nct_data.ent.addr);
937 		break;
938 	case NPF_CMD_TABLE_ADD:
939 		error = npf_table_insert(t, nct->nct_data.ent.alen,
940 		    &nct->nct_data.ent.addr, nct->nct_data.ent.mask);
941 		break;
942 	case NPF_CMD_TABLE_REMOVE:
943 		error = npf_table_remove(t, nct->nct_data.ent.alen,
944 		    &nct->nct_data.ent.addr, nct->nct_data.ent.mask);
945 		break;
946 	case NPF_CMD_TABLE_LIST:
947 		error = npf_table_list(t, nct->nct_data.buf.buf,
948 		    nct->nct_data.buf.len);
949 		break;
950 	case NPF_CMD_TABLE_FLUSH:
951 		error = npf_table_flush(t);
952 		break;
953 	default:
954 		error = EINVAL;
955 		break;
956 	}
957 	npf_table_gc(npf, t);
958 	npf_config_exit(npf);
959 
960 	return error;
961 }
962