xref: /netbsd/usr.sbin/npf/npfctl/npf_cmd.c (revision d6939920)
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