diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 8b76b0c3fdfd..926c18ee5dbc 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1,3459 +1,3459 @@ /* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #define PFIOC_USE_LATEST #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); void pfctl_clear_stats(struct pfctl_handle *, int); void pfctl_get_skip_ifaces(void); void pfctl_check_skip_ifaces(char *); void pfctl_adjust_skip_ifaces(struct pfctl *); void pfctl_clear_interface_flags(int, int); void pfctl_flush_eth_rules(int, int, char *); void pfctl_flush_rules(int, int, char *); void pfctl_flush_nat(int, int, char *); int pfctl_clear_altq(int, int); void pfctl_clear_src_nodes(int, int); void pfctl_clear_iface_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); void pfctl_kill_src_nodes(int, const char *, int); void pfctl_net_kill_states(int, const char *, int); void pfctl_gateway_kill_states(int, const char *, int); void pfctl_label_kill_states(int, const char *, int); void pfctl_id_kill_states(int, const char *, int); void pfctl_key_kill_states(int, const char *, int); int pfctl_parse_host(char *, struct pf_rule_addr *); void pfctl_init_options(struct pfctl *); int pfctl_load_options(struct pfctl *); int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); int pfctl_load_debug(struct pfctl *, unsigned int); int pfctl_load_logif(struct pfctl *, char *); int pfctl_load_hostid(struct pfctl *, u_int32_t); int pfctl_load_reassembly(struct pfctl *, u_int32_t); int pfctl_load_syncookies(struct pfctl *, u_int8_t); int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int, const char *, int); void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int); void pfctl_print_rule_counters(struct pfctl_rule *, int); int pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_nat(int, const char *, int, char *, int, int); int pfctl_show_src_nodes(int, int); int pfctl_show_states(int, const char *, int); int pfctl_show_status(int, int); int pfctl_show_running(int); int pfctl_show_timeouts(int, int); int pfctl_show_limits(int, int); void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); int pfctl_show_eth_anchors(int, int, char *); int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool); int pfctl_eth_ruleset_trans(struct pfctl *, char *, struct pfctl_eth_anchor *); int pfctl_load_eth_ruleset(struct pfctl *, char *, struct pfctl_eth_ruleset *, int); int pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *, int); int pfctl_load_ruleset(struct pfctl *, char *, struct pfctl_ruleset *, int, int); int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); const char *pfctl_lookup_option(char *, const char * const *); static struct pfctl_anchor_global pf_anchors; struct pfctl_anchor pf_main_anchor; struct pfctl_eth_anchor pf_eth_main_anchor; static struct pfr_buffer skip_b; static const char *clearopt; static char *rulesopt; static const char *showopt; static const char *debugopt; static char *anchoropt; static const char *optiopt = NULL; static const char *pf_device = PF_DEVICE; static char *ifaceopt; static char *tableopt; static const char *tblcmdopt; static int src_node_killers; static char *src_node_kill[2]; static int state_killers; static char *state_kill[2]; int loadopt; int altqsupport; int dev = -1; struct pfctl_handle *pfh = NULL; static int first_title = 1; static int labels = 0; #define INDENT(d, o) do { \ if (o) { \ int i; \ for (i=0; i < d; i++) \ printf(" "); \ } \ } while (0); \ static const struct { const char *name; int index; } pf_limits[] = { { "states", PF_LIMIT_STATES }, { "src-nodes", PF_LIMIT_SRC_NODES }, { "frags", PF_LIMIT_FRAGS }, { "table-entries", PF_LIMIT_TABLE_ENTRIES }, { NULL, 0 } }; struct pf_hint { const char *name; int timeout; }; static const struct pf_hint pf_hint_normal[] = { { "tcp.first", 2 * 60 }, { "tcp.opening", 30 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 }, { "tcp.finwait", 45 }, { "tcp.closed", 90 }, { "tcp.tsdiff", 30 }, { NULL, 0 } }; static const struct pf_hint pf_hint_satellite[] = { { "tcp.first", 3 * 60 }, { "tcp.opening", 30 + 5 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 + 5 }, { "tcp.finwait", 45 + 5 }, { "tcp.closed", 90 + 5 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_conservative[] = { { "tcp.first", 60 * 60 }, { "tcp.opening", 15 * 60 }, { "tcp.established", 5 * 24 * 60 * 60 }, { "tcp.closing", 60 * 60 }, { "tcp.finwait", 10 * 60 }, { "tcp.closed", 3 * 60 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_aggressive[] = { { "tcp.first", 30 }, { "tcp.opening", 5 }, { "tcp.established", 5 * 60 * 60 }, { "tcp.closing", 60 }, { "tcp.finwait", 30 }, { "tcp.closed", 30 }, { "tcp.tsdiff", 10 }, { NULL, 0 } }; static const struct { const char *name; const struct pf_hint *hint; } pf_hints[] = { { "normal", pf_hint_normal }, { "satellite", pf_hint_satellite }, { "high-latency", pf_hint_satellite }, { "conservative", pf_hint_conservative }, { "aggressive", pf_hint_aggressive }, { NULL, NULL } }; static const char * const clearopt_list[] = { "nat", "queue", "rules", "Sources", "states", "info", "Tables", "osfp", "all", "ethernet", NULL }; static const char * const showopt_list[] = { "ether", "nat", "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", "Running", "all", "creatorids", NULL }; static const char * const tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", "expire", "reset", NULL }; static const char * const debugopt_list[] = { "none", "urgent", "misc", "loud", NULL }; static const char * const optiopt_list[] = { "none", "basic", "profile", NULL }; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-AdeghMmNnOPqRSrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "\t[-f file] [-i interface] [-K host | network]\n" "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", __progname); exit(1); } /* * Cache protocol number to name translations. * * Translation is performed a lot e.g., when dumping states and * getprotobynumber is incredibly expensive. * * Note from the getprotobynumber(3) manpage: * * These functions use a thread-specific data space; if the data is needed * for future use, it should be copied before any subsequent calls overwrite * it. Only the Internet protocols are currently understood. * * * Consequently we only cache the name and strdup it for safety. * * At the time of writing this comment the last entry in /etc/protocols is: * divert 258 DIVERT # Divert pseudo-protocol [non IANA] */ const char * pfctl_proto2name(int proto) { static const char *pfctl_proto_cache[259]; struct protoent *p; if (proto >= nitems(pfctl_proto_cache)) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } return (p->p_name); } if (pfctl_proto_cache[proto] == NULL) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } pfctl_proto_cache[proto] = strdup(p->p_name); } return (pfctl_proto_cache[proto]); } int pfctl_enable(int dev, int opts) { int ret; if ((ret = pfctl_startstop(pfh, 1)) != 0) { if (ret == EEXIST) errx(1, "pf already enabled"); else if (ret == ESRCH) errx(1, "pfil registeration failed"); else errc(1, ret, "DIOCSTART"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf enabled\n"); if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) if (errno != EEXIST) err(1, "DIOCSTARTALTQ"); return (0); } int pfctl_disable(int dev, int opts) { int ret; if ((ret = pfctl_startstop(pfh, 0)) != 0) { if (ret == ENOENT) errx(1, "pf not enabled"); else errc(1, ret, "DIOCSTOP"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf disabled\n"); if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) if (errno != ENOENT) err(1, "DIOCSTOPALTQ"); return (0); } void pfctl_clear_stats(struct pfctl_handle *h, int opts) { int ret; if ((ret = pfctl_clear_status(h)) != 0) errc(1, ret, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); } void pfctl_get_skip_ifaces(void) { bzero(&skip_b, sizeof(skip_b)); skip_b.pfrb_type = PFRB_IFACES; for (;;) { pfr_buf_grow(&skip_b, skip_b.pfrb_size); skip_b.pfrb_size = skip_b.pfrb_msize; if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size)) err(1, "pfi_get_ifaces"); if (skip_b.pfrb_size <= skip_b.pfrb_msize) break; } } void pfctl_check_skip_ifaces(char *ifname) { struct pfi_kif *p; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (!strcmp(ifname, p->pfik_name) && (p->pfik_flags & PFI_IFLAG_SKIP)) p->pfik_flags &= ~PFI_IFLAG_SKIP; if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) { if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) { if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) continue; p->pfik_flags &= ~PFI_IFLAG_SKIP; } } } } void pfctl_adjust_skip_ifaces(struct pfctl *pf) { struct pfi_kif *p, *pp; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) PFRB_FOREACH(pp, &skip_b) { if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ)) continue; if (!(pp->pfik_flags & PFI_IFLAG_SKIP)) pfctl_set_interface_flags(pf, pp->pfik_name, PFI_IFLAG_SKIP, 1); if (pp->pfik_flags & PFI_IFLAG_SKIP) pp->pfik_flags &= ~PFI_IFLAG_SKIP; } } PFRB_FOREACH(p, &skip_b) { if (! (p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); } } void pfctl_clear_interface_flags(int dev, int opts) { struct pfioc_iface pi; if ((opts & PF_OPT_NOACTION) == 0) { bzero(&pi, sizeof(pi)); pi.pfiio_flags = PFI_IFLAG_SKIP; if (ioctl(dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: interface flags reset\n"); } } void pfctl_flush_eth_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_eth_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_eth_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "Ethernet rules cleared\n"); } void pfctl_flush_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); } void pfctl_flush_nat(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_nat(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_nat"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); } int pfctl_clear_altq(int dev, int opts) { struct pfr_buffer t; if (!altqsupport) return (-1); memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_altq"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "altq cleared\n"); return (0); } void pfctl_clear_src_nodes(int dev, int opts) { if (ioctl(dev, DIOCCLRSRCNODES)) err(1, "DIOCCLRSRCNODES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "source tracking entries cleared\n"); } void pfctl_clear_iface_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; int ret; memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((ret = pfctl_clear_states_h(pfh, &kill, &killed)) != 0) errc(1, ret, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "%d states cleared\n", killed); } void pfctl_addrprefix(char *addr, struct pf_addr *mask) { char *p; const char *errstr; int prefix, ret_ga, q, r; struct addrinfo hints, *res; if ((p = strchr(addr, '/')) == NULL) return; *p++ = '\0'; prefix = strtonum(p, 0, 128, &errstr); if (errstr) errx(1, "prefix is %s: %s", errstr, p); bzero(&hints, sizeof(hints)); /* prefix only with numeric addresses */ hints.ai_flags |= AI_NUMERICHOST; if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } if (res->ai_family == AF_INET && prefix > 32) errx(1, "prefix too long for AF_INET"); else if (res->ai_family == AF_INET6 && prefix > 128) errx(1, "prefix too long for AF_INET6"); q = prefix >> 3; r = prefix & 7; switch (res->ai_family) { case AF_INET: bzero(&mask->v4, sizeof(mask->v4)); mask->v4.s_addr = htonl((u_int32_t) (0xffffffffffULL << (32 - prefix))); break; case AF_INET6: bzero(&mask->v6, sizeof(mask->v6)); if (q > 0) memset((void *)&mask->v6, 0xff, q); if (r > 0) *((u_char *)&mask->v6 + q) = (0xff00 >> r) & 0xff; break; } freeaddrinfo(res); } void pfctl_kill_src_nodes(int dev, const char *iface, int opts) { struct pfioc_src_node_kill psnk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psnk, 0, sizeof(psnk)); memset(&psnk.psnk_src.addr.v.a.mask, 0xff, sizeof(psnk.psnk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psnk.psnk_af = resp[0]->ai_family; sources++; if (psnk.psnk_af == AF_INET) psnk.psnk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (src_node_killers > 1) { dests = 0; memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, sizeof(psnk.psnk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[1], &psnk.psnk_dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psnk.psnk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psnk.psnk_af == AF_INET) psnk.psnk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d src nodes from %d sources and %d " "destinations\n", killed, sources, dests); } void pfctl_net_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; unsigned int newkilled; int killed, sources, dests; int ret_ga, ret; killed = sources = dests = 0; memset(&kill, 0, sizeof(kill)); memset(&kill.src.addr.v.a.mask, 0xff, sizeof(kill.src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (state_killers == 2 && (strcmp(state_kill[0], "nat") == 0)) { kill.nat = true; state_kill[0] = state_kill[1]; state_killers = 1; } pfctl_addrprefix(state_kill[0], &kill.src.addr.v.a.mask); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; kill.af = resp[0]->ai_family; sources++; if (kill.af == AF_INET) kill.src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (state_killers > 1) { dests = 0; memset(&kill.dst.addr.v.a.mask, 0xff, sizeof(kill.dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(state_kill[1], &kill.dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (kill.af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (kill.af == AF_INET) kill.dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", kill.af); if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0) errc(1, ret, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res[1]); } else { if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0) errc(1, ret, "DIOCKILLSTATES"); killed += newkilled; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); } void pfctl_gateway_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res, *resp; struct sockaddr last_src; unsigned int newkilled; int killed = 0; int ret_ga; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no gateway specified"); usage(); } memset(&kill, 0, sizeof(kill)); memset(&kill.rt_addr.addr.v.a.mask, 0xff, sizeof(kill.rt_addr.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp = res; resp; resp = resp->ai_next) { if (resp->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp->ai_addr; kill.af = resp->ai_family; if (kill.af == AF_INET) kill.rt_addr.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.rt_addr.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (pfctl_kill_states_h(pfh, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); } void pfctl_label_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; int ret; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no label specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >= sizeof(kill.label)) errx(1, "label too long: %s", state_kill[1]); if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) errc(1, ret, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); } void pfctl_id_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; int ret; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no id specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((sscanf(state_kill[1], "%jx/%x", &kill.cmp.id, &kill.cmp.creatorid)) == 2) { } else if ((sscanf(state_kill[1], "%jx", &kill.cmp.id)) == 1) { kill.cmp.creatorid = 0; } else { warnx("wrong id format specified"); usage(); } if (kill.cmp.id == 0) { warnx("cannot kill id 0"); usage(); } if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) errc(1, ret, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); } void pfctl_key_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; char *s, *token, *tokens[4]; struct protoent *p; u_int i, sidx, didx; int ret, killed; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no key specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); s = strdup(state_kill[1]); if (!s) errx(1, "%s: strdup", __func__); i = 0; while ((token = strsep(&s, " \t")) != NULL) if (*token != '\0') { if (i < 4) tokens[i] = token; i++; } if (i != 4) errx(1, "%s: key must be " "\"protocol host1:port1 direction host2:port2\" format", __func__); if ((p = getprotobyname(tokens[0])) == NULL) errx(1, "invalid protocol: %s", tokens[0]); kill.proto = p->p_proto; if (strcmp(tokens[2], "->") == 0) { sidx = 1; didx = 3; } else if (strcmp(tokens[2], "<-") == 0) { sidx = 3; didx = 1; } else errx(1, "invalid direction: %s", tokens[2]); if (pfctl_parse_host(tokens[sidx], &kill.src) == -1) errx(1, "invalid host: %s", tokens[sidx]); if (pfctl_parse_host(tokens[didx], &kill.dst) == -1) errx(1, "invalid host: %s", tokens[didx]); if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0) errc(1, ret, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); } int pfctl_parse_host(char *str, struct pf_rule_addr *addr) { char *s = NULL, *sbs, *sbe; struct addrinfo hints, *ai; struct sockaddr_in *sin4; struct sockaddr_in6 *sin6; s = strdup(str); if (!s) errx(1, "pfctl_parse_host: strdup"); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; /* dummy */ hints.ai_flags = AI_NUMERICHOST; if ((sbs = strchr(s, '[')) != NULL && (sbe = strrchr(s, ']')) != NULL) { hints.ai_family = AF_INET6; *(sbs++) = *sbe = '\0'; } else if ((sbs = strchr(s, ':')) != NULL) { hints.ai_family = AF_INET; *(sbs++) = '\0'; } else { /* Assume that no ':' means port 0 */ } if (getaddrinfo(s, sbs, &hints, &ai) != 0) goto error; switch (ai->ai_family) { case AF_INET: sin4 = (struct sockaddr_in *)ai->ai_addr; addr->addr.v.a.addr.v4 = sin4->sin_addr; addr->port[0] = sin4->sin_port; break; case AF_INET6: sin6 = (struct sockaddr_in6 *)ai->ai_addr; addr->addr.v.a.addr.v6 = sin6->sin6_addr; addr->port[0] = sin6->sin6_port; break; } freeaddrinfo(ai); free(s); memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr)); addr->port_op = PF_OP_EQ; addr->addr.type = PF_ADDR_ADDRMASK; return (0); error: free(s); return (-1); } int pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, const char *anchorname, int which) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; int ret; memset(&pp, 0, sizeof(pp)); if ((ret = pfctl_get_addrs(pfh, ticket, nr, r_action, anchorname, &mpnr, which)) != 0) { warnc(ret, "DIOCGETADDRS"); return (-1); } TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { if ((ret = pfctl_get_addr(pfh, ticket, nr, r_action, anchorname, pnr, &pp, which)) != 0) { warnc(ret, "DIOCGETADDR"); return (-1); } pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "calloc"); bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); TAILQ_INSERT_TAIL(&pool->list, pa, entries); } return (0); } void pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&src->list)) != NULL) { TAILQ_REMOVE(&src->list, pa, entries); TAILQ_INSERT_TAIL(&dst->list, pa, entries); } } void pfctl_clear_pool(struct pfctl_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts) { if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1])); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "da", "sp", "dp" }; int i; printf(" [ Skip steps: "); for (i = 0; i < PF_SKIP_COUNT; ++i) { if (rule->skip[i].nr == rule->nr + 1) continue; printf("%s=", t[i]); if (rule->skip[i].nr == -1) printf("end "); else printf("%u ", rule->skip[i].nr); } printf("]\n"); printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); } if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu States: %-6ju]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1]), (uintmax_t)rule->states_cur); printf(" [ Source Nodes: %-6ju " "Limit: %-6ju " "NAT/RDR: %-6ju " "Route: %-6ju " "]\n", (uintmax_t)rule->src_nodes, (uintmax_t)rule->src_nodes_type[PF_SN_LIMIT], (uintmax_t)rule->src_nodes_type[PF_SN_NAT], (uintmax_t)rule->src_nodes_type[PF_SN_ROUTE]); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", (unsigned)rule->cuid, (unsigned)rule->cpid, (uintmax_t)rule->states_tot); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } int pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { char anchor_call[MAXPATHLEN]; struct pfctl_eth_rules_info info; struct pfctl_eth_rule rule; int brace; int dotitle = opts & PF_OPT_SHOWALL; int len = strlen(path); int ret; char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "calloc"); snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfctl_eth_rulesets_info ri; u_int32_t mnr, nr; if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, npath)) != 0) { if (ret == EINVAL) { fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); } else { warnc(ret, "DIOCGETETHRULESETS"); return (-1); } } mnr = ri.nr; pfctl_print_eth_rule_counters(&rule, opts); for (nr = 0; nr < mnr; ++nr) { struct pfctl_eth_ruleset_info rs; if ((ret = pfctl_get_eth_ruleset(dev, npath, nr, &rs)) != 0) errc(1, ret, "DIOCGETETHRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", rs.name); pfctl_show_eth_rules(dev, npath, opts, format, rs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if ((ret = pfctl_get_eth_rules_info(dev, &info, path)) != 0) { warnc(ret, "DIOCGETETHRULES"); return (-1); } for (int nr = 0; nr < info.nr; nr++) { brace = 0; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if ((ret = pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule, opts & PF_OPT_CLRRULECTRS, anchor_call)) != 0) { warnc(ret, "DIOCGETETHRULE"); return (-1); } if (anchor_call[0] && ((((p = strrchr(anchor_call, '_')) != NULL) && (p == anchor_call || *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { brace++; int aclen = strlen(anchor_call); if (anchor_call[aclen - 1] == '*') anchor_call[aclen - 2] = '\0'; } p = &anchor_call[0]; if (dotitle) { pfctl_print_title("ETH RULES:"); dotitle = 0; } print_eth_rule(&rule, anchor_call, opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); if (brace) printf(" {\n"); else printf("\n"); pfctl_print_eth_rule_counters(&rule, opts); if (brace) { pfctl_show_eth_rules(dev, path, opts, format, p, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } } path[len] = '\0'; return (0); } int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr, header = 0; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path), ret = 0; char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "calloc"); strlcpy(npath, anchorname, MAXPATHLEN); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfioc_ruleset prs; u_int32_t mnr, nr; memset(&prs, 0, sizeof(prs)); if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0) { if (ret == EINVAL) fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); else errc(1, ret, "DIOCGETRULESETS"); } for (nr = 0; nr < mnr; ++nr) { if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0) errc(1, ret, "DIOCGETRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", prs.name); pfctl_show_rules(dev, npath, opts, format, prs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if (opts & PF_OPT_SHOWALL) { ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path); if (ret != 0) { warnc(ret, "DIOCGETRULES"); goto error; } header++; } ret = pfctl_get_rules_info_h(pfh, &ri, PF_SCRUB, path); if (ret != 0) { warnc(ret, "DIOCGETRULES"); goto error; } if (opts & PF_OPT_SHOWALL) { if (format == PFCTL_SHOW_RULES && (ri.nr > 0 || header)) pfctl_print_title("FILTER RULES:"); else if (format == PFCTL_SHOW_LABELS && labels) pfctl_print_title("LABEL COUNTERS:"); } for (nr = 0; nr < ri.nr; ++nr) { if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_SCRUB, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) { warnc(ret, "DIOCGETRULENV"); goto error; } if (pfctl_get_pool(dev, &rule.rdr, nr, ri.ticket, PF_SCRUB, path, PF_RDR) != 0) goto error; if (pfctl_get_pool(dev, &rule.nat, nr, ri.ticket, PF_SCRUB, path, PF_NAT) != 0) goto error; if (pfctl_get_pool(dev, &rule.route, nr, ri.ticket, PF_SCRUB, path, PF_RT) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: break; case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; print_rule(&rule, anchor_call, rule_numbers, numeric); printf("\n"); pfctl_print_rule_counters(&rule, opts); break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rdr); pfctl_clear_pool(&rule.nat); pfctl_clear_pool(&rule.route); } ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path); if (ret != 0) { warnc(ret, "DIOCGETRULES"); goto error; } for (nr = 0; nr < ri.nr; ++nr) { if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_PASS, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) { warnc(ret, "DIOCGETRULE"); goto error; } if (pfctl_get_pool(dev, &rule.rdr, nr, ri.ticket, PF_PASS, path, PF_RDR) != 0) goto error; if (pfctl_get_pool(dev, &rule.nat, nr, ri.ticket, PF_PASS, path, PF_NAT) != 0) goto error; if (pfctl_get_pool(dev, &rule.route, nr, ri.ticket, PF_PASS, path, PF_RT) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: { bool show = false; int i = 0; while (rule.label[i][0]) { printf("%s ", rule.label[i++]); show = true; } if (show) { printf("%llu %llu %llu %llu" " %llu %llu %llu %ju\n", (unsigned long long)rule.evaluations, (unsigned long long)(rule.packets[0] + rule.packets[1]), (unsigned long long)(rule.bytes[0] + rule.bytes[1]), (unsigned long long)rule.packets[0], (unsigned long long)rule.bytes[0], (unsigned long long)rule.packets[1], (unsigned long long)rule.bytes[1], (uintmax_t)rule.states_tot); } if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { pfctl_show_rules(dev, npath, opts, format, anchor_call, depth, rule.anchor_wildcard); } break; } case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); print_rule(&rule, anchor_call, rule_numbers, numeric); /* * If this is a 'unnamed' brace notation * anchor, OR the user has explicitly requested * recursion, print it recursively. */ if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { printf(" {\n"); pfctl_print_rule_counters(&rule, opts); pfctl_show_rules(dev, npath, opts, format, anchor_call, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } else { printf("\n"); pfctl_print_rule_counters(&rule, opts); } break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rdr); pfctl_clear_pool(&rule.nat); } error: path[len] = '\0'; return (ret); } int pfctl_show_nat(int dev, const char *path, int opts, char *anchorname, int depth, int wildcard) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i, dotitle = opts & PF_OPT_SHOWALL; int ret; int len = strlen(path); char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "calloc"); if (anchorname[0] == '/') { snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { snprintf(npath, MAXPATHLEN, "%s", path); if (npath[0]) snprintf(&npath[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&npath[len], MAXPATHLEN - len, "%s", anchorname); } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfioc_ruleset prs; u_int32_t mnr, nr; memset(&prs, 0, sizeof(prs)); if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0) { if (ret == EINVAL) fprintf(stderr, "NAT anchor '%s' " "not found.\n", anchorname); else errc(1, ret, "DIOCGETRULESETS"); } for (nr = 0; nr < mnr; ++nr) { if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0) errc(1, ret, "DIOCGETRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("nat-anchor \"%s\" all {\n", prs.name); pfctl_show_nat(dev, npath, opts, prs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } npath[len] = '\0'; return (0); } for (i = 0; i < 3; i++) { ret = pfctl_get_rules_info_h(pfh, &ri, nattype[i], npath); if (ret != 0) { warnc(ret, "DIOCGETRULES"); return (-1); } for (nr = 0; nr < ri.nr; ++nr) { INDENT(depth, !(opts & PF_OPT_VERBOSE)); if ((ret = pfctl_get_rule_h(pfh, nr, ri.ticket, npath, nattype[i], &rule, anchor_call)) != 0) { warnc(ret, "DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &rule.rdr, nr, ri.ticket, nattype[i], npath, PF_RDR) != 0) return (-1); if (pfctl_get_pool(dev, &rule.nat, nr, ri.ticket, nattype[i], npath, PF_NAT) != 0) return (-1); if (pfctl_get_pool(dev, &rule.route, nr, ri.ticket, nattype[i], npath, PF_RT) != 0) return (-1); if (dotitle) { pfctl_print_title("TRANSLATION RULES:"); dotitle = 0; } print_rule(&rule, anchor_call, opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { printf(" {\n"); pfctl_print_rule_counters(&rule, opts); pfctl_show_nat(dev, npath, opts, anchor_call, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } else { printf("\n"); pfctl_print_rule_counters(&rule, opts); } } } return (0); } static int pfctl_print_src_node(struct pfctl_src_node *sn, void *arg) { int *opts = (int *)arg; if (*opts & PF_OPT_SHOWALL) { pfctl_print_title("SOURCE TRACKING NODES:"); *opts &= ~PF_OPT_SHOWALL; } print_src_node(sn, *opts); return (0); } int pfctl_show_src_nodes(int dev, int opts) { int error; error = pfctl_get_srcnodes(pfh, pfctl_print_src_node, &opts); return (error); } struct pfctl_show_state_arg { int opts; int dotitle; const char *iface; }; static int pfctl_show_state(struct pfctl_state *s, void *arg) { struct pfctl_show_state_arg *a = (struct pfctl_show_state_arg *)arg; if (a->dotitle) { pfctl_print_title("STATES:"); a->dotitle = 0; } print_state(s, a->opts); return (0); } int pfctl_show_states(int dev, const char *iface, int opts) { struct pfctl_show_state_arg arg; struct pfctl_state_filter filter = {}; if (iface != NULL) strncpy(filter.ifname, iface, IFNAMSIZ); arg.opts = opts; arg.dotitle = opts & PF_OPT_SHOWALL; arg.iface = iface; if (pfctl_get_filtered_states_iter(&filter, pfctl_show_state, &arg)) return (-1); return (0); } int pfctl_show_status(int dev, int opts) { struct pfctl_status *status; struct pfctl_syncookies cookies; int ret; if ((status = pfctl_get_status_h(pfh)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } if ((ret = pfctl_get_syncookies(dev, &cookies)) != 0) { pfctl_free_status(status); warnc(ret, "DIOCGETSYNCOOKIES"); return (-1); } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INFO:"); print_status(status, &cookies, opts); pfctl_free_status(status); return (0); } int pfctl_show_running(int dev) { struct pfctl_status *status; int running; if ((status = pfctl_get_status_h(pfh)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } running = status->running; print_running(status); pfctl_free_status(status); return (!running); } int pfctl_show_timeouts(int dev, int opts) { uint32_t seconds; int i; int ret; if (opts & PF_OPT_SHOWALL) pfctl_print_title("TIMEOUTS:"); for (i = 0; pf_timeouts[i].name; i++) { if ((ret = pfctl_get_timeout(pfh, pf_timeouts[i].timeout, &seconds)) != 0) errc(1, ret, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, seconds); if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START && pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END) printf(" states"); else printf("s"); printf("\n"); } return (0); } int pfctl_show_limits(int dev, int opts) { unsigned int limit; int i; int ret; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); for (i = 0; pf_limits[i].name; i++) { if ((ret = pfctl_get_limit(pfh, pf_limits[i].index, &limit)) != 0) errc(1, ret, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); if (limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %8u\n", limit); } return (0); } void pfctl_show_creators(int opts) { int ret; uint32_t creators[16]; size_t count = nitems(creators); ret = pfctl_get_creatorids(pfh, creators, &count); if (ret != 0) errx(ret, "Failed to retrieve creators"); printf("Creator IDs:\n"); for (size_t i = 0; i < count; i++) printf("%08x\n", creators[i]); } /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af, int which) { struct pf_pooladdr *pa; int ret; pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if ((ret = pfctl_add_addr(pf->h, &pf->paddr, which)) != 0) errc(1, ret, "DIOCADDADDR"); } } return (0); } void pfctl_init_rule(struct pfctl_rule *r) { memset(r, 0, sizeof(struct pfctl_rule)); TAILQ_INIT(&(r->rdr.list)); TAILQ_INIT(&(r->nat.list)); TAILQ_INIT(&(r->route.list)); } int pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, const char *anchor_call) { u_int8_t rs_num; struct pfctl_rule *rule; struct pfctl_ruleset *rs; char *p; rs_num = pf_get_ruleset_number(r->action); if (rs_num == PF_RULESET_MAX) errx(1, "Invalid rule type %d", r->action); rs = &pf->anchor->ruleset; if (anchor_call[0] && r->anchor == NULL) { /* * Don't make non-brace anchors part of the main anchor pool. */ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) err(1, "pfctl_append_rule: calloc"); pf_init_ruleset(&r->anchor->ruleset); r->anchor->ruleset.anchor = r->anchor; if (strlcpy(r->anchor->path, anchor_call, sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) errx(1, "pfctl_append_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_append_rule: bad anchor name %s", anchor_call); } else p = (char *)anchor_call; if (strlcpy(r->anchor->name, p, sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) errx(1, "pfctl_append_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INIT(&rule->rdr.list); pfctl_move_pool(&r->rdr, &rule->rdr); TAILQ_INIT(&rule->nat.list); pfctl_move_pool(&r->nat, &rule->nat); TAILQ_INIT(&rule->route.list); pfctl_move_pool(&r->route, &rule->route); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); return (0); } int pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r, const char *anchor_call) { struct pfctl_eth_rule *rule; struct pfctl_eth_ruleset *rs; char *p; rs = &pf->eanchor->ruleset; if (anchor_call[0] && r->anchor == NULL) { /* * Don't make non-brace anchors part of the main anchor pool. */ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) err(1, "pfctl_append_rule: calloc"); pf_init_eth_ruleset(&r->anchor->ruleset); r->anchor->ruleset.anchor = r->anchor; if (strlcpy(r->anchor->path, anchor_call, sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) errx(1, "pfctl_append_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_append_eth_rule: bad anchor name %s", anchor_call); } else p = (char *)anchor_call; if (strlcpy(r->anchor->name, p, sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) errx(1, "pfctl_append_eth_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INSERT_TAIL(&rs->rules, rule, entries); return (0); } int pfctl_eth_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_eth_anchor *a) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) return (1); } if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) { if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) return (1); } if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_RDR, path)) return (1); } if (a == pf->astack[0] && ((altqsupport && (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) { if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path)) return (2); } if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) || pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path)) return (3); } if (pf->loadopt & PFCTL_FLAG_TABLE) if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path)) return (4); if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int pfctl_load_eth_ruleset(struct pfctl *pf, char *path, struct pfctl_eth_ruleset *rs, int depth) { struct pfctl_eth_rule *r; int error, len = strlen(path); int brace = 0; pf->eanchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name); if (depth) { if (TAILQ_FIRST(&rs->rules) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_eth_ruleset_trans(pf, path, rs->anchor))) { printf("pfctl_load_eth_rulesets: " "pfctl_eth_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } while ((r = TAILQ_FIRST(&rs->rules)) != NULL) { TAILQ_REMOVE(&rs->rules, r, entries); error = pfctl_load_eth_rule(pf, path, r, depth); if (error) return (error); if (r->anchor) { if ((error = pfctl_load_eth_ruleset(pf, path, &r->anchor->ruleset, depth + 1))) return (error); } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); } if (brace && pf->opts & PF_OPT_VERBOSE) { INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (error); } int pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r, int depth) { char *name; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); int ret; if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_eth_rule: strlcpy"); if (r->anchor) { if (r->anchor->match) { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", r->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", r->anchor->name); name = r->anchor->name; } else name = r->anchor->path; } else name = ""; if ((pf->opts & PF_OPT_NOACTION) == 0) if ((ret = pfctl_add_eth_rule(pf->dev, r, anchor, name, pf->eth_ticket)) != 0) errc(1, ret, "DIOCADDETHRULENV"); if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_eth_rule(r, r->anchor ? r->anchor->name : "", pf->opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); } path[len] = '\0'; return (0); } int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) { struct pfctl_rule *r; int error, len = strlen(path); int brace = 0; pf->anchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name); if (depth) { if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_ruleset_trans(pf, path, rs->anchor, false))) { printf("pfctl_load_rulesets: " "pfctl_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } if (pf->optimize && rs_num == PF_RULESET_FILTER) pfctl_optimize_ruleset(pf, rs); while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries); for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) expand_label(r->label[i], PF_RULE_LABEL_SIZE, r); expand_label(r->tagname, PF_TAG_NAME_SIZE, r); expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r); if ((error = pfctl_load_rule(pf, path, r, depth))) goto error; if (r->anchor) { if ((error = pfctl_load_ruleset(pf, path, &r->anchor->ruleset, rs_num, depth + 1))) goto error; } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); } if (brace && pf->opts & PF_OPT_VERBOSE) { INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (error); } int pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth) { u_int8_t rs_num = pf_get_ruleset_number(r->action); char *name; u_int32_t ticket; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); int error; bool was_present; /* set up anchor before adding to path for anchor_call */ if ((pf->opts & PF_OPT_NOACTION) == 0) ticket = pfctl_get_ticket(pf->trans, rs_num, path); if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_rule: strlcpy"); if (r->anchor) { if (r->anchor->match) { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", r->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", r->anchor->name); name = r->anchor->name; } else name = r->anchor->path; } else name = ""; was_present = false; if ((pf->opts & PF_OPT_NOACTION) == 0) { if ((pf->opts & PF_OPT_NOACTION) == 0) { if ((error = pfctl_begin_addrs(pf->h, &pf->paddr.ticket)) != 0) errc(1, error, "DIOCBEGINADDRS"); } if (pfctl_add_pool(pf, &r->rdr, r->af, PF_RDR)) return (1); if (pfctl_add_pool(pf, &r->nat, r->naf ? r->naf : r->af, PF_NAT)) return (1); if (pfctl_add_pool(pf, &r->route, r->af, PF_RT)) return (1); error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket, pf->paddr.ticket); switch (error) { case 0: /* things worked, do nothing */ break; case EEXIST: /* an identical rule is already present */ was_present = true; break; default: errc(1, error, "DIOCADDRULE"); } } if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_rule(r, name, pf->opts & PF_OPT_VERBOSE2, pf->opts & PF_OPT_NUMERIC); if (was_present) printf(" -- rule was already present"); } path[len] = '\0'; pfctl_clear_pool(&r->rdr); pfctl_clear_pool(&r->nat); return (0); } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { if (altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0) { memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { if (errno == ENXIO) errx(1, "qtype not configured"); else if (errno == ENODEV) errx(1, "%s: driver does not support " "altq", a->ifname); else err(1, "DIOCADDALTQ"); } } pfaltq_store(&pf->paltq->altq); } return (0); } int pfctl_rules(int dev, char *filename, int opts, int optimize, char *anchorname, struct pfr_buffer *trans) { -#define ERR(x) do { warn(x); goto _error; } while(0) -#define ERRX(x) do { warnx(x); goto _error; } while(0) +#define ERR(...) do { warn(__VA_ARGS__); goto _error; } while(0) +#define ERRX(...) do { warnx(__VA_ARGS__); goto _error; } while(0) struct pfr_buffer *t, buf; struct pfioc_altq pa; struct pfctl pf; struct pfctl_ruleset *rs; struct pfctl_eth_ruleset *ethrs; struct pfr_table trs; char *path = NULL; int osize; RB_INIT(&pf_anchors); memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); pf_main_anchor.ruleset.anchor = &pf_main_anchor; memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor)); pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset); pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor; if (trans == NULL) { bzero(&buf, sizeof(buf)); buf.pfrb_type = PFRB_TRANS; t = &buf; osize = 0; } else { t = trans; osize = t->pfrb_size; } memset(&pa, 0, sizeof(pa)); pa.version = PFIOC_ALTQ_VERSION; memset(&pf, 0, sizeof(pf)); memset(&trs, 0, sizeof(trs)); if ((path = calloc(1, MAXPATHLEN)) == NULL) - ERRX("pfctl_rules: calloc"); + ERRX("%s: calloc", __func__); if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) - ERRX("pfctl_rules: strlcpy"); + ERRX("%s: strlcpy", __func__); pf.dev = dev; pf.h = pfh; pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) - ERRX("pfctl_rules: calloc"); + ERRX("%s: calloc", __func__); rs = &pf.anchor->ruleset; pf_init_ruleset(rs); rs->anchor = pf.anchor; if (strlcpy(pf.anchor->path, anchorname, sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) - errx(1, "pfctl_rules: strlcpy"); + errx(1, "%s: strlcpy", __func__); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) - errx(1, "pfctl_rules: strlcpy"); + errx(1, "%s: strlcpy", __func__); pf.astack[0] = pf.anchor; pf.asd = 0; if (anchorname[0]) pf.loadopt &= ~PFCTL_FLAG_ALTQ; pf.paltq = &pa; pf.trans = t; pfctl_init_options(&pf); /* Set up ethernet anchor */ if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL) - ERRX("pfctl_rules: calloc"); + ERRX("%s: calloc", __func__); if (strlcpy(pf.eanchor->path, anchorname, sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path)) - errx(1, "pfctl_rules: strlcpy"); + errx(1, "%s: strlcpy", __func__); if (strlcpy(pf.eanchor->name, anchorname, sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name)) - errx(1, "pfctl_rules: strlcpy"); + errx(1, "%s: strlcpy", __func__); ethrs = &pf.eanchor->ruleset; pf_init_eth_ruleset(ethrs); ethrs->anchor = pf.eanchor; pf.eastack[0] = pf.eanchor; if ((opts & PF_OPT_NOACTION) == 0) { /* * XXX For the time being we need to open transactions for * the main ruleset before parsing, because tables are still * loaded at parse time. */ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true)) - ERRX("pfctl_rules"); + ERRX("%s", __func__); if (pf.loadopt & PFCTL_FLAG_ETH) pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); if (pf.loadopt & PFCTL_FLAG_TABLE) pf.astack[0]->ruleset.tticket = pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname); } if (parse_config(filename, &pf) < 0) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Syntax error in config file: " "pf rules not loaded"); else goto _error; } if (loadopt & PFCTL_FLAG_OPTION) pfctl_adjust_skip_ifaces(&pf); if ((pf.loadopt & PFCTL_FLAG_FILTER && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || (pf.loadopt & PFCTL_FLAG_ETH && (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) || (pf.loadopt & PFCTL_FLAG_NAT && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) || (pf.loadopt & PFCTL_FLAG_FILTER && pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Unable to load rules into kernel"); else goto _error; } if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) if (check_commit_altq(dev, opts) != 0) ERRX("errors in altq config"); if (trans == NULL) { /* process "load anchor" directives */ if (pfctl_load_anchors(dev, &pf, t) == -1) ERRX("load anchors"); if ((opts & PF_OPT_NOACTION) == 0) { if (!anchorname[0] && pfctl_load_options(&pf)) goto _error; if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) ERR("DIOCXCOMMIT"); } } free(path); return (0); _error: if (trans == NULL) { /* main ruleset */ if ((opts & PF_OPT_NOACTION) == 0) if (pfctl_trans(dev, t, DIOCXROLLBACK, osize)) err(1, "DIOCXROLLBACK"); exit(1); } else { /* sub ruleset */ free(path); return (-1); } #undef ERR #undef ERRX } FILE * pfctl_fopen(const char *name, const char *mode) { struct stat st; FILE *fp; fp = fopen(name, mode); if (fp == NULL) return (NULL); if (fstat(fileno(fp), &st)) { fclose(fp); return (NULL); } if (S_ISDIR(st.st_mode)) { fclose(fp); errno = EISDIR; return (NULL); } return (fp); } void pfctl_init_options(struct pfctl *pf) { pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_SCTP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_SCTP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_SCTP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_SCTP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_SCTP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL; pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT; pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT; pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT; pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT; pf->debug = PF_DEBUG_URGENT; pf->reassemble = 0; pf->syncookies = false; pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT; pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT; } int pfctl_load_options(struct pfctl *pf) { int i, error = 0; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); /* load limits */ for (i = 0; i < PF_LIMIT_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i]) continue; if (pfctl_load_limit(pf, i, pf->limit[i])) error = 1; } /* * If we've set the states limit, but haven't explicitly set adaptive * timeouts, do it now with a start of 60% and end of 120%. */ if (pf->limit_set[PF_LIMIT_STATES] && !pf->timeout_set[PFTM_ADAPTIVE_START] && !pf->timeout_set[PFTM_ADAPTIVE_END]) { pf->timeout[PFTM_ADAPTIVE_START] = (pf->limit[PF_LIMIT_STATES] / 10) * 6; pf->timeout_set[PFTM_ADAPTIVE_START] = 1; pf->timeout[PFTM_ADAPTIVE_END] = (pf->limit[PF_LIMIT_STATES] / 10) * 12; pf->timeout_set[PFTM_ADAPTIVE_END] = 1; } /* load timeouts */ for (i = 0; i < PFTM_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i]) continue; if (pfctl_load_timeout(pf, i, pf->timeout[i])) error = 1; } /* load debug */ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set) if (pfctl_load_debug(pf, pf->debug)) error = 1; /* load logif */ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set) if (pfctl_load_logif(pf, pf->ifname)) error = 1; /* load hostid */ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set) if (pfctl_load_hostid(pf, pf->hostid)) error = 1; /* load reassembly settings */ if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set) if (pfctl_load_reassembly(pf, pf->reassemble)) error = 1; /* load keepcounters */ if (pfctl_set_keepcounters(pf->dev, pf->keep_counters)) error = 1; /* load syncookies settings */ if (pfctl_load_syncookies(pf, pf->syncookies)) error = 1; return (error); } int pfctl_apply_limit(struct pfctl *pf, const char *opt, unsigned int limit) { int i; for (i = 0; pf_limits[i].name; i++) { if (strcasecmp(opt, pf_limits[i].name) == 0) { pf->limit[pf_limits[i].index] = limit; pf->limit_set[pf_limits[i].index] = 1; break; } } if (pf_limits[i].name == NULL) { warnx("Bad pool name."); return (1); } if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); return (0); } int pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) { if (pfctl_set_limit(pf->h, index, limit)) { if (errno == EBUSY) warnx("Current pool size exceeds requested %s limit %u", pf_limits[index].name, limit); else warnx("Cannot set %s limit to %u", pf_limits[index].name, limit); return (1); } return (0); } int pfctl_apply_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_timeouts[i].name; i++) { if (strcasecmp(opt, pf_timeouts[i].name) == 0) { pf->timeout[pf_timeouts[i].timeout] = seconds; pf->timeout_set[pf_timeouts[i].timeout] = 1; break; } } if (pf_timeouts[i].name == NULL) { warnx("Bad timeout name."); return (1); } if (pf->opts & PF_OPT_VERBOSE && ! quiet) printf("set timeout %s %d\n", opt, seconds); return (0); } int pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds) { if (pfctl_set_timeout(pf->h, timeout, seconds)) { warnx("DIOCSETTIMEOUT"); return (1); } return (0); } int pfctl_set_reassembly(struct pfctl *pf, int on, int nodf) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); pf->reass_set = 1; if (on) { pf->reassemble = PF_REASS_ENABLED; if (nodf) pf->reassemble |= PF_REASS_NODF; } else { pf->reassemble = 0; } if (pf->opts & PF_OPT_VERBOSE) printf("set reassemble %s %s\n", on ? "yes" : "no", nodf ? "no-df" : ""); return (0); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { const struct pf_hint *hint; int i, r; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_hints[i].name; i++) if (strcasecmp(opt, pf_hints[i].name) == 0) break; hint = pf_hints[i].hint; if (hint == NULL) { warnx("invalid state timeouts optimization"); return (1); } for (i = 0; hint[i].name; i++) if ((r = pfctl_apply_timeout(pf, hint[i].name, hint[i].timeout, 1))) return (r); if (pf->opts & PF_OPT_VERBOSE) printf("set optimization %s\n", opt); return (0); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(ifname, "none")) { free(pf->ifname); pf->ifname = NULL; } else { pf->ifname = strdup(ifname); if (!pf->ifname) errx(1, "pfctl_set_logif: strdup"); } pf->ifname_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set loginterface %s\n", ifname); return (0); } int pfctl_load_logif(struct pfctl *pf, char *ifname) { if (ifname != NULL && strlen(ifname) >= IFNAMSIZ) { warnx("pfctl_load_logif: strlcpy"); return (1); } return (pfctl_set_statusif(pfh, ifname ? ifname : "")); } void pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return; HTONL(hostid); pf->hostid = hostid; pf->hostid_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set hostid 0x%08x\n", ntohl(hostid)); } int pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) { if (ioctl(dev, DIOCSETHOSTID, &hostid)) { warnx("DIOCSETHOSTID"); return (1); } return (0); } int pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly) { if (ioctl(dev, DIOCSETREASS, &reassembly)) { warnx("DIOCSETREASS"); return (1); } return (0); } int pfctl_load_syncookies(struct pfctl *pf, u_int8_t val) { struct pfctl_syncookies cookies; bzero(&cookies, sizeof(cookies)); cookies.mode = val; cookies.lowwater = pf->syncookieswat[0]; cookies.highwater = pf->syncookieswat[1]; if (pfctl_set_syncookies(dev, &cookies)) { warnx("DIOCSETSYNCOOKIES"); return (1); } return (0); } int pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w) { if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) { warnx("syncookies start/end only apply to adaptive"); return (1); } if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) { if (!w->hi) w->hi = PF_SYNCOOKIES_HIWATPCT; if (!w->lo) w->lo = w->hi / 2; if (w->lo >= w->hi) { warnx("start must be higher than end"); return (1); } pf->syncookieswat[0] = w->lo; pf->syncookieswat[1] = w->hi; pf->syncookieswat_set = 1; } if (pf->opts & PF_OPT_VERBOSE) { if (val == PF_SYNCOOKIES_NEVER) printf("set syncookies never\n"); else if (val == PF_SYNCOOKIES_ALWAYS) printf("set syncookies always\n"); else if (val == PF_SYNCOOKIES_ADAPTIVE) { if (pf->syncookieswat_set) printf("set syncookies adaptive (start %u%%, " "end %u%%)\n", pf->syncookieswat[1], pf->syncookieswat[0]); else printf("set syncookies adaptive\n"); } else { /* cannot happen */ warnx("king bula ate all syncookies"); return (1); } } pf->syncookies = val; return (0); } int pfctl_do_set_debug(struct pfctl *pf, char *d) { u_int32_t level; int ret; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(d, "none")) pf->debug = PF_DEBUG_NONE; else if (!strcmp(d, "urgent")) pf->debug = PF_DEBUG_URGENT; else if (!strcmp(d, "misc")) pf->debug = PF_DEBUG_MISC; else if (!strcmp(d, "loud")) pf->debug = PF_DEBUG_NOISY; else { warnx("unknown debug level \"%s\"", d); return (-1); } pf->debug_set = 1; level = pf->debug; if ((pf->opts & PF_OPT_NOACTION) == 0) if ((ret = pfctl_set_debug(pfh, level)) != 0) errc(1, ret, "DIOCSETDEBUG"); if (pf->opts & PF_OPT_VERBOSE) printf("set debug %s\n", d); return (0); } int pfctl_load_debug(struct pfctl *pf, unsigned int level) { if (pfctl_set_debug(pf->h, level)) { warnx("DIOCSETDEBUG"); return (1); } return (0); } int pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) { struct pfioc_iface pi; struct node_host *h = NULL, *n = NULL; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); bzero(&pi, sizeof(pi)); pi.pfiio_flags = flags; /* Make sure our cache matches the kernel. If we set or clear the flag * for a group this applies to all members. */ h = ifa_grouplookup(ifname, 0); for (n = h; n != NULL; n = n->next) pfctl_set_interface_flags(pf, n->ifname, flags, how); if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) errx(1, "pfctl_set_interface_flags: strlcpy"); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (how == 0) { if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); } else { if (ioctl(pf->dev, DIOCSETIFFLAG, &pi)) err(1, "DIOCSETIFFLAG"); pfctl_check_skip_ifaces(ifname); } } return (0); } void pfctl_debug(int dev, u_int32_t level, int opts) { int ret; if ((ret = pfctl_set_debug(pfh, level)) != 0) errc(1, ret, "DIOCSETDEBUG"); if ((opts & PF_OPT_QUIET) == 0) { fprintf(stderr, "debug level set to '"); switch (level) { case PF_DEBUG_NONE: fprintf(stderr, "none"); break; case PF_DEBUG_URGENT: fprintf(stderr, "urgent"); break; case PF_DEBUG_MISC: fprintf(stderr, "misc"); break; case PF_DEBUG_NOISY: fprintf(stderr, "loud"); break; default: fprintf(stderr, ""); break; } fprintf(stderr, "'\n"); } } int pfctl_test_altqsupport(int dev, int opts) { struct pfioc_altq pa; pa.version = PFIOC_ALTQ_VERSION; if (ioctl(dev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { if (opts & PF_OPT_VERBOSE) fprintf(stderr, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else err(1, "DIOCGETALTQS"); } return (1); } int pfctl_show_anchors(int dev, int opts, char *anchorname) { struct pfioc_ruleset pr; u_int32_t mnr, nr; int ret; memset(&pr, 0, sizeof(pr)); if ((ret = pfctl_get_rulesets(pfh, anchorname, &mnr)) != 0) { if (ret == EINVAL) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else errc(1, ret, "DIOCGETRULESETS"); return (-1); } for (nr = 0; nr < mnr; ++nr) { char sub[MAXPATHLEN]; if ((ret = pfctl_get_ruleset(pfh, anchorname, nr, &pr)) != 0) errc(1, ret, "DIOCGETRULESET"); if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (pr.path[0]) { strlcat(sub, pr.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, pr.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub)) return (-1); } return (0); } int pfctl_show_eth_anchors(int dev, int opts, char *anchorname) { struct pfctl_eth_rulesets_info ri; struct pfctl_eth_ruleset_info rs; int ret; if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) { if (ret == ENOENT) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else errc(1, ret, "DIOCGETETHRULESETS"); return (-1); } for (int nr = 0; nr < ri.nr; nr++) { char sub[MAXPATHLEN]; if ((ret = pfctl_get_eth_ruleset(dev, anchorname, nr, &rs)) != 0) errc(1, ret, "DIOCGETETHRULESET"); if (!strcmp(rs.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (rs.path[0]) { strlcat(sub, rs.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, rs.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub)) return (-1); } return (0); } const char * pfctl_lookup_option(char *cmd, const char * const *list) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int mode = O_RDONLY; int opts = 0; int optimize = PF_OPTIMIZE_BASIC; char anchorname[MAXPATHLEN]; char *path; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:St:T:vx:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; break; case 'd': opts |= PF_OPT_DISABLE; mode = O_RDWR; break; case 'D': if (pfctl_cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'e': opts |= PF_OPT_ENABLE; mode = O_RDWR; break; case 'q': opts |= PF_OPT_QUIET; break; case 'F': clearopt = pfctl_lookup_option(optarg, clearopt_list); if (clearopt == NULL) { warnx("Unknown flush modifier '%s'", optarg); usage(); } mode = O_RDWR; break; case 'i': ifaceopt = optarg; break; case 'k': if (state_killers >= 2) { warnx("can only specify -k twice"); usage(); /* NOTREACHED */ } state_kill[state_killers++] = optarg; mode = O_RDWR; break; case 'K': if (src_node_killers >= 2) { warnx("can only specify -K twice"); usage(); /* NOTREACHED */ } src_node_kill[src_node_killers++] = optarg; mode = O_RDWR; break; case 'm': opts |= PF_OPT_MERGE; break; case 'M': opts |= PF_OPT_KILLMATCH; break; case 'n': opts |= PF_OPT_NOACTION; break; case 'N': loadopt |= PFCTL_FLAG_NAT; break; case 'r': opts |= PF_OPT_USEDNS; break; case 'f': rulesopt = optarg; mode = O_RDWR; break; case 'g': opts |= PF_OPT_DEBUG; break; case 'A': loadopt |= PFCTL_FLAG_ALTQ; break; case 'R': loadopt |= PFCTL_FLAG_FILTER; break; case 'o': optiopt = pfctl_lookup_option(optarg, optiopt_list); if (optiopt == NULL) { warnx("Unknown optimization '%s'", optarg); usage(); } opts |= PF_OPT_OPTIMIZE; break; case 'O': loadopt |= PFCTL_FLAG_OPTION; break; case 'p': pf_device = optarg; break; case 'P': opts |= PF_OPT_NUMERIC; break; case 's': showopt = pfctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); } break; case 'S': opts |= PF_OPT_NODNS; break; case 't': tableopt = optarg; break; case 'T': tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); if (tblcmdopt == NULL) { warnx("Unknown table command '%s'", optarg); usage(); } break; case 'v': if (opts & PF_OPT_VERBOSE) opts |= PF_OPT_VERBOSE2; opts |= PF_OPT_VERBOSE; break; case 'x': debugopt = pfctl_lookup_option(optarg, debugopt_list); if (debugopt == NULL) { warnx("Unknown debug level '%s'", optarg); usage(); } mode = O_RDWR; break; case 'z': opts |= PF_OPT_CLRRULECTRS; mode = O_RDWR; break; case 'h': /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } } if (tblcmdopt != NULL) { argc -= optind; argv += optind; ch = *tblcmdopt; if (ch == 'l') { loadopt |= PFCTL_FLAG_TABLE; tblcmdopt = NULL; } else mode = strchr("st", ch) ? O_RDONLY : O_RDWR; } else if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (loadopt == 0) loadopt = ~0; if ((path = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl: calloc"); memset(anchorname, 0, sizeof(anchorname)); if (anchoropt != NULL) { int len = strlen(anchoropt); if (len >= 1 && anchoropt[len - 1] == '*') { if (len >= 2 && anchoropt[len - 2] == '/') anchoropt[len - 2] = '\0'; else anchoropt[len - 1] = '\0'; opts |= PF_OPT_RECURSE; } if (strlcpy(anchorname, anchoropt, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH; } if ((opts & PF_OPT_NOACTION) == 0) { dev = open(pf_device, mode); if (dev == -1) err(1, "%s", pf_device); altqsupport = pfctl_test_altqsupport(dev, opts); } else { dev = open(pf_device, O_RDONLY); if (dev >= 0) opts |= PF_OPT_DUMMYACTION; /* turn off options */ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); clearopt = showopt = debugopt = NULL; #if !defined(ENABLE_ALTQ) altqsupport = 0; #else altqsupport = 1; #endif } pfh = pfctl_open(pf_device); if (pfh == NULL) err(1, "Failed to open netlink"); if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); if (opts & PF_OPT_VERBOSE2) printf("Ethernet:\n"); pfctl_show_eth_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, anchorname, 0, 0); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, anchorname, 0, 0); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, path, opts, anchorname, 0, 0); break; case 'q': pfctl_show_altq(dev, ifaceopt, opts, opts & PF_OPT_VERBOSE2); break; case 's': pfctl_show_states(dev, ifaceopt, opts); break; case 'S': pfctl_show_src_nodes(dev, opts); break; case 'i': pfctl_show_status(dev, opts); break; case 'R': error = pfctl_show_running(dev); break; case 't': pfctl_show_timeouts(dev, opts); break; case 'm': pfctl_show_limits(dev, opts); break; case 'e': pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_nat(dev, path, opts, anchorname, 0, 0); pfctl_show_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_altq(dev, ifaceopt, opts, 0); pfctl_show_states(dev, ifaceopt, opts); pfctl_show_src_nodes(dev, opts); pfctl_show_status(dev, opts); pfctl_show_rules(dev, path, opts, 1, anchorname, 0, 0); pfctl_show_timeouts(dev, opts); pfctl_show_limits(dev, opts); pfctl_show_tables(anchorname, opts); pfctl_show_fingerprints(opts); break; case 'T': pfctl_show_tables(anchorname, opts); break; case 'o': pfctl_load_fingerprints(dev, opts); pfctl_show_fingerprints(opts); break; case 'I': pfctl_show_ifaces(ifaceopt, opts); break; case 'c': pfctl_show_creators(opts); break; } } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) { pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); } if (clearopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); switch (*clearopt) { case 'e': pfctl_flush_eth_rules(dev, opts, anchorname); break; case 'r': pfctl_flush_rules(dev, opts, anchorname); break; case 'n': pfctl_flush_nat(dev, opts, anchorname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_iface_states(dev, ifaceopt, opts); break; case 'S': pfctl_clear_src_nodes(dev, opts); break; case 'i': pfctl_clear_stats(pfh, opts); break; case 'a': pfctl_flush_eth_rules(dev, opts, anchorname); pfctl_flush_rules(dev, opts, anchorname); pfctl_flush_nat(dev, opts, anchorname); pfctl_do_clear_tables(anchorname, opts); if (!*anchorname) { pfctl_clear_altq(dev, opts); pfctl_clear_iface_states(dev, ifaceopt, opts); pfctl_clear_src_nodes(dev, opts); pfctl_clear_stats(pfh, opts); pfctl_clear_fingerprints(dev, opts); pfctl_clear_interface_flags(dev, opts); } break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_do_clear_tables(anchorname, opts); break; } } if (state_killers) { if (!strcmp(state_kill[0], "label")) pfctl_label_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "id")) pfctl_id_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "gateway")) pfctl_gateway_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "key")) pfctl_key_kill_states(dev, ifaceopt, opts); else pfctl_net_kill_states(dev, ifaceopt, opts); } if (src_node_killers) pfctl_kill_src_nodes(dev, ifaceopt, opts); if (tblcmdopt != NULL) { error = pfctl_command_tables(argc, argv, tableopt, tblcmdopt, rulesopt, anchorname, opts); rulesopt = NULL; } if (optiopt != NULL) { switch (*optiopt) { case 'n': optimize = 0; break; case 'b': optimize |= PF_OPTIMIZE_BASIC; break; case 'o': case 'p': optimize |= PF_OPTIMIZE_PROFILE; break; } } if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) && !anchorname[0] && !(opts & PF_OPT_NOACTION)) pfctl_get_skip_ifaces(); if (rulesopt != NULL && !(opts & PF_OPT_MERGE) && !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION)) if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) error = 1; if (rulesopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); if (pfctl_rules(dev, rulesopt, opts, optimize, anchorname, NULL)) error = 1; else if (!(opts & PF_OPT_NOACTION) && (loadopt & PFCTL_FLAG_TABLE)) warn_namespace_collision(NULL); } if (opts & PF_OPT_ENABLE) if (pfctl_enable(dev, opts)) error = 1; if (debugopt != NULL) { switch (*debugopt) { case 'n': pfctl_debug(dev, PF_DEBUG_NONE, opts); break; case 'u': pfctl_debug(dev, PF_DEBUG_URGENT, opts); break; case 'm': pfctl_debug(dev, PF_DEBUG_MISC, opts); break; case 'l': pfctl_debug(dev, PF_DEBUG_NOISY, opts); break; } } exit(error); } diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index 871a18ca6c28..6b47d75f77f3 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1,2113 +1,2113 @@ /* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void print_op (u_int8_t, const char *, const char *); void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int); void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned); void print_flags (uint16_t); void print_fromto(struct pf_rule_addr *, pf_osfp_t, struct pf_rule_addr *, sa_family_t, u_int8_t, int, int); int ifa_skip_if(const char *filter, struct node_host *p); struct node_host *host_if(const char *, int); struct node_host *host_v4(const char *, int); struct node_host *host_v6(const char *, int); struct node_host *host_dns(const char *, int, int); const char * const tcpflags = "FSRPAUEWe"; static const struct icmptypeent icmp_type[] = { { "echoreq", ICMP_ECHO }, { "echorep", ICMP_ECHOREPLY }, { "unreach", ICMP_UNREACH }, { "squench", ICMP_SOURCEQUENCH }, { "redir", ICMP_REDIRECT }, { "althost", ICMP_ALTHOSTADDR }, { "routeradv", ICMP_ROUTERADVERT }, { "routersol", ICMP_ROUTERSOLICIT }, { "timex", ICMP_TIMXCEED }, { "paramprob", ICMP_PARAMPROB }, { "timereq", ICMP_TSTAMP }, { "timerep", ICMP_TSTAMPREPLY }, { "inforeq", ICMP_IREQ }, { "inforep", ICMP_IREQREPLY }, { "maskreq", ICMP_MASKREQ }, { "maskrep", ICMP_MASKREPLY }, { "trace", ICMP_TRACEROUTE }, { "dataconv", ICMP_DATACONVERR }, { "mobredir", ICMP_MOBILE_REDIRECT }, { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, { "ipv6-here", ICMP_IPV6_IAMHERE }, { "mobregreq", ICMP_MOBILE_REGREQUEST }, { "mobregrep", ICMP_MOBILE_REGREPLY }, { "skip", ICMP_SKIP }, { "photuris", ICMP_PHOTURIS } }; static const struct icmptypeent icmp6_type[] = { { "unreach", ICMP6_DST_UNREACH }, { "toobig", ICMP6_PACKET_TOO_BIG }, { "timex", ICMP6_TIME_EXCEEDED }, { "paramprob", ICMP6_PARAM_PROB }, { "echoreq", ICMP6_ECHO_REQUEST }, { "echorep", ICMP6_ECHO_REPLY }, { "groupqry", ICMP6_MEMBERSHIP_QUERY }, { "listqry", MLD_LISTENER_QUERY }, { "grouprep", ICMP6_MEMBERSHIP_REPORT }, { "listenrep", MLD_LISTENER_REPORT }, { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, { "listendone", MLD_LISTENER_DONE }, { "routersol", ND_ROUTER_SOLICIT }, { "routeradv", ND_ROUTER_ADVERT }, { "neighbrsol", ND_NEIGHBOR_SOLICIT }, { "neighbradv", ND_NEIGHBOR_ADVERT }, { "redir", ND_REDIRECT }, { "routrrenum", ICMP6_ROUTER_RENUMBERING }, { "wrureq", ICMP6_WRUREQUEST }, { "wrurep", ICMP6_WRUREPLY }, { "fqdnreq", ICMP6_FQDN_QUERY }, { "fqdnrep", ICMP6_FQDN_REPLY }, { "niqry", ICMP6_NI_QUERY }, { "nirep", ICMP6_NI_REPLY }, { "mtraceresp", MLD_MTRACE_RESP }, { "mtrace", MLD_MTRACE }, { "listenrepv2", MLDV2_LISTENER_REPORT }, }; static const struct icmpcodeent icmp_code[] = { { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } }; static const struct icmpcodeent icmp6_code[] = { { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } }; const struct pf_timeout pf_timeouts[] = { { "tcp.first", PFTM_TCP_FIRST_PACKET }, { "tcp.opening", PFTM_TCP_OPENING }, { "tcp.established", PFTM_TCP_ESTABLISHED }, { "tcp.closing", PFTM_TCP_CLOSING }, { "tcp.finwait", PFTM_TCP_FIN_WAIT }, { "tcp.closed", PFTM_TCP_CLOSED }, { "tcp.tsdiff", PFTM_TS_DIFF }, { "sctp.first", PFTM_SCTP_FIRST_PACKET }, { "sctp.opening", PFTM_SCTP_OPENING }, { "sctp.established", PFTM_SCTP_ESTABLISHED }, { "sctp.closing", PFTM_SCTP_CLOSING }, { "sctp.closed", PFTM_SCTP_CLOSED }, { "udp.first", PFTM_UDP_FIRST_PACKET }, { "udp.single", PFTM_UDP_SINGLE }, { "udp.multiple", PFTM_UDP_MULTIPLE }, { "icmp.first", PFTM_ICMP_FIRST_PACKET }, { "icmp.error", PFTM_ICMP_ERROR_REPLY }, { "other.first", PFTM_OTHER_FIRST_PACKET }, { "other.single", PFTM_OTHER_SINGLE }, { "other.multiple", PFTM_OTHER_MULTIPLE }, { "frag", PFTM_FRAG }, { "interval", PFTM_INTERVAL }, { "adaptive.start", PFTM_ADAPTIVE_START }, { "adaptive.end", PFTM_ADAPTIVE_END }, { "src.track", PFTM_SRC_NODE }, { NULL, 0 } }; static struct hsearch_data isgroup_map; static __attribute__((constructor)) void pfctl_parser_init(void) { /* * As hdestroy() will never be called on these tables, it will be * safe to use references into the stored data as keys. */ if (hcreate_r(0, &isgroup_map) == 0) err(1, "Failed to create interface group query response map"); } const struct icmptypeent * geticmptypebynumber(u_int8_t type, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_type); i++) { if (type == icmp_type[i].type) return (&icmp_type[i]); } } else { for (i=0; i < nitems(icmp6_type); i++) { if (type == icmp6_type[i].type) return (&icmp6_type[i]); } } return (NULL); } const struct icmptypeent * geticmptypebyname(char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_type); i++) { if (!strcmp(w, icmp_type[i].name)) return (&icmp_type[i]); } } else { for (i=0; i < nitems(icmp6_type); i++) { if (!strcmp(w, icmp6_type[i].name)) return (&icmp6_type[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_code); i++) { if (type == icmp_code[i].type && code == icmp_code[i].code) return (&icmp_code[i]); } } else { for (i=0; i < nitems(icmp6_code); i++) { if (type == icmp6_code[i].type && code == icmp6_code[i].code) return (&icmp6_code[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebyname(u_long type, char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_code); i++) { if (type == icmp_code[i].type && !strcmp(w, icmp_code[i].name)) return (&icmp_code[i]); } } else { for (i=0; i < nitems(icmp6_code); i++) { if (type == icmp6_code[i].type && !strcmp(w, icmp6_code[i].name)) return (&icmp6_code[i]); } } return (NULL); } void print_op(u_int8_t op, const char *a1, const char *a2) { if (op == PF_OP_IRG) printf(" %s >< %s", a1, a2); else if (op == PF_OP_XRG) printf(" %s <> %s", a1, a2); else if (op == PF_OP_EQ) printf(" = %s", a1); else if (op == PF_OP_NE) printf(" != %s", a1); else if (op == PF_OP_LT) printf(" < %s", a1); else if (op == PF_OP_LE) printf(" <= %s", a1); else if (op == PF_OP_GT) printf(" > %s", a1); else if (op == PF_OP_GE) printf(" >= %s", a1); else if (op == PF_OP_RRG) printf(" %s:%s", a1, a2); } void print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int numeric) { char a1[6], a2[6]; struct servent *s; if (!numeric) s = getservbyport(p1, proto); else s = NULL; p1 = ntohs(p1); p2 = ntohs(p2); snprintf(a1, sizeof(a1), "%u", p1); snprintf(a2, sizeof(a2), "%u", p2); printf(" port"); if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, s->s_name, a2); else print_op(op, a1, a2); } void print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax) { char a1[11], a2[11]; snprintf(a1, sizeof(a1), "%u", u1); snprintf(a2, sizeof(a2), "%u", u2); printf(" %s", t); if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, "unknown", a2); else print_op(op, a1, a2); } void print_flags(uint16_t f) { int i; for (i = 0; tcpflags[i]; ++i) if (f & (1 << i)) printf("%c", tcpflags[i]); } void print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, sa_family_t af, u_int8_t proto, int verbose, int numeric) { char buf[PF_OSFP_LEN*3]; if (src->addr.type == PF_ADDR_ADDRMASK && dst->addr.type == PF_ADDR_ADDRMASK && PF_AZERO(&src->addr.v.a.addr, AF_INET6) && PF_AZERO(&src->addr.v.a.mask, AF_INET6) && PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && !src->neg && !dst->neg && !src->port_op && !dst->port_op && osfp == PF_OSFP_ANY) printf(" all"); else { printf(" from "); if (src->neg) printf("! "); print_addr(&src->addr, af, verbose); if (src->port_op) print_port(src->port_op, src->port[0], src->port[1], proto == IPPROTO_TCP ? "tcp" : "udp", numeric); if (osfp != PF_OSFP_ANY) printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, sizeof(buf))); printf(" to "); if (dst->neg) printf("! "); print_addr(&dst->addr, af, verbose); if (dst->port_op) print_port(dst->port_op, dst->port[0], dst->port[1], proto == IPPROTO_TCP ? "tcp" : "udp", numeric); } } void print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, sa_family_t af, int id) { struct pf_pooladdr *pooladdr; if ((TAILQ_FIRST(&pool->list) != NULL) && TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf("{ "); TAILQ_FOREACH(pooladdr, &pool->list, entries){ switch (id) { case PF_NAT: case PF_RDR: case PF_BINAT: print_addr(&pooladdr->addr, af, 0); break; case PF_PASS: case PF_MATCH: if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) printf("%s", pooladdr->ifname); else { printf("(%s ", pooladdr->ifname); print_addr(&pooladdr->addr, af, 0); printf(")"); } break; default: break; } if (TAILQ_NEXT(pooladdr, entries) != NULL) printf(", "); else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf(" }"); } switch (id) { case PF_NAT: if ((p1 != PF_NAT_PROXY_PORT_LOW || p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { if (p1 == p2) printf(" port %u", p1); else printf(" port %u:%u", p1, p2); } break; case PF_RDR: if (p1) { printf(" port %u", p1); if (p2 && (p2 != p1)) printf(":%u", p2); } break; default: break; } switch (pool->opts & PF_POOL_TYPEMASK) { case PF_POOL_NONE: break; case PF_POOL_BITMASK: printf(" bitmask"); break; case PF_POOL_RANDOM: printf(" random"); break; case PF_POOL_SRCHASH: printf(" source-hash 0x%08x%08x%08x%08x", pool->key.key32[0], pool->key.key32[1], pool->key.key32[2], pool->key.key32[3]); break; case PF_POOL_ROUNDROBIN: printf(" round-robin"); break; } if (pool->opts & PF_POOL_STICKYADDR) printf(" sticky-address"); if (pool->opts & PF_POOL_ENDPI) printf(" endpoint-independent"); if (id == PF_NAT && p1 == 0 && p2 == 0) printf(" static-port"); if (pool->mape.offset > 0) printf(" map-e-portset %u/%u/%u", pool->mape.offset, pool->mape.psidlen, pool->mape.psid); } void print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts) { struct pfctl_status_counter *c; char statline[80], *running; time_t runtime; int i; char buf[PF_MD5_DIGEST_LENGTH * 2 + 1]; static const char hex[] = "0123456789abcdef"; runtime = time(NULL) - s->since; running = s->running ? "Enabled" : "Disabled"; if (s->since) { unsigned int sec, min, hrs; time_t day = runtime; sec = day % 60; day /= 60; min = day % 60; day /= 60; hrs = day % 24; day /= 24; snprintf(statline, sizeof(statline), "Status: %s for %lld days %.2u:%.2u:%.2u", running, (long long)day, hrs, min, sec); } else snprintf(statline, sizeof(statline), "Status: %s", running); printf("%-44s", statline); switch (s->debug) { case PF_DEBUG_NONE: printf("%15s\n\n", "Debug: None"); break; case PF_DEBUG_URGENT: printf("%15s\n\n", "Debug: Urgent"); break; case PF_DEBUG_MISC: printf("%15s\n\n", "Debug: Misc"); break; case PF_DEBUG_NOISY: printf("%15s\n\n", "Debug: Loud"); break; } if (opts & PF_OPT_VERBOSE) { printf("Hostid: 0x%08x\n", s->hostid); for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) { buf[i + i] = hex[s->pf_chksum[i] >> 4]; buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f]; } buf[i + i] = '\0'; printf("Checksum: 0x%s\n\n", buf); } if (s->ifname[0] != 0) { printf("Interface Stats for %-16s %5s %16s\n", s->ifname, "IPv4", "IPv6"); printf(" %-25s %14llu %16llu\n", "Bytes In", (unsigned long long)s->bcounters[0][0], (unsigned long long)s->bcounters[1][0]); printf(" %-25s %14llu %16llu\n", "Bytes Out", (unsigned long long)s->bcounters[0][1], (unsigned long long)s->bcounters[1][1]); printf(" Packets In\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][0][PF_PASS], (unsigned long long)s->pcounters[1][0][PF_PASS]); printf(" %-23s %14llu %16llu\n", "Blocked", (unsigned long long)s->pcounters[0][0][PF_DROP], (unsigned long long)s->pcounters[1][0][PF_DROP]); printf(" Packets Out\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][1][PF_PASS], (unsigned long long)s->pcounters[1][1][PF_PASS]); printf(" %-23s %14llu %16llu\n\n", "Blocked", (unsigned long long)s->pcounters[0][1][PF_DROP], (unsigned long long)s->pcounters[1][1][PF_DROP]); } printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); printf(" %-25s %14ju %14s\n", "current entries", s->states, ""); TAILQ_FOREACH(c, &s->fcounters, entry) { printf(" %-25s %14ju ", c->name, c->counter); if (runtime > 0) printf("%14.1f/s\n", (double)c->counter / (double)runtime); else printf("%14s\n", ""); } if (opts & PF_OPT_VERBOSE) { printf("Source Tracking Table\n"); printf(" %-25s %14ju %14s\n", "current entries", s->src_nodes, ""); TAILQ_FOREACH(c, &s->scounters, entry) { printf(" %-25s %14ju ", c->name, c->counter); if (runtime > 0) printf("%14.1f/s\n", (double)c->counter / (double)runtime); else printf("%14s\n", ""); } } printf("Counters\n"); TAILQ_FOREACH(c, &s->counters, entry) { printf(" %-25s %14ju ", c->name, c->counter); if (runtime > 0) printf("%14.1f/s\n", (double)c->counter / (double)runtime); else printf("%14s\n", ""); } if (opts & PF_OPT_VERBOSE) { printf("Limit Counters\n"); TAILQ_FOREACH(c, &s->lcounters, entry) { printf(" %-25s %14ju ", c->name, c->counter); if (runtime > 0) printf("%14.1f/s\n", (double)c->counter / (double)runtime); else printf("%14s\n", ""); } printf("Syncookies\n"); assert(cookies->mode <= PFCTL_SYNCOOKIES_ADAPTIVE); printf(" %-25s %s\n", "mode", PFCTL_SYNCOOKIES_MODE_NAMES[cookies->mode]); printf(" %-25s %s\n", "active", s->syncookies_active ? "active" : "inactive"); if (opts & PF_OPT_VERBOSE2) { printf(" %-25s %d %%\n", "highwater", cookies->highwater); printf(" %-25s %d %%\n", "lowwater", cookies->lowwater); printf(" %-25s %d\n", "halfopen states", cookies->halfopen_states); } printf("Reassemble %24s %s\n", s->reass & PF_REASS_ENABLED ? "yes" : "no", s->reass & PF_REASS_NODF ? "no-df" : "" ); } } void print_running(struct pfctl_status *status) { printf("%s\n", status->running ? "Enabled" : "Disabled"); } void print_src_node(struct pfctl_src_node *sn, int opts) { struct pf_addr_wrap aw; uint64_t min, sec; const char *sn_type_names[] = PF_SN_TYPE_NAMES; memset(&aw, 0, sizeof(aw)); if (sn->af == AF_INET) aw.v.a.mask.addr32[0] = 0xffffffff; else memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); aw.v.a.addr = sn->addr; print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); printf(" -> "); aw.v.a.addr = sn->raddr; print_addr(&aw, sn->naf ? sn->naf : sn->af, opts & PF_OPT_VERBOSE2); printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states, sn->conn, sn->conn_rate.count / 1000, (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds); if (opts & PF_OPT_VERBOSE) { sec = sn->creation % 60; sn->creation /= 60; min = sn->creation % 60; sn->creation /= 60; printf(" age %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64, sn->creation, min, sec); if (sn->states == 0) { sec = sn->expire % 60; sn->expire /= 60; min = sn->expire % 60; sn->expire /= 60; printf(", expires in %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64, sn->expire, min, sec); } printf(", %" PRIu64 " pkts, %" PRIu64 " bytes", sn->packets[0] + sn->packets[1], sn->bytes[0] + sn->bytes[1]); switch (sn->ruletype) { case PF_NAT: if (sn->rule != -1) printf(", nat rule %u", sn->rule); break; case PF_RDR: if (sn->rule != -1) printf(", rdr rule %u", sn->rule); break; case PF_PASS: case PF_MATCH: if (sn->rule != -1) printf(", filter rule %u", sn->rule); break; } printf(", %s", sn_type_names[sn->type]); printf("\n"); } } static void print_eth_addr(const struct pfctl_eth_addr *a) { int i, masklen = ETHER_ADDR_LEN * 8; bool seen_unset = false; for (i = 0; i < ETHER_ADDR_LEN; i++) { if (a->addr[i] != 0) break; } /* Unset, so don't print anything. */ if (i == ETHER_ADDR_LEN) return; printf("%s%02x:%02x:%02x:%02x:%02x:%02x", a->neg ? "! " : "", a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->addr[4], a->addr[5]); for (i = 0; i < (ETHER_ADDR_LEN * 8); i++) { bool isset = a->mask[i / 8] & (1 << i % 8); if (! seen_unset) { if (isset) continue; seen_unset = true; masklen = i; } else { /* Not actually a continuous mask, so print the whole * thing. */ if (isset) break; continue; } } if (masklen == (ETHER_ADDR_LEN * 8)) return; if (i == (ETHER_ADDR_LEN * 8)) { printf("/%d", masklen); return; } printf("&%02x:%02x:%02x:%02x:%02x:%02x", a->mask[0], a->mask[1], a->mask[2], a->mask[3], a->mask[4], a->mask[5]); } void print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call, int rule_numbers) { static const char *actiontypes[] = { "pass", "block", "", "", "", "", "", "", "", "", "", "", "match" }; int i; if (rule_numbers) printf("@%u ", r->nr); printf("ether "); if (anchor_call[0]) { if (anchor_call[0] == '_') { printf("anchor"); } else printf("anchor \"%s\"", anchor_call); } else { printf("%s", actiontypes[r->action]); } if (r->direction == PF_IN) printf(" in"); else if (r->direction == PF_OUT) printf(" out"); if (r->quick) printf(" quick"); if (r->ifname[0]) { if (r->ifnot) printf(" on ! %s", r->ifname); else printf(" on %s", r->ifname); } if (r->bridge_to[0]) printf(" bridge-to %s", r->bridge_to); if (r->proto) printf(" proto 0x%04x", r->proto); if (r->src.isset) { printf(" from "); print_eth_addr(&r->src); } if (r->dst.isset) { printf(" to "); print_eth_addr(&r->dst); } printf(" l3"); print_fromto(&r->ipsrc, PF_OSFP_ANY, &r->ipdst, r->proto == ETHERTYPE_IP ? AF_INET : AF_INET6, 0, 0, 0); i = 0; while (r->label[i][0]) printf(" label \"%s\"", r->label[i++]); if (r->ridentifier) printf(" ridentifier %u", r->ridentifier); if (r->qname[0]) printf(" queue %s", r->qname); if (r->tagname[0]) printf(" tag %s", r->tagname); if (r->match_tagname[0]) { if (r->match_tag_not) printf(" !"); printf(" tagged %s", r->match_tagname); } if (r->dnpipe) printf(" %s %d", r->dnflags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", r->dnpipe); } void print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numeric) { static const char *actiontypes[] = { "pass", "block", "scrub", "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr", "", "", "match"}; static const char *anchortypes[] = { "anchor", "anchor", "anchor", "anchor", "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", "rdr-anchor", "rdr-anchor" }; int i, ropts; char *p; if (verbose) printf("@%d ", r->nr); if (r->action == PF_MATCH) printf("match"); else if (r->action > PF_NORDR) printf("action(%d)", r->action); else if (anchor_call[0]) { p = strrchr(anchor_call, '/'); if (p ? p[1] == '_' : anchor_call[0] == '_') printf("%s", anchortypes[r->action]); else printf("%s \"%s\"", anchortypes[r->action], anchor_call); } else { printf("%s", actiontypes[r->action]); if (r->natpass) printf(" pass"); } if (r->action == PF_DROP) { if (r->rule_flag & PFRULE_RETURN) printf(" return"); else if (r->rule_flag & PFRULE_RETURNRST) { if (!r->return_ttl) printf(" return-rst"); else printf(" return-rst(ttl %d)", r->return_ttl); } else if (r->rule_flag & PFRULE_RETURNICMP) { const struct icmpcodeent *ic, *ic6; ic = geticmpcodebynumber(r->return_icmp >> 8, r->return_icmp & 255, AF_INET); ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, r->return_icmp6 & 255, AF_INET6); switch (r->af) { case AF_INET: printf(" return-icmp"); if (ic == NULL) printf("(%u)", r->return_icmp & 255); else printf("(%s)", ic->name); break; case AF_INET6: printf(" return-icmp6"); if (ic6 == NULL) printf("(%u)", r->return_icmp6 & 255); else printf("(%s)", ic6->name); break; default: printf(" return-icmp"); if (ic == NULL) printf("(%u, ", r->return_icmp & 255); else printf("(%s, ", ic->name); if (ic6 == NULL) printf("%u)", r->return_icmp6 & 255); else printf("%s)", ic6->name); break; } } else printf(" drop"); } if (r->direction == PF_IN) printf(" in"); else if (r->direction == PF_OUT) printf(" out"); if (r->log) { printf(" log"); if (r->log & ~PF_LOG || r->logif) { int count = 0; printf(" ("); if (r->log & PF_LOG_ALL) printf("%sall", count++ ? ", " : ""); if (r->log & PF_LOG_MATCHES) printf("%smatches", count++ ? ", " : ""); if (r->log & PF_LOG_SOCKET_LOOKUP) printf("%suser", count++ ? ", " : ""); if (r->logif) printf("%sto pflog%u", count++ ? ", " : "", r->logif); printf(")"); } } if (r->quick) printf(" quick"); if (r->ifname[0]) { if (r->ifnot) printf(" on ! %s", r->ifname); else printf(" on %s", r->ifname); } if (r->rt) { if (r->rt == PF_ROUTETO) printf(" route-to"); else if (r->rt == PF_REPLYTO) printf(" reply-to"); else if (r->rt == PF_DUPTO) printf(" dup-to"); printf(" "); print_pool(&r->rdr, 0, 0, r->af, PF_PASS); print_pool(&r->route, 0, 0, r->rule_flag & PFRULE_AFTO && r->rt != PF_REPLYTO ? r->naf : r->af, PF_PASS); } if (r->af) { if (r->af == AF_INET) printf(" inet"); else printf(" inet6"); } if (r->proto) { const char *protoname; if ((protoname = pfctl_proto2name(r->proto)) != NULL) printf(" proto %s", protoname); else printf(" proto %u", r->proto); } print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, verbose, numeric); if (r->rcv_ifname[0]) printf(" %sreceived-on %s", r->rcvifnot ? "!" : "", r->rcv_ifname); if (r->uid.op) print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user", UID_MAX); if (r->gid.op) print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group", GID_MAX); if (r->flags || r->flagset) { printf(" flags "); print_flags(r->flags); printf("/"); print_flags(r->flagset); } else if ((r->action == PF_PASS || r->action == PF_MATCH) && (!r->proto || r->proto == IPPROTO_TCP) && !(r->rule_flag & PFRULE_FRAGMENT) && !anchor_call[0] && r->keep_state) printf(" flags any"); if (r->type) { const struct icmptypeent *it; it = geticmptypebynumber(r->type-1, r->af); if (r->af != AF_INET6) printf(" icmp-type"); else printf(" icmp6-type"); if (it != NULL) printf(" %s", it->name); else printf(" %u", r->type-1); if (r->code) { const struct icmpcodeent *ic; ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); if (ic != NULL) printf(" code %s", ic->name); else printf(" code %u", r->code-1); } } if (r->tos) printf(" tos 0x%2.2x", r->tos); if (r->prio) printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio); if (r->pktrate.limit) printf(" max-pkt-rate %u/%u", r->pktrate.limit, r->pktrate.seconds); if (r->max_pkt_size) printf( " max-pkt-size %u", r->max_pkt_size); if (r->scrub_flags & PFSTATE_SETMASK) { char *comma = ""; printf(" set ("); if (r->scrub_flags & PFSTATE_SETPRIO) { if (r->set_prio[0] == r->set_prio[1]) printf("%s prio %u", comma, r->set_prio[0]); else printf("%s prio(%u, %u)", comma, r->set_prio[0], r->set_prio[1]); comma = ","; } if (r->scrub_flags & PFSTATE_SETTOS) { printf("%s tos 0x%2.2x", comma, r->set_tos); comma = ","; } printf(" )"); } if (!r->keep_state && r->action == PF_PASS && !anchor_call[0]) printf(" no state"); else if (r->keep_state == PF_STATE_NORMAL) printf(" keep state"); else if (r->keep_state == PF_STATE_MODULATE) printf(" modulate state"); else if (r->keep_state == PF_STATE_SYNPROXY) printf(" synproxy state"); if (r->prob) { char buf[20]; snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0)); for (i = strlen(buf)-1; i > 0; i--) { if (buf[i] == '0') buf[i] = '\0'; else { if (buf[i] == '.') buf[i] = '\0'; break; } } printf(" probability %s%%", buf); } ropts = 0; if (r->max_states || r->max_src_nodes || r->max_src_states) ropts = 1; if (r->rule_flag & PFRULE_NOSYNC) ropts = 1; if (r->rule_flag & PFRULE_SRCTRACK) ropts = 1; if (r->rule_flag & PFRULE_IFBOUND) ropts = 1; if (r->rule_flag & PFRULE_STATESLOPPY) ropts = 1; if (r->rule_flag & PFRULE_PFLOW) ropts = 1; for (i = 0; !ropts && i < PFTM_MAX; ++i) if (r->timeout[i]) ropts = 1; if (ropts) { printf(" ("); if (r->max_states) { printf("max %u", r->max_states); ropts = 0; } if (r->rule_flag & PFRULE_NOSYNC) { if (!ropts) printf(", "); printf("no-sync"); ropts = 0; } if (r->rule_flag & PFRULE_SRCTRACK) { if (!ropts) printf(", "); printf("source-track"); if (r->rule_flag & PFRULE_RULESRCTRACK) printf(" rule"); else printf(" global"); ropts = 0; } if (r->max_src_states) { if (!ropts) printf(", "); printf("max-src-states %u", r->max_src_states); ropts = 0; } if (r->max_src_conn) { if (!ropts) printf(", "); printf("max-src-conn %u", r->max_src_conn); ropts = 0; } if (r->max_src_conn_rate.limit) { if (!ropts) printf(", "); printf("max-src-conn-rate %u/%u", r->max_src_conn_rate.limit, r->max_src_conn_rate.seconds); ropts = 0; } if (r->max_src_nodes) { if (!ropts) printf(", "); printf("max-src-nodes %u", r->max_src_nodes); ropts = 0; } if (r->overload_tblname[0]) { if (!ropts) printf(", "); printf("overload <%s>", r->overload_tblname); if (r->flush) printf(" flush"); if (r->flush & PF_FLUSH_GLOBAL) printf(" global"); } if (r->rule_flag & PFRULE_IFBOUND) { if (!ropts) printf(", "); printf("if-bound"); ropts = 0; } if (r->rule_flag & PFRULE_STATESLOPPY) { if (!ropts) printf(", "); printf("sloppy"); ropts = 0; } if (r->rule_flag & PFRULE_PFLOW) { if (!ropts) printf(", "); printf("pflow"); ropts = 0; } for (i = 0; i < PFTM_MAX; ++i) if (r->timeout[i]) { int j; if (!ropts) printf(", "); ropts = 0; for (j = 0; pf_timeouts[j].name != NULL; ++j) if (pf_timeouts[j].timeout == i) break; printf("%s %u", pf_timeouts[j].name == NULL ? "inv.timeout" : pf_timeouts[j].name, r->timeout[i]); } printf(")"); } if (r->allow_opts) printf(" allow-opts"); if (r->rule_flag & PFRULE_FRAGMENT) printf(" fragment"); if (r->action == PF_SCRUB) { /* Scrub flags for old-style scrub. */ if (r->rule_flag & PFRULE_NODF) printf(" no-df"); if (r->rule_flag & PFRULE_RANDOMID) printf(" random-id"); if (r->min_ttl) printf(" min-ttl %d", r->min_ttl); if (r->max_mss) printf(" max-mss %d", r->max_mss); if (r->rule_flag & PFRULE_SET_TOS) printf(" set-tos 0x%2.2x", r->set_tos); if (r->rule_flag & PFRULE_REASSEMBLE_TCP) printf(" reassemble tcp"); /* The PFRULE_FRAGMENT_NOREASS is set on all rules by default! */ printf(" fragment %sreassemble", r->rule_flag & PFRULE_FRAGMENT_NOREASS ? "no " : ""); } else if (r->scrub_flags & PFSTATE_SCRUBMASK || r->min_ttl || r->max_mss) { /* Scrub actions on normal rules. */ printf(" scrub("); if (r->scrub_flags & PFSTATE_NODF) printf(" no-df"); if (r->scrub_flags & PFSTATE_RANDOMID) printf(" random-id"); if (r->min_ttl) printf(" min-ttl %d", r->min_ttl); if (r->scrub_flags & PFSTATE_SETTOS) printf(" set-tos 0x%2.2x", r->set_tos); if (r->scrub_flags & PFSTATE_SCRUB_TCP) printf(" reassemble tcp"); if (r->max_mss) printf(" max-mss %d", r->max_mss); printf(")"); } i = 0; while (r->label[i][0]) printf(" label \"%s\"", r->label[i++]); if (r->ridentifier) printf(" ridentifier %u", r->ridentifier); /* Only dnrpipe as we might do (0, 42) to only queue return traffic. */ if (r->dnrpipe) printf(" %s(%d, %d)", r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", r->dnpipe, r->dnrpipe); else if (r->dnpipe) printf(" %s %d", r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", r->dnpipe); if (r->qname[0] && r->pqname[0]) printf(" queue(%s, %s)", r->qname, r->pqname); else if (r->qname[0]) printf(" queue %s", r->qname); if (r->tagname[0]) printf(" tag %s", r->tagname); if (r->match_tagname[0]) { if (r->match_tag_not) printf(" !"); printf(" tagged %s", r->match_tagname); } if (r->rtableid != -1) printf(" rtable %u", r->rtableid); if (r->divert.port) { #ifdef __FreeBSD__ printf(" divert-to %u", ntohs(r->divert.port)); #else if (PF_AZERO(&r->divert.addr, r->af)) { printf(" divert-reply"); } else { printf(" divert-to "); print_addr_str(r->af, &r->divert.addr); printf(" port %u", ntohs(r->divert.port)); } #endif } if (anchor_call[0]) return; if (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR) { printf(" -> "); print_pool(&r->rdr, r->rdr.proxy_port[0], r->rdr.proxy_port[1], r->af, r->action); } else { if (!TAILQ_EMPTY(&r->nat.list)) { if (r->rule_flag & PFRULE_AFTO) { printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6"); } else { printf(" nat-to "); } print_pool(&r->nat, r->nat.proxy_port[0], r->nat.proxy_port[1], r->naf ? r->naf : r->af, PF_NAT); } if (!TAILQ_EMPTY(&r->rdr.list)) { if (r->rule_flag & PFRULE_AFTO) { printf(" to "); } else { printf(" rdr-to "); } print_pool(&r->rdr, r->rdr.proxy_port[0], r->rdr.proxy_port[1], r->naf ? r->naf : r->af, PF_RDR); } } } void print_tabledef(const char *name, int flags, int addrs, struct node_tinithead *nodes) { struct node_tinit *ti, *nti; struct node_host *h; printf("table <%s>", name); if (flags & PFR_TFLAG_CONST) printf(" const"); if (flags & PFR_TFLAG_PERSIST) printf(" persist"); if (flags & PFR_TFLAG_COUNTERS) printf(" counters"); SIMPLEQ_FOREACH(ti, nodes, entries) { if (ti->file) { printf(" file \"%s\"", ti->file); continue; } printf(" {"); for (;;) { for (h = ti->host; h != NULL; h = h->next) { printf(h->not ? " !" : " "); print_addr(&h->addr, h->af, 0); } nti = SIMPLEQ_NEXT(ti, entries); if (nti != NULL && nti->file == NULL) ti = nti; /* merge lists */ else break; } printf(" }"); } if (addrs && SIMPLEQ_EMPTY(nodes)) printf(" { }"); printf("\n"); } int parse_flags(char *s) { char *p, *q; uint16_t f = 0; for (p = s; *p; p++) { if ((q = strchr(tcpflags, *p)) == NULL) return -1; else f |= 1 << (q - tcpflags); } return (f ? f : TH_FLAGS); } void set_ipmask(struct node_host *h, int bb) { struct pf_addr *m, *n; int i, j = 0; uint8_t b; m = &h->addr.v.a.mask; memset(m, 0, sizeof(*m)); if (bb == -1) b = h->af == AF_INET ? 32 : 128; else b = bb; while (b >= 32) { m->addr32[j++] = 0xffffffff; b -= 32; } for (i = 31; i > 31-b; --i) m->addr32[j] |= (1 << i); if (b) m->addr32[j] = htonl(m->addr32[j]); /* Mask off bits of the address that will never be used. */ n = &h->addr.v.a.addr; if (h->addr.type == PF_ADDR_ADDRMASK) for (i = 0; i < 4; i++) n->addr32[i] = n->addr32[i] & m->addr32[i]; } int check_netmask(struct node_host *h, sa_family_t af) { struct node_host *n = NULL; struct pf_addr *m; for (n = h; n != NULL; n = n->next) { if (h->addr.type == PF_ADDR_TABLE) continue; m = &h->addr.v.a.mask; /* netmasks > 32 bit are invalid on v4 */ if (af == AF_INET && (m->addr32[1] || m->addr32[2] || m->addr32[3])) { fprintf(stderr, "netmask %u invalid for IPv4 address\n", unmask(m)); return (1); } } return (0); } struct node_host * gen_dynnode(struct node_host *h, sa_family_t af) { struct node_host *n; struct pf_addr *m; if (h->addr.type != PF_ADDR_DYNIFTL) return (NULL); if ((n = calloc(1, sizeof(*n))) == NULL) return (NULL); bcopy(h, n, sizeof(*n)); n->ifname = NULL; n->next = NULL; n->tail = NULL; /* fix up netmask */ m = &n->addr.v.a.mask; if (af == AF_INET && unmask(m) > 32) set_ipmask(n, 32); return (n); } /* interface lookup routines */ static struct node_host *iftab; /* * Retrieve the list of groups this interface is a member of and make sure * each group is in the group map. */ static void ifa_add_groups_to_map(char *ifa_name) { int s, len; struct ifgroupreq ifgr; struct ifg_req *ifg; s = get_query_socket(); /* Get size of group list for this interface */ memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifa_name, IFNAMSIZ); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGROUP"); /* Retrieve group list for this interface */ len = ifgr.ifgr_len; ifgr.ifgr_groups = (struct ifg_req *)calloc(len / sizeof(struct ifg_req), sizeof(struct ifg_req)); if (ifgr.ifgr_groups == NULL) err(1, "calloc"); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGROUP"); ifg = ifgr.ifgr_groups; for (; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); if (strcmp(ifg->ifgrq_group, "all")) { ENTRY item; ENTRY *ret_item; int *answer; item.key = ifg->ifgrq_group; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) { struct ifgroupreq ifgr2; /* Don't know the answer yet */ if ((answer = malloc(sizeof(int))) == NULL) err(1, "malloc"); bzero(&ifgr2, sizeof(ifgr2)); strlcpy(ifgr2.ifgr_name, ifg->ifgrq_group, sizeof(ifgr2.ifgr_name)); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr2) == 0) *answer = ifgr2.ifgr_len; else *answer = 0; item.key = strdup(ifg->ifgrq_group); item.data = answer; if (hsearch_r(item, ENTER, &ret_item, &isgroup_map) == 0) err(1, "interface group query response" " map insert"); } } } free(ifgr.ifgr_groups); } void ifa_load(void) { struct ifaddrs *ifap, *ifa; struct node_host *n = NULL, *h = NULL; if (getifaddrs(&ifap) < 0) err(1, "getifaddrs"); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!(ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_LINK)) continue; n = calloc(1, sizeof(struct node_host)); if (n == NULL) - err(1, "address: calloc"); + err(1, "%s: calloc", __func__); n->af = ifa->ifa_addr->sa_family; n->ifa_flags = ifa->ifa_flags; #ifdef __KAME__ if (n->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr) && ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == 0) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | sin6->sin6_addr.s6_addr[3]; sin6->sin6_addr.s6_addr[2] = 0; sin6->sin6_addr.s6_addr[3] = 0; } #endif n->ifindex = 0; if (n->af == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr, sizeof(struct in_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *) ifa->ifa_netmask)->sin_addr.s_addr, sizeof(struct in_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in *) ifa->ifa_broadaddr)->sin_addr.s_addr, sizeof(struct in_addr)); if (ifa->ifa_dstaddr != NULL) memcpy(&n->peer, &((struct sockaddr_in *) ifa->ifa_dstaddr)->sin_addr.s_addr, sizeof(struct in_addr)); } else if (n->af == AF_INET6) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *) ifa->ifa_netmask)->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in6 *) ifa->ifa_broadaddr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (ifa->ifa_dstaddr != NULL) memcpy(&n->peer, &((struct sockaddr_in6 *) ifa->ifa_dstaddr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_scope_id; } else if (n->af == AF_LINK) { ifa_add_groups_to_map(ifa->ifa_name); n->ifindex = ((struct sockaddr_dl *) ifa->ifa_addr)->sdl_index; } if ((n->ifname = strdup(ifa->ifa_name)) == NULL) - err(1, "ifa_load: strdup"); + err(1, "%s: strdup", __func__); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } iftab = h; freeifaddrs(ifap); } static int get_socket_domain(void) { int sdom; sdom = AF_UNSPEC; #ifdef WITH_INET6 if (sdom == AF_UNSPEC && feature_present("inet6")) sdom = AF_INET6; #endif #ifdef WITH_INET if (sdom == AF_UNSPEC && feature_present("inet")) sdom = AF_INET; #endif if (sdom == AF_UNSPEC) sdom = AF_LINK; return (sdom); } int get_query_socket(void) { static int s = -1; if (s == -1) { if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1) err(1, "socket"); } return (s); } /* * Returns the response len if the name is a group, otherwise returns 0. */ static int is_a_group(char *name) { ENTRY item; ENTRY *ret_item; item.key = name; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) return (0); return (*(int *)ret_item->data); } unsigned int ifa_nametoindex(const char *ifa_name) { struct node_host *p; for (p = iftab; p; p = p->next) { if (p->af == AF_LINK && strcmp(p->ifname, ifa_name) == 0) return (p->ifindex); } errno = ENXIO; return (0); } char * ifa_indextoname(unsigned int ifindex, char *ifa_name) { struct node_host *p; for (p = iftab; p; p = p->next) { if (p->af == AF_LINK && ifindex == p->ifindex) { strlcpy(ifa_name, p->ifname, IFNAMSIZ); return (ifa_name); } } errno = ENXIO; return (NULL); } struct node_host * ifa_exists(char *ifa_name) { struct node_host *n; if (iftab == NULL) ifa_load(); /* check whether this is a group */ if (is_a_group(ifa_name)) { /* fake a node_host */ if ((n = calloc(1, sizeof(*n))) == NULL) err(1, "calloc"); if ((n->ifname = strdup(ifa_name)) == NULL) err(1, "strdup"); return (n); } for (n = iftab; n; n = n->next) { if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) return (n); } return (NULL); } struct node_host * ifa_grouplookup(char *ifa_name, int flags) { struct ifg_req *ifg; struct ifgroupreq ifgr; int s, len; struct node_host *n, *h = NULL; s = get_query_socket(); len = is_a_group(ifa_name); if (len == 0) return (NULL); bzero(&ifgr, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name)); ifgr.ifgr_len = len; if ((ifgr.ifgr_groups = calloc(1, len)) == NULL) err(1, "calloc"); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGMEMB"); for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL) continue; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n->tail; } } free(ifgr.ifgr_groups); return (h); } struct node_host * ifa_lookup(char *ifa_name, int flags) { struct node_host *p = NULL, *h = NULL, *n = NULL; int got4 = 0, got6 = 0; const char *last_if = NULL; /* first load iftab and isgroup_map */ if (iftab == NULL) ifa_load(); if ((h = ifa_grouplookup(ifa_name, flags)) != NULL) return (h); if (!strncmp(ifa_name, "self", IFNAMSIZ)) ifa_name = NULL; for (p = iftab; p; p = p->next) { if (ifa_skip_if(ifa_name, p)) continue; if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET) continue; if ((flags & PFI_AFLAG_BROADCAST) && !(p->ifa_flags & IFF_BROADCAST)) continue; if ((flags & PFI_AFLAG_BROADCAST) && p->bcast.v4.s_addr == 0) continue; if ((flags & PFI_AFLAG_PEER) && !(p->ifa_flags & IFF_POINTOPOINT)) continue; if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0) continue; if (last_if == NULL || strcmp(last_if, p->ifname)) got4 = got6 = 0; last_if = p->ifname; if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4) continue; if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&p->addr.v.a.addr.v6)) continue; if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6) continue; if (p->af == AF_INET) got4 = 1; else got6 = 1; n = calloc(1, sizeof(struct node_host)); if (n == NULL) - err(1, "address: calloc"); + err(1, "%s: calloc", __func__); n->af = p->af; if (flags & PFI_AFLAG_BROADCAST) memcpy(&n->addr.v.a.addr, &p->bcast, sizeof(struct pf_addr)); else if (flags & PFI_AFLAG_PEER) memcpy(&n->addr.v.a.addr, &p->peer, sizeof(struct pf_addr)); else memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, sizeof(struct pf_addr)); if (flags & PFI_AFLAG_NETWORK) set_ipmask(n, unmask(&p->addr.v.a.mask)); else set_ipmask(n, -1); n->ifindex = p->ifindex; n->ifname = strdup(p->ifname); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } return (h); } int ifa_skip_if(const char *filter, struct node_host *p) { int n; if (p->af != AF_INET && p->af != AF_INET6) return (1); if (filter == NULL || !*filter) return (0); if (!strcmp(p->ifname, filter)) return (0); /* exact match */ n = strlen(filter); if (n < 1 || n >= IFNAMSIZ) return (1); /* sanity check */ if (filter[n-1] >= '0' && filter[n-1] <= '9') return (1); /* only do exact match in that case */ if (strncmp(p->ifname, filter, n)) return (1); /* prefix doesn't match */ return (p->ifname[n] < '0' || p->ifname[n] > '9'); } struct node_host * host(const char *s, int opts) { struct node_host *h = NULL; int mask = -1; char *p, *ps; const char *errstr; if ((p = strrchr(s, '/')) != NULL) { mask = strtonum(p+1, 0, 128, &errstr); if (errstr) { fprintf(stderr, "netmask is %s: %s\n", errstr, p); goto error; } if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) err(1, "host: malloc"); strlcpy(ps, s, strlen(s) - strlen(p) + 1); } else { if ((ps = strdup(s)) == NULL) err(1, "host: strdup"); } if ((h = host_v4(s, mask)) == NULL && (h = host_v6(ps, mask)) == NULL && (h = host_if(ps, mask)) == NULL && (h = host_dns(ps, mask, (opts & PF_OPT_NODNS))) == NULL) { fprintf(stderr, "no IP address found for %s\n", s); goto error; } error: free(ps); return (h); } struct node_host * host_if(const char *s, int mask) { struct node_host *n, *h = NULL; char *p, *ps; int flags = 0; if ((ps = strdup(s)) == NULL) err(1, "host_if: strdup"); while ((p = strrchr(ps, ':')) != NULL) { if (!strcmp(p+1, "network")) flags |= PFI_AFLAG_NETWORK; else if (!strcmp(p+1, "broadcast")) flags |= PFI_AFLAG_BROADCAST; else if (!strcmp(p+1, "peer")) flags |= PFI_AFLAG_PEER; else if (!strcmp(p+1, "0")) flags |= PFI_AFLAG_NOALIAS; else goto error; *p = '\0'; } if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */ fprintf(stderr, "illegal combination of interface modifiers\n"); goto error; } if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) { fprintf(stderr, "network or broadcast lookup, but " "extra netmask given\n"); goto error; } if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { /* interface with this name exists */ h = ifa_lookup(ps, flags); if (mask > -1) for (n = h; n != NULL; n = n->next) set_ipmask(n, mask); } error: free(ps); return (h); } struct node_host * host_v4(const char *s, int mask) { struct node_host *h = NULL; struct in_addr ina; memset(&ina, 0, sizeof(ina)); if (mask > -1) { if (inet_net_pton(AF_INET, s, &ina, sizeof(ina)) == -1) return (NULL); } else { if (inet_pton(AF_INET, s, &ina) != 1) return (NULL); } h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET; h->addr.v.a.addr.addr32[0] = ina.s_addr; set_ipmask(h, mask); h->next = NULL; h->tail = h; return (h); } struct node_host * host_v6(const char *s, int mask) { struct addrinfo hints, *res; struct node_host *h = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; /*dummy*/ hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(s, "0", &hints, &res) == 0) { h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET6; memcpy(&h->addr.v.a.addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof(h->addr.v.a.addr)); h->ifindex = ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; set_ipmask(h, mask); freeaddrinfo(res); h->next = NULL; h->tail = h; } return (h); } struct node_host * host_dns(const char *s, int mask, int numeric) { struct addrinfo hints, *res0, *res; struct node_host *n, *h = NULL; int noalias = 0, got4 = 0, got6 = 0; char *p, *ps; if ((ps = strdup(s)) == NULL) err(1, "host_dns: strdup"); if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) { noalias = 1; *p = '\0'; } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* DUMMY */ if (numeric) hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(ps, NULL, &hints, &res0) != 0) goto error; for (res = res0; res; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; if (noalias) { if (res->ai_family == AF_INET) { if (got4) continue; got4 = 1; } else { if (got6) continue; got6 = 1; } } n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "host_dns: calloc"); n->ifname = NULL; n->af = res->ai_family; if (res->ai_family == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) res->ai_addr)->sin_addr.s_addr, sizeof(struct in_addr)); } else { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) res->ai_addr)->sin6_scope_id; } set_ipmask(n, mask); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } freeaddrinfo(res0); error: free(ps); return (h); } /* * convert a hostname to a list of addresses and put them in the given buffer. * test: * if set to 1, only simple addresses are accepted (no netblock, no "!"). */ int append_addr(struct pfr_buffer *b, char *s, int test, int opts) { char *r; struct node_host *h, *n; int rv, not = 0; for (r = s; *r == '!'; r++) not = !not; if ((n = host(r, opts)) == NULL) { errno = 0; return (-1); } rv = append_addr_host(b, n, test, not); do { h = n; n = n->next; free(h); } while (n != NULL); return (rv); } /* * same as previous function, but with a pre-parsed input and the ability * to "negate" the result. Does not free the node_host list. * not: * setting it to 1 is equivalent to adding "!" in front of parameter s. */ int append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) { int bits; struct pfr_addr addr; do { bzero(&addr, sizeof(addr)); addr.pfra_not = n->not ^ not; addr.pfra_af = n->af; addr.pfra_net = unmask(&n->addr.v.a.mask); switch (n->af) { case AF_INET: addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; bits = 32; break; case AF_INET6: memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, sizeof(struct in6_addr)); bits = 128; break; default: errno = EINVAL; return (-1); } if ((test && (not || addr.pfra_net != bits)) || addr.pfra_net > bits) { errno = EINVAL; return (-1); } if (pfr_buf_add(b, &addr)) return (-1); } while ((n = n->next) != NULL); return (0); } int pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor) { struct pfioc_trans_e trans; bzero(&trans, sizeof(trans)); trans.rs_num = rs_num; if (strlcpy(trans.anchor, anchor, sizeof(trans.anchor)) >= sizeof(trans.anchor)) errx(1, "pfctl_add_trans: strlcpy"); return pfr_buf_add(buf, &trans); } u_int32_t pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor) { struct pfioc_trans_e *p; PFRB_FOREACH(p, buf) if (rs_num == p->rs_num && !strcmp(anchor, p->anchor)) return (p->ticket); errx(1, "pfctl_get_ticket: assertion failed"); } int pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from) { struct pfioc_trans trans; bzero(&trans, sizeof(trans)); trans.size = buf->pfrb_size - from; trans.esize = sizeof(struct pfioc_trans_e); trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from; return ioctl(dev, cmd, &trans); }