npf_build.c revision 1.9 1 /* $NetBSD: npf_build.c,v 1.9 2012/06/16 01:34:10 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2011-2012 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.9 2012/06/16 01:34:10 christos 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 <assert.h>
46 #include <err.h>
47
48 #include "npfctl.h"
49
50 static nl_config_t * npf_conf = NULL;
51 static nl_rule_t * current_group = NULL;
52 static bool npf_debug = false;
53 static bool defgroup_set = false;
54
55 void
56 npfctl_config_init(bool debug)
57 {
58
59 npf_conf = npf_config_create();
60 if (npf_conf == NULL) {
61 errx(EXIT_FAILURE, "npf_config_create failed");
62 }
63 npf_debug = debug;
64 }
65
66 int
67 npfctl_config_send(int fd)
68 {
69 int error;
70
71 if (!fd) {
72 _npf_config_setsubmit(npf_conf, "./npf.plist");
73 }
74 if (!defgroup_set) {
75 errx(EXIT_FAILURE, "default group was not defined");
76 }
77 error = npf_config_submit(npf_conf, fd);
78 if (error) {
79 nl_error_t ne;
80 _npf_config_error(npf_conf, &ne);
81 npfctl_print_error(&ne);
82 }
83 npf_config_destroy(npf_conf);
84 return error;
85 }
86
87 bool
88 npfctl_table_exists_p(const char *id)
89 {
90 return npf_table_exists_p(npf_conf, atoi(id));
91 }
92
93 static in_port_t
94 npfctl_get_singleport(const npfvar_t *vp)
95 {
96 port_range_t *pr;
97 in_port_t *port;
98
99 if (npfvar_get_count(vp) > 1) {
100 yyerror("multiple ports are not valid");
101 }
102 pr = npfvar_get_data(vp, NPFVAR_PORT_RANGE, 0);
103 if (pr->pr_start != pr->pr_end) {
104 yyerror("port range is not valid");
105 }
106 port = &pr->pr_start;
107 return *port;
108 }
109
110 static fam_addr_mask_t *
111 npfctl_get_singlefam(const npfvar_t *vp)
112 {
113 if (npfvar_get_count(vp) > 1) {
114 yyerror("multiple addresses are not valid");
115 }
116 return npfvar_get_data(vp, NPFVAR_FAM, 0);
117 }
118
119 static void
120 npfctl_build_fam(nc_ctx_t *nc, sa_family_t family,
121 fam_addr_mask_t *fam, int opts)
122 {
123 /*
124 * If family is specified, address does not match it and the
125 * address is extracted from the interface, then simply ignore.
126 * Otherwise, address of invalid family was passed manually.
127 */
128 if (family != AF_UNSPEC && family != fam->fam_family) {
129 if (!fam->fam_interface) {
130 yyerror("specified address is not of the required "
131 "family %d", family);
132 }
133 return;
134 }
135
136 /*
137 * Optimise 0.0.0.0/0 case to be NOP. Otherwise, address with
138 * zero mask would never match and therefore is not valid.
139 */
140 if (fam->fam_mask == 0) {
141 npf_addr_t zero;
142 memset(&zero, 0, sizeof(npf_addr_t));
143 if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) {
144 yyerror("filter criterion would never match");
145 }
146 return;
147 }
148
149 switch (fam->fam_family) {
150 case AF_INET:
151 npfctl_gennc_v4cidr(nc, opts,
152 &fam->fam_addr, fam->fam_mask);
153 break;
154 case AF_INET6:
155 npfctl_gennc_v6cidr(nc, opts,
156 &fam->fam_addr, fam->fam_mask);
157 break;
158 default:
159 yyerror("family %d is not supported", fam->fam_family);
160 }
161 }
162
163 static void
164 npfctl_build_vars(nc_ctx_t *nc, sa_family_t family, npfvar_t *vars, int opts)
165 {
166 const int type = npfvar_get_type(vars, 0);
167 size_t i;
168
169 npfctl_ncgen_group(nc);
170 for (i = 0; i < npfvar_get_count(vars); i++) {
171 void *data = npfvar_get_data(vars, type, i);
172 assert(data != NULL);
173
174 switch (type) {
175 case NPFVAR_FAM: {
176 fam_addr_mask_t *fam = data;
177 npfctl_build_fam(nc, family, fam, opts);
178 break;
179 }
180 case NPFVAR_PORT_RANGE: {
181 port_range_t *pr = data;
182 if (opts & NC_MATCH_TCP) {
183 npfctl_gennc_ports(nc, opts & ~NC_MATCH_UDP,
184 pr->pr_start, pr->pr_end);
185 }
186 if (opts & NC_MATCH_UDP) {
187 npfctl_gennc_ports(nc, opts & ~NC_MATCH_TCP,
188 pr->pr_start, pr->pr_end);
189 }
190 break;
191 }
192 case NPFVAR_TABLE: {
193 u_int tid = atoi(data);
194 npfctl_gennc_tbl(nc, opts, tid);
195 break;
196 }
197 default:
198 assert(false);
199 }
200 }
201 npfctl_ncgen_endgroup(nc);
202 }
203
204 static int
205 npfctl_build_proto(nc_ctx_t *nc, const opt_proto_t *op)
206 {
207 const npfvar_t *popts = op->op_opts;
208 int pflag = 0;
209
210 switch (op->op_proto) {
211 case IPPROTO_TCP:
212 pflag = NC_MATCH_TCP;
213 if (!popts) {
214 break;
215 }
216 assert(npfvar_get_count(popts) == 2);
217
218 /* Build TCP flags block (optional). */
219 uint8_t *tf, *tf_mask;
220
221 tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0);
222 tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1);
223 npfctl_gennc_tcpfl(nc, *tf, *tf_mask);
224 break;
225 case IPPROTO_UDP:
226 pflag = NC_MATCH_UDP;
227 break;
228 case IPPROTO_ICMP:
229 /*
230 * Build ICMP block.
231 */
232 assert(npfvar_get_count(popts) == 2);
233
234 int *icmp_type, *icmp_code;
235 icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0);
236 icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1);
237 npfctl_gennc_icmp(nc, *icmp_type, *icmp_code);
238 break;
239 case -1:
240 pflag = NC_MATCH_TCP | NC_MATCH_UDP;
241 break;
242 default:
243 yyerror("protocol %d is not supported", op->op_proto);
244 }
245 return pflag;
246 }
247
248 static bool
249 npfctl_build_ncode(nl_rule_t *rl, sa_family_t family, const opt_proto_t *op,
250 const filt_opts_t *fopts, bool invert)
251 {
252 const addr_port_t *apfrom = &fopts->fo_from;
253 const addr_port_t *apto = &fopts->fo_to;
254 nc_ctx_t *nc;
255 void *code;
256 size_t len;
257
258 if (family == AF_UNSPEC && op->op_proto == -1 &&
259 op->op_opts == NULL && !apfrom->ap_netaddr && !apto->ap_netaddr &&
260 !apfrom->ap_portrange && !apto->ap_portrange)
261 return false;
262
263 int srcflag = NC_MATCH_SRC;
264 int dstflag = NC_MATCH_DST;
265
266 if (invert) {
267 srcflag = NC_MATCH_DST;
268 dstflag = NC_MATCH_SRC;
269 }
270
271 nc = npfctl_ncgen_create();
272
273 /* Build IP address blocks. */
274 npfctl_build_vars(nc, family, apfrom->ap_netaddr, srcflag);
275 npfctl_build_vars(nc, family, apto->ap_netaddr, dstflag);
276
277 /* Build layer 4 protocol blocks. */
278 int pflag = npfctl_build_proto(nc, op);
279
280 /* Build port-range blocks. */
281 if (apfrom->ap_portrange) {
282 npfctl_build_vars(nc, family, apfrom->ap_portrange,
283 srcflag | pflag);
284 }
285 if (apto->ap_portrange) {
286 npfctl_build_vars(nc, family, apto->ap_portrange,
287 dstflag | pflag);
288 }
289
290 /*
291 * Complete n-code (destroys the context) and pass to the rule.
292 */
293 code = npfctl_ncgen_complete(nc, &len);
294 if (npf_debug) {
295 extern int yylineno;
296 printf("RULE AT LINE %d\n", yylineno);
297 npfctl_ncgen_print(code, len);
298 }
299 if (npf_rule_setcode(rl, NPF_CODE_NCODE, code, len) == -1) {
300 errx(EXIT_FAILURE, "npf_rule_setcode failed");
301 }
302 free(code);
303 return true;
304 }
305
306 static void
307 npfctl_build_rpcall(nl_rproc_t *rp, const char *name, npfvar_t *args)
308 {
309 /*
310 * XXX/TODO: Hardcoded for the first release. However,
311 * rule procedures will become fully dynamic modules.
312 */
313
314 bool log = false, norm = false;
315 bool rnd = false, no_df = false;
316 int minttl = 0, maxmss = 0;
317
318 if (strcmp(name, "log") == 0) {
319 log = true;
320 } else if (strcmp(name, "normalise") == 0) {
321 norm = true;
322 } else {
323 yyerror("unknown rule procedure '%s'", name);
324 }
325
326 for (size_t i = 0; i < npfvar_get_count(args); i++) {
327 module_arg_t *arg;
328 const char *aval;
329
330 arg = npfvar_get_data(args, NPFVAR_MODULE_ARG, i);
331 aval = arg->ma_name;
332
333 if (log) {
334 u_int if_idx = npfctl_find_ifindex(aval);
335 if (!if_idx) {
336 yyerror("unknown interface '%s'", aval);
337 }
338 _npf_rproc_setlog(rp, if_idx);
339 return;
340 }
341
342 const int type = npfvar_get_type(arg->ma_opts, 0);
343 if (type != -1 && type != NPFVAR_NUM) {
344 yyerror("option '%s' is not numeric", aval);
345 }
346 unsigned long *opt;
347
348 if (strcmp(aval, "random-id") == 0) {
349 rnd = true;
350 } else if (strcmp(aval, "min-ttl") == 0) {
351 opt = npfvar_get_data(arg->ma_opts, NPFVAR_NUM, 0);
352 minttl = *opt;
353 } else if (strcmp(aval, "max-mss") == 0) {
354 opt = npfvar_get_data(arg->ma_opts, NPFVAR_NUM, 0);
355 maxmss = *opt;
356 } else if (strcmp(aval, "no-df") == 0) {
357 no_df = true;
358 } else {
359 yyerror("unknown argument '%s'", aval);
360 }
361 }
362 assert(norm == true);
363 _npf_rproc_setnorm(rp, rnd, no_df, minttl, maxmss);
364 }
365
366 /*
367 * npfctl_build_rproc: create and insert a rule procedure.
368 */
369 void
370 npfctl_build_rproc(const char *name, npfvar_t *procs)
371 {
372 nl_rproc_t *rp;
373 size_t i;
374
375 rp = npf_rproc_create(name);
376 if (rp == NULL) {
377 errx(EXIT_FAILURE, "npf_rproc_create failed");
378 }
379 npf_rproc_insert(npf_conf, rp);
380
381 for (i = 0; i < npfvar_get_count(procs); i++) {
382 proc_op_t *po = npfvar_get_data(procs, NPFVAR_PROC_OP, i);
383 npfctl_build_rpcall(rp, po->po_name, po->po_opts);
384 }
385 }
386
387 /*
388 * npfctl_build_group: create a group, insert into the global ruleset
389 * and update the current group pointer.
390 */
391 void
392 npfctl_build_group(const char *name, int attr, u_int if_idx)
393 {
394 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
395 nl_rule_t *rl;
396
397 if (attr & NPF_RULE_DEFAULT) {
398 if (defgroup_set) {
399 yyerror("multiple default groups are not valid");
400 }
401 defgroup_set = true;
402 attr |= attr_di;
403
404 } else if ((attr & attr_di) == 0) {
405 attr |= attr_di;
406 }
407
408 rl = npf_rule_create(name, attr | NPF_RULE_FINAL, if_idx);
409 npf_rule_insert(npf_conf, NULL, rl, NPF_PRI_NEXT);
410 current_group = rl;
411 }
412
413 /*
414 * npfctl_build_rule: create a rule, build n-code from filter options,
415 * if any, and insert into the ruleset of current group.
416 */
417 void
418 npfctl_build_rule(int attr, u_int if_idx, sa_family_t family,
419 const opt_proto_t *op, const filt_opts_t *fopts, const char *rproc)
420 {
421 nl_rule_t *rl;
422
423 rl = npf_rule_create(NULL, attr, if_idx);
424 npfctl_build_ncode(rl, family, op, fopts, false);
425 if (rproc && npf_rule_setproc(npf_conf, rl, rproc) != 0) {
426 yyerror("rule procedure '%s' is not defined", rproc);
427 }
428 assert(current_group != NULL);
429 npf_rule_insert(npf_conf, current_group, rl, NPF_PRI_NEXT);
430 }
431
432 /*
433 * npfctl_build_nat: create a NAT policy of a specified type with a
434 * given filter options.
435 */
436 void
437 npfctl_build_nat(int sd, int type, u_int if_idx, const addr_port_t *ap1,
438 const addr_port_t *ap2, const filt_opts_t *fopts)
439 {
440 const opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
441 fam_addr_mask_t *am1, *am2;
442 filt_opts_t imfopts;
443 sa_family_t family;
444 nl_nat_t *nat;
445
446 if (sd == NPFCTL_NAT_STATIC) {
447 yyerror("static NAT is not yet supported");
448 }
449 assert(sd == NPFCTL_NAT_DYNAMIC);
450 assert(if_idx != 0);
451
452 family = AF_INET;
453
454 if (type & NPF_NATIN) {
455 if (!ap1->ap_netaddr) {
456 yyerror("inbound network segment is not specified");
457 }
458 am1 = npfctl_get_singlefam(ap1->ap_netaddr);
459 if (am1->fam_family != AF_INET) {
460 yyerror("IPv6 NAT is not supported");
461 }
462 assert(am1 != NULL);
463 } else
464 am1 = NULL;
465
466 if (type & NPF_NATOUT) {
467 if (!ap2->ap_netaddr) {
468 yyerror("outbound network segment is not specified");
469 }
470 am2 = npfctl_get_singlefam(ap2->ap_netaddr);
471 if (am2->fam_family != family) {
472 yyerror("IPv6 NAT is not supported");
473 }
474 assert(am2 != NULL);
475 } else
476 am2 = NULL;
477
478 /*
479 * If filter criteria is not specified explicitly, apply implicit
480 * filtering according to the given network segements.
481 */
482 if (!fopts) {
483 memset(&imfopts, 0, sizeof(filt_opts_t));
484 if (type & NPF_NATOUT) {
485 memcpy(&imfopts.fo_from, ap1, sizeof(addr_port_t));
486 }
487 if (type & NPF_NATIN) {
488 memcpy(&imfopts.fo_to, ap2, sizeof(addr_port_t));
489 }
490 fopts = &imfopts;
491 }
492
493 switch (type) {
494 case NPF_NATIN:
495 assert(am1 != NULL);
496 /*
497 * Redirection: an inbound NAT with a specific port.
498 */
499 if (!ap1->ap_portrange) {
500 yyerror("inbound port is not specified");
501 }
502 in_port_t port = npfctl_get_singleport(ap1->ap_portrange);
503 nat = npf_nat_create(NPF_NATIN, NPF_NAT_PORTS,
504 if_idx, &am1->fam_addr, am1->fam_family, port);
505 break;
506
507 case (NPF_NATIN | NPF_NATOUT):
508 assert(am1 != NULL);
509 /*
510 * Bi-directional NAT: a combination of inbound NAT and
511 * outbound NAT policies. Note that the translation address
512 * is local IP and filter criteria is inverted accordingly.
513 */
514 nat = npf_nat_create(NPF_NATIN, 0, if_idx,
515 &am1->fam_addr, am1->fam_family, 0);
516 npfctl_build_ncode(nat, family, &op, fopts, true);
517 npf_nat_insert(npf_conf, nat, NPF_PRI_NEXT);
518 /* FALLTHROUGH */
519
520 case NPF_NATOUT:
521 assert(am2 != NULL);
522 /*
523 * Traditional NAPT: an outbound NAT policy with port.
524 * If this is another half for bi-directional NAT, then
525 * no port translation with mapping.
526 */
527 nat = npf_nat_create(NPF_NATOUT, type == NPF_NATOUT ?
528 (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0,
529 if_idx, &am2->fam_addr, am2->fam_family, 0);
530 break;
531
532 default:
533 assert(false);
534 }
535 npfctl_build_ncode(nat, family, &op, fopts, false);
536 npf_nat_insert(npf_conf, nat, NPF_PRI_NEXT);
537 }
538
539 /*
540 * npfctl_fill_table: fill NPF table with entries from a specified file.
541 */
542 static void
543 npfctl_fill_table(nl_table_t *tl, const char *fname)
544 {
545 char *buf = NULL;
546 int l = 0;
547 FILE *fp;
548 size_t n;
549
550 fp = fopen(fname, "r");
551 if (fp == NULL) {
552 err(EXIT_FAILURE, "open '%s'", fname);
553 }
554 while (l++, getline(&buf, &n, fp) != -1) {
555 fam_addr_mask_t *fam;
556
557 if (*buf == '\n' || *buf == '#') {
558 continue;
559 }
560 fam = npfctl_parse_cidr(buf);
561 if (fam == NULL) {
562 errx(EXIT_FAILURE, "%s:%d: invalid table entry",
563 fname, l);
564 }
565
566 /* Create and add a table entry. */
567 npf_table_add_entry(tl, &fam->fam_addr, fam->fam_mask);
568 }
569 if (buf != NULL) {
570 free(buf);
571 }
572 }
573
574 /*
575 * npfctl_build_table: create an NPF table, add to the configuration and,
576 * if required, fill with contents from a file.
577 */
578 void
579 npfctl_build_table(const char *tid, u_int type, const char *fname)
580 {
581 nl_table_t *tl;
582 u_int id;
583
584 id = atoi(tid);
585 tl = npf_table_create(id, type);
586 assert(tl != NULL);
587
588 if (npf_table_insert(npf_conf, tl)) {
589 errx(EXIT_FAILURE, "table '%d' is already defined\n", id);
590 }
591
592 if (fname) {
593 npfctl_fill_table(tl, fname);
594 }
595 }
596