1 /*-
2 * Copyright (c) 2009-2020 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 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: npf_cmd.c,v 1.1 2020/05/30 14:16:56 rmind Exp $");
32
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <err.h>
39
40 #ifdef __NetBSD__
41 #include <sha1.h>
42 #define SHA_DIGEST_LENGTH SHA1_DIGEST_LENGTH
43 #else
44 #include <openssl/sha.h>
45 #endif
46
47 #include "npfctl.h"
48
49 ////////////////////////////////////////////////////////////////////////////
50 //
51 // NPFCTL RULE COMMANDS
52 //
53
54 #ifdef __NetBSD__
55 static unsigned char *
SHA1(const unsigned char * d,size_t l,unsigned char * md)56 SHA1(const unsigned char *d, size_t l, unsigned char *md)
57 {
58 SHA1_CTX c;
59
60 SHA1Init(&c);
61 SHA1Update(&c, d, l);
62 SHA1Final(md, &c);
63 return md;
64 }
65 #endif
66
67 static void
npfctl_generate_key(nl_rule_t * rl,void * key)68 npfctl_generate_key(nl_rule_t *rl, void *key)
69 {
70 void *meta;
71 size_t len;
72
73 if ((meta = npf_rule_export(rl, &len)) == NULL) {
74 errx(EXIT_FAILURE, "error generating rule key");
75 }
76 __CTASSERT(NPF_RULE_MAXKEYLEN >= SHA_DIGEST_LENGTH);
77 memset(key, 0, NPF_RULE_MAXKEYLEN);
78 SHA1(meta, len, key);
79 free(meta);
80 }
81
82 int
npfctl_nat_ruleset_p(const char * name,bool * natset)83 npfctl_nat_ruleset_p(const char *name, bool *natset)
84 {
85 const size_t preflen = sizeof(NPF_RULESET_MAP_PREF) - 1;
86 *natset = strncmp(name, NPF_RULESET_MAP_PREF, preflen) == 0;
87 return (*natset && strlen(name) <= preflen) ? -1 : 0;
88 }
89
90 static nl_rule_t *
npfctl_parse_rule(int argc,char ** argv,parse_entry_t entry)91 npfctl_parse_rule(int argc, char **argv, parse_entry_t entry)
92 {
93 char rule_string[1024];
94 nl_rule_t *rl;
95
96 /* Get the rule string and parse it. */
97 if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) {
98 errx(EXIT_FAILURE, "command too long");
99 }
100 npfctl_parse_string(rule_string, entry);
101 if ((rl = npfctl_rule_ref()) == NULL) {
102 errx(EXIT_FAILURE, "could not parse the rule");
103 }
104 return rl;
105 }
106
107 void
npfctl_rule(int fd,int argc,char ** argv)108 npfctl_rule(int fd, int argc, char **argv)
109 {
110 static const struct ruleops_s {
111 const char * cmd;
112 int action;
113 bool extra_arg;
114 } ruleops[] = {
115 { "add", NPF_CMD_RULE_ADD, true },
116 { "rem", NPF_CMD_RULE_REMKEY, true },
117 { "del", NPF_CMD_RULE_REMKEY, true },
118 { "rem-id", NPF_CMD_RULE_REMOVE, true },
119 { "list", NPF_CMD_RULE_LIST, false },
120 { "flush", NPF_CMD_RULE_FLUSH, false },
121 { NULL, 0, 0 }
122 };
123 uint8_t key[NPF_RULE_MAXKEYLEN];
124 const char *ruleset_name = argv[0];
125 const char *cmd = argv[1];
126 int error, action = 0;
127 bool extra_arg, natset;
128 parse_entry_t entry;
129 uint64_t rule_id;
130 nl_rule_t *rl;
131
132 for (unsigned n = 0; ruleops[n].cmd != NULL; n++) {
133 if (strcmp(cmd, ruleops[n].cmd) == 0) {
134 action = ruleops[n].action;
135 extra_arg = ruleops[n].extra_arg;
136 break;
137 }
138 }
139 argc -= 2;
140 argv += 2;
141
142 if (!action || (extra_arg && argc == 0)) {
143 usage();
144 }
145
146 if (npfctl_nat_ruleset_p(ruleset_name, &natset) != 0) {
147 errx(EXIT_FAILURE,
148 "invalid NAT ruleset name (note: the name must be "
149 "prefixed with `" NPF_RULESET_MAP_PREF "`)");
150 }
151 entry = natset ? NPFCTL_PARSE_MAP : NPFCTL_PARSE_RULE;
152
153 switch (action) {
154 case NPF_CMD_RULE_ADD:
155 rl = npfctl_parse_rule(argc, argv, entry);
156 npfctl_generate_key(rl, key);
157 npf_rule_setkey(rl, key, sizeof(key));
158 error = npf_ruleset_add(fd, ruleset_name, rl, &rule_id);
159 break;
160 case NPF_CMD_RULE_REMKEY:
161 rl = npfctl_parse_rule(argc, argv, entry);
162 npfctl_generate_key(rl, key);
163 error = npf_ruleset_remkey(fd, ruleset_name, key, sizeof(key));
164 break;
165 case NPF_CMD_RULE_REMOVE:
166 rule_id = strtoull(argv[0], NULL, 16);
167 error = npf_ruleset_remove(fd, ruleset_name, rule_id);
168 break;
169 case NPF_CMD_RULE_LIST:
170 error = npfctl_ruleset_show(fd, ruleset_name);
171 break;
172 case NPF_CMD_RULE_FLUSH:
173 error = npf_ruleset_flush(fd, ruleset_name);
174 break;
175 default:
176 abort();
177 }
178
179 switch (error) {
180 case 0:
181 /* Success. */
182 break;
183 case ESRCH:
184 errx(EXIT_FAILURE, "ruleset \"%s\" not found", ruleset_name);
185 case ENOENT:
186 errx(EXIT_FAILURE, "rule was not found");
187 default:
188 errx(EXIT_FAILURE, "rule operation: %s", strerror(error));
189 }
190 if (action == NPF_CMD_RULE_ADD) {
191 printf("OK %" PRIx64 "\n", rule_id);
192 }
193 }
194
195 ////////////////////////////////////////////////////////////////////////////
196 //
197 // NPFCTL TABLE COMMANDS
198 //
199
200 static int
npfctl_table_type(const char * typename)201 npfctl_table_type(const char *typename)
202 {
203 static const struct tbltype_s {
204 const char * name;
205 unsigned type;
206 } tbltypes[] = {
207 { "ipset", NPF_TABLE_IPSET },
208 { "lpm", NPF_TABLE_LPM },
209 { "const", NPF_TABLE_CONST },
210 { NULL, 0 }
211 };
212
213 for (unsigned i = 0; tbltypes[i].name != NULL; i++) {
214 if (strcmp(typename, tbltypes[i].name) == 0) {
215 return tbltypes[i].type;
216 }
217 }
218 return 0;
219 }
220
221 void
npfctl_table_replace(int fd,int argc,char ** argv)222 npfctl_table_replace(int fd, int argc, char **argv)
223 {
224 const char *name, *newname, *path, *typename = NULL;
225 nl_config_t *ncf;
226 nl_table_t *t;
227 unsigned type = 0;
228 int c, tid = -1;
229 FILE *fp;
230
231 name = newname = argv[0];
232 optind = 2;
233 while ((c = getopt(argc, argv, "n:t:")) != -1) {
234 switch (c) {
235 case 't':
236 typename = optarg;
237 break;
238 case 'n':
239 newname = optarg;
240 break;
241 default:
242 errx(EXIT_FAILURE,
243 "Usage: %s table \"table-name\" replace "
244 "[-n \"name\"] [-t <type>] <table-file>\n",
245 getprogname());
246 }
247 }
248 argc -= optind;
249 argv += optind;
250
251 if (typename && (type = npfctl_table_type(typename)) == 0) {
252 errx(EXIT_FAILURE, "unsupported table type '%s'", typename);
253 }
254
255 if (argc != 1) {
256 usage();
257 }
258
259 path = argv[0];
260 if (strcmp(path, "-") == 0) {
261 path = "stdin";
262 fp = stdin;
263 } else if ((fp = fopen(path, "r")) == NULL) {
264 err(EXIT_FAILURE, "open '%s'", path);
265 }
266
267 /* Get existing config to lookup ID of existing table */
268 if ((ncf = npf_config_retrieve(fd)) == NULL) {
269 err(EXIT_FAILURE, "npf_config_retrieve()");
270 }
271 if ((t = npfctl_table_getbyname(ncf, name)) == NULL) {
272 errx(EXIT_FAILURE,
273 "table '%s' not found in the active configuration", name);
274 }
275 tid = npf_table_getid(t);
276 if (!type) {
277 type = npf_table_gettype(t);
278 }
279 npf_config_destroy(ncf);
280
281 if ((t = npfctl_load_table(newname, tid, type, path, fp)) == NULL) {
282 err(EXIT_FAILURE, "table load failed");
283 }
284
285 if (npf_table_replace(fd, t, NULL)) {
286 err(EXIT_FAILURE, "npf_table_replace(<%s>)", name);
287 }
288 }
289
290 void
npfctl_table(int fd,int argc,char ** argv)291 npfctl_table(int fd, int argc, char **argv)
292 {
293 static const struct tblops_s {
294 const char * cmd;
295 int action;
296 } tblops[] = {
297 { "add", NPF_CMD_TABLE_ADD },
298 { "rem", NPF_CMD_TABLE_REMOVE },
299 { "del", NPF_CMD_TABLE_REMOVE },
300 { "test", NPF_CMD_TABLE_LOOKUP },
301 { "list", NPF_CMD_TABLE_LIST },
302 { "flush", NPF_CMD_TABLE_FLUSH },
303 { NULL, 0 }
304 };
305 npf_ioctl_table_t nct;
306 fam_addr_mask_t fam;
307 size_t buflen = 512;
308 char *cmd, *arg;
309 int n, alen;
310
311 /* Default action is list. */
312 memset(&nct, 0, sizeof(npf_ioctl_table_t));
313 nct.nct_name = argv[0];
314 cmd = argv[1];
315
316 for (n = 0; tblops[n].cmd != NULL; n++) {
317 if (strcmp(cmd, tblops[n].cmd) != 0) {
318 continue;
319 }
320 nct.nct_cmd = tblops[n].action;
321 break;
322 }
323 if (tblops[n].cmd == NULL) {
324 errx(EXIT_FAILURE, "invalid command '%s'", cmd);
325 }
326
327 switch (nct.nct_cmd) {
328 case NPF_CMD_TABLE_LIST:
329 case NPF_CMD_TABLE_FLUSH:
330 arg = NULL;
331 break;
332 default:
333 if (argc < 3) {
334 usage();
335 }
336 arg = argv[2];
337 }
338
339 again:
340 switch (nct.nct_cmd) {
341 case NPF_CMD_TABLE_LIST:
342 nct.nct_data.buf.buf = ecalloc(1, buflen);
343 nct.nct_data.buf.len = buflen;
344 break;
345 case NPF_CMD_TABLE_FLUSH:
346 break;
347 default:
348 if (!npfctl_parse_cidr(arg, &fam, &alen)) {
349 errx(EXIT_FAILURE, "invalid CIDR '%s'", arg);
350 }
351 nct.nct_data.ent.alen = alen;
352 memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, alen);
353 nct.nct_data.ent.mask = fam.fam_mask;
354 }
355
356 if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) {
357 errno = 0;
358 }
359 switch (errno) {
360 case 0:
361 break;
362 case EEXIST:
363 errx(EXIT_FAILURE, "entry already exists or is conflicting");
364 case ENOENT:
365 errx(EXIT_FAILURE, "not found");
366 case EINVAL:
367 errx(EXIT_FAILURE, "invalid address, mask or table ID");
368 case ENOMEM:
369 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
370 /* XXX */
371 free(nct.nct_data.buf.buf);
372 buflen <<= 1;
373 goto again;
374 }
375 /* FALLTHROUGH */
376 default:
377 err(EXIT_FAILURE, "ioctl(IOC_NPF_TABLE)");
378 }
379
380 if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
381 npf_ioctl_ent_t *ent = nct.nct_data.buf.buf;
382 char *buf;
383
384 while (nct.nct_data.buf.len--) {
385 if (!ent->alen)
386 break;
387 buf = npfctl_print_addrmask(ent->alen, "%a",
388 &ent->addr, ent->mask);
389 puts(buf);
390 ent++;
391 }
392 free(nct.nct_data.buf.buf);
393 } else {
394 printf("%s: %s\n", getprogname(),
395 nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ?
396 "match" : "success");
397 }
398 }
399
400 ////////////////////////////////////////////////////////////////////////////
401 //
402 // NPFCTL CONNECTION COMMANDS
403 //
404
405 typedef struct {
406 FILE * fp;
407 unsigned alen;
408 const char * ifname;
409 bool nat;
410 bool nowide;
411 bool name;
412
413 bool v4;
414 unsigned pwidth;
415 } npf_conn_filter_t;
416
417 static int
npfctl_conn_print(unsigned alen,const npf_addr_t * a,const in_port_t * p,const char * ifname,void * arg)418 npfctl_conn_print(unsigned alen, const npf_addr_t *a, const in_port_t *p,
419 const char *ifname, void *arg)
420 {
421 const npf_conn_filter_t *fil = arg;
422 char *addrstr, *src, *dst;
423 const char *fmt;
424 FILE *fp = fil->fp;
425 bool nat_conn;
426
427 /*
428 * Filter connection entries by IP version, interface and/or
429 * applicability of NAT.
430 */
431 if (alen != fil->alen) {
432 return 0;
433 }
434 if (fil->ifname && (!ifname || strcmp(ifname, fil->ifname) != 0)) {
435 return 0;
436 }
437 nat_conn = !npfctl_addr_iszero(&a[2]) || p[2] != 0;
438 if (fil->nat && !nat_conn) {
439 return 0;
440 }
441
442 fmt = fil->name ? "%A" : (fil->v4 ? "%a" : "[%a]");
443
444 addrstr = npfctl_print_addrmask(alen, fmt, &a[0], NPF_NO_NETMASK);
445 easprintf(&src, "%s:%d", addrstr, p[0]);
446 free(addrstr);
447
448 addrstr = npfctl_print_addrmask(alen, fmt, &a[1], NPF_NO_NETMASK);
449 easprintf(&dst, "%s:%d", addrstr, p[1]);
450 free(addrstr);
451
452 fprintf(fp, "%-*s %-*s ", fil->pwidth, src, fil->pwidth, dst);
453 free(src);
454 free(dst);
455
456 fprintf(fp, "%-10s ", ifname ? ifname : "-");
457 if (nat_conn) {
458 addrstr = npfctl_print_addrmask(alen, fmt, &a[2], NPF_NO_NETMASK);
459 fprintf(fp, "%s", addrstr);
460 free(addrstr);
461 if (p[2]) {
462 fprintf(fp, ":%d", p[2]);
463 }
464 }
465 fputc('\n', fp);
466 return 1;
467 }
468
469 static void
npf_conn_list_v(int fd,unsigned alen,npf_conn_filter_t * f)470 npf_conn_list_v(int fd, unsigned alen, npf_conn_filter_t *f)
471 {
472 f->alen = alen;
473 f->v4 = alen == sizeof(struct in_addr);
474 f->pwidth = f->nowide ? 0 : ((f->v4 ? 15 : 40) + 1 + 5);
475 if (npf_conn_list(fd, npfctl_conn_print, f) != 0) {
476 err(EXIT_FAILURE, "npf_conn_list");
477 }
478 }
479
480 int
npfctl_conn_list(int fd,int argc,char ** argv)481 npfctl_conn_list(int fd, int argc, char **argv)
482 {
483 npf_conn_filter_t f;
484 bool header = true;
485 unsigned alen = 0;
486 int c;
487
488 argc--;
489 argv++;
490
491 memset(&f, 0, sizeof(f));
492 f.fp = stdout;
493
494 while ((c = getopt(argc, argv, "46hi:nNW")) != -1) {
495 switch (c) {
496 case '4':
497 alen = sizeof(struct in_addr);
498 break;
499 case '6':
500 alen = sizeof(struct in6_addr);
501 break;
502 case 'h':
503 header = false;
504 break;
505 case 'i':
506 f.ifname = optarg;
507 break;
508 case 'n':
509 f.nat = true;
510 break;
511 case 'N':
512 f.name = true;
513 break;
514 case 'W':
515 f.nowide = true;
516 break;
517 default:
518 errx(EXIT_FAILURE,
519 "Usage: %s list [-46hnNW] [-i <ifname>]\n",
520 getprogname());
521 }
522 }
523
524 if (header) {
525 fprintf(f.fp, "# %-*s %-*s %-*s %s\n",
526 21 - 2, "src-addr:port",
527 21, "dst-addr:port",
528 10, "interface",
529 "nat-addr:port");
530 }
531
532 if (!alen || alen == sizeof(struct in_addr)) {
533 npf_conn_list_v(fd, sizeof(struct in_addr), &f);
534 }
535 if (!alen || alen == sizeof(struct in6_addr)) {
536 npf_conn_list_v(fd, sizeof(struct in6_addr), &f);
537 }
538
539 return 0;
540 }
541