npf_data.c revision 1.33 1 /*-
2 * Copyright (c) 2009-2025 The NetBSD Foundation, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
15 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 /*
28 * npfctl(8) data manipulation and helper routines.
29 */
30
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: npf_data.c,v 1.33 2025/06/11 10:43:38 martin Exp $");
33
34 #include <stdlib.h>
35 #include <stddef.h>
36
37 #include <sys/types.h>
38 #include <netinet/in.h>
39 #include <netinet/in_systm.h>
40 #include <netinet/ip.h>
41 #define ICMP_STRINGS
42 #include <netinet/ip_icmp.h>
43 #define ICMP6_STRINGS
44 #include <netinet/icmp6.h>
45 #define __FAVOR_BSD
46 #include <netinet/tcp.h>
47 #include <net/if.h>
48
49 #include <string.h>
50 #include <ctype.h>
51 #include <err.h>
52 #include <errno.h>
53 #include <ifaddrs.h>
54 #include <netdb.h>
55 #include <pwd.h>
56 #include <grp.h>
57
58 #include "npfctl.h"
59
60 static struct ifaddrs * ifs_list = NULL;
61
62 void
63 npfctl_note_interface(const char *ifname)
64 {
65 unsigned long if_idx = if_nametoindex(ifname);
66 bool testif = npfctl_debug_addif(ifname);
67 const char *p = ifname;
68
69 /* If such interface exists or if it is a test interface - done. */
70 if (if_idx || testif) {
71 return;
72 }
73
74 /*
75 * Minimum sanity check. The interface name shall be non-empty
76 * string shorter than IFNAMSIZ and alphanumeric only.
77 */
78 if (*p == '\0') {
79 goto err;
80 }
81 while (*p) {
82 const size_t len = (ptrdiff_t)p - (ptrdiff_t)ifname;
83
84 if (!isalnum((unsigned char)*p) || len > IFNAMSIZ) {
85 goto err;
86 }
87 p++;
88 }
89
90 /* Throw a warning, so that the user could double check. */
91 warnx("warning - unknown interface '%s'", ifname);
92 return;
93 err:
94 yyerror("illegitimate interface name '%s'", ifname);
95 }
96
97 static unsigned long
98 npfctl_find_ifindex(const char *ifname)
99 {
100 unsigned long if_idx = if_nametoindex(ifname);
101 bool testif = npfctl_debug_addif(ifname);
102
103 if (!if_idx) {
104 if (testif) {
105 static u_int dummy_if_idx = (1 << 15);
106 return ++dummy_if_idx;
107 }
108 yyerror("unknown interface '%s'", ifname);
109 }
110 return if_idx;
111 }
112
113 static bool
114 npfctl_copy_address(sa_family_t fam, npf_addr_t *addr, const void *ptr)
115 {
116 memset(addr, 0, sizeof(npf_addr_t));
117
118 switch (fam) {
119 case AF_INET: {
120 const struct sockaddr_in *sin = ptr;
121 memcpy(addr, &sin->sin_addr, sizeof(sin->sin_addr));
122 return true;
123 }
124 case AF_INET6: {
125 const struct sockaddr_in6 *sin6 = ptr;
126 memcpy(addr, &sin6->sin6_addr, sizeof(sin6->sin6_addr));
127 return true;
128 }
129 default:
130 yyerror("unknown address family %u", fam);
131 return false;
132 }
133 }
134
135 /*
136 * npfctl_parse_fam_addr: parse a given a string and return the address
137 * family with the actual address as npf_addr_t.
138 *
139 * => Return true on success; false otherwise.
140 */
141 static bool
142 npfctl_parse_fam_addr(const char *name, sa_family_t *fam, npf_addr_t *addr)
143 {
144 static const struct addrinfo hint = {
145 .ai_family = AF_UNSPEC,
146 .ai_flags = AI_NUMERICHOST
147 };
148 struct addrinfo *ai;
149 int ret;
150
151 ret = getaddrinfo(name, NULL, &hint, &ai);
152 if (ret) {
153 yyerror("cannot parse '%s' (%s)", name, gai_strerror(ret));
154 return false;
155 }
156 if (fam) {
157 *fam = ai->ai_family;
158 }
159 if (!npfctl_copy_address(*fam, addr, ai->ai_addr)) {
160 return false;
161 }
162 freeaddrinfo(ai);
163 return true;
164 }
165
166 /*
167 * npfctl_parse_mask: parse a given string which represents a mask and
168 * can either be in quad-dot or CIDR block notation; validates the mask
169 * given the family.
170 *
171 * => Returns true if mask is valid (or is NULL); false otherwise.
172 */
173 static bool
174 npfctl_parse_mask(const char *s, sa_family_t fam, npf_netmask_t *mask)
175 {
176 unsigned max_mask = NPF_MAX_NETMASK;
177 char *ep = NULL;
178 npf_addr_t addr;
179 uint8_t *ap;
180
181 assert(fam == AF_INET || fam == AF_INET6);
182 if (!s) {
183 /* No mask. */
184 *mask = NPF_NO_NETMASK;
185 return true;
186 }
187
188 errno = 0;
189 *mask = (npf_netmask_t)strtol(s, &ep, 0);
190 if (*ep == '\0' && s != ep && errno != ERANGE) {
191 /* Just a number -- CIDR notation. */
192 goto check;
193 }
194
195 /* Other characters: try to parse a full address. */
196 if (!npfctl_parse_fam_addr(s, &fam, &addr)) {
197 return false;
198 }
199
200 /* Convert the address to CIDR block number. */
201 ap = addr.word8 + (*mask / 8) - 1;
202 while (ap >= addr.word8) {
203 for (int j = 8; j > 0; j--) {
204 if (*ap & 1)
205 goto check;
206 *ap >>= 1;
207 (*mask)--;
208 if (*mask == 0)
209 goto check;
210 }
211 ap--;
212 }
213 *mask = NPF_NO_NETMASK;
214 return true;
215 check:
216 switch (fam) {
217 case AF_INET:
218 max_mask = 32;
219 break;
220 case AF_INET6:
221 max_mask = 128;
222 break;
223 }
224 return *mask <= max_mask;
225 }
226
227 /*
228 * npfctl_parse_fam_addr_mask: return address family, address and mask.
229 *
230 * => Mask is optional and can be NULL.
231 * => Returns true on success or false if unable to parse.
232 */
233 npfvar_t *
234 npfctl_parse_fam_addr_mask(const char *addr, const char *mask,
235 unsigned long *nummask)
236 {
237 fam_addr_mask_t fam;
238 char buf[32];
239
240 memset(&fam, 0, sizeof(fam));
241
242 if (!npfctl_parse_fam_addr(addr, &fam.fam_family, &fam.fam_addr))
243 return NULL;
244
245 /*
246 * Mask may be NULL. In such case, "no mask" value will be set.
247 */
248 if (nummask) {
249 /* Let npfctl_parse_mask() validate the number. */
250 snprintf(buf, sizeof(buf), "%lu", *nummask);
251 mask = buf;
252 }
253 if (!npfctl_parse_mask(mask, fam.fam_family, &fam.fam_mask)) {
254 return NULL;
255 }
256 return npfvar_create_element(NPFVAR_FAM, &fam, sizeof(fam));
257 }
258
259 npfvar_t *
260 npfctl_parse_table_id(const char *name)
261 {
262 u_int tid;
263
264 tid = npfctl_table_getid(name);
265 if (tid == (unsigned)-1) {
266 yyerror("table '%s' is not defined", name);
267 return NULL;
268 }
269 return npfvar_create_element(NPFVAR_TABLE, &tid, sizeof(u_int));
270 }
271
272 int
273 npfctl_parse_user(const char *user, uint32_t *uid)
274 {
275 if (!strcmp(user, "unknown"))
276 *uid = UID_MAX;
277 else {
278 struct passwd *pw;
279
280 if ((pw = getpwnam(user)) == NULL) {
281 return -1;
282 }
283 *uid = pw->pw_uid;
284 }
285 return 0;
286 }
287
288 int
289 npfctl_parse_group(const char *group, uint32_t *gid)
290 {
291 if (!strcmp(group, "unknown"))
292 *gid = GID_MAX;
293 else {
294 struct group *grp;
295
296 if ((grp = getgrnam(group)) == NULL) {
297 return -1;
298 }
299 *gid = grp->gr_gid;
300 }
301 return 0;
302 }
303
304 /*
305 * this function is called for both gid and uid init in parser
306 * both uid and gid are both uint32_t
307 */
308 void
309 npfctl_init_rid(rid_t *rid, uint32_t id1, uint32_t id2, uint8_t op)
310 {
311 rid->id[0] = id1;
312 rid->id[1] = id2;
313 rid->op = op;
314 }
315
316 /*
317 * npfctl_parse_port_range: create a port-range variable. Note that the
318 * passed port numbers should be in host byte order.
319 */
320 npfvar_t *
321 npfctl_parse_port_range(in_port_t s, in_port_t e)
322 {
323 port_range_t pr;
324
325 pr.pr_start = htons(s);
326 pr.pr_end = htons(e);
327
328 return npfvar_create_element(NPFVAR_PORT_RANGE, &pr, sizeof(pr));
329 }
330
331 npfvar_t *
332 npfctl_parse_port_range_variable(const char *v, npfvar_t *vp)
333 {
334 size_t count = npfvar_get_count(vp);
335 npfvar_t *pvp = npfvar_create();
336 port_range_t *pr;
337
338 for (size_t i = 0; i < count; i++) {
339 int type = npfvar_get_type(vp, i);
340 void *data = npfvar_get_data(vp, type, i);
341 in_port_t p;
342
343 switch (type) {
344 case NPFVAR_IDENTIFIER:
345 case NPFVAR_STRING:
346 p = npfctl_portno(data);
347 npfvar_add_elements(pvp, npfctl_parse_port_range(p, p));
348 break;
349 case NPFVAR_PORT_RANGE:
350 pr = data;
351 npfvar_add_element(pvp, NPFVAR_PORT_RANGE, pr,
352 sizeof(*pr));
353 break;
354 case NPFVAR_NUM:
355 p = *(uint32_t *)data;
356 npfvar_add_elements(pvp, npfctl_parse_port_range(p, p));
357 break;
358 default:
359 if (v) {
360 yyerror("wrong variable '%s' type '%s' "
361 "for port range", v, npfvar_type(type));
362 } else {
363 yyerror("wrong element '%s' in the "
364 "inline list", npfvar_type(type));
365 }
366 npfvar_destroy(pvp);
367 return NULL;
368 }
369 }
370 return pvp;
371 }
372
373 npfvar_t *
374 npfctl_parse_ifnet(const char *ifname, const int family)
375 {
376 struct ifaddrs *ifa;
377 ifnet_addr_t ifna;
378 npfvar_t *vpa;
379
380 if (ifs_list == NULL && getifaddrs(&ifs_list) == -1) {
381 err(EXIT_FAILURE, "getifaddrs");
382 }
383
384 vpa = npfvar_create();
385 ifna.ifna_name = estrdup(ifname);
386 ifna.ifna_addrs = vpa;
387 ifna.ifna_index = npfctl_find_ifindex(ifname);
388 assert(ifna.ifna_index != 0);
389
390 for (ifa = ifs_list; ifa != NULL; ifa = ifa->ifa_next) {
391 fam_addr_mask_t fam;
392 struct sockaddr *sa;
393
394 if (strcmp(ifa->ifa_name, ifname) != 0)
395 continue;
396
397 if ((ifa->ifa_flags & IFF_UP) == 0)
398 warnx("interface '%s' is down", ifname);
399
400 sa = ifa->ifa_addr;
401 if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6)
402 continue;
403 if (family != AF_UNSPEC && sa->sa_family != family)
404 continue;
405
406 memset(&fam, 0, sizeof(fam));
407 fam.fam_family = sa->sa_family;
408 fam.fam_ifindex = ifna.ifna_index;
409 fam.fam_mask = NPF_NO_NETMASK;
410
411 if (!npfctl_copy_address(sa->sa_family, &fam.fam_addr, sa))
412 goto out;
413
414 if (!npfvar_add_element(vpa, NPFVAR_FAM, &fam, sizeof(fam)))
415 goto out;
416 }
417 if (npfvar_get_count(vpa) == 0) {
418 yyerror("no addresses matched for interface '%s'", ifname);
419 goto out;
420 }
421
422 return npfvar_create_element(NPFVAR_INTERFACE, &ifna, sizeof(ifna));
423 out:
424 npfvar_destroy(ifna.ifna_addrs);
425 return NULL;
426 }
427
428 bool
429 npfctl_parse_cidr(char *cidr, fam_addr_mask_t *fam, int *alen)
430 {
431 char *mask, *p;
432
433 p = strchr(cidr, '\n');
434 if (p) {
435 *p = '\0';
436 }
437 mask = strchr(cidr, '/');
438 if (mask) {
439 *mask++ = '\0';
440 }
441
442 memset(fam, 0, sizeof(*fam));
443 if (!npfctl_parse_fam_addr(cidr, &fam->fam_family, &fam->fam_addr)) {
444 return false;
445 }
446 if (!npfctl_parse_mask(mask, fam->fam_family, &fam->fam_mask)) {
447 return false;
448 }
449 switch (fam->fam_family) {
450 case AF_INET:
451 *alen = sizeof(struct in_addr);
452 break;
453 case AF_INET6:
454 *alen = sizeof(struct in6_addr);
455 break;
456 default:
457 return false;
458 }
459 return true;
460 }
461
462 int
463 npfctl_protono(const char *proto)
464 {
465 struct protoent *pe;
466
467 pe = getprotobyname(proto);
468 if (pe == NULL) {
469 yyerror("unknown protocol '%s'", proto);
470 return -1;
471 }
472 return pe->p_proto;
473 }
474
475 /*
476 * npfctl_portno: convert port identifier (string) to a number.
477 *
478 * => Returns port number in host byte order.
479 */
480 in_port_t
481 npfctl_portno(const char *port)
482 {
483 struct addrinfo *ai, *rai;
484 in_port_t p = 0;
485 int e;
486
487 e = getaddrinfo(NULL, port, NULL, &rai);
488 if (e != 0) {
489 yyerror("invalid port name '%s' (%s)", port, gai_strerror(e));
490 return 0;
491 }
492
493 for (ai = rai; ai; ai = ai->ai_next) {
494 switch (ai->ai_family) {
495 case AF_INET: {
496 struct sockaddr_in *sin = (void *)ai->ai_addr;
497 p = sin->sin_port;
498 goto out;
499 }
500 case AF_INET6: {
501 struct sockaddr_in6 *sin6 = (void *)ai->ai_addr;
502 p = sin6->sin6_port;
503 goto out;
504 }
505 default:
506 break;
507 }
508 }
509 out:
510 freeaddrinfo(rai);
511 return ntohs(p);
512 }
513
514 npfvar_t *
515 npfctl_parse_tcpflag(const char *s)
516 {
517 uint8_t tfl = 0;
518
519 while (*s) {
520 switch (*s) {
521 case 'F': tfl |= TH_FIN; break;
522 case 'S': tfl |= TH_SYN; break;
523 case 'R': tfl |= TH_RST; break;
524 case 'P': tfl |= TH_PUSH; break;
525 case 'A': tfl |= TH_ACK; break;
526 case 'U': tfl |= TH_URG; break;
527 case 'E': tfl |= TH_ECE; break;
528 case 'W': tfl |= TH_CWR; break;
529 default:
530 yyerror("invalid flag '%c'", *s);
531 return NULL;
532 }
533 s++;
534 }
535 return npfvar_create_element(NPFVAR_TCPFLAG, &tfl, sizeof(tfl));
536 }
537
538 uint8_t
539 npfctl_icmptype(int proto, const char *type)
540 {
541 #ifdef __NetBSD__
542 uint8_t ul;
543
544 switch (proto) {
545 case IPPROTO_ICMP:
546 for (ul = 0; icmp_type[ul]; ul++)
547 if (strcmp(icmp_type[ul], type) == 0)
548 return ul;
549 break;
550 case IPPROTO_ICMPV6:
551 for (ul = 0; icmp6_type_err[ul]; ul++)
552 if (strcmp(icmp6_type_err[ul], type) == 0)
553 return ul;
554 for (ul = 0; icmp6_type_info[ul]; ul++)
555 if (strcmp(icmp6_type_info[ul], type) == 0)
556 return ul + 128;
557 break;
558 default:
559 assert(false);
560 }
561 #else
562 (void)proto;
563 #endif
564 yyerror("unknown icmp-type %s", type);
565 return ~0;
566 }
567
568 uint8_t
569 npfctl_icmpcode(int proto, uint8_t type, const char *code)
570 {
571 #ifdef __NetBSD__
572 const char * const *arr;
573
574 switch (proto) {
575 case IPPROTO_ICMP:
576 switch (type) {
577 case ICMP_ECHOREPLY:
578 case ICMP_SOURCEQUENCH:
579 case ICMP_ALTHOSTADDR:
580 case ICMP_ECHO:
581 case ICMP_ROUTERSOLICIT:
582 case ICMP_TSTAMP:
583 case ICMP_TSTAMPREPLY:
584 case ICMP_IREQ:
585 case ICMP_IREQREPLY:
586 case ICMP_MASKREQ:
587 case ICMP_MASKREPLY:
588 arr = icmp_code_none;
589 break;
590 case ICMP_ROUTERADVERT:
591 arr = icmp_code_routeradvert;
592 break;
593 case ICMP_UNREACH:
594 arr = icmp_code_unreach;
595 break;
596 case ICMP_REDIRECT:
597 arr = icmp_code_redirect;
598 break;
599 case ICMP_TIMXCEED:
600 arr = icmp_code_timxceed;
601 break;
602 case ICMP_PARAMPROB:
603 arr = icmp_code_paramprob;
604 break;
605 case ICMP_PHOTURIS:
606 arr = icmp_code_photuris;
607 break;
608 default:
609 yyerror("unknown icmp-type %d while parsing code %s",
610 type, code);
611 return ~0;
612 }
613 break;
614 case IPPROTO_ICMPV6:
615 switch (type) {
616 case ICMP6_DST_UNREACH:
617 arr = icmp6_code_unreach;
618 break;
619 case ICMP6_TIME_EXCEEDED:
620 arr = icmp6_code_timxceed;
621 break;
622 case ICMP6_PARAM_PROB:
623 arr = icmp6_code_paramprob;
624 break;
625 case ICMP6_PACKET_TOO_BIG:
626 /* code-less info ICMPs */
627 case ICMP6_ECHO_REQUEST:
628 case ICMP6_ECHO_REPLY:
629 case MLD_LISTENER_QUERY:
630 case MLD_LISTENER_REPORT:
631 case MLD_LISTENER_DONE:
632 case ND_ROUTER_SOLICIT:
633 case ND_ROUTER_ADVERT:
634 case ND_NEIGHBOR_SOLICIT:
635 case ND_NEIGHBOR_ADVERT:
636 case ND_REDIRECT:
637 arr = icmp6_code_none;
638 break;
639 /* XXX TODO: info ICMPs with code values */
640 default:
641 yyerror("unknown icmp-type %d while parsing code %s",
642 type, code);
643 return ~0;
644 }
645 break;
646 default:
647 assert(false);
648 }
649
650 for (uint8_t ul = 0; arr[ul]; ul++) {
651 if (strcmp(arr[ul], code) == 0)
652 return ul;
653 }
654 #else
655 (void)proto;
656 #endif
657 yyerror("unknown code %s for icmp-type %d", code, type);
658 return ~0;
659 }
660
661 npfvar_t *
662 npfctl_parse_icmp(int proto __unused, int type, int code)
663 {
664 npfvar_t *vp = npfvar_create();
665
666 if (!npfvar_add_element(vp, NPFVAR_ICMP, &type, sizeof(type)))
667 goto out;
668
669 if (!npfvar_add_element(vp, NPFVAR_ICMP, &code, sizeof(code)))
670 goto out;
671
672 return vp;
673 out:
674 npfvar_destroy(vp);
675 return NULL;
676 }
677
678 /*
679 * npfctl_npt66_calcadj: calculate the adjustment for NPTv6 as per RFC 6296.
680 */
681 uint16_t
682 npfctl_npt66_calcadj(npf_netmask_t len, const npf_addr_t *pref_in,
683 const npf_addr_t *pref_out)
684 {
685 const uint16_t *addr6_in = (const uint16_t *)pref_in;
686 const uint16_t *addr6_out = (const uint16_t *)pref_out;
687 unsigned i, remnant, wordmask, preflen = len >> 4;
688 uint32_t adj, isum = 0, osum = 0;
689
690 /*
691 * Extract the bits within a 16-bit word (when prefix length is
692 * not dividable by 16) and include them into the sum.
693 */
694 remnant = len - (preflen << 4);
695 wordmask = (1U << remnant) - 1;
696 assert(wordmask == 0 || (len % 16) != 0);
697
698 /* Inner prefix - sum and fold. */
699 for (i = 0; i < preflen; i++) {
700 isum += addr6_in[i];
701 }
702 isum += addr6_in[i] & wordmask;
703 while (isum >> 16) {
704 isum = (isum >> 16) + (isum & 0xffff);
705 }
706
707 /* Outer prefix - sum and fold. */
708 for (i = 0; i < preflen; i++) {
709 osum += addr6_out[i];
710 }
711 osum += addr6_out[i] & wordmask;
712 while (osum >> 16) {
713 osum = (osum >> 16) + (osum & 0xffff);
714 }
715
716 /* Calculate 1's complement difference. */
717 adj = isum + ~osum;
718 while (adj >> 16) {
719 adj = (adj >> 16) + (adj & 0xffff);
720 }
721 return (uint16_t)adj;
722 }
723