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__ 55static unsigned char * 56SHA1(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 67static void 68npfctl_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 82int 83npfctl_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 90static nl_rule_t * 91npfctl_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 107void 108npfctl_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 200static int 201npfctl_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 221void 222npfctl_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 290void 291npfctl_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 339again: 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 405typedef 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 417static int 418npfctl_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 469static void 470npf_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 480int 481npfctl_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