npf_build.c revision 1.3 1 /* $NetBSD: npf_build.c,v 1.3 2012/02/05 00:37:13 rmind 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.3 2012/02/05 00:37:13 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 <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
98 if (npfvar_get_count(vp) > 1) {
99 yyerror("multiple ports are not valid");
100 }
101 pr = npfvar_get_data(vp, NPFVAR_PORT_RANGE, 0);
102 if (pr->pr_start != pr->pr_end) {
103 yyerror("port range is not valid");
104 }
105 return &pr->pr_start;
106 }
107
108 static fam_addr_mask_t *
109 npfctl_get_singlefam(const npfvar_t *vp)
110 {
111 if (npfvar_get_count(vp) > 1) {
112 yyerror("multiple addresses are not valid");
113 }
114 return npfvar_get_data(vp, NPFVAR_FAM, 0);
115 }
116
117 static void
118 npfctl_build_fam(nc_ctx_t *nc, sa_family_t family,
119 fam_addr_mask_t *fam, int opts)
120 {
121 /*
122 * If family is specified, address does not match it and the
123 * address is extracted from the interface, then simply ignore.
124 * Otherwise, address of invalid family was passed manually.
125 */
126 if (family != AF_UNSPEC && family != fam->fam_family) {
127 if (!fam->fam_interface) {
128 yyerror("specified address is not of the required "
129 "family %d", family);
130 }
131 return;
132 }
133
134 /*
135 * Optimise 0.0.0.0/0 case to be NOP. Otherwise, address with
136 * zero mask would never match and therefore is not valid.
137 */
138 if (fam->fam_mask == 0) {
139 npf_addr_t zero;
140 memset(&zero, 0, sizeof(npf_addr_t));
141 if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) {
142 yyerror("filter criterion would never match");
143 }
144 return;
145 }
146
147 switch (fam->fam_family) {
148 case AF_INET:
149 npfctl_gennc_v4cidr(nc, opts,
150 &fam->fam_addr, fam->fam_mask);
151 break;
152 case AF_INET6:
153 npfctl_gennc_v6cidr(nc, opts,
154 &fam->fam_addr, fam->fam_mask);
155 break;
156 default:
157 yyerror("family %d is not supported", fam->fam_family);
158 }
159 }
160
161 static void
162 npfctl_build_vars(nc_ctx_t *nc, sa_family_t family, npfvar_t *vars, int opts)
163 {
164 const int type = npfvar_get_type(vars);
165 size_t i;
166
167 npfctl_ncgen_group(nc);
168 for (i = 0; i < npfvar_get_count(vars); i++) {
169 void *data = npfvar_get_data(vars, type, i);
170 assert(data != NULL);
171
172 switch (type) {
173 case NPFVAR_FAM: {
174 fam_addr_mask_t *fam = data;
175 npfctl_build_fam(nc, family, fam, opts);
176 break;
177 }
178 case NPFVAR_PORT_RANGE: {
179 port_range_t *pr = data;
180 if (opts & NC_MATCH_TCP) {
181 npfctl_gennc_ports(nc, opts & ~NC_MATCH_UDP,
182 pr->pr_start, pr->pr_end);
183 }
184 if (opts & NC_MATCH_UDP) {
185 npfctl_gennc_ports(nc, opts & ~NC_MATCH_TCP,
186 pr->pr_start, pr->pr_end);
187 }
188 break;
189 }
190 case NPFVAR_TABLE: {
191 u_int tid = atoi(data);
192 npfctl_gennc_tbl(nc, opts, tid);
193 break;
194 }
195 default:
196 assert(false);
197 }
198 }
199 npfctl_ncgen_endgroup(nc);
200 }
201
202 static int
203 npfctl_build_proto(nc_ctx_t *nc, const opt_proto_t *op)
204 {
205 const npfvar_t *popts = op->op_opts;
206 int pflag = 0;
207
208 switch (op->op_proto) {
209 case IPPROTO_TCP:
210 pflag = NC_MATCH_TCP;
211 if (!popts) {
212 break;
213 }
214 assert(npfvar_get_count(popts) == 2);
215
216 /* Build TCP flags block (optional). */
217 uint8_t *tf, *tf_mask;
218
219 tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0);
220 tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1);
221 npfctl_gennc_tcpfl(nc, *tf, *tf_mask);
222 break;
223 case IPPROTO_UDP:
224 pflag = NC_MATCH_UDP;
225 break;
226 case IPPROTO_ICMP:
227 /*
228 * Build ICMP block.
229 */
230 assert(npfvar_get_count(popts) == 2);
231
232 int *icmp_type, *icmp_code;
233 icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0);
234 icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1);
235 npfctl_gennc_icmp(nc, *icmp_type, *icmp_code);
236 break;
237 case -1:
238 pflag = NC_MATCH_TCP | NC_MATCH_UDP;
239 break;
240 default:
241 yyerror("protocol %d is not supported", op->op_proto);
242 }
243 return pflag;
244 }
245
246 static bool
247 npfctl_build_ncode(nl_rule_t *rl, sa_family_t family, const opt_proto_t *op,
248 const filt_opts_t *fopts, bool invert)
249 {
250 nc_ctx_t *nc;
251 void *code;
252 size_t len;
253
254 if (family == AF_UNSPEC && op->op_proto == -1 &&
255 op->op_opts == NULL && !fopts->fo_from && !fopts->fo_to &&
256 !fopts->fo_from_port_range && !fopts->fo_to_port_range)
257 return false;
258
259 int srcflag = NC_MATCH_SRC;
260 int dstflag = NC_MATCH_DST;
261
262 if (invert) {
263 srcflag = NC_MATCH_DST;
264 dstflag = NC_MATCH_SRC;
265 }
266
267 nc = npfctl_ncgen_create();
268
269 /* Build IP address blocks. */
270 npfctl_build_vars(nc, family, fopts->fo_from, srcflag);
271 npfctl_build_vars(nc, family, fopts->fo_to, dstflag);
272
273 /* Build layer 4 protocol blocks. */
274 int pflag = npfctl_build_proto(nc, op);
275
276 /* Build port-range blocks. */
277 if (fopts->fo_from_port_range) {
278 npfctl_build_vars(nc, family, fopts->fo_from_port_range,
279 srcflag | pflag);
280 }
281 if (fopts->fo_to_port_range) {
282 npfctl_build_vars(nc, family, fopts->fo_to_port_range,
283 dstflag | pflag);
284 }
285
286 /*
287 * Complete n-code (destroys the context) and pass to the rule.
288 */
289 code = npfctl_ncgen_complete(nc, &len);
290 if (npf_debug) {
291 extern int yylineno;
292 printf("RULE AT LINE %d\n", yylineno - 1);
293 npfctl_ncgen_print(code, len);
294 }
295 if (npf_rule_setcode(rl, NPF_CODE_NCODE, code, len) == -1) {
296 errx(EXIT_FAILURE, "npf_rule_setcode failed");
297 }
298 free(code);
299 return true;
300 }
301
302 /*
303 * npfctl_build_rproc: create and insert a rule procedure.
304 */
305 void
306 npfctl_build_rproc(const char *name, npfvar_t *var)
307 {
308 nl_rproc_t *rp;
309
310 rp = npf_rproc_create(name);
311 if (rp == NULL) {
312 errx(EXIT_FAILURE, "npf_rproc_create failed");
313 }
314 npf_rproc_insert(npf_conf, rp);
315 }
316
317 /*
318 * npfctl_build_group: create a group, insert into the global ruleset
319 * and update the current group pointer.
320 */
321 void
322 npfctl_build_group(const char *name, int attr, u_int if_idx)
323 {
324 const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
325 nl_rule_t *rl;
326
327 if (attr & NPF_RULE_DEFAULT) {
328 if (defgroup_set) {
329 yyerror("multiple default groups are not valid");
330 }
331 defgroup_set = true;
332 attr |= attr_di;
333
334 } else if ((attr & attr_di) == 0) {
335 attr |= attr_di;
336 }
337 attr |= (NPF_RULE_PASS | NPF_RULE_FINAL);
338
339 rl = npf_rule_create(name, attr, if_idx);
340 npf_rule_insert(npf_conf, NULL, rl, NPF_PRI_NEXT);
341 current_group = rl;
342 }
343
344 /*
345 * npfctl_build_rule: create a rule, build n-code from filter options,
346 * if any, and insert into the ruleset of current group.
347 */
348 void
349 npfctl_build_rule(int attr, u_int if_idx, sa_family_t family,
350 const opt_proto_t *op, const filt_opts_t *fopts, const char *rproc)
351 {
352 nl_rule_t *rl;
353
354 rl = npf_rule_create(NULL, attr, if_idx);
355 npfctl_build_ncode(rl, family, op, fopts, false);
356 if (rproc && npf_rule_setproc(npf_conf, rl, rproc) != 0) {
357 yyerror("rule procedure '%s' is not defined", rproc);
358 }
359 assert(current_group != NULL);
360 npf_rule_insert(npf_conf, current_group, rl, NPF_PRI_NEXT);
361 }
362
363 /*
364 * npfctl_build_nat: create a NAT policy of a specified type with a
365 * given filter options.
366 */
367 void
368 npfctl_build_nat(int type, u_int if_idx, const filt_opts_t *fopts,
369 npfvar_t *var1, npfvar_t *var2)
370 {
371 opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
372 nl_nat_t *nat;
373 fam_addr_mask_t *ai;
374
375 assert(type != 0 && if_idx != 0);
376 assert(fopts != NULL && var1 != NULL);
377
378 ai = npfctl_get_singlefam(var1);
379 assert(ai != NULL);
380 if (ai->fam_family != AF_INET) {
381 yyerror("IPv6 NAT is not supported");
382 }
383
384 switch (type) {
385 case NPFCTL_RDR: {
386 /*
387 * Redirection: an inbound NAT with a specific port.
388 */
389 in_port_t *port = npfctl_get_singleport(var2);
390 nat = npf_nat_create(NPF_NATIN, NPF_NAT_PORTS,
391 if_idx, &ai->fam_addr, ai->fam_family, *port);
392 break;
393 }
394 case NPFCTL_BINAT: {
395 /*
396 * Bi-directional NAT: a combination of inbound NAT and
397 * outbound NAT policies. Note that the translation address
398 * is local IP and filter criteria is inverted accordingly.
399 */
400 fam_addr_mask_t *tai = npfctl_get_singlefam(var2);
401 assert(tai != NULL);
402 if (ai->fam_family != AF_INET) {
403 yyerror("IPv6 NAT is not supported");
404 }
405 nat = npf_nat_create(NPF_NATIN, 0, if_idx,
406 &tai->fam_addr, tai->fam_family, 0);
407 npfctl_build_ncode(nat, AF_INET, &op, fopts, true);
408 npf_nat_insert(npf_conf, nat, NPF_PRI_NEXT);
409 /* FALLTHROUGH */
410 }
411 case NPFCTL_NAT: {
412 /*
413 * Traditional NAPT: an outbound NAT policy with port.
414 * If this is another hald for bi-directional NAT, then
415 * no port translation with mapping.
416 */
417 nat = npf_nat_create(NPF_NATOUT, type == NPFCTL_NAT ?
418 (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0,
419 if_idx, &ai->fam_addr, ai->fam_family, 0);
420 break;
421 }
422 default:
423 assert(false);
424 }
425 npfctl_build_ncode(nat, AF_INET, &op, fopts, false);
426 npf_nat_insert(npf_conf, nat, NPF_PRI_NEXT);
427 }
428
429 /*
430 * npfctl_fill_table: fill NPF table with entries from a specified file.
431 */
432 static void
433 npfctl_fill_table(nl_table_t *tl, const char *fname)
434 {
435 char *buf = NULL;
436 int l = 0;
437 FILE *fp;
438 size_t n;
439
440 fp = fopen(fname, "r");
441 if (fp == NULL) {
442 err(EXIT_FAILURE, "open '%s'", fname);
443 }
444 while (l++, getline(&buf, &n, fp) != -1) {
445 fam_addr_mask_t *fam;
446
447 if (*buf == '\n' || *buf == '#') {
448 continue;
449 }
450 fam = npfctl_parse_cidr(buf);
451 if (fam == NULL) {
452 errx(EXIT_FAILURE, "%s:%d: invalid table entry",
453 fname, l);
454 }
455
456 /* Create and add a table entry. */
457 npf_table_add_entry(tl, &fam->fam_addr, fam->fam_mask);
458 }
459 if (buf != NULL) {
460 free(buf);
461 }
462 }
463
464 /*
465 * npfctl_build_table: create an NPF table, add to the configuration and,
466 * if required, fill with contents from a file.
467 */
468 void
469 npfctl_build_table(const char *tid, u_int type, const char *fname)
470 {
471 nl_table_t *tl;
472 u_int id;
473
474 id = atoi(tid);
475 tl = npf_table_create(id, type);
476 assert(tl != NULL);
477
478 if (npf_table_insert(npf_conf, tl)) {
479 errx(EXIT_FAILURE, "table '%d' is already defined\n", id);
480 }
481
482 if (fname) {
483 npfctl_fill_table(tl, fname);
484 }
485 }
486