Home | History | Annotate | Line # | Download | only in npfctl
      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 *
     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
     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
     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 *
     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
    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
    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
    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
    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
    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
    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
    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