npfctl.c revision 1.23 1 /* $NetBSD: npfctl.c,v 1.23 2012/11/05 23:47:12 rmind Exp $ */
2
3 /*-
4 * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This material is based upon work partially supported by The
8 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: npfctl.c,v 1.23 2012/11/05 23:47:12 rmind Exp $");
34
35 #include <sys/ioctl.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <err.h>
43 #include <fcntl.h>
44 #include <unistd.h>
45 #include <errno.h>
46
47 #include "npfctl.h"
48
49 extern int yylineno, yycolumn;
50 extern const char * yyfilename;
51 extern int yyparse(void);
52 extern void yyrestart(FILE *);
53
54 enum {
55 NPFCTL_START,
56 NPFCTL_STOP,
57 NPFCTL_RELOAD,
58 NPFCTL_SHOWCONF,
59 NPFCTL_FLUSH,
60 NPFCTL_TABLE,
61 NPFCTL_STATS,
62 NPFCTL_SESSIONS_SAVE,
63 NPFCTL_SESSIONS_LOAD,
64 };
65
66 static const struct operations_s {
67 const char * cmd;
68 int action;
69 } operations[] = {
70 /* Start, stop, reload */
71 { "start", NPFCTL_START },
72 { "stop", NPFCTL_STOP },
73 { "reload", NPFCTL_RELOAD },
74 { "show", NPFCTL_SHOWCONF, },
75 { "flush", NPFCTL_FLUSH },
76 /* Table */
77 { "table", NPFCTL_TABLE },
78 /* Stats */
79 { "stats", NPFCTL_STATS },
80 /* Sessions */
81 { "sess-save", NPFCTL_SESSIONS_SAVE },
82 { "sess-load", NPFCTL_SESSIONS_LOAD },
83 /* --- */
84 { NULL, 0 }
85 };
86
87 __dead static void
88 usage(void)
89 {
90 const char *progname = getprogname();
91
92 fprintf(stderr,
93 "usage:\t%s [ start | stop | reload | flush | show | stats ]\n",
94 progname);
95 fprintf(stderr,
96 "\t%s ( sess-save | sess-load )\n",
97 progname);
98 fprintf(stderr,
99 "\t%s table <tid> { add | rem | test } <address/mask>\n",
100 progname);
101 fprintf(stderr,
102 "\t%s table <tid> { list | flush }\n",
103 progname);
104
105 exit(EXIT_FAILURE);
106 }
107
108 static void
109 npfctl_parsecfg(const char *cfg)
110 {
111 FILE *fp;
112
113 fp = fopen(cfg, "r");
114 if (fp == NULL) {
115 err(EXIT_FAILURE, "open '%s'", cfg);
116 }
117 yyrestart(fp);
118 yylineno = 1;
119 yycolumn = 0;
120 yyfilename = cfg;
121 yyparse();
122 fclose(fp);
123 }
124
125 static int
126 npfctl_print_stats(int fd)
127 {
128 static const struct stats_s {
129 /* Note: -1 indicates a new section. */
130 int index;
131 const char * name;
132 } stats[] = {
133 { -1, "Packets passed" },
134 { NPF_STAT_PASS_DEFAULT, "default pass" },
135 { NPF_STAT_PASS_RULESET, "ruleset pass" },
136 { NPF_STAT_PASS_SESSION, "session pass" },
137
138 { -1, "Packets blocked" },
139 { NPF_STAT_BLOCK_DEFAULT, "default block" },
140 { NPF_STAT_BLOCK_RULESET, "ruleset block" },
141
142 { -1, "Session and NAT entries" },
143 { NPF_STAT_SESSION_CREATE, "session allocations" },
144 { NPF_STAT_SESSION_DESTROY, "session destructions" },
145 { NPF_STAT_NAT_CREATE, "NAT entry allocations" },
146 { NPF_STAT_NAT_DESTROY, "NAT entry destructions"},
147
148 { -1, "Invalid packet state cases" },
149 { NPF_STAT_INVALID_STATE, "cases in total" },
150 { NPF_STAT_INVALID_STATE_TCP1, "TCP case I" },
151 { NPF_STAT_INVALID_STATE_TCP2, "TCP case II" },
152 { NPF_STAT_INVALID_STATE_TCP3, "TCP case III" },
153
154 { -1, "Packet race cases" },
155 { NPF_STAT_RACE_NAT, "NAT association race" },
156 { NPF_STAT_RACE_SESSION, "duplicate session race"},
157
158 { -1, "Fragmentation" },
159 { NPF_STAT_FRAGMENTS, "fragments" },
160 { NPF_STAT_REASSEMBLY, "reassembled" },
161 { NPF_STAT_REASSFAIL, "failed reassembly" },
162
163 { -1, "Other" },
164 { NPF_STAT_ERROR, "unexpected errors" },
165 };
166 uint64_t *st = emalloc(NPF_STATS_SIZE);
167
168 if (ioctl(fd, IOC_NPF_STATS, &st) != 0) {
169 err(EXIT_FAILURE, "ioctl(IOC_NPF_STATS)");
170 }
171
172 for (unsigned i = 0; i < __arraycount(stats); i++) {
173 const char *sname = stats[i].name;
174 int sidx = stats[i].index;
175
176 if (sidx == -1) {
177 printf("%s:\n", sname);
178 } else {
179 printf("\t%"PRIu64" %s\n", st[sidx], sname);
180 }
181 }
182
183 free(st);
184 return 0;
185 }
186
187 void
188 npfctl_print_error(const nl_error_t *ne)
189 {
190 static const char *ncode_errors[] = {
191 [-NPF_ERR_OPCODE] = "invalid instruction",
192 [-NPF_ERR_JUMP] = "invalid jump",
193 [-NPF_ERR_REG] = "invalid register",
194 [-NPF_ERR_INVAL] = "invalid argument value",
195 [-NPF_ERR_RANGE] = "processing out of range"
196 };
197 const int nc_err = ne->ne_ncode_error;
198 const char *srcfile = ne->ne_source_file;
199
200 if (srcfile) {
201 warnx("source %s line %d", srcfile, ne->ne_source_line);
202 }
203 if (nc_err) {
204 warnx("n-code error (%d): %s at offset 0x%x",
205 nc_err, ncode_errors[-nc_err], ne->ne_ncode_errat);
206 }
207 if (ne->ne_id) {
208 warnx("object: %d", ne->ne_id);
209 }
210 }
211
212 char *
213 npfctl_print_addrmask(int alen, npf_addr_t *addr, npf_netmask_t mask)
214 {
215 struct sockaddr_storage ss;
216 char *buf = emalloc(64);
217 int len;
218
219 switch (alen) {
220 case 4: {
221 struct sockaddr_in *sin = (void *)&ss;
222 sin->sin_len = sizeof(*sin);
223 sin->sin_family = AF_INET;
224 sin->sin_port = 0;
225 memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr));
226 break;
227 }
228 case 16: {
229 struct sockaddr_in6 *sin6 = (void *)&ss;
230 sin6->sin6_len = sizeof(*sin6);
231 sin6->sin6_family = AF_INET6;
232 sin6->sin6_port = 0;
233 memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr));
234 break;
235 }
236 default:
237 assert(false);
238 }
239 len = sockaddr_snprintf(buf, 64, "%a", (struct sockaddr *)&ss);
240 if (mask) {
241 snprintf(&buf[len], 64 - len, "/%u", mask);
242 }
243 return buf;
244 }
245
246 __dead static void
247 npfctl_table(int fd, int argc, char **argv)
248 {
249 static const struct tblops_s {
250 const char * cmd;
251 int action;
252 } tblops[] = {
253 { "add", NPF_IOCTL_TBLENT_ADD },
254 { "rem", NPF_IOCTL_TBLENT_REM },
255 { "test", NPF_IOCTL_TBLENT_LOOKUP },
256 { "list", NPF_IOCTL_TBLENT_LIST },
257 { NULL, 0 }
258 };
259 npf_ioctl_table_t nct;
260 fam_addr_mask_t fam;
261 size_t buflen = 512;
262 char *cmd, *arg = NULL; /* XXX gcc */
263 int n, alen;
264
265 /* Default action is list. */
266 memset(&nct, 0, sizeof(npf_ioctl_table_t));
267 nct.nct_tid = atoi(argv[0]);
268 cmd = argv[1];
269
270 for (n = 0; tblops[n].cmd != NULL; n++) {
271 if (strcmp(cmd, tblops[n].cmd) != 0) {
272 continue;
273 }
274 nct.nct_action = tblops[n].action;
275 break;
276 }
277 if (tblops[n].cmd == NULL) {
278 errx(EXIT_FAILURE, "invalid command '%s'", cmd);
279 }
280 if (nct.nct_action != NPF_IOCTL_TBLENT_LIST) {
281 if (argc < 3) {
282 usage();
283 }
284 arg = argv[2];
285 }
286 again:
287 if (nct.nct_action == NPF_IOCTL_TBLENT_LIST) {
288 nct.nct_data.buf.buf = emalloc(buflen);
289 nct.nct_data.buf.len = buflen;
290 } else {
291 if (!npfctl_parse_cidr(arg, &fam, &alen)) {
292 errx(EXIT_FAILURE, "invalid CIDR '%s'", arg);
293 }
294 nct.nct_data.ent.alen = alen;
295 memcpy(&nct.nct_data.ent.addr, &fam.fam_addr, sizeof(npf_addr_t));
296 nct.nct_data.ent.mask = fam.fam_mask;
297 }
298
299 if (ioctl(fd, IOC_NPF_TABLE, &nct) != -1) {
300 errno = 0;
301 }
302 switch (errno) {
303 case 0:
304 break;
305 case EEXIST:
306 errx(EXIT_FAILURE, "entry already exists or is conflicting");
307 case ENOENT:
308 errx(EXIT_FAILURE, "no matching entry was not found");
309 case EINVAL:
310 errx(EXIT_FAILURE, "invalid address, mask or table ID");
311 case ENOMEM:
312 if (nct.nct_action == NPF_IOCTL_TBLENT_LIST) {
313 /* XXX */
314 free(nct.nct_data.buf.buf);
315 buflen <<= 1;
316 goto again;
317 }
318 /* FALLTHROUGH */
319 default:
320 err(EXIT_FAILURE, "ioctl");
321 }
322
323 if (nct.nct_action == NPF_IOCTL_TBLENT_LIST) {
324 npf_ioctl_ent_t *ent = nct.nct_data.buf.buf;
325 char *buf;
326
327 while (nct.nct_data.buf.len--) {
328 if (!ent->alen)
329 break;
330 buf = npfctl_print_addrmask(ent->alen,
331 &ent->addr, ent->mask);
332 puts(buf);
333 ent++;
334 }
335 free(nct.nct_data.buf.buf);
336 } else {
337 printf("%s: %s\n", getprogname(),
338 nct.nct_action == NPF_IOCTL_TBLENT_LOOKUP ?
339 "matching entry found" : "success");
340 }
341 exit(EXIT_SUCCESS);
342 }
343
344 static void
345 npfctl(int action, int argc, char **argv)
346 {
347 int fd, ret, ver, boolval;
348
349 fd = open(NPF_DEV_PATH, O_RDONLY);
350 if (fd == -1) {
351 err(EXIT_FAILURE, "cannot open '%s'", NPF_DEV_PATH);
352 }
353 ret = ioctl(fd, IOC_NPF_VERSION, &ver);
354 if (ret == -1) {
355 err(EXIT_FAILURE, "ioctl");
356 }
357 if (ver != NPF_VERSION) {
358 errx(EXIT_FAILURE,
359 "incompatible NPF interface version (%d, kernel %d)\n"
360 "Hint: update userland?", NPF_VERSION, ver);
361 }
362 switch (action) {
363 case NPFCTL_START:
364 boolval = true;
365 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
366 break;
367 case NPFCTL_STOP:
368 boolval = false;
369 ret = ioctl(fd, IOC_NPF_SWITCH, &boolval);
370 break;
371 case NPFCTL_RELOAD:
372 npfctl_config_init(false);
373 npfctl_parsecfg(argc < 3 ? NPF_CONF_PATH : argv[2]);
374 ret = npfctl_config_send(fd, NULL);
375 if (ret) {
376 errx(EXIT_FAILURE, "ioctl: %s", strerror(ret));
377 }
378 break;
379 case NPFCTL_SHOWCONF:
380 ret = npfctl_config_show(fd);
381 break;
382 case NPFCTL_FLUSH:
383 ret = npf_config_flush(fd);
384 break;
385 case NPFCTL_TABLE:
386 if ((argc -= 2) < 2) {
387 usage();
388 }
389 argv += 2;
390 npfctl_table(fd, argc, argv);
391 break;
392 case NPFCTL_STATS:
393 ret = npfctl_print_stats(fd);
394 break;
395 case NPFCTL_SESSIONS_SAVE:
396 if (npf_sessions_recv(fd, NPF_SESSDB_PATH) != 0) {
397 errx(EXIT_FAILURE, "could not save sessions to '%s'",
398 NPF_SESSDB_PATH);
399 }
400 break;
401 case NPFCTL_SESSIONS_LOAD:
402 if (npf_sessions_send(fd, NPF_SESSDB_PATH) != 0) {
403 errx(EXIT_FAILURE, "no sessions loaded from '%s'",
404 NPF_SESSDB_PATH);
405 }
406 break;
407 }
408 if (ret) {
409 err(EXIT_FAILURE, "ioctl");
410 }
411 close(fd);
412 }
413
414 int
415 main(int argc, char **argv)
416 {
417 char *cmd;
418
419 if (argc < 2) {
420 usage();
421 }
422 cmd = argv[1];
423
424 if (strcmp(cmd, "debug") == 0) {
425 const char *cfg = argc > 2 ? argv[2] : "/etc/npf.conf";
426 const char *out = argc > 3 ? argv[3] : "/tmp/npf.plist";
427
428 npfctl_config_init(true);
429 npfctl_parsecfg(cfg);
430 npfctl_config_send(0, out);
431 return EXIT_SUCCESS;
432 }
433
434 /* Find and call the subroutine. */
435 for (int n = 0; operations[n].cmd != NULL; n++) {
436 if (strcmp(cmd, operations[n].cmd) != 0)
437 continue;
438 npfctl(operations[n].action, argc, argv);
439 return EXIT_SUCCESS;
440 }
441 usage();
442 }
443