npfctl.c revision 1.60.2.2 1 1.1 rmind /*-
2 1.41 rmind * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
3 1.1 rmind * All rights reserved.
4 1.1 rmind *
5 1.1 rmind * This material is based upon work partially supported by The
6 1.1 rmind * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
7 1.1 rmind *
8 1.1 rmind * Redistribution and use in source and binary forms, with or without
9 1.1 rmind * modification, are permitted provided that the following conditions
10 1.1 rmind * are met:
11 1.1 rmind * 1. Redistributions of source code must retain the above copyright
12 1.1 rmind * notice, this list of conditions and the following disclaimer.
13 1.1 rmind * 2. Redistributions in binary form must reproduce the above copyright
14 1.1 rmind * notice, this list of conditions and the following disclaimer in the
15 1.1 rmind * documentation and/or other materials provided with the distribution.
16 1.1 rmind *
17 1.1 rmind * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18 1.1 rmind * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 1.1 rmind * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 1.1 rmind * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21 1.1 rmind * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 1.1 rmind * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 1.1 rmind * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 1.1 rmind * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 1.1 rmind * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 1.1 rmind * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 1.1 rmind * POSSIBILITY OF SUCH DAMAGE.
28 1.1 rmind */
29 1.1 rmind
30 1.2 rmind #include <sys/cdefs.h>
31 1.60.2.2 martin __RCSID("$NetBSD: npfctl.c,v 1.60.2.2 2019/10/04 08:06:34 martin Exp $");
32 1.2 rmind
33 1.1 rmind #include <sys/stat.h>
34 1.1 rmind #include <sys/types.h>
35 1.48 christos #include <sys/mman.h>
36 1.48 christos #ifdef __NetBSD__
37 1.48 christos #include <sha1.h>
38 1.48 christos #include <sys/ioctl.h>
39 1.44 rmind #include <sys/module.h>
40 1.49 christos #define SHA_DIGEST_LENGTH SHA1_DIGEST_LENGTH
41 1.49 christos #else
42 1.49 christos #include <openssl/sha.h>
43 1.48 christos #endif
44 1.1 rmind
45 1.1 rmind #include <stdio.h>
46 1.1 rmind #include <stdlib.h>
47 1.1 rmind #include <string.h>
48 1.1 rmind #include <err.h>
49 1.1 rmind #include <fcntl.h>
50 1.1 rmind #include <unistd.h>
51 1.15 rmind #include <errno.h>
52 1.48 christos
53 1.48 christos #include <arpa/inet.h>
54 1.29 rmind
55 1.1 rmind #include "npfctl.h"
56 1.1 rmind
57 1.11 rmind enum {
58 1.11 rmind NPFCTL_START,
59 1.11 rmind NPFCTL_STOP,
60 1.11 rmind NPFCTL_RELOAD,
61 1.11 rmind NPFCTL_SHOWCONF,
62 1.11 rmind NPFCTL_FLUSH,
63 1.25 rmind NPFCTL_VALIDATE,
64 1.11 rmind NPFCTL_TABLE,
65 1.29 rmind NPFCTL_RULE,
66 1.11 rmind NPFCTL_STATS,
67 1.41 rmind NPFCTL_SAVE,
68 1.41 rmind NPFCTL_LOAD,
69 1.52 rmind NPFCTL_DEBUG,
70 1.50 christos NPFCTL_CONN_LIST,
71 1.11 rmind };
72 1.1 rmind
73 1.15 rmind static const struct operations_s {
74 1.1 rmind const char * cmd;
75 1.1 rmind int action;
76 1.1 rmind } operations[] = {
77 1.1 rmind /* Start, stop, reload */
78 1.41 rmind { "start", NPFCTL_START },
79 1.41 rmind { "stop", NPFCTL_STOP },
80 1.41 rmind { "reload", NPFCTL_RELOAD },
81 1.41 rmind { "show", NPFCTL_SHOWCONF, },
82 1.41 rmind { "flush", NPFCTL_FLUSH },
83 1.1 rmind /* Table */
84 1.41 rmind { "table", NPFCTL_TABLE },
85 1.29 rmind /* Rule */
86 1.41 rmind { "rule", NPFCTL_RULE },
87 1.3 rmind /* Stats */
88 1.41 rmind { "stats", NPFCTL_STATS },
89 1.41 rmind /* Full state save/load */
90 1.41 rmind { "save", NPFCTL_SAVE },
91 1.41 rmind { "load", NPFCTL_LOAD },
92 1.50 christos { "list", NPFCTL_CONN_LIST },
93 1.52 rmind /* Misc. */
94 1.52 rmind { "valid", NPFCTL_VALIDATE },
95 1.52 rmind { "debug", NPFCTL_DEBUG },
96 1.1 rmind /* --- */
97 1.41 rmind { NULL, 0 }
98 1.1 rmind };
99 1.1 rmind
100 1.38 rmind bool
101 1.38 rmind join(char *buf, size_t buflen, int count, char **args, const char *sep)
102 1.29 rmind {
103 1.38 rmind const u_int seplen = strlen(sep);
104 1.29 rmind char *s = buf, *p = NULL;
105 1.29 rmind
106 1.29 rmind for (int i = 0; i < count; i++) {
107 1.29 rmind size_t len;
108 1.29 rmind
109 1.29 rmind p = stpncpy(s, args[i], buflen);
110 1.38 rmind len = p - s + seplen;
111 1.29 rmind if (len >= buflen) {
112 1.29 rmind return false;
113 1.29 rmind }
114 1.29 rmind buflen -= len;
115 1.38 rmind strcpy(p, sep);
116 1.38 rmind s = p + seplen;
117 1.29 rmind }
118 1.29 rmind *p = '\0';
119 1.29 rmind return true;
120 1.29 rmind }
121 1.29 rmind
122 1.6 joerg __dead static void
123 1.1 rmind usage(void)
124 1.1 rmind {
125 1.1 rmind const char *progname = getprogname();
126 1.1 rmind
127 1.1 rmind fprintf(stderr,
128 1.37 rmind "Usage:\t%s start | stop | flush | show | stats\n",
129 1.33 christos progname);
130 1.33 christos fprintf(stderr,
131 1.33 christos "\t%s validate | reload [<rule-file>]\n",
132 1.3 rmind progname);
133 1.3 rmind fprintf(stderr,
134 1.29 rmind "\t%s rule \"rule-name\" { add | rem } <rule-syntax>\n",
135 1.29 rmind progname);
136 1.29 rmind fprintf(stderr,
137 1.29 rmind "\t%s rule \"rule-name\" rem-id <rule-id>\n",
138 1.1 rmind progname);
139 1.1 rmind fprintf(stderr,
140 1.31 rmind "\t%s rule \"rule-name\" { list | flush }\n",
141 1.31 rmind progname);
142 1.31 rmind fprintf(stderr,
143 1.60.2.2 martin "\t%s table \"table-name\" { add | rem | test } <address/mask>\n",
144 1.60.2.2 martin progname);
145 1.60.2.2 martin fprintf(stderr,
146 1.60.2.2 martin "\t%s table \"table-name\" { list | flush }\n",
147 1.1 rmind progname);
148 1.1 rmind fprintf(stderr,
149 1.60.2.2 martin "\t%s table \"table-name\" replace [-n \"name\"]"
150 1.60.2.2 martin " [-t <type>] <table-file>\n",
151 1.1 rmind progname);
152 1.37 rmind fprintf(stderr,
153 1.41 rmind "\t%s save | load\n",
154 1.37 rmind progname);
155 1.50 christos fprintf(stderr,
156 1.51 wiz "\t%s list [-46hNnw] [-i <ifname>]\n",
157 1.50 christos progname);
158 1.57 rmind fprintf(stderr,
159 1.57 rmind "\t%s debug [<rule-file>] [<raw-output>]\n",
160 1.57 rmind progname);
161 1.1 rmind exit(EXIT_FAILURE);
162 1.1 rmind }
163 1.1 rmind
164 1.3 rmind static int
165 1.3 rmind npfctl_print_stats(int fd)
166 1.3 rmind {
167 1.17 rmind static const struct stats_s {
168 1.17 rmind /* Note: -1 indicates a new section. */
169 1.17 rmind int index;
170 1.17 rmind const char * name;
171 1.17 rmind } stats[] = {
172 1.17 rmind { -1, "Packets passed" },
173 1.17 rmind { NPF_STAT_PASS_DEFAULT, "default pass" },
174 1.17 rmind { NPF_STAT_PASS_RULESET, "ruleset pass" },
175 1.41 rmind { NPF_STAT_PASS_CONN, "state pass" },
176 1.17 rmind
177 1.17 rmind { -1, "Packets blocked" },
178 1.17 rmind { NPF_STAT_BLOCK_DEFAULT, "default block" },
179 1.17 rmind { NPF_STAT_BLOCK_RULESET, "ruleset block" },
180 1.17 rmind
181 1.41 rmind { -1, "State and NAT entries" },
182 1.41 rmind { NPF_STAT_CONN_CREATE, "state allocations"},
183 1.41 rmind { NPF_STAT_CONN_DESTROY, "state destructions"},
184 1.17 rmind { NPF_STAT_NAT_CREATE, "NAT entry allocations" },
185 1.17 rmind { NPF_STAT_NAT_DESTROY, "NAT entry destructions"},
186 1.17 rmind
187 1.27 rmind { -1, "Network buffers" },
188 1.27 rmind { NPF_STAT_NBUF_NONCONTIG, "non-contiguous cases" },
189 1.27 rmind { NPF_STAT_NBUF_CONTIG_FAIL, "contig alloc failures" },
190 1.27 rmind
191 1.17 rmind { -1, "Invalid packet state cases" },
192 1.17 rmind { NPF_STAT_INVALID_STATE, "cases in total" },
193 1.17 rmind { NPF_STAT_INVALID_STATE_TCP1, "TCP case I" },
194 1.17 rmind { NPF_STAT_INVALID_STATE_TCP2, "TCP case II" },
195 1.17 rmind { NPF_STAT_INVALID_STATE_TCP3, "TCP case III" },
196 1.17 rmind
197 1.17 rmind { -1, "Packet race cases" },
198 1.17 rmind { NPF_STAT_RACE_NAT, "NAT association race" },
199 1.41 rmind { NPF_STAT_RACE_CONN, "duplicate state race" },
200 1.17 rmind
201 1.17 rmind { -1, "Fragmentation" },
202 1.17 rmind { NPF_STAT_FRAGMENTS, "fragments" },
203 1.17 rmind { NPF_STAT_REASSEMBLY, "reassembled" },
204 1.17 rmind { NPF_STAT_REASSFAIL, "failed reassembly" },
205 1.17 rmind
206 1.17 rmind { -1, "Other" },
207 1.17 rmind { NPF_STAT_ERROR, "unexpected errors" },
208 1.17 rmind };
209 1.24 rmind uint64_t *st = ecalloc(1, NPF_STATS_SIZE);
210 1.3 rmind
211 1.3 rmind if (ioctl(fd, IOC_NPF_STATS, &st) != 0) {
212 1.3 rmind err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)");
213 1.3 rmind }
214 1.3 rmind
215 1.17 rmind for (unsigned i = 0; i < __arraycount(stats); i++) {
216 1.17 rmind const char *sname = stats[i].name;
217 1.17 rmind int sidx = stats[i].index;
218 1.17 rmind
219 1.17 rmind if (sidx == -1) {
220 1.17 rmind printf("%s:\n", sname);
221 1.17 rmind } else {
222 1.17 rmind printf("\t%"PRIu64" %s\n", st[sidx], sname);
223 1.17 rmind }
224 1.17 rmind }
225 1.4 rmind
226 1.3 rmind free(st);
227 1.3 rmind return 0;
228 1.3 rmind }
229 1.3 rmind
230 1.10 rmind void
231 1.48 christos npfctl_print_error(const npf_error_t *ne)
232 1.10 rmind {
233 1.48 christos const char *srcfile = ne->source_file;
234 1.10 rmind
235 1.59 rmind if (ne->error_msg) {
236 1.60 rmind errx(EXIT_FAILURE, "%s", ne->error_msg);
237 1.59 rmind }
238 1.10 rmind if (srcfile) {
239 1.48 christos warnx("source %s line %d", srcfile, ne->source_line);
240 1.10 rmind }
241 1.48 christos if (ne->id) {
242 1.48 christos warnx("object: %" PRIi64, ne->id);
243 1.10 rmind }
244 1.10 rmind }
245 1.10 rmind
246 1.21 rmind char *
247 1.50 christos npfctl_print_addrmask(int alen, const char *fmt, const npf_addr_t *addr,
248 1.50 christos npf_netmask_t mask)
249 1.21 rmind {
250 1.50 christos const unsigned buflen = 256;
251 1.48 christos char *buf = ecalloc(1, buflen);
252 1.21 rmind struct sockaddr_storage ss;
253 1.48 christos
254 1.48 christos memset(&ss, 0, sizeof(ss));
255 1.21 rmind
256 1.21 rmind switch (alen) {
257 1.21 rmind case 4: {
258 1.21 rmind struct sockaddr_in *sin = (void *)&ss;
259 1.21 rmind sin->sin_family = AF_INET;
260 1.21 rmind memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
261 1.21 rmind break;
262 1.21 rmind }
263 1.21 rmind case 16: {
264 1.21 rmind struct sockaddr_in6 *sin6 = (void *)&ss;
265 1.21 rmind sin6->sin6_family = AF_INET6;
266 1.21 rmind memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
267 1.21 rmind break;
268 1.21 rmind }
269 1.21 rmind default:
270 1.21 rmind assert(false);
271 1.21 rmind }
272 1.50 christos sockaddr_snprintf(buf, buflen, fmt, (const void *)&ss);
273 1.38 rmind if (mask && mask != NPF_NO_NETMASK) {
274 1.48 christos const unsigned len = strlen(buf);
275 1.48 christos snprintf(&buf[len], buflen - len, "/%u", mask);
276 1.21 rmind }
277 1.21 rmind return buf;
278 1.21 rmind }
279 1.21 rmind
280 1.60.2.2 martin static int
281 1.60.2.2 martin npfctl_table_type(const char *typename)
282 1.60.2.2 martin {
283 1.60.2.2 martin static const struct tbltype_s {
284 1.60.2.2 martin const char * name;
285 1.60.2.2 martin unsigned type;
286 1.60.2.2 martin } tbltypes[] = {
287 1.60.2.2 martin { "ipset", NPF_TABLE_IPSET },
288 1.60.2.2 martin { "lpm", NPF_TABLE_LPM },
289 1.60.2.2 martin { "const", NPF_TABLE_CONST },
290 1.60.2.2 martin { NULL, 0 }
291 1.60.2.2 martin };
292 1.60.2.2 martin
293 1.60.2.2 martin for (unsigned i = 0; tbltypes[i].name != NULL; i++) {
294 1.60.2.2 martin if (strcmp(typename, tbltypes[i].name) == 0) {
295 1.60.2.2 martin return tbltypes[i].type;
296 1.60.2.2 martin }
297 1.60.2.2 martin }
298 1.60.2.2 martin return 0;
299 1.60.2.2 martin }
300 1.60.2.2 martin
301 1.60.2.2 martin static void
302 1.60.2.2 martin npfctl_table_replace(int fd, int argc, char **argv)
303 1.60.2.2 martin {
304 1.60.2.2 martin const char *name, *newname, *path, *typename = NULL;
305 1.60.2.2 martin int c, tid = -1;
306 1.60.2.2 martin FILE *fp;
307 1.60.2.2 martin nl_config_t *ncf;
308 1.60.2.2 martin nl_table_t *t;
309 1.60.2.2 martin u_int type = 0;
310 1.60.2.2 martin
311 1.60.2.2 martin name = newname = argv[0];
312 1.60.2.2 martin optind = 2;
313 1.60.2.2 martin while ((c = getopt(argc, argv, "n:t:")) != -1) {
314 1.60.2.2 martin switch (c) {
315 1.60.2.2 martin case 't':
316 1.60.2.2 martin typename = optarg;
317 1.60.2.2 martin break;
318 1.60.2.2 martin case 'n':
319 1.60.2.2 martin newname = optarg;
320 1.60.2.2 martin break;
321 1.60.2.2 martin default:
322 1.60.2.2 martin fprintf(stderr,
323 1.60.2.2 martin "Usage: %s table \"table-name\" replace "
324 1.60.2.2 martin "[-n \"name\"] [-t <type>] <table-file>\n",
325 1.60.2.2 martin getprogname());
326 1.60.2.2 martin exit(EXIT_FAILURE);
327 1.60.2.2 martin }
328 1.60.2.2 martin }
329 1.60.2.2 martin argc -= optind;
330 1.60.2.2 martin argv += optind;
331 1.60.2.2 martin
332 1.60.2.2 martin if (typename && (type = npfctl_table_type(typename)) == 0) {
333 1.60.2.2 martin errx(EXIT_FAILURE, "unsupported table type '%s'", typename);
334 1.60.2.2 martin }
335 1.60.2.2 martin
336 1.60.2.2 martin if (argc != 1) {
337 1.60.2.2 martin usage();
338 1.60.2.2 martin }
339 1.60.2.2 martin
340 1.60.2.2 martin path = argv[0];
341 1.60.2.2 martin if (strcmp(path, "-") == 0) {
342 1.60.2.2 martin path = "stdin";
343 1.60.2.2 martin fp = stdin;
344 1.60.2.2 martin } else if ((fp = fopen(path, "r")) == NULL) {
345 1.60.2.2 martin err(EXIT_FAILURE, "open '%s'", path);
346 1.60.2.2 martin }
347 1.60.2.2 martin
348 1.60.2.2 martin /* Get existing config to lookup ID of existing table */
349 1.60.2.2 martin if ((ncf = npf_config_retrieve(fd)) == NULL) {
350 1.60.2.2 martin err(EXIT_FAILURE, "npf_config_retrieve()");
351 1.60.2.2 martin }
352 1.60.2.2 martin if ((t = npfctl_table_getbyname(ncf, name)) == NULL) {
353 1.60.2.2 martin errx(EXIT_FAILURE,
354 1.60.2.2 martin "table '%s' not found in the active configuration", name);
355 1.60.2.2 martin }
356 1.60.2.2 martin tid = npf_table_getid(t);
357 1.60.2.2 martin if (!type) {
358 1.60.2.2 martin type = npf_table_gettype(t);
359 1.60.2.2 martin }
360 1.60.2.2 martin npf_config_destroy(ncf);
361 1.60.2.2 martin
362 1.60.2.2 martin if ((t = npfctl_load_table(newname, tid, type, path, fp)) == NULL) {
363 1.60.2.2 martin err(EXIT_FAILURE, "table load failed");
364 1.60.2.2 martin }
365 1.60.2.2 martin
366 1.60.2.2 martin if (npf_table_replace(fd, t, NULL)) {
367 1.60.2.2 martin err(EXIT_FAILURE, "npf_table_replace(<%s>)", name);
368 1.60.2.2 martin }
369 1.60.2.2 martin }
370 1.60.2.2 martin
371 1.60.2.2 martin static void
372 1.21 rmind npfctl_table(int fd, int argc, char **argv)
373 1.15 rmind {
374 1.15 rmind static const struct tblops_s {
375 1.15 rmind const char * cmd;
376 1.15 rmind int action;
377 1.15 rmind } tblops[] = {
378 1.29 rmind { "add", NPF_CMD_TABLE_ADD },
379 1.29 rmind { "rem", NPF_CMD_TABLE_REMOVE },
380 1.29 rmind { "del", NPF_CMD_TABLE_REMOVE },
381 1.29 rmind { "test", NPF_CMD_TABLE_LOOKUP },
382 1.29 rmind { "list", NPF_CMD_TABLE_LIST },
383 1.37 rmind { "flush", NPF_CMD_TABLE_FLUSH },
384 1.21 rmind { NULL, 0 }
385 1.15 rmind };
386 1.15 rmind npf_ioctl_table_t nct;
387 1.15 rmind fam_addr_mask_t fam;
388 1.21 rmind size_t buflen = 512;
389 1.40 rmind char *cmd, *arg;
390 1.15 rmind int n, alen;
391 1.15 rmind
392 1.21 rmind /* Default action is list. */
393 1.15 rmind memset(&nct, 0, sizeof(npf_ioctl_table_t));
394 1.40 rmind nct.nct_name = argv[0];
395 1.21 rmind cmd = argv[1];
396 1.15 rmind
397 1.15 rmind for (n = 0; tblops[n].cmd != NULL; n++) {
398 1.21 rmind if (strcmp(cmd, tblops[n].cmd) != 0) {
399 1.21 rmind continue;
400 1.21 rmind }
401 1.29 rmind nct.nct_cmd = tblops[n].action;
402 1.21 rmind break;
403 1.21 rmind }
404 1.21 rmind if (tblops[n].cmd == NULL) {
405 1.21 rmind errx(EXIT_FAILURE, "invalid command '%s'", cmd);
406 1.21 rmind }
407 1.37 rmind
408 1.37 rmind switch (nct.nct_cmd) {
409 1.37 rmind case NPF_CMD_TABLE_LIST:
410 1.37 rmind case NPF_CMD_TABLE_FLUSH:
411 1.40 rmind arg = NULL;
412 1.37 rmind break;
413 1.37 rmind default:
414 1.21 rmind if (argc < 3) {
415 1.21 rmind usage();
416 1.15 rmind }
417 1.21 rmind arg = argv[2];
418 1.15 rmind }
419 1.37 rmind
420 1.21 rmind again:
421 1.37 rmind switch (nct.nct_cmd) {
422 1.37 rmind case NPF_CMD_TABLE_LIST:
423 1.24 rmind nct.nct_data.buf.buf = ecalloc(1, buflen);
424 1.21 rmind nct.nct_data.buf.len = buflen;
425 1.37 rmind break;
426 1.37 rmind case NPF_CMD_TABLE_FLUSH:
427 1.37 rmind break;
428 1.37 rmind default:
429 1.21 rmind if (!npfctl_parse_cidr(arg, &fam, &alen)) {
430 1.21 rmind errx(EXIT_FAILURE, "invalid CIDR '%s'", arg);
431 1.21 rmind }
432 1.21 rmind nct.nct_data.ent.alen = alen;
433 1.26 rmind memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, alen);
434 1.21 rmind nct.nct_data.ent.mask = fam.fam_mask;
435 1.15 rmind }
436 1.15 rmind
437 1.15 rmind if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) {
438 1.15 rmind errno = 0;
439 1.15 rmind }
440 1.15 rmind switch (errno) {
441 1.21 rmind case 0:
442 1.21 rmind break;
443 1.15 rmind case EEXIST:
444 1.21 rmind errx(EXIT_FAILURE, "entry already exists or is conflicting");
445 1.15 rmind case ENOENT:
446 1.57 rmind errx(EXIT_FAILURE, "not found");
447 1.15 rmind case EINVAL:
448 1.21 rmind errx(EXIT_FAILURE, "invalid address, mask or table ID");
449 1.21 rmind case ENOMEM:
450 1.29 rmind if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
451 1.21 rmind /* XXX */
452 1.21 rmind free(nct.nct_data.buf.buf);
453 1.21 rmind buflen <<= 1;
454 1.21 rmind goto again;
455 1.21 rmind }
456 1.21 rmind /* FALLTHROUGH */
457 1.21 rmind default:
458 1.32 christos err(EXIT_FAILURE, "ioctl(IOC_NPF_TABLE)");
459 1.21 rmind }
460 1.21 rmind
461 1.29 rmind if (nct.nct_cmd == NPF_CMD_TABLE_LIST) {
462 1.21 rmind npf_ioctl_ent_t *ent = nct.nct_data.buf.buf;
463 1.21 rmind char *buf;
464 1.21 rmind
465 1.21 rmind while (nct.nct_data.buf.len--) {
466 1.21 rmind if (!ent->alen)
467 1.21 rmind break;
468 1.50 christos buf = npfctl_print_addrmask(ent->alen, "%a",
469 1.21 rmind &ent->addr, ent->mask);
470 1.21 rmind puts(buf);
471 1.21 rmind ent++;
472 1.21 rmind }
473 1.21 rmind free(nct.nct_data.buf.buf);
474 1.21 rmind } else {
475 1.21 rmind printf("%s: %s\n", getprogname(),
476 1.29 rmind nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ?
477 1.57 rmind "match" : "success");
478 1.15 rmind }
479 1.15 rmind }
480 1.15 rmind
481 1.29 rmind static nl_rule_t *
482 1.60.2.2 martin npfctl_parse_rule(int argc, char **argv, parse_entry_t entry)
483 1.29 rmind {
484 1.29 rmind char rule_string[1024];
485 1.29 rmind nl_rule_t *rl;
486 1.29 rmind
487 1.29 rmind /* Get the rule string and parse it. */
488 1.38 rmind if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) {
489 1.29 rmind errx(EXIT_FAILURE, "command too long");
490 1.29 rmind }
491 1.60.2.2 martin npfctl_parse_string(rule_string, entry);
492 1.29 rmind if ((rl = npfctl_rule_ref()) == NULL) {
493 1.29 rmind errx(EXIT_FAILURE, "could not parse the rule");
494 1.29 rmind }
495 1.29 rmind return rl;
496 1.29 rmind }
497 1.29 rmind
498 1.48 christos #ifdef __NetBSD__
499 1.49 christos static unsigned char *
500 1.49 christos SHA1(const unsigned char *d, size_t l, unsigned char *md)
501 1.48 christos {
502 1.48 christos SHA1_CTX c;
503 1.48 christos
504 1.48 christos SHA1Init(&c);
505 1.48 christos SHA1Update(&c, d, l);
506 1.48 christos SHA1Final(md, &c);
507 1.48 christos return md;
508 1.47 christos }
509 1.48 christos #endif
510 1.47 christos
511 1.47 christos static void
512 1.29 rmind npfctl_generate_key(nl_rule_t *rl, void *key)
513 1.29 rmind {
514 1.29 rmind void *meta;
515 1.29 rmind size_t len;
516 1.29 rmind
517 1.29 rmind if ((meta = npf_rule_export(rl, &len)) == NULL) {
518 1.29 rmind errx(EXIT_FAILURE, "error generating rule key");
519 1.29 rmind }
520 1.48 christos __CTASSERT(NPF_RULE_MAXKEYLEN >= SHA_DIGEST_LENGTH);
521 1.29 rmind memset(key, 0, NPF_RULE_MAXKEYLEN);
522 1.48 christos SHA1(meta, len, key);
523 1.29 rmind free(meta);
524 1.29 rmind }
525 1.29 rmind
526 1.60.2.2 martin int
527 1.60.2.2 martin npfctl_nat_ruleset_p(const char *name, bool *natset)
528 1.60.2.2 martin {
529 1.60.2.2 martin const size_t preflen = sizeof(NPF_RULESET_MAP_PREF) - 1;
530 1.60.2.2 martin *natset = strncmp(name, NPF_RULESET_MAP_PREF, preflen) == 0;
531 1.60.2.2 martin return (*natset && strlen(name) <= preflen) ? -1 : 0;
532 1.60.2.2 martin }
533 1.60.2.2 martin
534 1.60.2.2 martin static void
535 1.29 rmind npfctl_rule(int fd, int argc, char **argv)
536 1.29 rmind {
537 1.29 rmind static const struct ruleops_s {
538 1.29 rmind const char * cmd;
539 1.29 rmind int action;
540 1.36 rmind bool extra_arg;
541 1.29 rmind } ruleops[] = {
542 1.36 rmind { "add", NPF_CMD_RULE_ADD, true },
543 1.36 rmind { "rem", NPF_CMD_RULE_REMKEY, true },
544 1.36 rmind { "del", NPF_CMD_RULE_REMKEY, true },
545 1.36 rmind { "rem-id", NPF_CMD_RULE_REMOVE, true },
546 1.36 rmind { "list", NPF_CMD_RULE_LIST, false },
547 1.36 rmind { "flush", NPF_CMD_RULE_FLUSH, false },
548 1.36 rmind { NULL, 0, 0 }
549 1.29 rmind };
550 1.29 rmind uint8_t key[NPF_RULE_MAXKEYLEN];
551 1.29 rmind const char *ruleset_name = argv[0];
552 1.29 rmind const char *cmd = argv[1];
553 1.29 rmind int error, action = 0;
554 1.60.2.2 martin bool extra_arg, natset;
555 1.60.2.2 martin parse_entry_t entry;
556 1.31 rmind uint64_t rule_id;
557 1.29 rmind nl_rule_t *rl;
558 1.29 rmind
559 1.60.2.2 martin for (unsigned n = 0; ruleops[n].cmd != NULL; n++) {
560 1.29 rmind if (strcmp(cmd, ruleops[n].cmd) == 0) {
561 1.29 rmind action = ruleops[n].action;
562 1.36 rmind extra_arg = ruleops[n].extra_arg;
563 1.29 rmind break;
564 1.29 rmind }
565 1.29 rmind }
566 1.36 rmind argc -= 2;
567 1.36 rmind argv += 2;
568 1.29 rmind
569 1.36 rmind if (!action || (extra_arg && argc == 0)) {
570 1.29 rmind usage();
571 1.29 rmind }
572 1.29 rmind
573 1.60.2.2 martin if (npfctl_nat_ruleset_p(ruleset_name, &natset) != 0) {
574 1.60.2.2 martin errx(EXIT_FAILURE,
575 1.60.2.2 martin "invalid NAT ruleset name (note: the name must be "
576 1.60.2.2 martin "prefixed with `" NPF_RULESET_MAP_PREF "`)");
577 1.60.2.2 martin }
578 1.60.2.2 martin entry = natset ? NPFCTL_PARSE_MAP : NPFCTL_PARSE_RULE;
579 1.60.2.2 martin
580 1.29 rmind switch (action) {
581 1.29 rmind case NPF_CMD_RULE_ADD:
582 1.60.2.2 martin rl = npfctl_parse_rule(argc, argv, entry);
583 1.29 rmind npfctl_generate_key(rl, key);
584 1.29 rmind npf_rule_setkey(rl, key, sizeof(key));
585 1.29 rmind error = npf_ruleset_add(fd, ruleset_name, rl, &rule_id);
586 1.29 rmind break;
587 1.29 rmind case NPF_CMD_RULE_REMKEY:
588 1.60.2.2 martin rl = npfctl_parse_rule(argc, argv, entry);
589 1.29 rmind npfctl_generate_key(rl, key);
590 1.29 rmind error = npf_ruleset_remkey(fd, ruleset_name, key, sizeof(key));
591 1.29 rmind break;
592 1.29 rmind case NPF_CMD_RULE_REMOVE:
593 1.31 rmind rule_id = strtoull(argv[0], NULL, 16);
594 1.29 rmind error = npf_ruleset_remove(fd, ruleset_name, rule_id);
595 1.29 rmind break;
596 1.30 rmind case NPF_CMD_RULE_LIST:
597 1.30 rmind error = npfctl_ruleset_show(fd, ruleset_name);
598 1.30 rmind break;
599 1.30 rmind case NPF_CMD_RULE_FLUSH:
600 1.30 rmind error = npf_ruleset_flush(fd, ruleset_name);
601 1.30 rmind break;
602 1.29 rmind default:
603 1.48 christos abort();
604 1.29 rmind }
605 1.29 rmind
606 1.29 rmind switch (error) {
607 1.29 rmind case 0:
608 1.29 rmind /* Success. */
609 1.29 rmind break;
610 1.31 rmind case ESRCH:
611 1.31 rmind errx(EXIT_FAILURE, "ruleset \"%s\" not found", ruleset_name);
612 1.29 rmind case ENOENT:
613 1.31 rmind errx(EXIT_FAILURE, "rule was not found");
614 1.29 rmind default:
615 1.29 rmind errx(EXIT_FAILURE, "rule operation: %s", strerror(error));
616 1.29 rmind }
617 1.29 rmind if (action == NPF_CMD_RULE_ADD) {
618 1.31 rmind printf("OK %" PRIx64 "\n", rule_id);
619 1.29 rmind }
620 1.29 rmind }
621 1.29 rmind
622 1.45 christos static bool bpfjit = true;
623 1.45 christos
624 1.45 christos void
625 1.45 christos npfctl_bpfjit(bool onoff)
626 1.45 christos {
627 1.45 christos bpfjit = onoff;
628 1.45 christos }
629 1.45 christos
630 1.44 rmind static void
631 1.44 rmind npfctl_preload_bpfjit(void)
632 1.44 rmind {
633 1.48 christos #ifdef __NetBSD__
634 1.44 rmind modctl_load_t args = {
635 1.44 rmind .ml_filename = "bpfjit",
636 1.44 rmind .ml_flags = MODCTL_NO_PROP,
637 1.44 rmind .ml_props = NULL,
638 1.44 rmind .ml_propslen = 0
639 1.44 rmind };
640 1.44 rmind
641 1.45 christos if (!bpfjit)
642 1.45 christos return;
643 1.45 christos
644 1.44 rmind if (modctl(MODCTL_LOAD, &args) != 0 && errno != EEXIST) {
645 1.45 christos static const char *p = "; performance will be degraded";
646 1.45 christos if (errno == ENOENT)
647 1.45 christos warnx("the bpfjit module seems to be missing%s", p);
648 1.45 christos else
649 1.45 christos warn("error loading the bpfjit module%s", p);
650 1.45 christos warnx("To disable this warning `set bpf.jit off' in "
651 1.45 christos "/etc/npf.conf");
652 1.44 rmind }
653 1.48 christos #endif
654 1.44 rmind }
655 1.44 rmind
656 1.41 rmind static int
657 1.48 christos npfctl_load(int fd)
658 1.41 rmind {
659 1.41 rmind nl_config_t *ncf;
660 1.48 christos npf_error_t errinfo;
661 1.48 christos struct stat sb;
662 1.48 christos size_t blen;
663 1.48 christos void *blob;
664 1.41 rmind int error;
665 1.41 rmind
666 1.48 christos /*
667 1.48 christos * The file may change while reading - we are not handling this,
668 1.48 christos * leaving this responsibility for the caller.
669 1.48 christos */
670 1.48 christos if (stat(NPF_DB_PATH, &sb) == -1) {
671 1.48 christos err(EXIT_FAILURE, "stat");
672 1.48 christos }
673 1.48 christos if ((blen = sb.st_size) == 0) {
674 1.48 christos err(EXIT_FAILURE, "saved configuration file is empty");
675 1.48 christos }
676 1.48 christos if ((blob = mmap(NULL, blen, PROT_READ,
677 1.48 christos MAP_FILE | MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
678 1.48 christos err(EXIT_FAILURE, "mmap");
679 1.48 christos }
680 1.48 christos ncf = npf_config_import(blob, blen);
681 1.48 christos munmap(blob, blen);
682 1.41 rmind if (ncf == NULL) {
683 1.41 rmind return errno;
684 1.41 rmind }
685 1.41 rmind
686 1.48 christos /*
687 1.48 christos * Configuration imported - submit it now.
688 1.48 christos **/
689 1.48 christos errno = error = npf_config_submit(ncf, fd, &errinfo);
690 1.43 rmind if (error) {
691 1.48 christos npfctl_print_error(&errinfo);
692 1.43 rmind }
693 1.41 rmind npf_config_destroy(ncf);
694 1.41 rmind return error;
695 1.41 rmind }
696 1.41 rmind
697 1.50 christos struct npf_conn_filter {
698 1.59 rmind uint16_t alen;
699 1.59 rmind const char * ifname;
700 1.59 rmind bool nat;
701 1.59 rmind bool wide;
702 1.59 rmind bool name;
703 1.59 rmind int width;
704 1.59 rmind FILE * fp;
705 1.50 christos };
706 1.50 christos
707 1.50 christos static int
708 1.52 rmind npfctl_conn_print(unsigned alen, const npf_addr_t *a, const in_port_t *p,
709 1.50 christos const char *ifname, void *v)
710 1.50 christos {
711 1.50 christos struct npf_conn_filter *fil = v;
712 1.50 christos FILE *fp = fil->fp;
713 1.50 christos char *src, *dst;
714 1.50 christos
715 1.50 christos if (fil->ifname && strcmp(ifname, fil->ifname) != 0)
716 1.50 christos return 0;
717 1.50 christos if (fil->alen && alen != fil->alen)
718 1.50 christos return 0;
719 1.50 christos if (fil->nat && !p[2])
720 1.50 christos return 0;
721 1.50 christos
722 1.50 christos int w = fil->width;
723 1.50 christos const char *fmt = fil->name ? "%A" :
724 1.50 christos (alen == sizeof(struct in_addr) ? "%a" : "[%a]");
725 1.50 christos src = npfctl_print_addrmask(alen, fmt, &a[0], NPF_NO_NETMASK);
726 1.50 christos dst = npfctl_print_addrmask(alen, fmt, &a[1], NPF_NO_NETMASK);
727 1.59 rmind
728 1.59 rmind if (fil->wide) {
729 1.50 christos fprintf(fp, "%s:%d %s:%d", src, p[0], dst, p[1]);
730 1.59 rmind } else {
731 1.50 christos fprintf(fp, "%*.*s:%-5d %*.*s:%-5d", w, w, src, p[0],
732 1.50 christos w, w, dst, p[1]);
733 1.59 rmind }
734 1.50 christos free(src);
735 1.50 christos free(dst);
736 1.50 christos if (!p[2]) {
737 1.50 christos fputc('\n', fp);
738 1.50 christos return 1;
739 1.50 christos }
740 1.54 ozaki fprintf(fp, " via %s:%d\n", ifname, p[2]);
741 1.50 christos return 1;
742 1.50 christos }
743 1.50 christos
744 1.50 christos static int
745 1.50 christos npfctl_conn_list(int fd, int argc, char **argv)
746 1.50 christos {
747 1.50 christos struct npf_conn_filter f;
748 1.59 rmind int c, w, header = true;
749 1.59 rmind
750 1.50 christos memset(&f, 0, sizeof(f));
751 1.50 christos argc--;
752 1.50 christos argv++;
753 1.50 christos
754 1.50 christos while ((c = getopt(argc, argv, "46hi:nNw")) != -1) {
755 1.50 christos switch (c) {
756 1.50 christos case '4':
757 1.50 christos f.alen = sizeof(struct in_addr);
758 1.50 christos break;
759 1.50 christos case '6':
760 1.50 christos f.alen = sizeof(struct in6_addr);
761 1.50 christos break;
762 1.50 christos case 'h':
763 1.50 christos header = false;
764 1.58 mrg break;
765 1.50 christos case 'i':
766 1.50 christos f.ifname = optarg;
767 1.50 christos break;
768 1.50 christos case 'n':
769 1.50 christos f.nat = true;
770 1.50 christos break;
771 1.50 christos case 'N':
772 1.50 christos f.name = true;
773 1.50 christos break;
774 1.50 christos case 'w':
775 1.50 christos f.wide = true;
776 1.50 christos break;
777 1.50 christos default:
778 1.50 christos fprintf(stderr,
779 1.50 christos "Usage: %s list [-46hnNw] [-i <ifname>]\n",
780 1.50 christos getprogname());
781 1.50 christos exit(EXIT_FAILURE);
782 1.50 christos }
783 1.50 christos }
784 1.50 christos f.width = f.alen == sizeof(struct in_addr) ? 25 : 41;
785 1.59 rmind w = f.width + 6;
786 1.50 christos f.fp = stdout;
787 1.59 rmind
788 1.59 rmind if (header) {
789 1.50 christos fprintf(f.fp, "%*.*s %*.*s\n",
790 1.50 christos w, w, "From address:port ", w, w, "To address:port ");
791 1.59 rmind }
792 1.50 christos npf_conn_list(fd, npfctl_conn_print, &f);
793 1.50 christos return 0;
794 1.50 christos }
795 1.50 christos
796 1.52 rmind static int
797 1.52 rmind npfctl_open_dev(const char *path)
798 1.1 rmind {
799 1.56 rmind int fd, kernver;
800 1.1 rmind
801 1.52 rmind fd = open(path, O_RDONLY);
802 1.1 rmind if (fd == -1) {
803 1.52 rmind err(EXIT_FAILURE, "cannot open '%s'", path);
804 1.1 rmind }
805 1.56 rmind if (ioctl(fd, IOC_NPF_VERSION, &kernver) == -1) {
806 1.32 christos err(EXIT_FAILURE, "ioctl(IOC_NPF_VERSION)");
807 1.15 rmind }
808 1.56 rmind if (kernver != NPF_VERSION) {
809 1.4 rmind errx(EXIT_FAILURE,
810 1.14 rmind "incompatible NPF interface version (%d, kernel %d)\n"
811 1.60.2.1 martin "Hint: update %s?", NPF_VERSION, kernver,
812 1.56 rmind kernver > NPF_VERSION ? "userland" : "kernel");
813 1.1 rmind }
814 1.52 rmind return fd;
815 1.52 rmind }
816 1.52 rmind
817 1.52 rmind static void
818 1.52 rmind npfctl(int action, int argc, char **argv)
819 1.52 rmind {
820 1.52 rmind int fd, boolval, ret = 0;
821 1.52 rmind const char *fun = "";
822 1.52 rmind nl_config_t *ncf;
823 1.52 rmind
824 1.52 rmind switch (action) {
825 1.52 rmind case NPFCTL_VALIDATE:
826 1.52 rmind case NPFCTL_DEBUG:
827 1.52 rmind fd = 0;
828 1.52 rmind break;
829 1.52 rmind default:
830 1.52 rmind fd = npfctl_open_dev(NPF_DEV_PATH);
831 1.52 rmind }
832 1.29 rmind
833 1.1 rmind switch (action) {
834 1.1 rmind case NPFCTL_START:
835 1.1 rmind boolval = true;
836 1.1 rmind ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
837 1.32 christos fun = "ioctl(IOC_NPF_SWITCH)";
838 1.1 rmind break;
839 1.1 rmind case NPFCTL_STOP:
840 1.1 rmind boolval = false;
841 1.1 rmind ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
842 1.32 christos fun = "ioctl(IOC_NPF_SWITCH)";
843 1.1 rmind break;
844 1.1 rmind case NPFCTL_RELOAD:
845 1.8 rmind npfctl_config_init(false);
846 1.29 rmind npfctl_parse_file(argc < 3 ? NPF_CONF_PATH : argv[2]);
847 1.46 christos npfctl_preload_bpfjit();
848 1.56 rmind errno = ret = npfctl_config_send(fd);
849 1.32 christos fun = "npfctl_config_send";
850 1.1 rmind break;
851 1.11 rmind case NPFCTL_SHOWCONF:
852 1.11 rmind ret = npfctl_config_show(fd);
853 1.32 christos fun = "npfctl_config_show";
854 1.11 rmind break;
855 1.1 rmind case NPFCTL_FLUSH:
856 1.9 rmind ret = npf_config_flush(fd);
857 1.32 christos fun = "npf_config_flush";
858 1.1 rmind break;
859 1.1 rmind case NPFCTL_TABLE:
860 1.21 rmind if ((argc -= 2) < 2) {
861 1.1 rmind usage();
862 1.1 rmind }
863 1.21 rmind argv += 2;
864 1.60.2.2 martin if (strcmp(argv[1], "replace") == 0) {
865 1.60.2.2 martin npfctl_table_replace(fd, argc, argv);
866 1.60.2.2 martin } else {
867 1.60.2.2 martin npfctl_table(fd, argc, argv);
868 1.60.2.2 martin }
869 1.1 rmind break;
870 1.29 rmind case NPFCTL_RULE:
871 1.29 rmind if ((argc -= 2) < 2) {
872 1.29 rmind usage();
873 1.29 rmind }
874 1.29 rmind argv += 2;
875 1.29 rmind npfctl_rule(fd, argc, argv);
876 1.29 rmind break;
877 1.41 rmind case NPFCTL_LOAD:
878 1.44 rmind npfctl_preload_bpfjit();
879 1.41 rmind ret = npfctl_load(fd);
880 1.41 rmind fun = "npfctl_config_load";
881 1.41 rmind break;
882 1.41 rmind case NPFCTL_SAVE:
883 1.48 christos ncf = npf_config_retrieve(fd);
884 1.48 christos if (ncf) {
885 1.48 christos npfctl_config_save(ncf, NPF_DB_PATH);
886 1.48 christos npf_config_destroy(ncf);
887 1.48 christos } else {
888 1.48 christos ret = errno;
889 1.48 christos }
890 1.41 rmind fun = "npfctl_config_save";
891 1.41 rmind break;
892 1.3 rmind case NPFCTL_STATS:
893 1.3 rmind ret = npfctl_print_stats(fd);
894 1.32 christos fun = "npfctl_print_stats";
895 1.3 rmind break;
896 1.50 christos case NPFCTL_CONN_LIST:
897 1.50 christos ret = npfctl_conn_list(fd, argc, argv);
898 1.50 christos fun = "npfctl_conn_list";
899 1.50 christos break;
900 1.52 rmind case NPFCTL_VALIDATE:
901 1.52 rmind npfctl_config_init(false);
902 1.52 rmind npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
903 1.52 rmind ret = npfctl_config_show(0);
904 1.52 rmind fun = "npfctl_config_show";
905 1.52 rmind break;
906 1.52 rmind case NPFCTL_DEBUG:
907 1.52 rmind npfctl_config_init(true);
908 1.52 rmind npfctl_parse_file(argc > 2 ? argv[2] : NPF_CONF_PATH);
909 1.56 rmind npfctl_config_debug(argc > 3 ? argv[3] : "/tmp/npf.nvlist");
910 1.52 rmind break;
911 1.1 rmind }
912 1.7 zoltan if (ret) {
913 1.32 christos err(EXIT_FAILURE, "%s", fun);
914 1.1 rmind }
915 1.52 rmind if (fd) {
916 1.52 rmind close(fd);
917 1.52 rmind }
918 1.1 rmind }
919 1.1 rmind
920 1.1 rmind int
921 1.1 rmind main(int argc, char **argv)
922 1.1 rmind {
923 1.1 rmind char *cmd;
924 1.1 rmind
925 1.1 rmind if (argc < 2) {
926 1.1 rmind usage();
927 1.1 rmind }
928 1.48 christos npfctl_show_init();
929 1.1 rmind cmd = argv[1];
930 1.1 rmind
931 1.12 rmind /* Find and call the subroutine. */
932 1.8 rmind for (int n = 0; operations[n].cmd != NULL; n++) {
933 1.25 rmind const char *opcmd = operations[n].cmd;
934 1.25 rmind if (strncmp(cmd, opcmd, strlen(opcmd)) != 0)
935 1.1 rmind continue;
936 1.1 rmind npfctl(operations[n].action, argc, argv);
937 1.8 rmind return EXIT_SUCCESS;
938 1.1 rmind }
939 1.3 rmind usage();
940 1.1 rmind }
941