1 /*-
2  * Copyright (c) 2009-2014 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: npfctl.c,v 1.65 2021/07/14 09:15:01 christos Exp $");
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/socket.h>
36 #include <sys/mman.h>
37 #include <sys/un.h>
38 #ifdef __NetBSD__
39 #include <sys/module.h>
40 #endif
41 
42 #include <stdio.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <fcntl.h>
47 #include <errno.h>
48 #include <err.h>
49 
50 #include "npfctl.h"
51 
52 enum {
53           NPFCTL_START,
54           NPFCTL_STOP,
55           NPFCTL_RELOAD,
56           NPFCTL_SHOWCONF,
57           NPFCTL_FLUSH,
58           NPFCTL_VALIDATE,
59           NPFCTL_TABLE,
60           NPFCTL_RULE,
61           NPFCTL_STATS,
62           NPFCTL_SAVE,
63           NPFCTL_LOAD,
64           NPFCTL_DEBUG,
65           NPFCTL_CONN_LIST,
66 };
67 
68 bool
join(char * buf,size_t buflen,int count,char ** args,const char * sep)69 join(char *buf, size_t buflen, int count, char **args, const char *sep)
70 {
71           const unsigned seplen = strlen(sep);
72           char *s = buf, *p = NULL;
73 
74           for (int i = 0; i < count; i++) {
75                     size_t len;
76 
77                     p = stpncpy(s, args[i], buflen);
78                     len = p - s + seplen;
79                     if (len >= buflen) {
80                               return false;
81                     }
82                     buflen -= len;
83                     strcpy(p, sep);
84                     s = p + seplen;
85           }
86           *p = '\0';
87           return true;
88 }
89 
90 __dead void
usage(void)91 usage(void)
92 {
93           const char *progname = getprogname();
94 
95           fprintf(stderr,
96               "Usage:\t%s start | stop | flush | show | stats\n",
97               progname);
98           fprintf(stderr,
99               "\t%s validate | reload [<rule-file>]\n",
100               progname);
101           fprintf(stderr,
102               "\t%s rule \"rule-name\" { add | rem } <rule-syntax>\n",
103               progname);
104           fprintf(stderr,
105               "\t%s rule \"rule-name\" rem-id <rule-id>\n",
106               progname);
107           fprintf(stderr,
108               "\t%s rule \"rule-name\" { list | flush }\n",
109               progname);
110           fprintf(stderr,
111               "\t%s table \"table-name\" { add | rem | test } <address/mask>\n",
112               progname);
113           fprintf(stderr,
114               "\t%s table \"table-name\" { list | flush }\n",
115               progname);
116           fprintf(stderr,
117               "\t%s table \"table-name\" replace [-n \"name\"]"
118               " [-t <type>] <table-file>\n",
119               progname);
120           fprintf(stderr,
121               "\t%s save | load\n",
122               progname);
123           fprintf(stderr,
124               "\t%s list [-46hNnw] [-i <ifname>]\n",
125               progname);
126           fprintf(stderr,
127               "\t%s debug { -a | -b <binary-config> | -c <config> } "
128               "[ -o <outfile> ]\n",
129               progname);
130           exit(EXIT_FAILURE);
131 }
132 
133 static int
npfctl_print_stats(int fd)134 npfctl_print_stats(int fd)
135 {
136           static const struct stats_s {
137                     /* Note: -1 indicates a new section. */
138                     int                 index;
139                     const char *        name;
140           } stats[] = {
141                     { -1, "Packets passed"                                                },
142                     { NPF_STAT_PASS_DEFAULT,      "default pass"                },
143                     { NPF_STAT_PASS_RULESET,      "ruleset pass"                },
144                     { NPF_STAT_PASS_CONN,                   "state pass"                  },
145 
146                     { -1, "Packets blocked"                                               },
147                     { NPF_STAT_BLOCK_DEFAULT,     "default block"               },
148                     { NPF_STAT_BLOCK_RULESET,     "ruleset block"               },
149 
150                     { -1, "State and NAT entries"                               },
151                     { NPF_STAT_CONN_CREATE,                 "state allocations"},
152                     { NPF_STAT_CONN_DESTROY,      "state destructions"},
153                     { NPF_STAT_NAT_CREATE,                  "NAT entry allocations"       },
154                     { NPF_STAT_NAT_DESTROY,                 "NAT entry destructions"},
155 
156                     { -1, "Network buffers"                                               },
157                     { NPF_STAT_NBUF_NONCONTIG,    "non-contiguous cases"        },
158                     { NPF_STAT_NBUF_CONTIG_FAIL,  "contig alloc failures"       },
159 
160                     { -1, "Invalid packet state cases"                          },
161                     { NPF_STAT_INVALID_STATE,     "cases in total"    },
162                     { NPF_STAT_INVALID_STATE_TCP1,          "TCP case I"                  },
163                     { NPF_STAT_INVALID_STATE_TCP2,          "TCP case II"                 },
164                     { NPF_STAT_INVALID_STATE_TCP3,          "TCP case III"                },
165 
166                     { -1, "Packet race cases"                                   },
167                     { NPF_STAT_RACE_NAT,                    "NAT association race"        },
168                     { NPF_STAT_RACE_CONN,                   "duplicate state race"        },
169 
170                     { -1, "Fragmentation"                                                 },
171                     { NPF_STAT_FRAGMENTS,                   "fragments"                   },
172                     { NPF_STAT_REASSEMBLY,                  "reassembled"                 },
173                     { NPF_STAT_REASSFAIL,                   "failed reassembly" },
174 
175                     { -1, "Other"                                                         },
176                     { NPF_STAT_ERROR,             "unexpected errors" },
177           };
178           uint64_t *st = ecalloc(1, NPF_STATS_SIZE);
179 
180           if (ioctl(fd, IOC_NPF_STATS, &st) != 0) {
181                     err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)");
182           }
183 
184           for (unsigned i = 0; i < __arraycount(stats); i++) {
185                     const char *sname = stats[i].name;
186                     int sidx = stats[i].index;
187 
188                     if (sidx == -1) {
189                               printf("%s:\n", sname);
190                     } else {
191                               printf("\t%"PRIu64" %s\n", st[sidx], sname);
192                     }
193           }
194 
195           free(st);
196           return 0;
197 }
198 
199 void
npfctl_print_error(const npf_error_t * ne)200 npfctl_print_error(const npf_error_t *ne)
201 {
202           const char *srcfile = ne->source_file;
203 
204           if (ne->error_msg) {
205                     errx(EXIT_FAILURE, "%s", ne->error_msg);
206           }
207           if (srcfile) {
208                     warnx("source %s line %d", srcfile, ne->source_line);
209           }
210           if (ne->id) {
211                     warnx("object: %" PRIi64, ne->id);
212           }
213 }
214 
215 char *
npfctl_print_addrmask(int alen,const char * fmt,const npf_addr_t * addr,npf_netmask_t mask)216 npfctl_print_addrmask(int alen, const char *fmt, const npf_addr_t *addr,
217     npf_netmask_t mask)
218 {
219           const unsigned buflen = 256;
220           char *buf = ecalloc(1, buflen);
221           struct sockaddr_storage ss;
222 
223           memset(&ss, 0, sizeof(ss));
224 
225           switch (alen) {
226           case 4: {
227                     struct sockaddr_in *sin = (void *)&ss;
228                     sin->sin_family = AF_INET;
229                     memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
230                     break;
231           }
232           case 16: {
233                     struct sockaddr_in6 *sin6 = (void *)&ss;
234                     sin6->sin6_family = AF_INET6;
235                     memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
236                     break;
237           }
238           default:
239                     abort();
240           }
241           sockaddr_snprintf(buf, buflen, fmt, (const void *)&ss);
242           if (mask && mask != NPF_NO_NETMASK) {
243                     const unsigned len = strlen(buf);
244                     snprintf(&buf[len], buflen - len, "/%u", mask);
245           }
246           return buf;
247 }
248 
249 bool
npfctl_addr_iszero(const npf_addr_t * addr)250 npfctl_addr_iszero(const npf_addr_t *addr)
251 {
252           static const npf_addr_t zero; /* must be static */
253           return memcmp(addr, &zero, sizeof(npf_addr_t)) == 0;
254 }
255 
256 static bool bpfjit = true;
257 
258 void
npfctl_bpfjit(bool onoff)259 npfctl_bpfjit(bool onoff)
260 {
261           bpfjit = onoff;
262 }
263 
264 static void
npfctl_preload_bpfjit(void)265 npfctl_preload_bpfjit(void)
266 {
267 #ifdef __NetBSD__
268           modctl_load_t args = {
269                     .ml_filename = "bpfjit",
270                     .ml_flags = MODCTL_NO_PROP,
271                     .ml_props = NULL,
272                     .ml_propslen = 0
273           };
274 
275           if (!bpfjit)
276                     return;
277 
278           if (modctl(MODCTL_LOAD, &args) != 0 && errno != EEXIST) {
279                     static const char *p = "; performance will be degraded";
280                     if (errno == ENOENT)
281                               warnx("the bpfjit module seems to be missing%s", p);
282                     else
283                               warn("error loading the bpfjit module%s", p);
284                     warnx("To disable this warning `set bpf.jit off' in "
285                         "/etc/npf.conf");
286           }
287 #endif
288 }
289 
290 static nl_config_t *
npfctl_import(const char * path)291 npfctl_import(const char *path)
292 {
293           nl_config_t *ncf;
294           struct stat sb;
295           size_t blen;
296           void *blob;
297           int fd;
298 
299           /*
300            * The file may change while reading - we are not handling this,
301            * just leaving this responsibility for the caller.
302            */
303           if ((fd = open(path, O_RDONLY)) == -1) {
304                     err(EXIT_FAILURE, "open: '%s'", path);
305           }
306           if (fstat(fd, &sb) == -1) {
307                     err(EXIT_FAILURE, "stat: '%s'", path);
308           }
309           if ((blen = sb.st_size) == 0) {
310                     errx(EXIT_FAILURE,
311                         "the binary configuration file '%s' is empty", path);
312           }
313           blob = mmap(NULL, blen, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
314           if (blob == MAP_FAILED) {
315                     err(EXIT_FAILURE, "mmap: '%s'", path);
316           }
317           ncf = npf_config_import(blob, blen);
318           munmap(blob, blen);
319           return ncf;
320 }
321 
322 static int
npfctl_load(int fd)323 npfctl_load(int fd)
324 {
325           nl_config_t *ncf;
326           npf_error_t errinfo;
327 
328           /*
329            * Import the configuration, submit it and destroy.
330            */
331           ncf = npfctl_import(NPF_DB_PATH);
332           if (ncf == NULL) {
333                     err(EXIT_FAILURE, "npf_config_import: '%s'", NPF_DB_PATH);
334           }
335           if ((errno = npf_config_submit(ncf, fd, &errinfo)) != 0) {
336                     npfctl_print_error(&errinfo);
337           }
338           npf_config_destroy(ncf);
339           return errno;
340 }
341 
342 static int
npfctl_open_dev(const char * path)343 npfctl_open_dev(const char *path)
344 {
345           struct stat st;
346           int fd;
347 
348           if (lstat(path, &st) == -1) {
349                     err(EXIT_FAILURE, "fstat: '%s'", path);
350           }
351           if ((st.st_mode & S_IFMT) == S_IFSOCK) {
352                     struct sockaddr_un addr;
353 
354                     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
355                               err(EXIT_FAILURE, "socket");
356                     }
357                     memset(&addr, 0, sizeof(addr));
358                     addr.sun_family = AF_UNIX;
359                     strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
360 
361                     if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
362                               err(EXIT_FAILURE, "connect: '%s'", path);
363                     }
364           } else {
365                     if ((fd = open(path, O_RDONLY)) == -1) {
366                               err(EXIT_FAILURE, "open: '%s'", path);
367                     }
368           }
369           return fd;
370 }
371 
372 static void
npfctl_debug(int argc,char ** argv)373 npfctl_debug(int argc, char **argv)
374 {
375           const char *conf = NULL, *bconf = NULL, *outfile = NULL;
376           bool use_active = false;
377           nl_config_t *ncf = NULL;
378           int fd, c, optcount;
379 
380           argc--;
381           argv++;
382 
383           npfctl_config_init(true);
384           while ((c = getopt(argc, argv, "ab:c:o:")) != -1) {
385                     switch (c) {
386                     case 'a':
387                               use_active = true;
388                               break;
389                     case 'b':
390                               bconf = optarg;
391                               break;
392                     case 'c':
393                               conf = optarg;
394                               break;
395                     case 'o':
396                               outfile = optarg;
397                               break;
398                     default:
399                               usage();
400                     }
401           }
402 
403           /*
404            * Options -a, -b and -c are mutually exclusive, so allow only one.
405            * If no options were specified, then set the defaults.
406            */
407           optcount = (int)!!use_active + (int)!!conf + (int)!!bconf;
408           if (optcount != 1) {
409                     if (optcount > 1) {
410                               usage();
411                     }
412                     conf = NPF_CONF_PATH;
413                     outfile = outfile ? outfile : "npf.nvlist";
414           }
415 
416           if (use_active) {
417                     puts("Loading the active configuration");
418                     fd = npfctl_open_dev(NPF_DEV_PATH);
419                     if ((ncf = npf_config_retrieve(fd)) == NULL) {
420                               err(EXIT_FAILURE, "npf_config_retrieve: '%s'",
421                                   NPF_DEV_PATH);
422                     }
423           }
424 
425           if (conf) {
426                     printf("Loading %s\n", conf);
427                     npfctl_parse_file(conf);
428                     npfctl_config_build();
429                     ncf = npfctl_config_ref();
430           }
431 
432           if (bconf) {
433                     printf("Importing %s\n", bconf);
434                     ncf = npfctl_import(bconf);
435           }
436 
437           printf("Configuration:\n\n");
438           _npf_config_dump(ncf, STDOUT_FILENO);
439           if (outfile) {
440                     printf("\nSaving binary to %s\n", outfile);
441                     npfctl_config_save(ncf, outfile);
442           }
443           npf_config_destroy(ncf);
444 }
445 
446 static void
npfctl(int action,int argc,char ** argv)447 npfctl(int action, int argc, char **argv)
448 {
449           int fd, boolval, ret = 0;
450           const char *fun = "";
451           nl_config_t *ncf;
452 
453           switch (action) {
454           case NPFCTL_VALIDATE:
455           case NPFCTL_DEBUG:
456                     fd = 0;
457                     break;
458           default:
459                     fd = npfctl_open_dev(NPF_DEV_PATH);
460           }
461 
462           switch (action) {
463           case NPFCTL_START:
464                     boolval = true;
465                     ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
466                     fun = "ioctl(IOC_NPF_SWITCH)";
467                     break;
468           case NPFCTL_STOP:
469                     boolval = false;
470                     ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
471                     fun = "ioctl(IOC_NPF_SWITCH)";
472                     break;
473           case NPFCTL_RELOAD:
474                     npfctl_config_init(false);
475                     npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
476                     npfctl_preload_bpfjit();
477                     errno = ret = npfctl_config_send(fd);
478                     fun = "npfctl_config_send";
479                     break;
480           case NPFCTL_SHOWCONF:
481                     ret = npfctl_config_show(fd);
482                     fun = "npfctl_config_show";
483                     break;
484           case NPFCTL_FLUSH:
485                     ret = npf_config_flush(fd);
486                     fun = "npf_config_flush";
487                     break;
488           case NPFCTL_TABLE:
489                     if ((argc -= 2) < 2) {
490                               usage();
491                     }
492                     argv += 2;
493                     if (strcmp(argv[1], "replace") == 0) {
494                               npfctl_table_replace(fd, argc, argv);
495                     } else {
496                               npfctl_table(fd, argc, argv);
497                     }
498                     break;
499           case NPFCTL_RULE:
500                     if ((argc -= 2) < 2) {
501                               usage();
502                     }
503                     argv += 2;
504                     npfctl_rule(fd, argc, argv);
505                     break;
506           case NPFCTL_LOAD:
507                     npfctl_preload_bpfjit();
508                     ret = npfctl_load(fd);
509                     fun = "npfctl_config_load";
510                     break;
511           case NPFCTL_SAVE:
512                     ncf = npf_config_retrieve(fd);
513                     if (ncf) {
514                               npfctl_config_save(ncf,
515                                   argc > 2 ? argv[2] : NPF_DB_PATH);
516                               npf_config_destroy(ncf);
517                     } else {
518                               ret = errno;
519                     }
520                     fun = "npfctl_config_save";
521                     break;
522           case NPFCTL_STATS:
523                     ret = npfctl_print_stats(fd);
524                     fun = "npfctl_print_stats";
525                     break;
526           case NPFCTL_CONN_LIST:
527                     ret = npfctl_conn_list(fd, argc, argv);
528                     fun = "npfctl_conn_list";
529                     break;
530           case NPFCTL_VALIDATE:
531                     npfctl_config_init(false);
532                     npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
533                     ret = npfctl_config_show(0);
534                     fun = "npfctl_config_show";
535                     break;
536           case NPFCTL_DEBUG:
537                     npfctl_debug(argc, argv);
538                     break;
539           }
540           if (ret) {
541                     err(EXIT_FAILURE, "%s", fun);
542           }
543           if (fd) {
544                     close(fd);
545           }
546 }
547 
548 int
main(int argc,char ** argv)549 main(int argc, char **argv)
550 {
551           static const struct operations_s {
552                     const char *                  cmd;
553                     int                           action;
554           } operations[] = {
555                     /* Start, stop, reload */
556                     {         "start",  NPFCTL_START                  },
557                     {         "stop",             NPFCTL_STOP                   },
558                     {         "reload", NPFCTL_RELOAD                 },
559                     {         "show",             NPFCTL_SHOWCONF,    },
560                     {         "flush",  NPFCTL_FLUSH                  },
561                     /* Table */
562                     {         "table",  NPFCTL_TABLE                  },
563                     /* Rule */
564                     {         "rule",             NPFCTL_RULE                   },
565                     /* Stats */
566                     {         "stats",  NPFCTL_STATS                  },
567                     /* Full state save/load */
568                     {         "save",             NPFCTL_SAVE                   },
569                     {         "load",             NPFCTL_LOAD                   },
570                     {         "list",             NPFCTL_CONN_LIST    },
571                     /* Misc. */
572                     {         "valid",  NPFCTL_VALIDATE               },
573                     {         "debug",  NPFCTL_DEBUG                  },
574                     /* --- */
575                     {         NULL,               0                             }
576           };
577           char *cmd;
578 
579           if (argc < 2) {
580                     usage();
581           }
582           cmd = argv[1];
583 
584           /* Find and call the subroutine. */
585           for (int n = 0; operations[n].cmd != NULL; n++) {
586                     const char *opcmd = operations[n].cmd;
587 
588                     if (strncmp(cmd, opcmd, strlen(opcmd)) != 0) {
589                               continue;
590                     }
591                     npfctl(operations[n].action, argc, argv);
592                     return EXIT_SUCCESS;
593           }
594           usage();
595 }
596