diff --git a/sbin/pfctl/Makefile b/sbin/pfctl/Makefile index 74cefe6824a4..c84d558c989d 100644 --- a/sbin/pfctl/Makefile +++ b/sbin/pfctl/Makefile @@ -1,35 +1,35 @@ # $FreeBSD$ .include PACKAGE=pf CONFS= pf.os PROG= pfctl MAN= pfctl.8 SRCS = pfctl.c parse.y pfctl_parser.c pf_print_state.c pfctl_altq.c SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c pfctl_qstats.c -SRCS+= pfctl_optimize.c +SRCS+= pfctl_optimize.c pfctl_ioctl.c SRCS+= pf_ruleset.c WARNS?= 2 CFLAGS+= -Wall -Wmissing-prototypes -Wno-uninitialized CFLAGS+= -Wstrict-prototypes CFLAGS+= -DENABLE_ALTQ -I${.CURDIR} # Need to use "WITH_" prefix to not conflict with the l/y INET/INET6 keywords .if ${MK_INET6_SUPPORT} != "no" CFLAGS+= -DWITH_INET6 .endif .if ${MK_INET_SUPPORT} != "no" CFLAGS+= -DWITH_INET .endif YFLAGS= LIBADD= m md nv HAS_TESTS= SUBDIR.${MK_TESTS}+= tests .include diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 58a87a2b8395..1aa17065597b 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1,2706 +1,2710 @@ /* $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 __FBSDID("$FreeBSD$"); #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 +#include "pfctl_ioctl.h" #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); int pfctl_clear_stats(int, int); int pfctl_get_skip_ifaces(void); int pfctl_check_skip_ifaces(char *); int pfctl_adjust_skip_ifaces(struct pfctl *); int pfctl_clear_interface_flags(int, int); int pfctl_clear_rules(int, int, char *); int pfctl_clear_nat(int, int, char *); int pfctl_clear_altq(int, int); int pfctl_clear_src_nodes(int, int); int pfctl_clear_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); int pfctl_kill_src_nodes(int, const char *, int); int pfctl_net_kill_states(int, const char *, int); int pfctl_label_kill_states(int, const char *, int); int pfctl_id_kill_states(int, const char *, int); 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_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int, char *); void pfctl_print_rule_counters(struct pf_rule *, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int); int pfctl_show_nat(int, int, char *); 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_ruleset_trans(struct pfctl *, char *, struct pf_anchor *); int pfctl_load_ruleset(struct pfctl *, char *, struct pf_ruleset *, int, int); int pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int); const char *pfctl_lookup_option(char *, const char * const *); static struct pf_anchor_global pf_anchors; static struct pf_anchor pf_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 = "/dev/pf"; 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; 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", NULL }; static const char * const showopt_list[] = { "nat", "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", "Running", "all", NULL }; static const char * const tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", "expire", 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 [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "\t[-f file] [-i interface] [-K host | network]\n" "\t[-k host | network | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", __progname); exit(1); } int pfctl_enable(int dev, int opts) { if (ioctl(dev, DIOCSTART)) { if (errno == EEXIST) errx(1, "pf already enabled"); else if (errno == ESRCH) errx(1, "pfil registeration failed"); else err(1, "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) { if (ioctl(dev, DIOCSTOP)) { if (errno == ENOENT) errx(1, "pf not enabled"); else err(1, "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); } int pfctl_clear_stats(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATUS)) err(1, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); return (0); } int 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; } return (0); } int 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 (p->pfik_ifp == NULL) continue; if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) continue; p->pfik_flags &= ~PFI_IFLAG_SKIP; } } } return (0); } int 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 (pp->pfik_ifp == NULL) continue; 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_ifp == NULL || ! (p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); } return (0); } int 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"); } return (0); } int pfctl_clear_rules(int dev, int opts, char *anchorname) { struct pfr_buffer t; memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_SCRUB, anchorname) || pfctl_add_trans(&t, PF_RULESET_FILTER, anchorname) || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } int pfctl_clear_nat(int dev, int opts, char *anchorname) { struct pfr_buffer t; memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_NAT, anchorname) || pfctl_add_trans(&t, PF_RULESET_BINAT, anchorname) || pfctl_add_trans(&t, PF_RULESET_RDR, anchorname) || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_nat"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } 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); } int 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"); return (0); } int pfctl_clear_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; memset(&psk, 0, sizeof(psk)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); if (ioctl(dev, DIOCCLRSTATES, &psk)) err(1, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "%d states cleared\n", psk.psk_killed); return (0); } 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); } int 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); return (0); } int pfctl_net_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psk, 0, sizeof(psk)); memset(&psk.psk_src.addr.v.a.mask, 0xff, sizeof(psk.psk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); pfctl_addrprefix(state_kill[0], &psk.psk_src.addr.v.a.mask); 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; psk.psk_af = resp[0]->ai_family; sources++; if (psk.psk_af == AF_INET) psk.psk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (state_killers > 1) { dests = 0; memset(&psk.psk_dst.addr.v.a.mask, 0xff, sizeof(psk.psk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(state_kill[1], &psk.psk_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 (psk.psk_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 (psk.psk_af == AF_INET) psk.psk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_label_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no label specified"); usage(); } memset(&psk, 0, sizeof(psk)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); if (strlcpy(psk.psk_label, state_kill[1], sizeof(psk.psk_label)) >= sizeof(psk.psk_label)) errx(1, "label too long: %s", state_kill[1]); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", psk.psk_killed); return (0); } int pfctl_id_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no id specified"); usage(); } memset(&psk, 0, sizeof(psk)); if ((sscanf(state_kill[1], "%jx/%x", &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2) HTONL(psk.psk_pfcmp.creatorid); else if ((sscanf(state_kill[1], "%jx", &psk.psk_pfcmp.id)) == 1) { psk.psk_pfcmp.creatorid = 0; } else { warnx("wrong id format specified"); usage(); } if (psk.psk_pfcmp.id == 0) { warnx("cannot kill id 0"); usage(); } psk.psk_pfcmp.id = htobe64(psk.psk_pfcmp.id); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", psk.psk_killed); return (0); } int pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, char *anchorname) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; memset(&pp, 0, sizeof(pp)); memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); pp.r_action = r_action; pp.r_num = nr; pp.ticket = ticket; if (ioctl(dev, DIOCGETADDRS, &pp)) { warn("DIOCGETADDRS"); return (-1); } mpnr = pp.nr; TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { pp.nr = pnr; if (ioctl(dev, DIOCGETADDR, &pp)) { warn("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 pf_pool *src, struct pf_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 pf_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void pfctl_print_rule_counters(struct pf_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "sp", "da", "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->u_states_cur); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", (unsigned)rule->cuid, (unsigned)rule->cpid, (uintmax_t)rule->u_states_tot); } } void pfctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth) { struct pfioc_rule pr; u_int32_t nr, mnr, header = 0; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path); int brace; char *p; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, path, sizeof(pr.anchor)); if (opts & PF_OPT_SHOWALL) { pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } header++; } pr.rule.action = PF_SCRUB; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } if (opts & PF_OPT_SHOWALL) { if (format == PFCTL_SHOW_RULES && (pr.nr > 0 || header)) pfctl_print_title("FILTER RULES:"); else if (format == PFCTL_SHOW_LABELS && labels) pfctl_print_title("LABEL COUNTERS:"); } mnr = pr.nr; if (opts & PF_OPT_CLRRULECTRS) pr.action = PF_GET_CLR_CNTR; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; - if (ioctl(dev, DIOCGETRULE, &pr)) { - warn("DIOCGETRULE"); + if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_SCRUB, + &pr.rule, pr.anchor_call)) { + warn("DIOCGETRULENV"); goto error; } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, PF_SCRUB, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: break; case PFCTL_SHOW_RULES: if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) labels = 1; print_rule(&pr.rule, pr.anchor_call, rule_numbers, numeric); printf("\n"); pfctl_print_rule_counters(&pr.rule, opts); break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&pr.rule.rpool); } pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; - if (ioctl(dev, DIOCGETRULE, &pr)) { + if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_PASS, + &pr.rule, pr.anchor_call)) { warn("DIOCGETRULE"); goto error; } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, PF_PASS, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: if (pr.rule.label[0]) { printf("%s %llu %llu %llu %llu" " %llu %llu %llu %ju\n", pr.rule.label, (unsigned long long)pr.rule.evaluations, (unsigned long long)(pr.rule.packets[0] + pr.rule.packets[1]), (unsigned long long)(pr.rule.bytes[0] + pr.rule.bytes[1]), (unsigned long long)pr.rule.packets[0], (unsigned long long)pr.rule.bytes[0], (unsigned long long)pr.rule.packets[1], (unsigned long long)pr.rule.bytes[1], (uintmax_t)pr.rule.u_states_tot); } break; case PFCTL_SHOW_RULES: brace = 0; if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if (pr.anchor_call[0] && ((((p = strrchr(pr.anchor_call, '_')) != NULL) && ((void *)p == (void *)pr.anchor_call || *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { brace++; if ((p = strrchr(pr.anchor_call, '/')) != NULL) p++; else p = &pr.anchor_call[0]; } else p = &pr.anchor_call[0]; print_rule(&pr.rule, p, rule_numbers, numeric); if (brace) printf(" {\n"); else printf("\n"); pfctl_print_rule_counters(&pr.rule, opts); if (brace) { pfctl_show_rules(dev, path, opts, format, p, depth + 1); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&pr.rule.rpool); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (-1); } int pfctl_show_nat(int dev, int opts, char *anchorname) { struct pfioc_rule pr; u_int32_t mnr, nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i, dotitle = opts & PF_OPT_SHOWALL; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); for (i = 0; i < 3; i++) { pr.rule.action = nattype[i]; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; - if (ioctl(dev, DIOCGETRULE, &pr)) { + if (pfctl_get_rule(dev, nr, pr.ticket, anchorname, + nattype[i], &pr.rule, pr.anchor_call)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &pr.rule.rpool, nr, pr.ticket, nattype[i], anchorname) != 0) return (-1); if (dotitle) { pfctl_print_title("TRANSLATION RULES:"); dotitle = 0; } print_rule(&pr.rule, pr.anchor_call, opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); printf("\n"); pfctl_print_rule_counters(&pr.rule, opts); pfctl_clear_pool(&pr.rule.rpool); } } return (0); } int pfctl_show_src_nodes(int dev, int opts) { struct pfioc_src_nodes psn; struct pf_src_node *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i; memset(&psn, 0, sizeof(psn)); for (;;) { psn.psn_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); psn.psn_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) { warn("DIOCGETSRCNODES"); free(inbuf); return (-1); } if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len) break; if (len == 0 && psn.psn_len == 0) goto done; if (len == 0 && psn.psn_len != 0) len = psn.psn_len; if (psn.psn_len == 0) goto done; /* no src_nodes */ len *= 2; } p = psn.psn_src_nodes; if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL)) pfctl_print_title("SOURCE TRACKING NODES:"); for (i = 0; i < psn.psn_len; i += sizeof(*p)) { print_src_node(p, opts); p++; } done: free(inbuf); return (0); } int pfctl_show_states(int dev, const char *iface, int opts) { struct pfioc_states ps; struct pfsync_state *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i, dotitle = (opts & PF_OPT_SHOWALL); memset(&ps, 0, sizeof(ps)); for (;;) { ps.ps_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); ps.ps_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { warn("DIOCGETSTATES"); free(inbuf); return (-1); } if (ps.ps_len + sizeof(struct pfioc_states) < len) break; if (len == 0 && ps.ps_len == 0) goto done; if (len == 0 && ps.ps_len != 0) len = ps.ps_len; if (ps.ps_len == 0) goto done; /* no states */ len *= 2; } p = ps.ps_states; for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) { if (iface != NULL && strcmp(p->ifname, iface)) continue; if (dotitle) { pfctl_print_title("STATES:"); dotitle = 0; } print_state(p, opts); } done: free(inbuf); return (0); } int pfctl_show_status(int dev, int opts) { struct pf_status status; if (ioctl(dev, DIOCGETSTATUS, &status)) { warn("DIOCGETSTATUS"); return (-1); } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INFO:"); print_status(&status, opts); return (0); } int pfctl_show_running(int dev) { struct pf_status status; if (ioctl(dev, DIOCGETSTATUS, &status)) { warn("DIOCGETSTATUS"); return (-1); } print_running(&status); return (!status.running); } int pfctl_show_timeouts(int dev, int opts) { struct pfioc_tm pt; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("TIMEOUTS:"); memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { pt.timeout = pf_timeouts[i].timeout; if (ioctl(dev, DIOCGETTIMEOUT, &pt)) err(1, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, pt.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) { struct pfioc_limit pl; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { pl.index = pf_limits[i].index; if (ioctl(dev, DIOCGETLIMIT, &pl)) err(1, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); if (pl.limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %8u\n", pl.limit); } return (0); } /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) err(1, "DIOCBEGINADDRS"); } 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 (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) err(1, "DIOCADDADDR"); } } return (0); } int pfctl_add_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call) { u_int8_t rs_num; struct pf_rule *rule; struct pf_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_add_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_add_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_add_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_add_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INIT(&rule->rpool.list); pfctl_move_pool(&r->rpool, &rule->rpool); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); return (0); } int pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pf_anchor *a) { int osize = pf->trans->pfrb_size; 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_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs, int rs_num, int depth) { struct pf_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))) { 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); 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); } static void pfctl_nv_add_addr(nvlist_t *nvparent, const char *name, const struct pf_addr *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "addr", addr, sizeof(*addr)); nvlist_add_nvlist(nvparent, name, nvl); } static void pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name, const struct pf_addr_wrap *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_number(nvl, "type", addr->type); nvlist_add_number(nvl, "iflags", addr->iflags); nvlist_add_string(nvl, "ifname", addr->v.ifname); nvlist_add_string(nvl, "tblname", addr->v.tblname); pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr); pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask); nvlist_add_nvlist(nvparent, name, nvl); } static void pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name, const struct pf_rule_addr *addr) { u_int64_t ports[2]; nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr); ports[0] = addr->port[0]; ports[1] = addr->port[1]; nvlist_add_number_array(nvl, "port", ports, 2); nvlist_add_number(nvl, "neg", addr->neg); nvlist_add_number(nvl, "port_op", addr->port_op); nvlist_add_nvlist(nvparent, name, nvl); } static void pfctl_nv_add_pool(nvlist_t *nvparent, const char *name, const struct pf_pool *pool) { u_int64_t ports[2]; nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "key", &pool->key, sizeof(pool->key)); pfctl_nv_add_addr(nvl, "counter", &pool->counter); nvlist_add_number(nvl, "tblidx", pool->tblidx); ports[0] = pool->proxy_port[0]; ports[1] = pool->proxy_port[1]; nvlist_add_number_array(nvl, "proxy_port", ports, 2); nvlist_add_number(nvl, "opts", pool->opts); nvlist_add_nvlist(nvparent, name, nvl); } static void pfctl_nv_add_uid(nvlist_t *nvparent, const char *name, const struct pf_rule_uid *uid) { u_int64_t uids[2]; nvlist_t *nvl = nvlist_create(0); uids[0] = uid->uid[0]; uids[1] = uid->uid[1]; nvlist_add_number_array(nvl, "uid", uids, 2); nvlist_add_number(nvl, "op", uid->op); nvlist_add_nvlist(nvparent, name, nvl); } static void pfctl_nv_add_divert(nvlist_t *nvparent, const char *name, const struct pf_rule *r) { nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr(nvl, "addr", &r->divert.addr); nvlist_add_number(nvl, "port", r->divert.port); nvlist_add_nvlist(nvparent, name, nvl); } static int pfctl_addrule(struct pfctl *pf, const struct pf_rule *r, const char *anchor, const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket) { struct pfioc_nv nv; u_int64_t timeouts[PFTM_MAX]; u_int64_t set_prio[2]; nvlist_t *nvl, *nvlr; int ret; nvl = nvlist_create(0); nvlr = nvlist_create(0); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_number(nvl, "pool_ticket", pool_ticket); nvlist_add_string(nvl, "anchor", anchor); nvlist_add_string(nvl, "anchor_call", anchor_call); nvlist_add_number(nvlr, "nr", r->nr); pfctl_nv_add_rule_addr(nvlr, "src", &r->src); pfctl_nv_add_rule_addr(nvlr, "dst", &r->dst); nvlist_add_string(nvlr, "label", r->label); nvlist_add_string(nvlr, "ifname", r->ifname); nvlist_add_string(nvlr, "qname", r->qname); nvlist_add_string(nvlr, "pqname", r->pqname); nvlist_add_string(nvlr, "tagname", r->tagname); nvlist_add_string(nvlr, "match_tagname", r->match_tagname); nvlist_add_string(nvlr, "overload_tblname", r->overload_tblname); pfctl_nv_add_pool(nvlr, "rpool", &r->rpool); nvlist_add_number(nvlr, "os_fingerprint", r->os_fingerprint); nvlist_add_number(nvlr, "rtableid", r->rtableid); for (int i = 0; i < PFTM_MAX; i++) timeouts[i] = r->timeout[i]; nvlist_add_number_array(nvlr, "timeout", timeouts, PFTM_MAX); nvlist_add_number(nvlr, "max_states", r->max_states); nvlist_add_number(nvlr, "max_src_nodes", r->max_src_nodes); nvlist_add_number(nvlr, "max_src_states", r->max_src_states); nvlist_add_number(nvlr, "max_src_conn", r->max_src_conn); nvlist_add_number(nvlr, "max_src_conn_rate.limit", r->max_src_conn_rate.limit); nvlist_add_number(nvlr, "max_src_conn_rate.seconds", r->max_src_conn_rate.seconds); nvlist_add_number(nvlr, "prob", r->prob); nvlist_add_number(nvlr, "cuid", r->cuid); nvlist_add_number(nvlr, "cpid", r->cpid); nvlist_add_number(nvlr, "return_icmp", r->return_icmp); nvlist_add_number(nvlr, "return_icmp6", r->return_icmp6); nvlist_add_number(nvlr, "max_mss", r->max_mss); nvlist_add_number(nvlr, "scrub_flags", r->scrub_flags); pfctl_nv_add_uid(nvlr, "uid", &r->uid); pfctl_nv_add_uid(nvlr, "gid", (struct pf_rule_uid *)&r->gid); nvlist_add_number(nvlr, "rule_flag", r->rule_flag); nvlist_add_number(nvlr, "action", r->action); nvlist_add_number(nvlr, "direction", r->direction); nvlist_add_number(nvlr, "log", r->log); nvlist_add_number(nvlr, "logif", r->logif); nvlist_add_number(nvlr, "quick", r->quick); nvlist_add_number(nvlr, "ifnot", r->ifnot); nvlist_add_number(nvlr, "match_tag_not", r->match_tag_not); nvlist_add_number(nvlr, "natpass", r->natpass); nvlist_add_number(nvlr, "keep_state", r->keep_state); nvlist_add_number(nvlr, "af", r->af); nvlist_add_number(nvlr, "proto", r->proto); nvlist_add_number(nvlr, "type", r->type); nvlist_add_number(nvlr, "code", r->code); nvlist_add_number(nvlr, "flags", r->flags); nvlist_add_number(nvlr, "flagset", r->flagset); nvlist_add_number(nvlr, "min_ttl", r->min_ttl); nvlist_add_number(nvlr, "allow_opts", r->allow_opts); nvlist_add_number(nvlr, "rt", r->rt); nvlist_add_number(nvlr, "return_ttl", r->return_ttl); nvlist_add_number(nvlr, "tos", r->tos); nvlist_add_number(nvlr, "set_tos", r->set_tos); nvlist_add_number(nvlr, "anchor_relative", r->anchor_relative); nvlist_add_number(nvlr, "anchor_wildcard", r->anchor_wildcard); nvlist_add_number(nvlr, "flush", r->flush); nvlist_add_number(nvlr, "prio", r->prio); set_prio[0] = r->set_prio[0]; set_prio[1] = r->set_prio[1]; nvlist_add_number_array(nvlr, "set_prio", set_prio, 2); pfctl_nv_add_divert(nvlr, "divert", r); nvlist_add_nvlist(nvl, "rule", nvlr); /* Now do the call. */ nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; ret = ioctl(pf->dev, DIOCADDRULENV, &nv); free(nv.data); nvlist_destroy(nvl); return (ret); } int pfctl_load_rule(struct pfctl *pf, char *path, struct pf_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); /* 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 = ""; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); if (pfctl_addrule(pf, r, anchor, name, ticket, pf->paddr.ticket)) err(1, "DIOCADDRULENV"); } if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_rule(r, r->anchor ? r->anchor->name : "", pf->opts & PF_OPT_VERBOSE2, pf->opts & PF_OPT_NUMERIC); } path[len] = '\0'; pfctl_clear_pool(&r->rpool); 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) struct pfr_buffer *t, buf; struct pfioc_altq pa; struct pfctl pf; struct pf_ruleset *rs; struct pfr_table trs; char *path; 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; 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"); if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) ERRX("pfctl_rules: strlcpy"); pf.dev = dev; 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"); 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_add_rule: strlcpy"); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) errx(1, "pfctl_add_rule: strlcpy"); 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); 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)) ERRX("pfctl_rules"); 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_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"); /* process "load anchor" directives */ if (!anchorname[0]) if (pfctl_load_anchors(dev, &pf, t) == -1) ERRX("load anchors"); if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) { if (!anchorname[0]) if (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_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; } 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 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; return (error); } int pfctl_set_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) { struct pfioc_limit pl; memset(&pl, 0, sizeof(pl)); pl.index = index; pl.limit = limit; if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { if (errno == EBUSY) warnx("Current pool size exceeds requested hard limit"); else warnx("DIOCSETLIMIT"); return (1); } return (0); } int pfctl_set_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) { struct pfioc_tm pt; memset(&pt, 0, sizeof(pt)); pt.timeout = timeout; pt.seconds = seconds; if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) { warnx("DIOCSETTIMEOUT"); return (1); } 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_set_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) { struct pfioc_if pi; memset(&pi, 0, sizeof(pi)); if (ifname && strlcpy(pi.ifname, ifname, sizeof(pi.ifname)) >= sizeof(pi.ifname)) { warnx("pfctl_load_logif: strlcpy"); return (1); } if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) { warnx("DIOCSETSTATUSIF"); return (1); } return (0); } int pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); HTONL(hostid); pf->hostid = hostid; pf->hostid_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set hostid 0x%08x\n", ntohl(hostid)); return (0); } int pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) { if (ioctl(dev, DIOCSETHOSTID, &hostid)) { warnx("DIOCSETHOSTID"); return (1); } return (0); } int pfctl_set_debug(struct pfctl *pf, char *d) { u_int32_t level; 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 (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "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 (ioctl(pf->dev, DIOCSETDEBUG, &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) { if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "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; memset(&pr, 0, sizeof(pr)); memcpy(pr.path, anchorname, sizeof(pr.path)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { char sub[MAXPATHLEN]; pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "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); } 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:mnNOo:Pp:rRs:t: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 '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 '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("acdefkrz", ch) ? O_RDWR : O_RDONLY; } 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 (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; } 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 } 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); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, anchorname, 0); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, anchorname, 0); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, opts, anchorname); 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 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, opts, anchorname); pfctl_show_rules(dev, path, opts, 0, anchorname, 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); 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; } } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 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 'r': pfctl_clear_rules(dev, opts, anchorname); break; case 'n': pfctl_clear_nat(dev, opts, anchorname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_states(dev, ifaceopt, opts); break; case 'S': pfctl_clear_src_nodes(dev, opts); break; case 'i': pfctl_clear_stats(dev, opts); break; case 'a': pfctl_clear_rules(dev, opts, anchorname); pfctl_clear_nat(dev, opts, anchorname); pfctl_clear_tables(anchorname, opts); if (!*anchorname) { pfctl_clear_altq(dev, opts); pfctl_clear_states(dev, ifaceopt, opts); pfctl_clear_src_nodes(dev, opts); pfctl_clear_stats(dev, opts); pfctl_clear_fingerprints(dev, opts); pfctl_clear_interface_flags(dev, opts); } break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_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 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)) if (pfctl_get_skip_ifaces()) error = 1; if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) && !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_ioctl.c b/sbin/pfctl/pfctl_ioctl.c new file mode 100644 index 000000000000..878a57de0fe4 --- /dev/null +++ b/sbin/pfctl/pfctl_ioctl.c @@ -0,0 +1,339 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) + * 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. + * + * $FreeBSD$ + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pfctl_ioctl.h" + +static void +pf_nvuint_8_array(const nvlist_t *nvl, const char *name, size_t maxelems, + u_int8_t *numbers, size_t *nelems) +{ + const uint64_t *tmp; + size_t elems; + + tmp = nvlist_get_number_array(nvl, name, &elems); + assert(elems <= maxelems); + + for (size_t i = 0; i < elems; i++) + numbers[i] = tmp[i]; + + if (nelems) + *nelems = elems; +} + +static void +pf_nvuint_16_array(const nvlist_t *nvl, const char *name, size_t maxelems, + u_int16_t *numbers, size_t *nelems) +{ + const uint64_t *tmp; + size_t elems; + + tmp = nvlist_get_number_array(nvl, name, &elems); + assert(elems <= maxelems); + + for (size_t i = 0; i < elems; i++) + numbers[i] = tmp[i]; + + if (nelems) + *nelems = elems; +} + +static void +pf_nvuint_32_array(const nvlist_t *nvl, const char *name, size_t maxelems, + u_int32_t *numbers, size_t *nelems) +{ + const uint64_t *tmp; + size_t elems; + + tmp = nvlist_get_number_array(nvl, name, &elems); + assert(elems <= maxelems); + + for (size_t i = 0; i < elems; i++) + numbers[i] = tmp[i]; + + if (nelems) + *nelems = elems; +} + +static void +pf_nvuint_64_array(const nvlist_t *nvl, const char *name, size_t maxelems, + u_int64_t *numbers, size_t *nelems) +{ + const uint64_t *tmp; + size_t elems; + + tmp = nvlist_get_number_array(nvl, name, &elems); + assert(elems <= maxelems); + + for (size_t i = 0; i < elems; i++) + numbers[i] = tmp[i]; + + if (nelems) + *nelems = elems; +} + +static void +pf_nvaddr_to_addr(const nvlist_t *nvl, struct pf_addr *addr) +{ + size_t len; + const void *data; + + data = nvlist_get_binary(nvl, "addr", &len); + assert(len == sizeof(struct pf_addr)); + memcpy(addr, data, len); +} + +static void +pf_nvaddr_wrap_to_addr_wrap(const nvlist_t *nvl, struct pf_addr_wrap *addr) +{ + addr->type = nvlist_get_number(nvl, "type"); + addr->iflags = nvlist_get_number(nvl, "iflags"); + strlcpy(addr->v.ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); + strlcpy(addr->v.tblname, nvlist_get_string(nvl, "tblname"), + PF_TABLE_NAME_SIZE); + + pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &addr->v.a.addr); + pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "mask"), &addr->v.a.mask); +} + +static void +pf_nvrule_addr_to_rule_addr(const nvlist_t *nvl, struct pf_rule_addr *addr) +{ + pf_nvaddr_wrap_to_addr_wrap(nvlist_get_nvlist(nvl, "addr"), &addr->addr); + + pf_nvuint_16_array(nvl, "port", 2, addr->port, NULL); + addr->neg = nvlist_get_number(nvl, "neg"); + addr->port_op = nvlist_get_number(nvl, "port_op"); +} + +static void +pf_nvpool_to_pool(const nvlist_t *nvl, struct pf_pool *pool) +{ + size_t len; + const void *data; + + data = nvlist_get_binary(nvl, "key", &len); + assert(len == sizeof(pool->key)); + memcpy(&pool->key, data, len); + + pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "counter"), &pool->counter); + + pool->tblidx = nvlist_get_number(nvl, "tblidx"); + pf_nvuint_16_array(nvl, "proxy_port", 2, pool->proxy_port, NULL); + pool->opts = nvlist_get_number(nvl, "opts"); +} + +static void +pf_nvrule_uid_to_rule_uid(const nvlist_t *nvl, struct pf_rule_uid *uid) +{ + pf_nvuint_32_array(nvl, "uid", 2, uid->uid, NULL); + uid->op = nvlist_get_number(nvl, "op"); +} + +static void +pf_nvdivert_to_divert(const nvlist_t *nvl, struct pf_rule *rule) +{ + pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &rule->divert.addr); + rule->divert.port = nvlist_get_number(nvl, "port"); +} + +static void +pf_nvrule_to_rule(const nvlist_t *nvl, struct pf_rule *rule) +{ + const uint64_t *skip; + size_t skipcount; + + rule->nr = nvlist_get_number(nvl, "nr"); + + pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); + pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); + + skip = nvlist_get_number_array(nvl, "skip", &skipcount); + assert(skip); + assert(skipcount == PF_SKIP_COUNT); + for (int i = 0; i < PF_SKIP_COUNT; i++) + rule->skip[i].nr = skip[i]; + + strlcpy(rule->label, nvlist_get_string(nvl, "label"), PF_RULE_LABEL_SIZE); + strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); + strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); + strlcpy(rule->pqname, nvlist_get_string(nvl, "pqname"), PF_QNAME_SIZE); + strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), + PF_TAG_NAME_SIZE); + strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"), + PF_TAG_NAME_SIZE); + + strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"), + PF_TABLE_NAME_SIZE); + + pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool); + + rule->evaluations = nvlist_get_number(nvl, "evaluations"); + pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL); + pf_nvuint_64_array(nvl, "bytes", 2, rule->bytes, NULL); + + rule->os_fingerprint = nvlist_get_number(nvl, "os_fingerprint"); + + rule->rtableid = nvlist_get_number(nvl, "rtableid"); + pf_nvuint_32_array(nvl, "timeout", PFTM_MAX, rule->timeout, NULL); + rule->max_states = nvlist_get_number(nvl, "max_states"); + rule->max_src_nodes = nvlist_get_number(nvl, "max_src_nodes"); + rule->max_src_states = nvlist_get_number(nvl, "max_src_states"); + rule->max_src_conn = nvlist_get_number(nvl, "max_src_conn"); + rule->max_src_conn_rate.limit = + nvlist_get_number(nvl, "max_src_conn_rate.limit"); + rule->max_src_conn_rate.seconds = + nvlist_get_number(nvl, "max_src_conn_rate.seconds"); + rule->qid = nvlist_get_number(nvl, "qid"); + rule->pqid = nvlist_get_number(nvl, "pqid"); + rule->prob = nvlist_get_number(nvl, "prob"); + rule->cuid = nvlist_get_number(nvl, "cuid"); + rule->cpid = nvlist_get_number(nvl, "cpid"); + + rule->return_icmp = nvlist_get_number(nvl, "return_icmp"); + rule->return_icmp6 = nvlist_get_number(nvl, "return_icmp6"); + rule->max_mss = nvlist_get_number(nvl, "max_mss"); + rule->scrub_flags = nvlist_get_number(nvl, "scrub_flags"); + + pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "uid"), &rule->uid); + pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "gid"), + (struct pf_rule_uid *)&rule->gid); + + rule->rule_flag = nvlist_get_number(nvl, "rule_flag"); + rule->action = nvlist_get_number(nvl, "action"); + rule->direction = nvlist_get_number(nvl, "direction"); + rule->log = nvlist_get_number(nvl, "log"); + rule->logif = nvlist_get_number(nvl, "logif"); + rule->quick = nvlist_get_number(nvl, "quick"); + rule->ifnot = nvlist_get_number(nvl, "ifnot"); + rule->match_tag_not = nvlist_get_number(nvl, "match_tag_not"); + rule->natpass = nvlist_get_number(nvl, "natpass"); + + rule->keep_state = nvlist_get_number(nvl, "keep_state"); + rule->af = nvlist_get_number(nvl, "af"); + rule->proto = nvlist_get_number(nvl, "proto"); + rule->type = nvlist_get_number(nvl, "type"); + rule->code = nvlist_get_number(nvl, "code"); + rule->flags = nvlist_get_number(nvl, "flags"); + rule->flagset = nvlist_get_number(nvl, "flagset"); + rule->min_ttl = nvlist_get_number(nvl, "min_ttl"); + rule->allow_opts = nvlist_get_number(nvl, "allow_opts"); + rule->rt = nvlist_get_number(nvl, "rt"); + rule->return_ttl = nvlist_get_number(nvl, "return_ttl"); + rule->tos = nvlist_get_number(nvl, "tos"); + rule->set_tos = nvlist_get_number(nvl, "set_tos"); + rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); + rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); + + rule->flush = nvlist_get_number(nvl, "flush"); + rule->prio = nvlist_get_number(nvl, "prio"); + pf_nvuint_8_array(nvl, "set_prio", 2, rule->set_prio, NULL); + + pf_nvdivert_to_divert(nvlist_get_nvlist(nvl, "divert"), rule); + + rule->u_states_cur = nvlist_get_number(nvl, "states_cur"); + rule->u_states_tot = nvlist_get_number(nvl, "states_tot"); + rule->u_src_nodes = nvlist_get_number(nvl, "src_nodes"); +} + + +int +pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, const char *anchor, + u_int32_t ruleset, struct pf_rule *rule, char *anchor_call) +{ + struct pfioc_nv nv; + nvlist_t *nvl; + void *nvlpacked; + int ret; + + nvl = nvlist_create(0); + if (nvl == 0) + return (ENOMEM); + + nvlist_add_number(nvl, "nr", nr); + nvlist_add_number(nvl, "ticket", ticket); + nvlist_add_string(nvl, "anchor", anchor); + nvlist_add_number(nvl, "ruleset", ruleset); + + nvlpacked = nvlist_pack(nvl, &nv.len); + if (nvlpacked == NULL) { + nvlist_destroy(nvl); + return (ENOMEM); + } + nv.data = malloc(8182); + nv.size = 8192; + assert(nv.len <= nv.size); + memcpy(nv.data, nvlpacked, nv.len); + nvlist_destroy(nvl); + nvl = NULL; + free(nvlpacked); + + ret = ioctl(dev, DIOCGETRULENV, &nv); + if (ret != 0) { + free(nv.data); + return (ret); + } + + nvl = nvlist_unpack(nv.data, nv.len, 0); + if (nvl == NULL) { + free(nv.data); + return (EIO); + } + + pf_nvrule_to_rule(nvlist_get_nvlist(nvl, "rule"), rule); + + if (anchor_call) + strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), + MAXPATHLEN); + + free(nv.data); + nvlist_destroy(nvl); + + return (0); +} diff --git a/sbin/pfctl/pfctl_ioctl.h b/sbin/pfctl/pfctl_ioctl.h new file mode 100644 index 000000000000..41dd0776854a --- /dev/null +++ b/sbin/pfctl/pfctl_ioctl.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) + * 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. + * + * $FreeBSD$ + */ + +#ifndef _PFCTL_IOCTL_H_ +#define _PFCTL_IOCTL_H_ + +#include + +int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, + const char *anchor, u_int32_t ruleset, struct pf_rule *rule, + char *anchor_call); + +#endif diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c index 599ed2424ebf..d3f0aa1bf3a4 100644 --- a/sbin/pfctl/pfctl_optimize.c +++ b/sbin/pfctl/pfctl_optimize.c @@ -1,1666 +1,1669 @@ /* $OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */ /* * Copyright (c) 2004 Mike Frantzen * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "pfctl_ioctl.h" #include "pfctl_parser.h" #include "pfctl.h" /* The size at which a table becomes faster than individual rules */ #define TABLE_THRESHOLD 6 /* #define OPT_DEBUG 1 */ #ifdef OPT_DEBUG # define DEBUG(str, v...) \ printf("%s: " str "\n", __FUNCTION__ , ## v) #else # define DEBUG(str, v...) ((void)0) #endif /* * A container that lets us sort a superblock to optimize the skip step jumps */ struct pf_skip_step { int ps_count; /* number of items */ TAILQ_HEAD( , pf_opt_rule) ps_rules; TAILQ_ENTRY(pf_skip_step) ps_entry; }; /* * A superblock is a block of adjacent rules of similar action. If there * are five PASS rules in a row, they all become members of a superblock. * Once we have a superblock, we are free to re-order any rules within it * in order to improve performance; if a packet is passed, it doesn't matter * who passed it. */ struct superblock { TAILQ_HEAD( , pf_opt_rule) sb_rules; TAILQ_ENTRY(superblock) sb_entry; struct superblock *sb_profiled_block; TAILQ_HEAD(skiplist, pf_skip_step) sb_skipsteps[PF_SKIP_COUNT]; }; TAILQ_HEAD(superblocks, superblock); /* * Description of the PF rule structure. */ enum { BARRIER, /* the presence of the field puts the rule in its own block */ BREAK, /* the field may not differ between rules in a superblock */ NOMERGE, /* the field may not differ between rules when combined */ COMBINED, /* the field may itself be combined with other rules */ DC, /* we just don't care about the field */ NEVER}; /* we should never see this field set?!? */ static struct pf_rule_field { const char *prf_name; int prf_type; size_t prf_offset; size_t prf_size; } pf_rule_desc[] = { #define PF_RULE_FIELD(field, ty) \ {#field, \ ty, \ offsetof(struct pf_rule, field), \ sizeof(((struct pf_rule *)0)->field)} /* * The presence of these fields in a rule put the rule in its own * superblock. Thus it will not be optimized. It also prevents the * rule from being re-ordered at all. */ PF_RULE_FIELD(label, BARRIER), PF_RULE_FIELD(prob, BARRIER), PF_RULE_FIELD(max_states, BARRIER), PF_RULE_FIELD(max_src_nodes, BARRIER), PF_RULE_FIELD(max_src_states, BARRIER), PF_RULE_FIELD(max_src_conn, BARRIER), PF_RULE_FIELD(max_src_conn_rate, BARRIER), PF_RULE_FIELD(anchor, BARRIER), /* for now */ /* * These fields must be the same between all rules in the same superblock. * These rules are allowed to be re-ordered but only among like rules. * For instance we can re-order all 'tag "foo"' rules because they have the * same tag. But we can not re-order between a 'tag "foo"' and a * 'tag "bar"' since that would change the meaning of the ruleset. */ PF_RULE_FIELD(tagname, BREAK), PF_RULE_FIELD(keep_state, BREAK), PF_RULE_FIELD(qname, BREAK), PF_RULE_FIELD(pqname, BREAK), PF_RULE_FIELD(rt, BREAK), PF_RULE_FIELD(allow_opts, BREAK), PF_RULE_FIELD(rule_flag, BREAK), PF_RULE_FIELD(action, BREAK), PF_RULE_FIELD(log, BREAK), PF_RULE_FIELD(quick, BREAK), PF_RULE_FIELD(return_ttl, BREAK), PF_RULE_FIELD(overload_tblname, BREAK), PF_RULE_FIELD(flush, BREAK), PF_RULE_FIELD(rpool, BREAK), PF_RULE_FIELD(logif, BREAK), /* * Any fields not listed in this structure act as BREAK fields */ /* * These fields must not differ when we merge two rules together but * their difference isn't enough to put the rules in different superblocks. * There are no problems re-ordering any rules with these fields. */ PF_RULE_FIELD(af, NOMERGE), PF_RULE_FIELD(ifnot, NOMERGE), PF_RULE_FIELD(ifname, NOMERGE), /* hack for IF groups */ PF_RULE_FIELD(match_tag_not, NOMERGE), PF_RULE_FIELD(match_tagname, NOMERGE), PF_RULE_FIELD(os_fingerprint, NOMERGE), PF_RULE_FIELD(timeout, NOMERGE), PF_RULE_FIELD(return_icmp, NOMERGE), PF_RULE_FIELD(return_icmp6, NOMERGE), PF_RULE_FIELD(uid, NOMERGE), PF_RULE_FIELD(gid, NOMERGE), PF_RULE_FIELD(direction, NOMERGE), PF_RULE_FIELD(proto, NOMERGE), PF_RULE_FIELD(type, NOMERGE), PF_RULE_FIELD(code, NOMERGE), PF_RULE_FIELD(flags, NOMERGE), PF_RULE_FIELD(flagset, NOMERGE), PF_RULE_FIELD(tos, NOMERGE), PF_RULE_FIELD(src.port, NOMERGE), PF_RULE_FIELD(dst.port, NOMERGE), PF_RULE_FIELD(src.port_op, NOMERGE), PF_RULE_FIELD(dst.port_op, NOMERGE), PF_RULE_FIELD(src.neg, NOMERGE), PF_RULE_FIELD(dst.neg, NOMERGE), /* These fields can be merged */ PF_RULE_FIELD(src.addr, COMBINED), PF_RULE_FIELD(dst.addr, COMBINED), /* We just don't care about these fields. They're set by the kernel */ PF_RULE_FIELD(skip, DC), PF_RULE_FIELD(evaluations, DC), PF_RULE_FIELD(packets, DC), PF_RULE_FIELD(bytes, DC), PF_RULE_FIELD(kif, DC), PF_RULE_FIELD(states_cur, DC), PF_RULE_FIELD(states_tot, DC), PF_RULE_FIELD(src_nodes, DC), PF_RULE_FIELD(nr, DC), PF_RULE_FIELD(entries, DC), PF_RULE_FIELD(qid, DC), PF_RULE_FIELD(pqid, DC), PF_RULE_FIELD(anchor_relative, DC), PF_RULE_FIELD(anchor_wildcard, DC), PF_RULE_FIELD(tag, DC), PF_RULE_FIELD(match_tag, DC), PF_RULE_FIELD(overload_tbl, DC), /* These fields should never be set in a PASS/BLOCK rule */ PF_RULE_FIELD(natpass, NEVER), PF_RULE_FIELD(max_mss, NEVER), PF_RULE_FIELD(min_ttl, NEVER), PF_RULE_FIELD(set_tos, NEVER), }; int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t, struct pf_rule_addr *); int addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *); int addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *); int block_feedback(struct pfctl *, struct superblock *); int combine_rules(struct pfctl *, struct superblock *); void comparable_rule(struct pf_rule *, const struct pf_rule *, int); int construct_superblocks(struct pfctl *, struct pf_opt_queue *, struct superblocks *); void exclude_supersets(struct pf_rule *, struct pf_rule *); int interface_group(const char *); int load_feedback_profile(struct pfctl *, struct superblocks *); int optimize_superblock(struct pfctl *, struct superblock *); int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *); void remove_from_skipsteps(struct skiplist *, struct superblock *, struct pf_opt_rule *, struct pf_skip_step *); int remove_identical_rules(struct pfctl *, struct superblock *); int reorder_rules(struct pfctl *, struct superblock *, int); int rules_combineable(struct pf_rule *, struct pf_rule *); void skip_append(struct superblock *, int, struct pf_skip_step *, struct pf_opt_rule *); int skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *); void skip_init(void); int skip_cmp_af(struct pf_rule *, struct pf_rule *); int skip_cmp_dir(struct pf_rule *, struct pf_rule *); int skip_cmp_dst_addr(struct pf_rule *, struct pf_rule *); int skip_cmp_dst_port(struct pf_rule *, struct pf_rule *); int skip_cmp_ifp(struct pf_rule *, struct pf_rule *); int skip_cmp_proto(struct pf_rule *, struct pf_rule *); int skip_cmp_src_addr(struct pf_rule *, struct pf_rule *); int skip_cmp_src_port(struct pf_rule *, struct pf_rule *); int superblock_inclusive(struct superblock *, struct pf_opt_rule *); void superblock_free(struct pfctl *, struct superblock *); static int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *, struct pf_rule *); static const char *skip_comparitors_names[PF_SKIP_COUNT]; #define PF_SKIP_COMPARITORS { \ { "ifp", PF_SKIP_IFP, skip_cmp_ifp }, \ { "dir", PF_SKIP_DIR, skip_cmp_dir }, \ { "af", PF_SKIP_AF, skip_cmp_af }, \ { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \ { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \ { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \ { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \ { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \ } static struct pfr_buffer table_buffer; static int table_identifier; int pfctl_optimize_ruleset(struct pfctl *pf, struct pf_ruleset *rs) { struct superblocks superblocks; struct pf_opt_queue opt_queue; struct superblock *block; struct pf_opt_rule *por; struct pf_rule *r; struct pf_rulequeue *old_rules; DEBUG("optimizing ruleset"); memset(&table_buffer, 0, sizeof(table_buffer)); skip_init(); TAILQ_INIT(&opt_queue); old_rules = rs->rules[PF_RULESET_FILTER].active.ptr; rs->rules[PF_RULESET_FILTER].active.ptr = rs->rules[PF_RULESET_FILTER].inactive.ptr; rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules; /* * XXX expanding the pf_opt_rule format throughout pfctl might allow * us to avoid all this copying. */ while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r, entries); if ((por = calloc(1, sizeof(*por))) == NULL) err(1, "calloc"); memcpy(&por->por_rule, r, sizeof(*r)); if (TAILQ_FIRST(&r->rpool.list) != NULL) { TAILQ_INIT(&por->por_rule.rpool.list); pfctl_move_pool(&r->rpool, &por->por_rule.rpool); } else bzero(&por->por_rule.rpool, sizeof(por->por_rule.rpool)); TAILQ_INSERT_TAIL(&opt_queue, por, por_entry); } TAILQ_INIT(&superblocks); if (construct_superblocks(pf, &opt_queue, &superblocks)) goto error; if (pf->optimize & PF_OPTIMIZE_PROFILE) { if (load_feedback_profile(pf, &superblocks)) goto error; } TAILQ_FOREACH(block, &superblocks, sb_entry) { if (optimize_superblock(pf, block)) goto error; } rs->anchor->refcnt = 0; while ((block = TAILQ_FIRST(&superblocks))) { TAILQ_REMOVE(&superblocks, block, sb_entry); while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); por->por_rule.nr = rs->anchor->refcnt++; if ((r = calloc(1, sizeof(*r))) == NULL) err(1, "calloc"); memcpy(r, &por->por_rule, sizeof(*r)); TAILQ_INIT(&r->rpool.list); pfctl_move_pool(&por->por_rule.rpool, &r->rpool); TAILQ_INSERT_TAIL( rs->rules[PF_RULESET_FILTER].active.ptr, r, entries); free(por); } free(block); } return (0); error: while ((por = TAILQ_FIRST(&opt_queue))) { TAILQ_REMOVE(&opt_queue, por, por_entry); if (por->por_src_tbl) { pfr_buf_clear(por->por_src_tbl->pt_buf); free(por->por_src_tbl->pt_buf); free(por->por_src_tbl); } if (por->por_dst_tbl) { pfr_buf_clear(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl); } free(por); } while ((block = TAILQ_FIRST(&superblocks))) { TAILQ_REMOVE(&superblocks, block, sb_entry); superblock_free(pf, block); } return (1); } /* * Go ahead and optimize a superblock */ int optimize_superblock(struct pfctl *pf, struct superblock *block) { #ifdef OPT_DEBUG struct pf_opt_rule *por; #endif /* OPT_DEBUG */ /* We have a few optimization passes: * 1) remove duplicate rules or rules that are a subset of other * rules * 2) combine otherwise identical rules with different IP addresses * into a single rule and put the addresses in a table. * 3) re-order the rules to improve kernel skip steps * 4) re-order the 'quick' rules based on feedback from the * active ruleset statistics * * XXX combine_rules() doesn't combine v4 and v6 rules. would just * have to keep af in the table container, make af 'COMBINE' and * twiddle the af on the merged rule * XXX maybe add a weighting to the metric on skipsteps when doing * reordering. sometimes two sequential tables will be better * that four consecutive interfaces. * XXX need to adjust the skipstep count of everything after PROTO, * since they aren't actually checked on a proto mismatch in * pf_test_{tcp, udp, icmp}() * XXX should i treat proto=0, af=0 or dir=0 special in skepstep * calculation since they are a DC? * XXX keep last skiplist of last superblock to influence this * superblock. '5 inet6 log' should make '3 inet6' come before '4 * inet' in the next superblock. * XXX would be useful to add tables for ports * XXX we can also re-order some mutually exclusive superblocks to * try merging superblocks before any of these optimization passes. * for instance a single 'log in' rule in the middle of non-logging * out rules. */ /* shortcut. there will be a lot of 1-rule superblocks */ if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry)) return (0); #ifdef OPT_DEBUG printf("--- Superblock ---\n"); TAILQ_FOREACH(por, &block->sb_rules, por_entry) { printf(" "); print_rule(&por->por_rule, por->por_rule.anchor ? por->por_rule.anchor->name : "", 1, 0); } #endif /* OPT_DEBUG */ if (remove_identical_rules(pf, block)) return (1); if (combine_rules(pf, block)) return (1); if ((pf->optimize & PF_OPTIMIZE_PROFILE) && TAILQ_FIRST(&block->sb_rules)->por_rule.quick && block->sb_profiled_block) { if (block_feedback(pf, block)) return (1); } else if (reorder_rules(pf, block, 0)) { return (1); } /* * Don't add any optimization passes below reorder_rules(). It will * have divided superblocks into smaller blocks for further refinement * and doesn't put them back together again. What once was a true * superblock might have been split into multiple superblocks. */ #ifdef OPT_DEBUG printf("--- END Superblock ---\n"); #endif /* OPT_DEBUG */ return (0); } /* * Optimization pass #1: remove identical rules */ int remove_identical_rules(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *por1, *por2, *por_next, *por2_next; struct pf_rule a, a2, b, b2; for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) { por_next = TAILQ_NEXT(por1, por_entry); for (por2 = por_next; por2; por2 = por2_next) { por2_next = TAILQ_NEXT(por2, por_entry); comparable_rule(&a, &por1->por_rule, DC); comparable_rule(&b, &por2->por_rule, DC); memcpy(&a2, &a, sizeof(a2)); memcpy(&b2, &b, sizeof(b2)); exclude_supersets(&a, &b); exclude_supersets(&b2, &a2); if (memcmp(&a, &b, sizeof(a)) == 0) { DEBUG("removing identical rule nr%d = *nr%d*", por1->por_rule.nr, por2->por_rule.nr); TAILQ_REMOVE(&block->sb_rules, por2, por_entry); if (por_next == por2) por_next = TAILQ_NEXT(por1, por_entry); free(por2); } else if (memcmp(&a2, &b2, sizeof(a2)) == 0) { DEBUG("removing identical rule *nr%d* = nr%d", por1->por_rule.nr, por2->por_rule.nr); TAILQ_REMOVE(&block->sb_rules, por1, por_entry); free(por1); break; } } } return (0); } /* * Optimization pass #2: combine similar rules with different addresses * into a single rule and a table */ int combine_rules(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *p1, *p2, *por_next; int src_eq, dst_eq; if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) { warnx("Must enable table loading for optimizations"); return (1); } /* First we make a pass to combine the rules. O(n log n) */ TAILQ_FOREACH(p1, &block->sb_rules, por_entry) { for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) { por_next = TAILQ_NEXT(p2, por_entry); src_eq = addrs_equal(&p1->por_rule.src, &p2->por_rule.src); dst_eq = addrs_equal(&p1->por_rule.dst, &p2->por_rule.dst); if (src_eq && !dst_eq && p1->por_src_tbl == NULL && p2->por_dst_tbl == NULL && p2->por_src_tbl == NULL && rules_combineable(&p1->por_rule, &p2->por_rule) && addrs_combineable(&p1->por_rule.dst, &p2->por_rule.dst)) { DEBUG("can combine rules nr%d = nr%d", p1->por_rule.nr, p2->por_rule.nr); if (p1->por_dst_tbl == NULL && add_opt_table(pf, &p1->por_dst_tbl, p1->por_rule.af, &p1->por_rule.dst)) return (1); if (add_opt_table(pf, &p1->por_dst_tbl, p1->por_rule.af, &p2->por_rule.dst)) return (1); p2->por_dst_tbl = p1->por_dst_tbl; if (p1->por_dst_tbl->pt_rulecount >= TABLE_THRESHOLD) { TAILQ_REMOVE(&block->sb_rules, p2, por_entry); free(p2); } } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL && p2->por_src_tbl == NULL && p2->por_dst_tbl == NULL && rules_combineable(&p1->por_rule, &p2->por_rule) && addrs_combineable(&p1->por_rule.src, &p2->por_rule.src)) { DEBUG("can combine rules nr%d = nr%d", p1->por_rule.nr, p2->por_rule.nr); if (p1->por_src_tbl == NULL && add_opt_table(pf, &p1->por_src_tbl, p1->por_rule.af, &p1->por_rule.src)) return (1); if (add_opt_table(pf, &p1->por_src_tbl, p1->por_rule.af, &p2->por_rule.src)) return (1); p2->por_src_tbl = p1->por_src_tbl; if (p1->por_src_tbl->pt_rulecount >= TABLE_THRESHOLD) { TAILQ_REMOVE(&block->sb_rules, p2, por_entry); free(p2); } } } } /* * Then we make a final pass to create a valid table name and * insert the name into the rules. */ for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) { por_next = TAILQ_NEXT(p1, por_entry); assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL); if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >= TABLE_THRESHOLD) { if (p1->por_src_tbl->pt_generated) { /* This rule is included in a table */ TAILQ_REMOVE(&block->sb_rules, p1, por_entry); free(p1); continue; } p1->por_src_tbl->pt_generated = 1; if ((pf->opts & PF_OPT_NOACTION) == 0 && pf_opt_create_table(pf, p1->por_src_tbl)) return (1); pf->tdirty = 1; if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_src_tbl->pt_name, PFR_TFLAG_CONST, 1, &p1->por_src_tbl->pt_nodes); memset(&p1->por_rule.src.addr, 0, sizeof(p1->por_rule.src.addr)); p1->por_rule.src.addr.type = PF_ADDR_TABLE; strlcpy(p1->por_rule.src.addr.v.tblname, p1->por_src_tbl->pt_name, sizeof(p1->por_rule.src.addr.v.tblname)); pfr_buf_clear(p1->por_src_tbl->pt_buf); free(p1->por_src_tbl->pt_buf); p1->por_src_tbl->pt_buf = NULL; } if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >= TABLE_THRESHOLD) { if (p1->por_dst_tbl->pt_generated) { /* This rule is included in a table */ TAILQ_REMOVE(&block->sb_rules, p1, por_entry); free(p1); continue; } p1->por_dst_tbl->pt_generated = 1; if ((pf->opts & PF_OPT_NOACTION) == 0 && pf_opt_create_table(pf, p1->por_dst_tbl)) return (1); pf->tdirty = 1; if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_dst_tbl->pt_name, PFR_TFLAG_CONST, 1, &p1->por_dst_tbl->pt_nodes); memset(&p1->por_rule.dst.addr, 0, sizeof(p1->por_rule.dst.addr)); p1->por_rule.dst.addr.type = PF_ADDR_TABLE; strlcpy(p1->por_rule.dst.addr.v.tblname, p1->por_dst_tbl->pt_name, sizeof(p1->por_rule.dst.addr.v.tblname)); pfr_buf_clear(p1->por_dst_tbl->pt_buf); free(p1->por_dst_tbl->pt_buf); p1->por_dst_tbl->pt_buf = NULL; } } return (0); } /* * Optimization pass #3: re-order rules to improve skip steps */ int reorder_rules(struct pfctl *pf, struct superblock *block, int depth) { struct superblock *newblock; struct pf_skip_step *skiplist; struct pf_opt_rule *por; int i, largest, largest_list, rule_count = 0; TAILQ_HEAD( , pf_opt_rule) head; /* * Calculate the best-case skip steps. We put each rule in a list * of other rules with common fields */ for (i = 0; i < PF_SKIP_COUNT; i++) { TAILQ_FOREACH(por, &block->sb_rules, por_entry) { TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i], ps_entry) { if (skip_compare(i, skiplist, por) == 0) break; } if (skiplist == NULL) { if ((skiplist = calloc(1, sizeof(*skiplist))) == NULL) err(1, "calloc"); TAILQ_INIT(&skiplist->ps_rules); TAILQ_INSERT_TAIL(&block->sb_skipsteps[i], skiplist, ps_entry); } skip_append(block, i, skiplist, por); } } TAILQ_FOREACH(por, &block->sb_rules, por_entry) rule_count++; /* * Now we're going to ignore any fields that are identical between * all of the rules in the superblock and those fields which differ * between every rule in the superblock. */ largest = 0; for (i = 0; i < PF_SKIP_COUNT; i++) { skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); if (skiplist->ps_count == rule_count) { DEBUG("(%d) original skipstep '%s' is all rules", depth, skip_comparitors_names[i]); skiplist->ps_count = 0; } else if (skiplist->ps_count == 1) { skiplist->ps_count = 0; } else { DEBUG("(%d) original skipstep '%s' largest jump is %d", depth, skip_comparitors_names[i], skiplist->ps_count); if (skiplist->ps_count > largest) largest = skiplist->ps_count; } } if (largest == 0) { /* Ugh. There is NO commonality in the superblock on which * optimize the skipsteps optimization. */ goto done; } /* * Now we're going to empty the superblock rule list and re-create * it based on a more optimal skipstep order. */ TAILQ_INIT(&head); while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); TAILQ_INSERT_TAIL(&head, por, por_entry); } while (!TAILQ_EMPTY(&head)) { largest = 1; /* * Find the most useful skip steps remaining */ for (i = 0; i < PF_SKIP_COUNT; i++) { skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); if (skiplist->ps_count > largest) { largest = skiplist->ps_count; largest_list = i; } } if (largest <= 1) { /* * Nothing useful left. Leave remaining rules in order. */ DEBUG("(%d) no more commonality for skip steps", depth); while ((por = TAILQ_FIRST(&head))) { TAILQ_REMOVE(&head, por, por_entry); TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry); } } else { /* * There is commonality. Extract those common rules * and place them in the ruleset adjacent to each * other. */ skiplist = TAILQ_FIRST(&block->sb_skipsteps[ largest_list]); DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d", depth, skip_comparitors_names[largest_list], largest, TAILQ_FIRST(&TAILQ_FIRST(&block-> sb_skipsteps [largest_list])->ps_rules)-> por_rule.nr); TAILQ_REMOVE(&block->sb_skipsteps[largest_list], skiplist, ps_entry); /* * There may be further commonality inside these * rules. So we'll split them off into they're own * superblock and pass it back into the optimizer. */ if (skiplist->ps_count > 2) { if ((newblock = calloc(1, sizeof(*newblock))) == NULL) { warn("calloc"); return (1); } TAILQ_INIT(&newblock->sb_rules); for (i = 0; i < PF_SKIP_COUNT; i++) TAILQ_INIT(&newblock->sb_skipsteps[i]); TAILQ_INSERT_BEFORE(block, newblock, sb_entry); DEBUG("(%d) splitting off %d rules from superblock @ #%d", depth, skiplist->ps_count, TAILQ_FIRST(&skiplist->ps_rules)-> por_rule.nr); } else { newblock = block; } while ((por = TAILQ_FIRST(&skiplist->ps_rules))) { TAILQ_REMOVE(&head, por, por_entry); TAILQ_REMOVE(&skiplist->ps_rules, por, por_skip_entry[largest_list]); TAILQ_INSERT_TAIL(&newblock->sb_rules, por, por_entry); /* Remove this rule from all other skiplists */ remove_from_skipsteps(&block->sb_skipsteps[ largest_list], block, por, skiplist); } free(skiplist); if (newblock != block) if (reorder_rules(pf, newblock, depth + 1)) return (1); } } done: for (i = 0; i < PF_SKIP_COUNT; i++) { while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) { TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist, ps_entry); free(skiplist); } } return (0); } /* * Optimization pass #4: re-order 'quick' rules based on feedback from the * currently running ruleset */ int block_feedback(struct pfctl *pf, struct superblock *block) { TAILQ_HEAD( , pf_opt_rule) queue; struct pf_opt_rule *por1, *por2; u_int64_t total_count = 0; struct pf_rule a, b; /* * Walk through all of the profiled superblock's rules and copy * the counters onto our rules. */ TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) { comparable_rule(&a, &por1->por_rule, DC); total_count += por1->por_rule.packets[0] + por1->por_rule.packets[1]; TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { if (por2->por_profile_count) continue; comparable_rule(&b, &por2->por_rule, DC); if (memcmp(&a, &b, sizeof(a)) == 0) { por2->por_profile_count = por1->por_rule.packets[0] + por1->por_rule.packets[1]; break; } } } superblock_free(pf, block->sb_profiled_block); block->sb_profiled_block = NULL; /* * Now we pull all of the rules off the superblock and re-insert them * in sorted order. */ TAILQ_INIT(&queue); while ((por1 = TAILQ_FIRST(&block->sb_rules)) != NULL) { TAILQ_REMOVE(&block->sb_rules, por1, por_entry); TAILQ_INSERT_TAIL(&queue, por1, por_entry); } while ((por1 = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, por1, por_entry); /* XXX I should sort all of the unused rules based on skip steps */ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { if (por1->por_profile_count > por2->por_profile_count) { TAILQ_INSERT_BEFORE(por2, por1, por_entry); break; } } #ifdef __FreeBSD__ if (por2 == NULL) #else if (por2 == TAILQ_END(&block->sb_rules)) #endif TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry); } return (0); } /* * Load the current ruleset from the kernel and try to associate them with * the ruleset we're optimizing. */ int load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks) { struct superblock *block, *blockcur; struct superblocks prof_superblocks; struct pf_opt_rule *por; struct pf_opt_queue queue; struct pfioc_rule pr; struct pf_rule a, b; int nr, mnr; TAILQ_INIT(&queue); TAILQ_INIT(&prof_superblocks); memset(&pr, 0, sizeof(pr)); pr.rule.action = PF_PASS; if (ioctl(pf->dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (1); } mnr = pr.nr; DEBUG("Loading %d active rules for a feedback profile", mnr); for (nr = 0; nr < mnr; ++nr) { struct pf_ruleset *rs; if ((por = calloc(1, sizeof(*por))) == NULL) { warn("calloc"); return (1); } pr.nr = nr; - if (ioctl(pf->dev, DIOCGETRULE, &pr)) { - warn("DIOCGETRULES"); + + if (pfctl_get_rule(pf->dev, nr, pr.ticket, "", PF_PASS, + &pr.rule, pr.anchor_call)) { + warn("DIOCGETRULENV"); return (1); } memcpy(&por->por_rule, &pr.rule, sizeof(por->por_rule)); rs = pf_find_or_create_ruleset(pr.anchor_call); por->por_rule.anchor = rs->anchor; if (TAILQ_EMPTY(&por->por_rule.rpool.list)) memset(&por->por_rule.rpool, 0, sizeof(por->por_rule.rpool)); TAILQ_INSERT_TAIL(&queue, por, por_entry); /* XXX pfctl_get_pool(pf->dev, &pr.rule.rpool, nr, pr.ticket, * PF_PASS, pf->anchor) ??? * ... pfctl_clear_pool(&pr.rule.rpool) */ } if (construct_superblocks(pf, &queue, &prof_superblocks)) return (1); /* * Now we try to associate the active ruleset's superblocks with * the superblocks we're compiling. */ block = TAILQ_FIRST(superblocks); blockcur = TAILQ_FIRST(&prof_superblocks); while (block && blockcur) { comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, BREAK); comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule, BREAK); if (memcmp(&a, &b, sizeof(a)) == 0) { /* The two superblocks lined up */ block->sb_profiled_block = blockcur; } else { DEBUG("superblocks don't line up between #%d and #%d", TAILQ_FIRST(&block->sb_rules)->por_rule.nr, TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr); break; } block = TAILQ_NEXT(block, sb_entry); blockcur = TAILQ_NEXT(blockcur, sb_entry); } /* Free any superblocks we couldn't link */ while (blockcur) { block = TAILQ_NEXT(blockcur, sb_entry); superblock_free(pf, blockcur); blockcur = block; } return (0); } /* * Compare a rule to a skiplist to see if the rule is a member */ int skip_compare(int skipnum, struct pf_skip_step *skiplist, struct pf_opt_rule *por) { struct pf_rule *a, *b; if (skipnum >= PF_SKIP_COUNT || skipnum < 0) errx(1, "skip_compare() out of bounds"); a = &por->por_rule; b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule; return ((skip_comparitors[skipnum])(a, b)); } /* * Add a rule to a skiplist */ void skip_append(struct superblock *superblock, int skipnum, struct pf_skip_step *skiplist, struct pf_opt_rule *por) { struct pf_skip_step *prev; skiplist->ps_count++; TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]); /* Keep the list of skiplists sorted by whichever is larger */ while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) && prev->ps_count < skiplist->ps_count) { TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum], skiplist, ps_entry); TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry); } } /* * Remove a rule from the other skiplist calculations. */ void remove_from_skipsteps(struct skiplist *head, struct superblock *block, struct pf_opt_rule *por, struct pf_skip_step *active_list) { struct pf_skip_step *sk, *next; struct pf_opt_rule *p2; int i, found; for (i = 0; i < PF_SKIP_COUNT; i++) { sk = TAILQ_FIRST(&block->sb_skipsteps[i]); if (sk == NULL || sk == active_list || sk->ps_count <= 1) continue; found = 0; do { TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i]) if (p2 == por) { TAILQ_REMOVE(&sk->ps_rules, p2, por_skip_entry[i]); found = 1; sk->ps_count--; break; } } while (!found && (sk = TAILQ_NEXT(sk, ps_entry))); if (found && sk) { /* Does this change the sorting order? */ while ((next = TAILQ_NEXT(sk, ps_entry)) && next->ps_count > sk->ps_count) { TAILQ_REMOVE(head, sk, ps_entry); TAILQ_INSERT_AFTER(head, next, sk, ps_entry); } #ifdef OPT_DEBUG next = TAILQ_NEXT(sk, ps_entry); assert(next == NULL || next->ps_count <= sk->ps_count); #endif /* OPT_DEBUG */ } } } /* Compare two rules AF field for skiplist construction */ int skip_cmp_af(struct pf_rule *a, struct pf_rule *b) { if (a->af != b->af || a->af == 0) return (1); return (0); } /* Compare two rules DIRECTION field for skiplist construction */ int skip_cmp_dir(struct pf_rule *a, struct pf_rule *b) { if (a->direction == 0 || a->direction != b->direction) return (1); return (0); } /* Compare two rules DST Address field for skiplist construction */ int skip_cmp_dst_addr(struct pf_rule *a, struct pf_rule *b) { if (a->dst.neg != b->dst.neg || a->dst.addr.type != b->dst.addr.type) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ switch (a->dst.addr.type) { case PF_ADDR_ADDRMASK: if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr, sizeof(a->dst.addr.v.a.addr)) || memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, sizeof(a->dst.addr.v.a.mask)) || (a->dst.addr.v.a.addr.addr32[0] == 0 && a->dst.addr.v.a.addr.addr32[1] == 0 && a->dst.addr.v.a.addr.addr32[2] == 0 && a->dst.addr.v.a.addr.addr32[3] == 0)) return (1); return (0); case PF_ADDR_DYNIFTL: if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 || a->dst.addr.iflags != b->dst.addr.iflags || memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, sizeof(a->dst.addr.v.a.mask))) return (1); return (0); case PF_ADDR_NOROUTE: case PF_ADDR_URPFFAILED: return (0); case PF_ADDR_TABLE: return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname)); } return (1); } /* Compare two rules DST port field for skiplist construction */ int skip_cmp_dst_port(struct pf_rule *a, struct pf_rule *b) { /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op || a->dst.port[0] != b->dst.port[0] || a->dst.port[1] != b->dst.port[1]) return (1); return (0); } /* Compare two rules IFP field for skiplist construction */ int skip_cmp_ifp(struct pf_rule *a, struct pf_rule *b) { if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0') return (1); return (a->ifnot != b->ifnot); } /* Compare two rules PROTO field for skiplist construction */ int skip_cmp_proto(struct pf_rule *a, struct pf_rule *b) { return (a->proto != b->proto || a->proto == 0); } /* Compare two rules SRC addr field for skiplist construction */ int skip_cmp_src_addr(struct pf_rule *a, struct pf_rule *b) { if (a->src.neg != b->src.neg || a->src.addr.type != b->src.addr.type) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ switch (a->src.addr.type) { case PF_ADDR_ADDRMASK: if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr, sizeof(a->src.addr.v.a.addr)) || memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, sizeof(a->src.addr.v.a.mask)) || (a->src.addr.v.a.addr.addr32[0] == 0 && a->src.addr.v.a.addr.addr32[1] == 0 && a->src.addr.v.a.addr.addr32[2] == 0 && a->src.addr.v.a.addr.addr32[3] == 0)) return (1); return (0); case PF_ADDR_DYNIFTL: if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 || a->src.addr.iflags != b->src.addr.iflags || memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, sizeof(a->src.addr.v.a.mask))) return (1); return (0); case PF_ADDR_NOROUTE: case PF_ADDR_URPFFAILED: return (0); case PF_ADDR_TABLE: return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname)); } return (1); } /* Compare two rules SRC port field for skiplist construction */ int skip_cmp_src_port(struct pf_rule *a, struct pf_rule *b) { if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op || a->src.port[0] != b->src.port[0] || a->src.port[1] != b->src.port[1]) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ return (0); } void skip_init(void) { struct { char *name; int skipnum; int (*func)(struct pf_rule *, struct pf_rule *); } comps[] = PF_SKIP_COMPARITORS; int skipnum, i; for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) { for (i = 0; i < sizeof(comps)/sizeof(*comps); i++) if (comps[i].skipnum == skipnum) { skip_comparitors[skipnum] = comps[i].func; skip_comparitors_names[skipnum] = comps[i].name; } } for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) if (skip_comparitors[skipnum] == NULL) errx(1, "Need to add skip step comparitor to pfctl?!"); } /* * Add a host/netmask to a table */ int add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af, struct pf_rule_addr *addr) { #ifdef OPT_DEBUG char buf[128]; #endif /* OPT_DEBUG */ static int tablenum = 0; struct node_host node_host; if (*tbl == NULL) { if ((*tbl = calloc(1, sizeof(**tbl))) == NULL || ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) == NULL) err(1, "calloc"); (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS; SIMPLEQ_INIT(&(*tbl)->pt_nodes); /* This is just a temporary table name */ snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d", PF_OPT_TABLE_PREFIX, tablenum++); DEBUG("creating table <%s>", (*tbl)->pt_name); } memset(&node_host, 0, sizeof(node_host)); node_host.af = af; node_host.addr = addr->addr; #ifdef OPT_DEBUG DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af, &node_host.addr.v.a.addr, buf, sizeof(buf)), unmask(&node_host.addr.v.a.mask, af)); #endif /* OPT_DEBUG */ if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) { warn("failed to add host"); return (1); } if (pf->opts & PF_OPT_VERBOSE) { struct node_tinit *ti; if ((ti = calloc(1, sizeof(*ti))) == NULL) err(1, "malloc"); if ((ti->host = malloc(sizeof(*ti->host))) == NULL) err(1, "malloc"); memcpy(ti->host, &node_host, sizeof(*ti->host)); SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries); } (*tbl)->pt_rulecount++; if ((*tbl)->pt_rulecount == TABLE_THRESHOLD) DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name); return (0); } /* * Do the dirty work of choosing an unused table name and creating it. * (be careful with the table name, it might already be used in another anchor) */ int pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl) { static int tablenum; struct pfr_table *t; if (table_buffer.pfrb_type == 0) { /* Initialize the list of tables */ table_buffer.pfrb_type = PFRB_TABLES; for (;;) { pfr_buf_grow(&table_buffer, table_buffer.pfrb_size); table_buffer.pfrb_size = table_buffer.pfrb_msize; if (pfr_get_tables(NULL, table_buffer.pfrb_caddr, &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS)) err(1, "pfr_get_tables"); if (table_buffer.pfrb_size <= table_buffer.pfrb_msize) break; } table_identifier = arc4random(); } /* XXX would be *really* nice to avoid duplicating identical tables */ /* Now we have to pick a table name that isn't used */ again: DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name, PF_OPT_TABLE_PREFIX, table_identifier, tablenum); snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d", PF_OPT_TABLE_PREFIX, table_identifier, tablenum); PFRB_FOREACH(t, &table_buffer) { if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) { /* Collision. Try again */ DEBUG("wow, table <%s> in use. trying again", tbl->pt_name); table_identifier = arc4random(); goto again; } } tablenum++; if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1, pf->astack[0]->name, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) { warn("failed to create table %s in %s", tbl->pt_name, pf->astack[0]->name); return (1); } return (0); } /* * Partition the flat ruleset into a list of distinct superblocks */ int construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue, struct superblocks *superblocks) { struct superblock *block = NULL; struct pf_opt_rule *por; int i; while (!TAILQ_EMPTY(opt_queue)) { por = TAILQ_FIRST(opt_queue); TAILQ_REMOVE(opt_queue, por, por_entry); if (block == NULL || !superblock_inclusive(block, por)) { if ((block = calloc(1, sizeof(*block))) == NULL) { warn("calloc"); return (1); } TAILQ_INIT(&block->sb_rules); for (i = 0; i < PF_SKIP_COUNT; i++) TAILQ_INIT(&block->sb_skipsteps[i]); TAILQ_INSERT_TAIL(superblocks, block, sb_entry); } TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry); } return (0); } /* * Compare two rule addresses */ int addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b) { if (a->neg != b->neg) return (0); return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0); } /* * The addresses are not equal, but can we combine them into one table? */ int addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b) { if (a->addr.type != PF_ADDR_ADDRMASK || b->addr.type != PF_ADDR_ADDRMASK) return (0); if (a->neg != b->neg || a->port_op != b->port_op || a->port[0] != b->port[0] || a->port[1] != b->port[1]) return (0); return (1); } /* * Are we allowed to combine these two rules */ int rules_combineable(struct pf_rule *p1, struct pf_rule *p2) { struct pf_rule a, b; comparable_rule(&a, p1, COMBINED); comparable_rule(&b, p2, COMBINED); return (memcmp(&a, &b, sizeof(a)) == 0); } /* * Can a rule be included inside a superblock */ int superblock_inclusive(struct superblock *block, struct pf_opt_rule *por) { struct pf_rule a, b; int i, j; /* First check for hard breaks */ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) { if (pf_rule_desc[i].prf_type == BARRIER) { for (j = 0; j < pf_rule_desc[i].prf_size; j++) if (((char *)&por->por_rule)[j + pf_rule_desc[i].prf_offset] != 0) return (0); } } /* per-rule src-track is also a hard break */ if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK) return (0); /* * Have to handle interface groups separately. Consider the following * rules: * block on EXTIFS to any port 22 * pass on em0 to any port 22 * (where EXTIFS is an arbitrary interface group) * The optimizer may decide to re-order the pass rule in front of the * block rule. But what if EXTIFS includes em0??? Such a reordering * would change the meaning of the ruleset. * We can't just lookup the EXTIFS group and check if em0 is a member * because the user is allowed to add interfaces to a group during * runtime. * Ergo interface groups become a defacto superblock break :-( */ if (interface_group(por->por_rule.ifname) || interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) { if (strcasecmp(por->por_rule.ifname, TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0) return (0); } comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE); comparable_rule(&b, &por->por_rule, NOMERGE); if (memcmp(&a, &b, sizeof(a)) == 0) return (1); #ifdef OPT_DEBUG for (i = 0; i < sizeof(por->por_rule); i++) { int closest = -1; if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) { for (j = 0; j < sizeof(pf_rule_desc) / sizeof(*pf_rule_desc); j++) { if (i >= pf_rule_desc[j].prf_offset && i < pf_rule_desc[j].prf_offset + pf_rule_desc[j].prf_size) { DEBUG("superblock break @ %d due to %s", por->por_rule.nr, pf_rule_desc[j].prf_name); return (0); } if (i > pf_rule_desc[j].prf_offset) { if (closest == -1 || i-pf_rule_desc[j].prf_offset < i-pf_rule_desc[closest].prf_offset) closest = j; } } if (closest >= 0) DEBUG("superblock break @ %d on %s+%xh", por->por_rule.nr, pf_rule_desc[closest].prf_name, i - pf_rule_desc[closest].prf_offset - pf_rule_desc[closest].prf_size); else DEBUG("superblock break @ %d on field @ %d", por->por_rule.nr, i); return (0); } } #endif /* OPT_DEBUG */ return (0); } /* * Figure out if an interface name is an actual interface or actually a * group of interfaces. */ int interface_group(const char *ifname) { int s; struct ifgroupreq ifgr; if (ifname == NULL || !ifname[0]) return (0); s = get_query_socket(); memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) { if (errno == ENOENT) return (0); else err(1, "SIOCGIFGMEMB"); } return (1); } /* * Make a rule that can directly compared by memcmp() */ void comparable_rule(struct pf_rule *dst, const struct pf_rule *src, int type) { int i; /* * To simplify the comparison, we just zero out the fields that are * allowed to be different and then do a simple memcmp() */ memcpy(dst, src, sizeof(*dst)); for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) if (pf_rule_desc[i].prf_type >= type) { #ifdef OPT_DEBUG assert(pf_rule_desc[i].prf_type != NEVER || *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0); #endif /* OPT_DEBUG */ memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0, pf_rule_desc[i].prf_size); } } /* * Remove superset information from two rules so we can directly compare them * with memcmp() */ void exclude_supersets(struct pf_rule *super, struct pf_rule *sub) { if (super->ifname[0] == '\0') memset(sub->ifname, 0, sizeof(sub->ifname)); if (super->direction == PF_INOUT) sub->direction = PF_INOUT; if ((super->proto == 0 || super->proto == sub->proto) && super->flags == 0 && super->flagset == 0 && (sub->flags || sub->flagset)) { sub->flags = super->flags; sub->flagset = super->flagset; } if (super->proto == 0) sub->proto = 0; if (super->src.port_op == 0) { sub->src.port_op = 0; sub->src.port[0] = 0; sub->src.port[1] = 0; } if (super->dst.port_op == 0) { sub->dst.port_op = 0; sub->dst.port[0] = 0; sub->dst.port[1] = 0; } if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg && !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 && super->src.addr.v.a.mask.addr32[1] == 0 && super->src.addr.v.a.mask.addr32[2] == 0 && super->src.addr.v.a.mask.addr32[3] == 0) memset(&sub->src.addr, 0, sizeof(sub->src.addr)); else if (super->src.addr.type == PF_ADDR_ADDRMASK && sub->src.addr.type == PF_ADDR_ADDRMASK && super->src.neg == sub->src.neg && super->af == sub->af && unmask(&super->src.addr.v.a.mask, super->af) < unmask(&sub->src.addr.v.a.mask, sub->af) && super->src.addr.v.a.addr.addr32[0] == (sub->src.addr.v.a.addr.addr32[0] & super->src.addr.v.a.mask.addr32[0]) && super->src.addr.v.a.addr.addr32[1] == (sub->src.addr.v.a.addr.addr32[1] & super->src.addr.v.a.mask.addr32[1]) && super->src.addr.v.a.addr.addr32[2] == (sub->src.addr.v.a.addr.addr32[2] & super->src.addr.v.a.mask.addr32[2]) && super->src.addr.v.a.addr.addr32[3] == (sub->src.addr.v.a.addr.addr32[3] & super->src.addr.v.a.mask.addr32[3])) { /* sub->src.addr is a subset of super->src.addr/mask */ memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr)); } if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg && !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 && super->dst.addr.v.a.mask.addr32[1] == 0 && super->dst.addr.v.a.mask.addr32[2] == 0 && super->dst.addr.v.a.mask.addr32[3] == 0) memset(&sub->dst.addr, 0, sizeof(sub->dst.addr)); else if (super->dst.addr.type == PF_ADDR_ADDRMASK && sub->dst.addr.type == PF_ADDR_ADDRMASK && super->dst.neg == sub->dst.neg && super->af == sub->af && unmask(&super->dst.addr.v.a.mask, super->af) < unmask(&sub->dst.addr.v.a.mask, sub->af) && super->dst.addr.v.a.addr.addr32[0] == (sub->dst.addr.v.a.addr.addr32[0] & super->dst.addr.v.a.mask.addr32[0]) && super->dst.addr.v.a.addr.addr32[1] == (sub->dst.addr.v.a.addr.addr32[1] & super->dst.addr.v.a.mask.addr32[1]) && super->dst.addr.v.a.addr.addr32[2] == (sub->dst.addr.v.a.addr.addr32[2] & super->dst.addr.v.a.mask.addr32[2]) && super->dst.addr.v.a.addr.addr32[3] == (sub->dst.addr.v.a.addr.addr32[3] & super->dst.addr.v.a.mask.addr32[3])) { /* sub->dst.addr is a subset of super->dst.addr/mask */ memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr)); } if (super->af == 0) sub->af = 0; } void superblock_free(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *por; while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); if (por->por_src_tbl) { if (por->por_src_tbl->pt_buf) { pfr_buf_clear(por->por_src_tbl->pt_buf); free(por->por_src_tbl->pt_buf); } free(por->por_src_tbl); } if (por->por_dst_tbl) { if (por->por_dst_tbl->pt_buf) { pfr_buf_clear(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl->pt_buf); } free(por->por_dst_tbl); } free(por); } if (block->sb_profiled_block) superblock_free(pf, block->sb_profiled_block); free(block); }