1/*	$NetBSD: npf_build.c,v 1.4.2.11 2013/02/11 21:49:48 riz Exp $	*/
2
3/*-
4 * Copyright (c) 2011-2013 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This material is based upon work partially supported by The
8 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * npfctl(8) building of the configuration.
34 */
35
36#include <sys/cdefs.h>
37__RCSID("$NetBSD: npf_build.c,v 1.4.2.11 2013/02/11 21:49:48 riz Exp $");
38
39#include <sys/types.h>
40#include <sys/ioctl.h>
41
42#include <stdlib.h>
43#include <inttypes.h>
44#include <string.h>
45#include <errno.h>
46#include <err.h>
47
48#include "npfctl.h"
49
50#define	MAX_RULE_NESTING	16
51
52static nl_config_t *		npf_conf = NULL;
53static bool			npf_debug = false;
54static nl_rule_t *		the_rule = NULL;
55
56static nl_rule_t *		current_group[MAX_RULE_NESTING];
57static unsigned			rule_nesting_level = 0;
58static nl_rule_t *		defgroup = NULL;
59
60void
61npfctl_config_init(bool debug)
62{
63	npf_conf = npf_config_create();
64	if (npf_conf == NULL) {
65		errx(EXIT_FAILURE, "npf_config_create failed");
66	}
67	npf_debug = debug;
68	memset(current_group, 0, sizeof(current_group));
69}
70
71int
72npfctl_config_send(int fd, const char *out)
73{
74	int error;
75
76	if (out) {
77		_npf_config_setsubmit(npf_conf, out);
78		printf("\nSaving to %s\n", out);
79	}
80	if (!defgroup) {
81		errx(EXIT_FAILURE, "default group was not defined");
82	}
83	npf_rule_insert(npf_conf, NULL, defgroup);
84	error = npf_config_submit(npf_conf, fd);
85	if (error) {
86		nl_error_t ne;
87		_npf_config_error(npf_conf, &ne);
88		npfctl_print_error(&ne);
89	}
90	npf_config_destroy(npf_conf);
91	return error;
92}
93
94nl_config_t *
95npfctl_config_ref(void)
96{
97	return npf_conf;
98}
99
100nl_rule_t *
101npfctl_rule_ref(void)
102{
103	return the_rule;
104}
105
106unsigned long
107npfctl_debug_addif(const char *ifname)
108{
109	char tname[] = "npftest";
110	const size_t tnamelen = sizeof(tname) - 1;
111
112	if (!npf_debug || strncmp(ifname, tname, tnamelen) != 0) {
113		return 0;
114	}
115	struct ifaddrs ifa = {
116		.ifa_name = __UNCONST(ifname),
117		.ifa_flags = 0
118	};
119	unsigned long if_idx = atol(ifname + tnamelen) + 1;
120	_npf_debug_addif(npf_conf, &ifa, if_idx);
121	return if_idx;
122}
123
124bool
125npfctl_table_exists_p(const char *id)
126{
127	return npf_table_exists_p(npf_conf, atoi(id));
128}
129
130static in_port_t
131npfctl_get_singleport(const npfvar_t *vp)
132{
133	port_range_t *pr;
134	in_port_t *port;
135
136	if (npfvar_get_count(vp) > 1) {
137		yyerror("multiple ports are not valid");
138	}
139	pr = npfvar_get_data(vp, NPFVAR_PORT_RANGE, 0);
140	if (pr->pr_start != pr->pr_end) {
141		yyerror("port range is not valid");
142	}
143	port = &pr->pr_start;
144	return *port;
145}
146
147static fam_addr_mask_t *
148npfctl_get_singlefam(const npfvar_t *vp)
149{
150	if (npfvar_get_count(vp) > 1) {
151		yyerror("multiple addresses are not valid");
152	}
153	return npfvar_get_data(vp, NPFVAR_FAM, 0);
154}
155
156static bool
157npfctl_build_fam(nc_ctx_t *nc, sa_family_t family,
158    fam_addr_mask_t *fam, int opts)
159{
160	/*
161	 * If family is specified, address does not match it and the
162	 * address is extracted from the interface, then simply ignore.
163	 * Otherwise, address of invalid family was passed manually.
164	 */
165	if (family != AF_UNSPEC && family != fam->fam_family) {
166		if (!fam->fam_ifindex) {
167			yyerror("specified address is not of the required "
168			    "family %d", family);
169		}
170		return false;
171	}
172
173	/*
174	 * Optimise 0.0.0.0/0 case to be NOP.  Otherwise, address with
175	 * zero mask would never match and therefore is not valid.
176	 */
177	if (fam->fam_mask == 0) {
178		npf_addr_t zero;
179
180		memset(&zero, 0, sizeof(npf_addr_t));
181		if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) {
182			yyerror("filter criterion would never match");
183		}
184		return false;
185	}
186
187	switch (fam->fam_family) {
188	case AF_INET:
189		npfctl_gennc_v4cidr(nc, opts,
190		    &fam->fam_addr, fam->fam_mask);
191		break;
192	case AF_INET6:
193		npfctl_gennc_v6cidr(nc, opts,
194		    &fam->fam_addr, fam->fam_mask);
195		break;
196	default:
197		yyerror("family %d is not supported", fam->fam_family);
198	}
199	return true;
200}
201
202static void
203npfctl_build_vars(nc_ctx_t *nc, sa_family_t family, npfvar_t *vars, int opts)
204{
205	const int type = npfvar_get_type(vars, 0);
206	size_t i;
207
208	npfctl_ncgen_group(nc);
209	for (i = 0; i < npfvar_get_count(vars); i++) {
210		void *data = npfvar_get_data(vars, type, i);
211		assert(data != NULL);
212
213		switch (type) {
214		case NPFVAR_FAM: {
215			fam_addr_mask_t *fam = data;
216			npfctl_build_fam(nc, family, fam, opts);
217			break;
218		}
219		case NPFVAR_PORT_RANGE: {
220			port_range_t *pr = data;
221			if (opts & NC_MATCH_TCP) {
222				npfctl_gennc_ports(nc, opts & ~NC_MATCH_UDP,
223				    pr->pr_start, pr->pr_end);
224			}
225			if (opts & NC_MATCH_UDP) {
226				npfctl_gennc_ports(nc, opts & ~NC_MATCH_TCP,
227				    pr->pr_start, pr->pr_end);
228			}
229			break;
230		}
231		case NPFVAR_TABLE: {
232			u_int tid = atoi(data);
233			npfctl_gennc_tbl(nc, opts, tid);
234			break;
235		}
236		default:
237			assert(false);
238		}
239	}
240	npfctl_ncgen_endgroup(nc);
241}
242
243static int
244npfctl_build_proto(nc_ctx_t *nc, sa_family_t family,
245    const opt_proto_t *op, bool noaddrs, bool noports)
246{
247	const npfvar_t *popts = op->op_opts;
248	const int proto = op->op_proto;
249	int pflag = 0;
250
251	switch (proto) {
252	case IPPROTO_TCP:
253		pflag = NC_MATCH_TCP;
254		if (!popts) {
255			break;
256		}
257		assert(npfvar_get_count(popts) == 2);
258
259		/* Build TCP flags block (optional). */
260		uint8_t *tf, *tf_mask;
261
262		tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0);
263		tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1);
264		npfctl_gennc_tcpfl(nc, *tf, *tf_mask);
265		noports = false;
266		break;
267	case IPPROTO_UDP:
268		pflag = NC_MATCH_UDP;
269		break;
270	case IPPROTO_ICMP:
271		/*
272		 * Build ICMP block.
273		 */
274		if (!noports) {
275			goto invop;
276		}
277		assert(npfvar_get_count(popts) == 2);
278
279		int *icmp_type, *icmp_code;
280		icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0);
281		icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1);
282		npfctl_gennc_icmp(nc, *icmp_type, *icmp_code);
283		noports = false;
284		break;
285	case IPPROTO_ICMPV6:
286		/*
287		 * Build ICMP block.
288		 */
289		if (!noports) {
290			goto invop;
291		}
292		assert(npfvar_get_count(popts) == 2);
293
294		int *icmp6_type, *icmp6_code;
295		icmp6_type = npfvar_get_data(popts, NPFVAR_ICMP6, 0);
296		icmp6_code = npfvar_get_data(popts, NPFVAR_ICMP6, 1);
297		npfctl_gennc_icmp6(nc, *icmp6_type, *icmp6_code);
298		noports = false;
299		break;
300	case -1:
301		pflag = NC_MATCH_TCP | NC_MATCH_UDP;
302		noports = false;
303		break;
304	default:
305		/*
306		 * No filter options are supported for other protocols,
307		 * only the IP addresses are allowed.
308		 */
309		if (noports) {
310			break;
311		}
312invop:
313		yyerror("invalid filter options for protocol %d", proto);
314	}
315
316	/*
317	 * Build the protocol block, unless other blocks will implicitly
318	 * perform the family/protocol checks for us.
319	 */
320	if ((family != AF_UNSPEC && noaddrs) || (proto != -1 && noports)) {
321		uint8_t addrlen;
322
323		switch (family) {
324		case AF_INET:
325			addrlen = sizeof(struct in_addr);
326			break;
327		case AF_INET6:
328			addrlen = sizeof(struct in6_addr);
329			break;
330		default:
331			addrlen = 0;
332		}
333		npfctl_gennc_proto(nc,
334		    noaddrs ? addrlen : 0,
335		    noports ? proto : 0xff);
336	}
337	return pflag;
338}
339
340static bool
341npfctl_build_ncode(nl_rule_t *rl, sa_family_t family, const opt_proto_t *op,
342    const filt_opts_t *fopts, bool invert)
343{
344	const addr_port_t *apfrom = &fopts->fo_from;
345	const addr_port_t *apto = &fopts->fo_to;
346	const int proto = op->op_proto;
347	bool noaddrs, noports;
348	nc_ctx_t *nc;
349	void *code;
350	size_t len;
351
352	/*
353	 * If none specified, no n-code.
354	 */
355	noaddrs = !apfrom->ap_netaddr && !apto->ap_netaddr;
356	noports = !apfrom->ap_portrange && !apto->ap_portrange;
357	if (family == AF_UNSPEC && proto == -1 && !op->op_opts &&
358	    noaddrs && noports)
359		return false;
360
361	int srcflag = NC_MATCH_SRC;
362	int dstflag = NC_MATCH_DST;
363
364	if (invert) {
365		srcflag = NC_MATCH_DST;
366		dstflag = NC_MATCH_SRC;
367	}
368
369	nc = npfctl_ncgen_create();
370
371	/* Build layer 4 protocol blocks. */
372	int pflag = npfctl_build_proto(nc, family, op, noaddrs, noports);
373
374	/* Build IP address blocks. */
375	npfctl_build_vars(nc, family, apfrom->ap_netaddr, srcflag);
376	npfctl_build_vars(nc, family, apto->ap_netaddr, dstflag);
377
378	/* Build port-range blocks. */
379	npfctl_build_vars(nc, family, apfrom->ap_portrange, srcflag | pflag);
380	npfctl_build_vars(nc, family, apto->ap_portrange, dstflag | pflag);
381
382	/*
383	 * Complete n-code (destroys the context) and pass to the rule.
384	 */
385	code = npfctl_ncgen_complete(nc, &len);
386	if (npf_debug) {
387		extern int yylineno;
388		printf("RULE AT LINE %d\n", yylineno);
389		npfctl_ncgen_print(code, len);
390	}
391	assert(code && len > 0);
392
393	if (npf_rule_setcode(rl, NPF_CODE_NC, code, len) == -1) {
394		errx(EXIT_FAILURE, "npf_rule_setcode failed");
395	}
396	free(code);
397	return true;
398}
399
400static void
401npfctl_build_rpcall(nl_rproc_t *rp, const char *name, npfvar_t *args)
402{
403	npf_extmod_t *extmod;
404	nl_ext_t *extcall;
405	int error;
406
407	extmod = npf_extmod_get(name, &extcall);
408	if (extmod == NULL) {
409		yyerror("unknown rule procedure '%s'", name);
410	}
411
412	for (size_t i = 0; i < npfvar_get_count(args); i++) {
413		const char *param, *value;
414		proc_param_t *p;
415
416		p = npfvar_get_data(args, NPFVAR_PROC_PARAM, i);
417		param = p->pp_param;
418		value = p->pp_value;
419
420		error = npf_extmod_param(extmod, extcall, param, value);
421		switch (error) {
422		case EINVAL:
423			yyerror("invalid parameter '%s'", param);
424		default:
425			break;
426		}
427	}
428	error = npf_rproc_extcall(rp, extcall);
429	if (error) {
430		yyerror(error == EEXIST ?
431		    "duplicate procedure call" : "unexpected error");
432	}
433}
434
435/*
436 * npfctl_build_rproc: create and insert a rule procedure.
437 */
438void
439npfctl_build_rproc(const char *name, npfvar_t *procs)
440{
441	nl_rproc_t *rp;
442	size_t i;
443
444	rp = npf_rproc_create(name);
445	if (rp == NULL) {
446		errx(EXIT_FAILURE, "npf_rproc_create failed");
447	}
448	npf_rproc_insert(npf_conf, rp);
449
450	for (i = 0; i < npfvar_get_count(procs); i++) {
451		proc_call_t *pc = npfvar_get_data(procs, NPFVAR_PROC, i);
452		npfctl_build_rpcall(rp, pc->pc_name, pc->pc_opts);
453	}
454}
455
456/*
457 * npfctl_build_group: create a group, insert into the global ruleset,
458 * update the current group pointer and increase the nesting level.
459 */
460void
461npfctl_build_group(const char *name, int attr, u_int if_idx, bool def)
462{
463	const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
464	nl_rule_t *rl;
465
466	if (def || (attr & attr_di) == 0) {
467		attr |= attr_di;
468	}
469
470	rl = npf_rule_create(name, attr | NPF_RULE_GROUP, if_idx);
471	npf_rule_setprio(rl, NPF_PRI_LAST);
472	if (def) {
473		if (defgroup) {
474			yyerror("multiple default groups are not valid");
475		}
476		if (rule_nesting_level) {
477			yyerror("default group can only be at the top level");
478		}
479		defgroup = rl;
480	} else {
481		nl_rule_t *cg = current_group[rule_nesting_level];
482		npf_rule_insert(npf_conf, cg, rl);
483	}
484
485	/* Set the current group and increase the nesting level. */
486	if (rule_nesting_level >= MAX_RULE_NESTING) {
487		yyerror("rule nesting limit reached");
488	}
489	current_group[++rule_nesting_level] = rl;
490}
491
492void
493npfctl_build_group_end(void)
494{
495	assert(rule_nesting_level > 0);
496	current_group[rule_nesting_level--] = NULL;
497}
498
499/*
500 * npfctl_build_rule: create a rule, build n-code from filter options,
501 * if any, and insert into the ruleset of current group, or set the rule.
502 */
503void
504npfctl_build_rule(uint32_t attr, u_int if_idx, sa_family_t family,
505    const opt_proto_t *op, const filt_opts_t *fopts, const char *rproc)
506{
507	nl_rule_t *rl;
508
509	attr |= (npf_conf ? 0 : NPF_RULE_DYNAMIC);
510
511	rl = npf_rule_create(NULL, attr, if_idx);
512	npfctl_build_ncode(rl, family, op, fopts, false);
513	if (rproc) {
514		npf_rule_setproc(rl, rproc);
515	}
516
517	if (npf_conf) {
518		nl_rule_t *cg = current_group[rule_nesting_level];
519
520		if (rproc && !npf_rproc_exists_p(npf_conf, rproc)) {
521			yyerror("rule procedure '%s' is not defined", rproc);
522		}
523		assert(cg != NULL);
524		npf_rule_setprio(rl, NPF_PRI_LAST);
525		npf_rule_insert(npf_conf, cg, rl);
526	} else {
527		/* We have parsed a single rule - set it. */
528		the_rule = rl;
529	}
530}
531
532/*
533 * npfctl_build_nat: create a single NAT policy of a specified
534 * type with a given filter options.
535 */
536static void
537npfctl_build_nat(int type, u_int if_idx, sa_family_t family,
538    const addr_port_t *ap, const filt_opts_t *fopts, bool binat)
539{
540	const opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
541	fam_addr_mask_t *am;
542	in_port_t port;
543	nl_nat_t *nat;
544
545	if (!ap->ap_netaddr) {
546		yyerror("%s network segment is not specified",
547		    type == NPF_NATIN ? "inbound" : "outbound");
548	}
549	am = npfctl_get_singlefam(ap->ap_netaddr);
550	if (am->fam_family != family) {
551		yyerror("IPv6 NAT is not supported");
552	}
553
554	switch (type) {
555	case NPF_NATOUT:
556		/*
557		 * Outbound NAT (or source NAT) policy, usually used for the
558		 * traditional NAPT.  If it is a half for bi-directional NAT,
559		 * then no port translation with mapping.
560		 */
561		nat = npf_nat_create(NPF_NATOUT, !binat ?
562		    (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0,
563		    if_idx, &am->fam_addr, am->fam_family, 0);
564		break;
565	case NPF_NATIN:
566		/*
567		 * Inbound NAT (or destination NAT).  Unless bi-NAT, a port
568		 * must be specified, since it has to be redirection.
569		 */
570		port = 0;
571		if (!binat) {
572			if (!ap->ap_portrange) {
573				yyerror("inbound port is not specified");
574			}
575			port = npfctl_get_singleport(ap->ap_portrange);
576		}
577		nat = npf_nat_create(NPF_NATIN, !binat ? NPF_NAT_PORTS : 0,
578		    if_idx, &am->fam_addr, am->fam_family, port);
579		break;
580	default:
581		assert(false);
582	}
583
584	npfctl_build_ncode(nat, family, &op, fopts, false);
585	npf_nat_insert(npf_conf, nat, NPF_PRI_LAST);
586}
587
588/*
589 * npfctl_build_natseg: validate and create NAT policies.
590 */
591void
592npfctl_build_natseg(int sd, int type, u_int if_idx, const addr_port_t *ap1,
593    const addr_port_t *ap2, const filt_opts_t *fopts)
594{
595	sa_family_t af = AF_INET;
596	filt_opts_t imfopts;
597	bool binat;
598
599	if (sd == NPFCTL_NAT_STATIC) {
600		yyerror("static NAT is not yet supported");
601	}
602	assert(sd == NPFCTL_NAT_DYNAMIC);
603	assert(if_idx != 0);
604
605	/*
606	 * Bi-directional NAT is a combination of inbound NAT and outbound
607	 * NAT policies.  Note that the translation address is local IP and
608	 * the filter criteria is inverted accordingly.
609	 */
610	binat = (NPF_NATIN | NPF_NATOUT) == type;
611
612	/*
613	 * If the filter criteria is not specified explicitly, apply implicit
614	 * filtering according to the given network segments.
615	 *
616	 * Note: filled below, depending on the type.
617	 */
618	if (__predict_true(!fopts)) {
619		fopts = &imfopts;
620	}
621
622	if (type & NPF_NATIN) {
623		memset(&imfopts, 0, sizeof(filt_opts_t));
624		memcpy(&imfopts.fo_to, ap2, sizeof(addr_port_t));
625		npfctl_build_nat(NPF_NATIN, if_idx, af, ap1, fopts, binat);
626	}
627	if (type & NPF_NATOUT) {
628		memset(&imfopts, 0, sizeof(filt_opts_t));
629		memcpy(&imfopts.fo_from, ap1, sizeof(addr_port_t));
630		npfctl_build_nat(NPF_NATOUT, if_idx, af, ap2, fopts, binat);
631	}
632}
633
634/*
635 * npfctl_fill_table: fill NPF table with entries from a specified file.
636 */
637static void
638npfctl_fill_table(nl_table_t *tl, u_int type, const char *fname)
639{
640	char *buf = NULL;
641	int l = 0;
642	FILE *fp;
643	size_t n;
644
645	fp = fopen(fname, "r");
646	if (fp == NULL) {
647		err(EXIT_FAILURE, "open '%s'", fname);
648	}
649	while (l++, getline(&buf, &n, fp) != -1) {
650		fam_addr_mask_t fam;
651		int alen;
652
653		if (*buf == '\n' || *buf == '#') {
654			continue;
655		}
656
657		if (!npfctl_parse_cidr(buf, &fam, &alen)) {
658			errx(EXIT_FAILURE,
659			    "%s:%d: invalid table entry", fname, l);
660		}
661		if (type == NPF_TABLE_HASH && fam.fam_mask != NPF_NO_NETMASK) {
662			errx(EXIT_FAILURE,
663			    "%s:%d: mask used with the hash table", fname, l);
664		}
665
666		/* Create and add a table entry. */
667		npf_table_add_entry(tl, fam.fam_family,
668		    &fam.fam_addr, fam.fam_mask);
669	}
670	if (buf != NULL) {
671		free(buf);
672	}
673}
674
675/*
676 * npfctl_build_table: create an NPF table, add to the configuration and,
677 * if required, fill with contents from a file.
678 */
679void
680npfctl_build_table(const char *tid, u_int type, const char *fname)
681{
682	nl_table_t *tl;
683	u_int id;
684
685	id = atoi(tid);
686	tl = npf_table_create(id, type);
687	assert(tl != NULL);
688
689	if (npf_table_insert(npf_conf, tl)) {
690		errx(EXIT_FAILURE, "table '%d' is already defined\n", id);
691	}
692
693	if (fname) {
694		npfctl_fill_table(tl, type, fname);
695	}
696}
697