npf_build.c revision 1.31 1 /* $NetBSD: npf_build.c,v 1.31 2013/11/22 00:25:51 rmind 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.31 2013/11/22 00:25:51 rmind 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 <ctype.h>
46 #include <errno.h>
47 #include <err.h>
48
49 #include <pcap/pcap.h>
50
51 #include "npfctl.h"
52
53 #define MAX_RULE_NESTING 16
54
55 static nl_config_t * npf_conf = NULL;
56 static bool npf_debug = false;
57 static nl_rule_t * the_rule = NULL;
58
59 static nl_rule_t * current_group[MAX_RULE_NESTING];
60 static unsigned rule_nesting_level = 0;
61 static nl_rule_t * defgroup = NULL;
62
63 static void npfctl_dump_bpf(struct bpf_program *);
64
65 void
66 npfctl_config_init(bool debug)
67 {
68 npf_conf = npf_config_create();
69 if (npf_conf == NULL) {
70 errx(EXIT_FAILURE, "npf_config_create failed");
71 }
72 npf_debug = debug;
73 memset(current_group, 0, sizeof(current_group));
74 }
75
76 int
77 npfctl_config_send(int fd, const char *out)
78 {
79 int error;
80
81 if (out) {
82 _npf_config_setsubmit(npf_conf, out);
83 printf("\nSaving to %s\n", out);
84 }
85 if (!defgroup) {
86 errx(EXIT_FAILURE, "default group was not defined");
87 }
88 npf_rule_insert(npf_conf, NULL, defgroup);
89 error = npf_config_submit(npf_conf, fd);
90 if (error) {
91 nl_error_t ne;
92 _npf_config_error(npf_conf, &ne);
93 npfctl_print_error(&ne);
94 }
95 if (fd) {
96 npf_config_destroy(npf_conf);
97 }
98 return error;
99 }
100
101 nl_config_t *
102 npfctl_config_ref(void)
103 {
104 return npf_conf;
105 }
106
107 nl_rule_t *
108 npfctl_rule_ref(void)
109 {
110 return the_rule;
111 }
112
113 bool
114 npfctl_debug_addif(const char *ifname)
115 {
116 const char tname[] = "npftest";
117 const size_t tnamelen = sizeof(tname) - 1;
118
119 if (npf_debug) {
120 _npf_debug_addif(npf_conf, ifname);
121 return strncmp(ifname, tname, tnamelen) == 0;
122 }
123 return 0;
124 }
125
126 bool
127 npfctl_table_exists_p(const char *name)
128 {
129 return npf_conf ? npf_table_exists_p(npf_conf, name) : false;
130 }
131
132 static in_port_t
133 npfctl_get_singleport(const npfvar_t *vp)
134 {
135 port_range_t *pr;
136 in_port_t *port;
137
138 if (npfvar_get_count(vp) > 1) {
139 yyerror("multiple ports are not valid");
140 }
141 pr = npfvar_get_data(vp, NPFVAR_PORT_RANGE, 0);
142 if (pr->pr_start != pr->pr_end) {
143 yyerror("port range is not valid");
144 }
145 port = &pr->pr_start;
146 return *port;
147 }
148
149 static fam_addr_mask_t *
150 npfctl_get_singlefam(const npfvar_t *vp)
151 {
152 if (npfvar_get_count(vp) > 1) {
153 yyerror("multiple addresses are not valid");
154 }
155 return npfvar_get_data(vp, NPFVAR_FAM, 0);
156 }
157
158 static bool
159 npfctl_build_fam(npf_bpf_t *ctx, sa_family_t family,
160 fam_addr_mask_t *fam, int opts)
161 {
162 /*
163 * If family is specified, address does not match it and the
164 * address is extracted from the interface, then simply ignore.
165 * Otherwise, address of invalid family was passed manually.
166 */
167 if (family != AF_UNSPEC && family != fam->fam_family) {
168 if (!fam->fam_ifindex) {
169 yyerror("specified address is not of the required "
170 "family %d", family);
171 }
172 return false;
173 }
174
175 family = fam->fam_family;
176 if (family != AF_INET && family != AF_INET6) {
177 yyerror("family %d is not supported", family);
178 }
179
180 /*
181 * Optimise 0.0.0.0/0 case to be NOP. Otherwise, address with
182 * zero mask would never match and therefore is not valid.
183 */
184 if (fam->fam_mask == 0) {
185 static const npf_addr_t zero; /* must be static */
186
187 if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) {
188 yyerror("filter criterion would never match");
189 }
190 return false;
191 }
192
193 npfctl_bpf_cidr(ctx, opts, family, &fam->fam_addr, fam->fam_mask);
194 return true;
195 }
196
197 static void
198 npfctl_build_vars(npf_bpf_t *ctx, sa_family_t family, npfvar_t *vars, int opts)
199 {
200 const int type = npfvar_get_type(vars, 0);
201 size_t i;
202
203 npfctl_bpf_group(ctx);
204 for (i = 0; i < npfvar_get_count(vars); i++) {
205 void *data = npfvar_get_data(vars, type, i);
206 assert(data != NULL);
207
208 switch (type) {
209 case NPFVAR_FAM: {
210 fam_addr_mask_t *fam = data;
211 npfctl_build_fam(ctx, family, fam, opts);
212 break;
213 }
214 case NPFVAR_PORT_RANGE: {
215 port_range_t *pr = data;
216 npfctl_bpf_ports(ctx, opts, pr->pr_start, pr->pr_end);
217 break;
218 }
219 case NPFVAR_TABLE: {
220 u_int tid = atoi(data);
221 npfctl_bpf_table(ctx, opts, tid);
222 break;
223 }
224 default:
225 assert(false);
226 }
227 }
228 npfctl_bpf_endgroup(ctx);
229 }
230
231 static void
232 npfctl_build_proto(npf_bpf_t *ctx, sa_family_t family, const opt_proto_t *op)
233 {
234 const npfvar_t *popts = op->op_opts;
235 const int proto = op->op_proto;
236
237 /* IP version and/or L4 protocol matching. */
238 if (family != AF_UNSPEC || proto != -1) {
239 npfctl_bpf_proto(ctx, family, proto);
240 }
241
242 switch (proto) {
243 case IPPROTO_TCP:
244 /* Build TCP flags matching (optional). */
245 if (popts) {
246 uint8_t *tf, *tf_mask;
247
248 assert(npfvar_get_count(popts) == 2);
249 tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0);
250 tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1);
251 npfctl_bpf_tcpfl(ctx, *tf, *tf_mask);
252 }
253 break;
254 case IPPROTO_ICMP:
255 case IPPROTO_ICMPV6:
256 /* Build ICMP/ICMPv6 type and/or code matching. */
257 if (popts) {
258 int *icmp_type, *icmp_code;
259
260 assert(npfvar_get_count(popts) == 2);
261 icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0);
262 icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1);
263 npfctl_bpf_icmp(ctx, *icmp_type, *icmp_code);
264 }
265 break;
266 default:
267 /* No options for other protocols. */
268 break;
269 }
270 }
271
272 static bool
273 npfctl_build_code(nl_rule_t *rl, sa_family_t family, const opt_proto_t *op,
274 const filt_opts_t *fopts)
275 {
276 const addr_port_t *apfrom = &fopts->fo_from;
277 const addr_port_t *apto = &fopts->fo_to;
278 const int proto = op->op_proto;
279 bool noproto, noaddrs, noports;
280 npf_bpf_t *bc;
281 size_t len;
282
283 /* If none specified, then no byte-code. */
284 noproto = family == AF_UNSPEC && proto == -1 && !op->op_opts;
285 noaddrs = !apfrom->ap_netaddr && !apto->ap_netaddr;
286 noports = !apfrom->ap_portrange && !apto->ap_portrange;
287 if (noproto && noaddrs && noports) {
288 return false;
289 }
290
291 /*
292 * Sanity check: ports can only be used with TCP or UDP protocol.
293 * No filter options are supported for other protocols, only the
294 * IP addresses are allowed.
295 */
296 if (!noports) {
297 switch (proto) {
298 case IPPROTO_TCP:
299 case IPPROTO_UDP:
300 case -1:
301 break;
302 default:
303 yyerror("invalid filter options for protocol %d", proto);
304 }
305 }
306
307 bc = npfctl_bpf_create();
308
309 /* Build layer 4 protocol blocks. */
310 npfctl_build_proto(bc, family, op);
311
312 /* Build IP address blocks. */
313 npfctl_build_vars(bc, family, apfrom->ap_netaddr, MATCH_SRC);
314 npfctl_build_vars(bc, family, apto->ap_netaddr, MATCH_DST);
315
316 /* Build port-range blocks. */
317 npfctl_build_vars(bc, family, apfrom->ap_portrange, MATCH_SRC);
318 npfctl_build_vars(bc, family, apto->ap_portrange, MATCH_DST);
319
320 /* Set the byte-code marks, if any. */
321 const void *bmarks = npfctl_bpf_bmarks(bc, &len);
322 if (npf_rule_setinfo(rl, bmarks, len) == -1) {
323 errx(EXIT_FAILURE, "npf_rule_setinfo failed");
324 }
325
326 /* Complete BPF byte-code and pass to the rule. */
327 struct bpf_program *bf = npfctl_bpf_complete(bc);
328 len = bf->bf_len * sizeof(struct bpf_insn);
329
330 if (npf_rule_setcode(rl, NPF_CODE_BPF, bf->bf_insns, len) == -1) {
331 errx(EXIT_FAILURE, "npf_rule_setcode failed");
332 }
333 npfctl_dump_bpf(bf);
334 npfctl_bpf_destroy(bc);
335
336 return true;
337 }
338
339 static void
340 npfctl_build_pcap(nl_rule_t *rl, const char *filter)
341 {
342 const size_t maxsnaplen = 64 * 1024;
343 struct bpf_program bf;
344 size_t len;
345
346 if (pcap_compile_nopcap(maxsnaplen, DLT_RAW, &bf,
347 filter, 1, PCAP_NETMASK_UNKNOWN) == -1) {
348 yyerror("invalid pcap-filter(7) syntax");
349 }
350 len = bf.bf_len * sizeof(struct bpf_insn);
351
352 if (npf_rule_setcode(rl, NPF_CODE_BPF, bf.bf_insns, len) == -1) {
353 errx(EXIT_FAILURE, "npf_rule_setcode failed");
354 }
355 npfctl_dump_bpf(&bf);
356 pcap_freecode(&bf);
357 }
358
359 static void
360 npfctl_build_rpcall(nl_rproc_t *rp, const char *name, npfvar_t *args)
361 {
362 npf_extmod_t *extmod;
363 nl_ext_t *extcall;
364 int error;
365
366 extmod = npf_extmod_get(name, &extcall);
367 if (extmod == NULL) {
368 yyerror("unknown rule procedure '%s'", name);
369 }
370
371 for (size_t i = 0; i < npfvar_get_count(args); i++) {
372 const char *param, *value;
373 proc_param_t *p;
374
375 p = npfvar_get_data(args, NPFVAR_PROC_PARAM, i);
376 param = p->pp_param;
377 value = p->pp_value;
378
379 error = npf_extmod_param(extmod, extcall, param, value);
380 switch (error) {
381 case EINVAL:
382 yyerror("invalid parameter '%s'", param);
383 default:
384 break;
385 }
386 }
387 error = npf_rproc_extcall(rp, extcall);
388 if (error) {
389 yyerror(error == EEXIST ?
390 "duplicate procedure call" : "unexpected error");
391 }
392 }
393
394 /*
395 * npfctl_build_rproc: create and insert a rule procedure.
396 */
397 void
398 npfctl_build_rproc(const char *name, npfvar_t *procs)
399 {
400 nl_rproc_t *rp;
401 size_t i;
402
403 rp = npf_rproc_create(name);
404 if (rp == NULL) {
405 errx(EXIT_FAILURE, "%s failed", __func__);
406 }
407 npf_rproc_insert(npf_conf, rp);
408
409 for (i = 0; i < npfvar_get_count(procs); i++) {
410 proc_call_t *pc = npfvar_get_data(procs, NPFVAR_PROC, i);
411 npfctl_build_rpcall(rp, pc->pc_name, pc->pc_opts);
412 }
413 }
414
415 void
416 npfctl_build_maprset(const char *name, int attr, const char *ifname)
417 {
418 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
419 nl_rule_t *rl;
420
421 /* If no direction is not specified, then both. */
422 if ((attr & attr_di) == 0) {
423 attr |= attr_di;
424 }
425 /* Allow only "in/out" attributes. */
426 attr = NPF_RULE_GROUP | NPF_RULE_GROUP | (attr & attr_di);
427 rl = npf_rule_create(name, attr, ifname);
428 npf_nat_insert(npf_conf, rl, NPF_PRI_LAST);
429 }
430
431 /*
432 * npfctl_build_group: create a group, insert into the global ruleset,
433 * update the current group pointer and increase the nesting level.
434 */
435 void
436 npfctl_build_group(const char *name, int attr, const char *ifname, bool def)
437 {
438 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
439 nl_rule_t *rl;
440
441 if (def || (attr & attr_di) == 0) {
442 attr |= attr_di;
443 }
444
445 rl = npf_rule_create(name, attr | NPF_RULE_GROUP, ifname);
446 npf_rule_setprio(rl, NPF_PRI_LAST);
447 if (def) {
448 if (defgroup) {
449 yyerror("multiple default groups are not valid");
450 }
451 if (rule_nesting_level) {
452 yyerror("default group can only be at the top level");
453 }
454 defgroup = rl;
455 } else {
456 nl_rule_t *cg = current_group[rule_nesting_level];
457 npf_rule_insert(npf_conf, cg, rl);
458 }
459
460 /* Set the current group and increase the nesting level. */
461 if (rule_nesting_level >= MAX_RULE_NESTING) {
462 yyerror("rule nesting limit reached");
463 }
464 current_group[++rule_nesting_level] = rl;
465 }
466
467 void
468 npfctl_build_group_end(void)
469 {
470 assert(rule_nesting_level > 0);
471 current_group[rule_nesting_level--] = NULL;
472 }
473
474 /*
475 * npfctl_build_rule: create a rule, build byte-code from filter options,
476 * if any, and insert into the ruleset of current group, or set the rule.
477 */
478 void
479 npfctl_build_rule(uint32_t attr, const char *ifname, sa_family_t family,
480 const opt_proto_t *op, const filt_opts_t *fopts,
481 const char *pcap_filter, const char *rproc)
482 {
483 nl_rule_t *rl;
484
485 attr |= (npf_conf ? 0 : NPF_RULE_DYNAMIC);
486
487 rl = npf_rule_create(NULL, attr, ifname);
488 if (pcap_filter) {
489 npfctl_build_pcap(rl, pcap_filter);
490 } else {
491 npfctl_build_code(rl, family, op, fopts);
492 }
493
494 if (rproc) {
495 npf_rule_setproc(rl, rproc);
496 }
497
498 if (npf_conf) {
499 nl_rule_t *cg = current_group[rule_nesting_level];
500
501 if (rproc && !npf_rproc_exists_p(npf_conf, rproc)) {
502 yyerror("rule procedure '%s' is not defined", rproc);
503 }
504 assert(cg != NULL);
505 npf_rule_setprio(rl, NPF_PRI_LAST);
506 npf_rule_insert(npf_conf, cg, rl);
507 } else {
508 /* We have parsed a single rule - set it. */
509 the_rule = rl;
510 }
511 }
512
513 /*
514 * npfctl_build_nat: create a single NAT policy of a specified
515 * type with a given filter options.
516 */
517 static void
518 npfctl_build_nat(int type, const char *ifname, sa_family_t family,
519 const addr_port_t *ap, const filt_opts_t *fopts, bool binat)
520 {
521 const opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
522 fam_addr_mask_t *am;
523 in_port_t port;
524 nl_nat_t *nat;
525
526 if (!ap->ap_netaddr) {
527 yyerror("%s network segment is not specified",
528 type == NPF_NATIN ? "inbound" : "outbound");
529 }
530 am = npfctl_get_singlefam(ap->ap_netaddr);
531 if (am->fam_family != family) {
532 yyerror("IPv6 NAT is not supported");
533 }
534
535 switch (type) {
536 case NPF_NATOUT:
537 /*
538 * Outbound NAT (or source NAT) policy, usually used for the
539 * traditional NAPT. If it is a half for bi-directional NAT,
540 * then no port translation with mapping.
541 */
542 nat = npf_nat_create(NPF_NATOUT, !binat ?
543 (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0,
544 ifname, &am->fam_addr, am->fam_family, 0);
545 break;
546 case NPF_NATIN:
547 /*
548 * Inbound NAT (or destination NAT). Unless bi-NAT, a port
549 * must be specified, since it has to be redirection.
550 */
551 port = 0;
552 if (!binat) {
553 if (!ap->ap_portrange) {
554 yyerror("inbound port is not specified");
555 }
556 port = npfctl_get_singleport(ap->ap_portrange);
557 }
558 nat = npf_nat_create(NPF_NATIN, !binat ? NPF_NAT_PORTS : 0,
559 ifname, &am->fam_addr, am->fam_family, port);
560 break;
561 default:
562 assert(false);
563 }
564
565 npfctl_build_code(nat, family, &op, fopts);
566 npf_nat_insert(npf_conf, nat, NPF_PRI_LAST);
567 }
568
569 /*
570 * npfctl_build_natseg: validate and create NAT policies.
571 */
572 void
573 npfctl_build_natseg(int sd, int type, const char *ifname,
574 const addr_port_t *ap1, const addr_port_t *ap2,
575 const filt_opts_t *fopts)
576 {
577 sa_family_t af = AF_INET;
578 filt_opts_t imfopts;
579 bool binat;
580
581 if (sd == NPFCTL_NAT_STATIC) {
582 yyerror("static NAT is not yet supported");
583 }
584 assert(sd == NPFCTL_NAT_DYNAMIC);
585 assert(ifname != NULL);
586
587 /*
588 * Bi-directional NAT is a combination of inbound NAT and outbound
589 * NAT policies. Note that the translation address is local IP and
590 * the filter criteria is inverted accordingly.
591 */
592 binat = (NPF_NATIN | NPF_NATOUT) == type;
593
594 /*
595 * If the filter criteria is not specified explicitly, apply implicit
596 * filtering according to the given network segments.
597 *
598 * Note: filled below, depending on the type.
599 */
600 if (__predict_true(!fopts)) {
601 fopts = &imfopts;
602 }
603
604 if (type & NPF_NATIN) {
605 memset(&imfopts, 0, sizeof(filt_opts_t));
606 memcpy(&imfopts.fo_to, ap2, sizeof(addr_port_t));
607 npfctl_build_nat(NPF_NATIN, ifname, af, ap1, fopts, binat);
608 }
609 if (type & NPF_NATOUT) {
610 memset(&imfopts, 0, sizeof(filt_opts_t));
611 memcpy(&imfopts.fo_from, ap1, sizeof(addr_port_t));
612 npfctl_build_nat(NPF_NATOUT, ifname, af, ap2, fopts, binat);
613 }
614 }
615
616 /*
617 * npfctl_fill_table: fill NPF table with entries from a specified file.
618 */
619 static void
620 npfctl_fill_table(nl_table_t *tl, u_int type, const char *fname)
621 {
622 char *buf = NULL;
623 int l = 0;
624 FILE *fp;
625 size_t n;
626
627 fp = fopen(fname, "r");
628 if (fp == NULL) {
629 err(EXIT_FAILURE, "open '%s'", fname);
630 }
631 while (l++, getline(&buf, &n, fp) != -1) {
632 fam_addr_mask_t fam;
633 int alen;
634
635 if (*buf == '\n' || *buf == '#') {
636 continue;
637 }
638
639 if (!npfctl_parse_cidr(buf, &fam, &alen)) {
640 errx(EXIT_FAILURE,
641 "%s:%d: invalid table entry", fname, l);
642 }
643 if (type == NPF_TABLE_HASH && fam.fam_mask != NPF_NO_NETMASK) {
644 errx(EXIT_FAILURE,
645 "%s:%d: mask used with the hash table", fname, l);
646 }
647
648 /* Create and add a table entry. */
649 npf_table_add_entry(tl, fam.fam_family,
650 &fam.fam_addr, fam.fam_mask);
651 }
652 if (buf != NULL) {
653 free(buf);
654 }
655 }
656
657 /*
658 * npfctl_build_table: create an NPF table, add to the configuration and,
659 * if required, fill with contents from a file.
660 */
661 void
662 npfctl_build_table(const char *tname, u_int type, const char *fname)
663 {
664 static unsigned tid = 0;
665 nl_table_t *tl;
666
667 tl = npf_table_create(tname, tid++, type);
668 assert(tl != NULL);
669
670 if (npf_table_insert(npf_conf, tl)) {
671 yyerror("table '%s' is already defined", tname);
672 }
673
674 if (fname) {
675 npfctl_fill_table(tl, type, fname);
676 }
677 }
678
679 /*
680 * npfctl_build_alg: create an NPF application level gateway and add it
681 * to the configuration.
682 */
683 void
684 npfctl_build_alg(const char *al_name)
685 {
686 if (_npf_alg_load(npf_conf, al_name) != 0) {
687 errx(EXIT_FAILURE, "ALG '%s' already loaded", al_name);
688 }
689 }
690
691 static void
692 npfctl_dump_bpf(struct bpf_program *bf)
693 {
694 if (npf_debug) {
695 extern char *yytext;
696 extern int yylineno;
697
698 int rule_line = yylineno - (int)(*yytext == '\n');
699 printf("\nRULE AT LINE %d\n", rule_line);
700 bpf_dump(bf, 0);
701 }
702 }
703