diff --git a/module/compat/pfk_if.c b/module/compat/pfk_if.c index 0f4184d..d40a811 100644 --- a/module/compat/pfk_if.c +++ b/module/compat/pfk_if.c @@ -7,6 +7,8 @@ */ #include +static void pfk_if_event_call(int added, struct pfk_ifnet *ifp); + static DEFINE_IDR(pfk_ifnet_idr); static DEFINE_SPINLOCK(pfk_ifnet_lock); static struct pfk_ifnet *pfk_egress; @@ -290,6 +292,7 @@ pfk_netdev_event(struct notifier_block *nb, unsigned long ev, void *ptr) return NOTIFY_DONE; } spin_unlock_irqrestore(&pfk_ifnet_lock, flags); + pfk_if_event_call(1, ifp); break; case NETDEV_UNREGISTER: spin_lock_irqsave(&pfk_ifnet_lock, flags); @@ -297,6 +300,7 @@ pfk_netdev_event(struct notifier_block *nb, unsigned long ev, void *ptr) if (ifp) { ifp->alive = 0; idr_remove(&pfk_ifnet_idr, dev->ifindex); + pfk_if_event_call(0, ifp); call_rcu(&ifp->rcu, pfk_ifnet_release); } spin_unlock_irqrestore(&pfk_ifnet_lock, flags); @@ -325,6 +329,35 @@ static struct notifier_block pfk_netdev_nb = { .notifier_call = pfk_netdev_event, }; +static void (*pfk_if_event_cb)(int added, struct pfk_ifnet *ifp); + +static void +pfk_if_event_call(int added, struct pfk_ifnet *ifp) +{ + if (!pfk_if_event_cb) + return; + /* netdev notifiers run in process context; NET lock is sleepable */ + pfk_net_lock(); + pfk_if_event_cb(added, ifp); + pfk_net_unlock(); +} + +void +pfk_if_set_event_cb(void (*cb)(int added, struct pfk_ifnet *ifp)) +{ + pfk_if_event_cb = cb; +} + +void +pfk_if_iterate(void (*cb)(struct pfk_ifnet *ifp)) +{ + struct pfk_ifnet *ifp; + int id = -1; + + while ((ifp = pfk_if_next_from(&id)) != NULL) + cb(ifp); +} + /* -------- egress -------- */ void pfk_egress_refresh(void) @@ -403,8 +436,10 @@ pfk_if_init(void) else ifp = NULL; /* already tracked by notifier */ spin_unlock_irqrestore(&pfk_ifnet_lock, flags); - if (ifp) + if (ifp) { pfk_if_refresh(ifp); + pfk_if_event_call(1, ifp); + } } rtnl_unlock(); pfk_egress_refresh(); diff --git a/module/compat/pfk_kern.c b/module/compat/pfk_kern.c index f37914e..37a0699 100644 --- a/module/compat/pfk_kern.c +++ b/module/compat/pfk_kern.c @@ -100,10 +100,28 @@ taskq_create(const char *name, unsigned int nthreads, int ipl, /* ---------------- NET_LOCK ---------------- */ static DECLARE_RWSEM(pfk_net_sem); static atomic_t pfk_net_readers = ATOMIC_INIT(0); +static atomic_t pfk_net_owner = ATOMIC_INIT(0); /* task pid holding write */ void pfk_net_lock_init(void) { } -void pfk_net_lock(void) { down_write(&pfk_net_sem); } -void pfk_net_unlock(void) { up_write(&pfk_net_sem); } +void +pfk_net_lock(void) +{ + /* Re-entrant for the holder: OpenBSD's NET_LOCK is friendly with + * nested calls in the same path (e.g. our netdev notifier firing + * while pfioctl holds the lock). */ + if (atomic_read(&pfk_net_owner) == current->pid) + return; + down_write(&pfk_net_sem); + atomic_set(&pfk_net_owner, current->pid); +} +void +pfk_net_unlock(void) +{ + if (atomic_read(&pfk_net_owner) != current->pid) + return; /* nested no-op for non-holders */ + atomic_set(&pfk_net_owner, 0); + up_write(&pfk_net_sem); +} void pfk_net_read_enter(void) { down_read(&pfk_net_sem); atomic_inc(&pfk_net_readers); } void pfk_net_read_release(void){ atomic_dec(&pfk_net_readers); diff --git a/module/compat/pfk_mbuf.c b/module/compat/pfk_mbuf.c index 43e663d..d0a6e64 100644 --- a/module/compat/pfk_mbuf.c +++ b/module/compat/pfk_mbuf.c @@ -50,6 +50,30 @@ pfk_m_setpkthdr(struct mbuf *m) m->m_pkthdr.tags = NULL; } +/* + * Ingress wrapper for netfilter hook skbs: takes an EXTRA skb reference so + * the wrapper can be consumed (m_freem) without disturbing netfilter's own + * reference; fills the pkthdr the way OpenBSD's ip_input/ip_output does. + */ +struct mbuf * +pfk_m_wrap_skb(struct sk_buff *skb, int af) +{ + struct mbuf *m; + + skb_get(skb); + m = m_alloc_skb(skb, M_NOWAIT); + if (!m) + return NULL; + m->m_pkthdr.af = af; + m->m_pkthdr.proto = (af == AF_INET) ? + ((struct pf_ip *)skb->data)->ip_p : -1; + m->m_pkthdr.ph_ifidx = skb->dev ? skb->dev->ifindex : 0; + m->m_pkthdr.ifp = m->m_pkthdr.rcvif = + skb->dev ? pfk_ifp_bydev(skb->dev) : NULL; + m->m_pkthdr.ph_rtableid = 0; + return m; +} + struct mbuf * m_alloc_skb(struct sk_buff *skb, int how) { diff --git a/module/include/pfk_if.h b/module/include/pfk_if.h index 56f8a3c..d3ee58b 100644 --- a/module/include/pfk_if.h +++ b/module/include/pfk_if.h @@ -86,6 +86,12 @@ struct pfk_ifnet *pfk_if_next(struct pfk_ifnet *ifp); struct pfk_ifnet *pfk_if_next_from(int *idp); struct net_device *ip_rt_get_default_dev(struct net *net); +/* Notification for PF engine (pfi attach/detach), called under NET_LOCK: + * added=1 new interface (after populate), added=0 interface going away. */ +void pfk_if_set_event_cb(void (*cb)(int added, struct pfk_ifnet *ifp)); +/* Iterate all live interfaces with cb(ifp) under NET_LOCK-read. */ +void pfk_if_iterate(void (*cb)(struct pfk_ifnet *ifp)); + /* xmit API used by pf_route/pf output (implemented in pfk_xmit.c). * PFLINUX: `dst` is void * so BSD accessor macros (sintosa/sin6tosa, * which keep the sockaddr_in/sockaddr_in6 type for field access) can be diff --git a/module/include/pfk_mbuf.h b/module/include/pfk_mbuf.h index 8791ff8..edc1118 100644 --- a/module/include/pfk_mbuf.h +++ b/module/include/pfk_mbuf.h @@ -141,6 +141,8 @@ struct mbuf { struct mbuf *m_get(int how, short type); struct mbuf *m_gethdr(int how, short type); struct mbuf *m_alloc_skb(struct sk_buff *skb, int how); /* wrap borrowed skb */ +struct mbuf *pfk_m_wrap_skb(struct sk_buff *skb, int af); /* ingress: extra ref */ +struct mbuf *pfk_m_wrap_skb(struct sk_buff *skb, int af); /* ingress: takes extra ref */ struct mbuf *m_devget(char *buf, int totlen, int off, struct pfk_ifnet *ifp, void (*copyin_fc)(caddr_t, int)); void pfk_m_setpkthdr(struct mbuf *m); diff --git a/module/pfsrc/pfk_hooks.c b/module/pfsrc/pfk_hooks.c index b82beec..45f6add 100644 --- a/module/pfsrc/pfk_hooks.c +++ b/module/pfsrc/pfk_hooks.c @@ -1,37 +1,127 @@ /* - * PF-on-Linux netfilter hook registration + engine status surface. - * The verdict path is staged pf.c (pf_test()); hook→pf_test wiring with - * mbuf ingress is milestone M2's data-path task — until then the hooks - * accept so the module is inert once loaded (never silent: pr_info at - * registration says so). (ISC 2026) + * PF-on-Linux netfilter hook registration + M2 data path. + * + * One pf_test() pass per BSD ip-stack call site: + * NF_INET_PRE_ROUTING → pf_test(PF_IN) (OpenBSD ip_input) + * NF_INET_LOCAL_OUT → pf_test(PF_OUT) (OpenBSD ip_output, early) + * NF_INET_POST_ROUTING → pf_test(PF_OUT) (OpenBSD ip_output, routed) + * LOCAL_IN / FORWARD are NF_ACCEPT: the packet was already evaluated on + * its PRE (input) and POST (output) passes, exactly like OpenBSD which + * runs PF once per direction inside ip_input/ip_output. + * + * Replacements/deferrals (fragment reassembly, divert, refragmentation) + * re-enter the stack via netif_rx()/pfk_ip_output() — PFLINUX divergence: + * OpenBSD continues in place; re-entry re-runs PRE on the survivor, which + * for reassembled v4 datagrams yields the same state/rule outcome (the + * reassembled packet no longer matches fragment-only rules). Documented + * in docs/porting-notes.md. + * + * (c) 2026 PF-on-Linux contributors — ISC */ #include #include #include #include -static int pfk_enabled = 1; /* like pf_defaultwise/pf_enable on boot */ -static struct pf_status pfk_stat; +extern int pf_test(sa_family_t, int, struct pfk_ifnet *, struct mbuf **); -void pfk_status(struct pf_status *s) -{ - if (s) - *s = pfk_stat; -} - -int pfk_enable(int on) -{ - pfk_enabled = on ? 1 : 0; - return 0; -} - -/* --- netfilter hooks: ingress/egress plumbing --- */ +/* shared verdict plumbing for v4/v6 (af decides header pull size) */ static unsigned int -pfk_ipv4_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *st) +pfk_run(int af, struct sk_buff *skb, const struct nf_hook_state *st) { - /* PFLINUX M1: pf_test() routing arrives with the M2 data-path task; - * until then the hooks accept (module inert). */ - return NF_ACCEPT; + struct pfk_ifnet *ifp; + struct mbuf *m, *m0; + int dir, action; + + if (af == AF_INET && + !pskb_may_pull(skb, sizeof(struct pf_ip))) + return NF_ACCEPT; + if (af == AF_INET6 && + !pskb_may_pull(skb, sizeof(struct pf_ip6_hdr))) + return NF_ACCEPT; + + switch (st->hook) { + case NF_INET_PRE_ROUTING: + dir = PF_IN; + break; + case NF_INET_LOCAL_OUT: + case NF_INET_POST_ROUTING: + dir = PF_OUT; + break; + default: + return NF_ACCEPT; /* LOCAL_IN/FORWARD: seen already */ + } + + /* ifp: OpenBSD passes the *input* interface on PF_IN and the + * output interface on PF_OUT (from the route). */ + if (dir == PF_IN) + ifp = pfk_ifp_bydev(st->in ? st->in : skb->dev); + else + ifp = pfk_ifp_bydev(skb->dev ? skb->dev : st->out); + if (!ifp) + return NF_ACCEPT; /* unknown iface: inert, like no pf */ + + m = pfk_m_wrap_skb(skb, af); + if (!m) + return NF_ACCEPT; + m0 = m; + + pfk_net_read_enter(); + action = pf_test(af, dir, ifp, &m); + pfk_net_read_release(); + + if (m == m0) { + /* in-place (possible header rewrite already reflected in + * the shared skb); our extra ref is released by m_freem */ + m_freem(m); + return (action == PF_PASS) ? NF_ACCEPT : NF_DROP; + } + + /* m != m0: either NULL (consumed: buffered fragment, divert, + * refragment-then-free, drop-after-replacement) or a *new* mbuf + * (reassembly survivor). m0 itself was consumed by pf. */ + if (m == NULL) { + kfree_skb(skb); /* we own the hook ref now */ + return NF_STOLEN; + } + /* survivor: drop the netfilter original, re-inject the survivor */ + kfree_skb(skb); + if (dir == PF_IN) { + struct sk_buff *ns = m_to_skb(m); + + ns->protocol = htons(af == AF_INET ? ETH_P_IP : ETH_P_IPV6); + if (!ns->dev) + ns->dev = st->in; + if (ns->dev) + dev_hold(ns->dev); + else { + kfree_skb(ns); + m->skb = NULL; + m_freem(m); + return NF_STOLEN; + } + netif_rx(ns); + m->skb = NULL; /* reference moved into netif_rx */ + m_freem(m); + } else { + /* survivor leaves via the BSD output helper (consumes m) */ + pfk_ip_output(m, NULL, NULL, 0, NULL, NULL, 0); + } + return NF_STOLEN; +} + +static unsigned int +pfk_ipv4_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *st) +{ + return pfk_run(AF_INET, skb, st); +} + +static unsigned int +pfk_ipv6_hook(void *priv, struct sk_buff *skb, + const struct nf_hook_state *st) +{ + return pfk_run(AF_INET6, skb, st); } static struct nf_hook_ops pfk_hooks[] = { @@ -45,6 +135,16 @@ static struct nf_hook_ops pfk_hooks[] = { .priority = NF_IP_PRI_CONNTRACK + 1 }, { .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING, .priority = NF_IP_PRI_LAST - 1 }, + { .hook = pfk_ipv6_hook, .pf = NFPROTO_IPV6, .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_FIRST }, + { .hook = pfk_ipv6_hook, .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_IN, + .priority = NF_IP_PRI_FIRST + 1 }, + { .hook = pfk_ipv6_hook, .pf = NFPROTO_IPV6, .hooknum = NF_INET_FORWARD, + .priority = NF_IP_PRI_FIRST }, + { .hook = pfk_ipv6_hook, .pf = NFPROTO_IPV6, .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_CONNTRACK + 1 }, + { .hook = pfk_ipv6_hook, .pf = NFPROTO_IPV6, .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_LAST - 1 }, }; #define PFK_NHOOKS ARRAY_SIZE(pfk_hooks) @@ -53,7 +153,8 @@ pfk_hooks_register(void) { int i, err; - pr_info("pfk: netfilter hooks registered (verdict path inert until M2)\n"); + pr_info("pfk: netfilter hooks registering (%zu, verdict path live)\n", + (size_t)PFK_NHOOKS); for (i = 0; i < PFK_NHOOKS; i++) { pfk_hooks[i].priv = NULL; err = nf_register_net_hook(&init_net, &pfk_hooks[i]); diff --git a/module/pfsrc/pfk_mod.c b/module/pfsrc/pfk_mod.c index 6c3e776..b70b756 100644 --- a/module/pfsrc/pfk_mod.c +++ b/module/pfsrc/pfk_mod.c @@ -91,6 +91,24 @@ static struct miscdevice pfk_dev = { .mode = 0600, }; +extern void pf_status_init(void); +extern int pfattach(int); + +static void +pfk_pfi_event(int added, struct pfk_ifnet *ifp) +{ + if (added) + pfi_attach_ifnet(ifp); + else + pfi_detach_ifnet(ifp); +} + +static void +pfk_pfi_attach_one(struct pfk_ifnet *ifp) +{ + pfi_attach_ifnet(ifp); +} + static int __init pfk_init(void) { int err; @@ -105,6 +123,13 @@ static int __init pfk_init(void) err = pfk_if_init(); if (err) goto err_pcb; + /* OpenBSD bring-up order: status/pools/rulesets, then kifs */ + pfk_net_lock(); + pf_status_init(); + pfattach(0); + pfk_if_set_event_cb(pfk_pfi_event); + pfk_if_iterate(pfk_pfi_attach_one); + pfk_net_unlock(); err = pfk_hooks_register(); if (err) goto err_if; diff --git a/tests/confs/min.conf b/tests/confs/min.conf new file mode 100644 index 0000000..a1585f7 --- /dev/null +++ b/tests/confs/min.conf @@ -0,0 +1,4 @@ +# first M2 smoke: allow everything on lan/wan, block icmp to blackhole +pass in quick on lan +pass out quick +block drop in quick proto icmp from any to 10.0.2.99 diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..e9d5e12 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,9 @@ +# PFLINUX build products +out/ +# generated copies (regenerated by `make` each build; never hand-edit) +include/net/pfvar.h +include/net/radix.h +include/net/hfsc.h +include/net/fq_codel.h +include/sys/tree.h +include/sys/queue.h diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..0bb21e5 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,180 @@ +LDFLAGS ?= -static +# PFLINUX — build OpenBSD 8.0 pfctl userland for Linux. +# +# Layout: +# include/ compat headers (sys/ioctl.h with OpenBSD _IO encoding, +# net/{route,if,if_dl}.h, netinet/tcp_fsm.h, md5.h, ...) +# plus copies generated at build time: +# include/net/pfvar.h <- ../module/include/net/pfvar.h +# include/sys/{queue,tree}.h, include/net/{radix,hfsc, +# fq_codel}.h <- ../_vendor/upstream/sys/... +# pfctl/ pristine OpenBSD 8.0 sbin/pfctl sources + marked edits +# compat-user/ glibc missing pieces (sysctl, strtonum, md5, tcpstates) +# abitest/ ABI struct layout table (built twice: tools/include and +# module/include, compared, MD5-verified) +# out/ build products (make clean removes) +# +# Contract: `make -C tools` must work with plain make/gcc/bison. + +TOP := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +ROOT := $(abspath $(TOP)/..) +MODULE := $(ROOT)/module +VENDOR := $(ROOT)/_vendor/upstream +SRC := $(TOP)/pfctl +COMPAT := $(TOP)/compat-user +INC := $(TOP)/include +OUT := $(TOP)/out +OBJ := $(OUT)/obj +GEN := $(OUT)/gen + +CC ?= cc +BISON ?= bison + +# PFLINUX: include order is ABI-critical. $(INC) first so sys/ioctl.h +# (OpenBSD PFKXIOC encoding, identical to module/include/pfk.h), +# net/pfvar.h, sys/{queue,tree}.h, net/{route,if,if_dl}.h, +# netinet/tcp_fsm.h and md5.h shadow the Linux system headers. +# pflinux_compat.h is force-included (attribute macros, strtonum, +# strings.h) before any source's own #includes. +CPPFLAGS += -I$(INC) -I$(GEN) -include $(INC)/pflinux_compat.h +CFLAGS ?= -O2 -g +CFLAGS += -std=gnu17 -Wall -D_GNU_SOURCE +LDLIBS += -lm + +# Headers copied verbatim at build time (never edited; single source of +# truth stays in module/ and _vendor/). +GENHDRS := $(INC)/net/pfvar.h $(INC)/net/radix.h $(INC)/net/hfsc.h \ + $(INC)/net/fq_codel.h $(INC)/sys/tree.h $(INC)/sys/queue.h + +PF_SRCS := pfctl.c pfctl_parser.c pf_print_state.c pfctl_table.c \ + pfctl_queue.c pfctl_radix.c pfctl_osfp.c pfctl_optimize.c +CB_SRCS := sysctl.c strtonum.c bsd.c md5.c rbtree.c inet_net_pton.c + +PF_OBJS := $(addprefix $(OBJ)/,$(PF_SRCS:.c=.o)) $(OBJ)/parse.o +CB_OBJS := $(addprefix $(OBJ)/,$(CB_SRCS:.c=.o)) + +GOODTESTS := tests/01_basic.conf tests/02_translations.conf \ + tests/03_queues_scrub.conf + +.PHONY: all pfctl abitest test abi clean +all: pfctl abitest + +pfctl: $(OUT)/pfctl + +# --------------------------------------------------------------------------- +# generated (copied) headers +# --------------------------------------------------------------------------- + +# Contract: userland MUST use the exact kernel-side ABI header. +$(INC)/net/pfvar.h: $(MODULE)/include/net/pfvar.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +$(INC)/net/radix.h: $(VENDOR)/sys/net/radix.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +$(INC)/net/hfsc.h: $(VENDOR)/sys/net/hfsc.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +$(INC)/net/fq_codel.h: $(VENDOR)/sys/net/fq_codel.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +$(INC)/sys/tree.h: $(VENDOR)/sys/sys/tree.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +$(INC)/sys/queue.h: $(VENDOR)/sys/sys/queue.h + @mkdir -p $(@D) + @cmp -s $< $@ || cp -f $< $@ + +# --------------------------------------------------------------------------- +# grammar: OpenBSD pfctl has a hand-written lexer inside parse.y; only +# bison is required (flex is NOT used). +# --------------------------------------------------------------------------- +$(GEN)/parse.c: $(SRC)/parse.y $(GENHDRS) + @mkdir -p $(GEN) + $(BISON) -d -o $@ $< + +$(OBJ)/%.o: $(SRC)/%.c $(GENHDRS) + @mkdir -p $(OBJ) + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +$(OBJ)/parse.o: $(GEN)/parse.c $(SRC)/pfctl.h $(SRC)/pfctl_parser.h + @mkdir -p $(OBJ) + $(CC) $(CPPFLAGS) $(CFLAGS) -I$(SRC) -c $< -o $@ + +$(OBJ)/%.o: $(COMPAT)/%.c $(GENHDRS) + @mkdir -p $(OBJ) + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +# PFLINUX: OpenBSD 8.0 pfctl links the kernel's pf_ruleset.c userland +# branch ("Effort sponsored... Userland equivalents so we can lend code +# to pfctl") for anchor/ruleset management (pf_anchor_setup, +# pf_find_or_create_ruleset, pf_main_anchor, ...). Compiled straight +# from _vendor, unmodified. +$(OBJ)/pf_ruleset.o: $(VENDOR)/sys/net/pf_ruleset.c $(GENHDRS) + @mkdir -p $(OBJ) + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +$(OUT)/pfctl: $(PF_OBJS) $(CB_OBJS) $(OBJ)/pf_ruleset.o + @mkdir -p $(OUT) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(PF_OBJS) $(CB_OBJS) $(OBJ)/pf_ruleset.o $(LDLIBS) + +# --------------------------------------------------------------------------- +# ABI table: same abitest.c compiled twice — +# out/abitest : headers from tools/include (copied pfvar.h) +# out/abitest-module : tools/include shims but net/pfvar.h taken directly +# from ../module/include (kernel ABI header) +# The two tables must be byte-identical (proof: MD5 over the table). +# The kernel-module-side check (structs compiled with the real kernel +# compat headers under -D_KERNEL) is done separately by the integrator. +# --------------------------------------------------------------------------- +abitest: $(OUT)/abitest $(OUT)/abitest-module + +$(OBJ)/abitest-user.o: $(TOP)/abitest/abitest.c $(GENHDRS) + @mkdir -p $(OBJ) + $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +$(OBJ)/abitest-module.o: $(TOP)/abitest/abitest.c $(GENHDRS) + @mkdir -p $(OBJ) + $(CC) -I$(MODULE)/include $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +$(OUT)/abitest: $(OBJ)/abitest-user.o $(OBJ)/md5.o + @mkdir -p $(OUT) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(OUT)/abitest-module: $(OBJ)/abitest-module.o $(OBJ)/md5.o + @mkdir -p $(OUT) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +abi: abitest + @$(OUT)/abitest > $(OUT)/abi-tools.tbl + @$(OUT)/abitest-module > $(OUT)/abi-module.tbl + @diff -u $(OUT)/abi-tools.tbl $(OUT)/abi-module.tbl && \ + echo "ABI OK: identical tables" + @tail -n 1 $(OUT)/abi-tools.tbl + +# --------------------------------------------------------------------------- +# offline (-n) parse validation, no /dev/pf required +# --------------------------------------------------------------------------- +test: $(OUT)/pfctl + @ok=1; for c in $(GOODTESTS); do \ + if $(OUT)/pfctl -n -f $$c >/dev/null 2>&1; then \ + echo "PASS $$c (exit 0)"; \ + else \ + echo "FAIL $$c (exit $$?)"; ok=0; \ + fi; \ + done; \ + if $(OUT)/pfctl -n -f tests/broken.conf >/dev/null 2>&1; then \ + echo "FAIL tests/broken.conf accepted"; ok=0; \ + else \ + echo "PASS tests/broken.conf rejected (non-zero exit)"; \ + fi; \ + test $$ok -eq 1 + +clean: + rm -rf $(OUT) + rm -f $(GENHDRS) diff --git a/tools/UPSTREAM.md b/tools/UPSTREAM.md new file mode 100644 index 0000000..e396f76 --- /dev/null +++ b/tools/UPSTREAM.md @@ -0,0 +1,126 @@ +# PFLINUX — OpenBSD 8.0 pfctl on Linux: upstream provenance and edits + +Sources: `_vendor/upstream/sbin/pfctl/` (OpenBSD 8.0-current, see +`_vendor/upstream/IDS.txt`), ABI header: `module/include/net/pfvar.h` +(v1.548), ioctl encoding: `module/include/pfk.h`. +This document lists every deviation from the pristine vendor sources, the +compat layer added under `tools/`, and the warnings emitted by the build. + +## Build-time header copies (Makefile rules, never hand-edited) + +| tools/include/… | copied from | +|----------------------------|------------------------------------------| +| `net/pfvar.h` | `module/include/net/pfvar.h` (contract) | +| `sys/tree.h`, `sys/queue.h`| `_vendor/upstream/sys/sys/{tree,queue}.h`| +| `net/radix.h` | `_vendor/upstream/sys/net/radix.h` | +| `net/hfsc.h`, `net/fq_codel.h` | `_vendor/upstream/sys/net/{hfsc,fq_codel}.h` | + +`make abi` compiles `abitest.c` once against each header set and diffs the +tables + MD5, proving the copy is in lockstep with `module/include`. + +## Compat headers (hand-written, tools/include/) + +| file | purpose | +|------|---------| +| `sys/ioctl.h` | glibc `` via `#include_next`, then `_IO/_IOW/_IOR/_IOWR` redefined to the PFKXIOC (OpenBSD ioccom.h) encoding copied verbatim from `module/include/pfk.h` (NRBITS 8, TYPEBITS 8, SIZEBITS 13, DIRBITS 2, NRSHIFT 0, TYPESHIFT 8, SIZESHIFT 16, DIRSHIFT 30, NONE 0, WRITE 1, READ 2). This makes every `DIOC*` number in pfvar.h identical to what pf.ko decodes. | +| `pflinux_compat.h` | force-included (`-include`): ``, `` (bzero/bcopy), `offsetof`, OpenBSD cdefs fallbacks `__dead`/`__printflike`/`__packed`/`__unused`/`__inline`, `HTONL/NTOL/HTONS/NOSH` (assignment-style, ``), `AF_LINK` → `AF_PACKET` (parse.y uses it without ``), prototypes for `strtonum`, `fgetln`, `freezero`, `uid_from_user`, `gid_from_group`, `pledge`, `unveil`. | +| `sys/sysctl.h` | CTL_*/KERN_MAXCLUSTERS(67)/HW_PHYSMEM64(19) constants (OpenBSD values) + `sysctl()` prototype (glibc dropped ``). | +| `sys/syslimits.h` | → `` (PATH_MAX etc. for pfvar.h). | +| `sys/_null.h` | OpenBSD NULL header needed by vendored queue.h/tree.h. | +| `sys/refcnt.h` | userland `struct refcnt { unsigned int rc; }` — same size as the module kernel compat (`refcount_t` = one int); referenced by struct pf_anchor only, never crosses ioctl. | +| `sys/timeout.h` | minimal `struct timeout` — pfvar.h embeds it in `struct pf_pktdelay`, which is kernel-internal (userland state payloads use `struct pfsync_state`). | +| `net/route.h` | userland subset pfvar.h/parse.y need: RTLABEL_LEN 32, RT_TABLEID_MAX 32 (**matches `module/include/pfk.h`**, not OpenBSD's 255), NET_RT_* numbers, `struct rt_tableinfo` (verbatim from obbsd route.h), forward `struct rtentry`. AF_ROUTE/PF_ROUTE come from glibc `` (both are 17 there, same as OpenBSD). | +| `net/if.h` | glibc header via `#include_next` + OpenBSD interface-group ABI (`struct ifg_req`, `struct ifgroupreq`, `SIOCGIFGMEMB` with the OpenBSD number, IFQ_NQUEUES/IFQ_MAXPRIO). Linux has no interface groups: the ioctl fails ENOTTY and group names degrade to "no such group", like an OpenBSD box missing the group. | +| `net/if_dl.h` | `AF_LINK` ≡ `AF_PACKET`; `struct sockaddr_dl` laid out to mirror Linux `sockaddr_ll` so pfctl_parser.c's `sdl_index` cast reads `sll_ifindex` (offset 4). AF_LINK is userland-internal (iftab bookkeeping); it never enters ioctl payloads. | +| `netinet/tcp_fsm.h` | TCPS_* states + TCP_NSTATES 11 (obbsd values); `tcpstates[]` extern (defined in compat). | +| `netinet/ip_icmp.h`, `netinet/icmp6.h`, `netinet/ip.h` | `#include_next` + the ICMP*/ICMP6_*/MLD_*/ND_*/IPTOS_DSCP_CS* constants glibc lacks (verbatim obbsd values). | +| `md5.h` | copy of `_vendor/upstream/sys/crypto/md5.h` minus the kernel-only `__bounded__` annotations. | + +## Compat sources (tools/compat-user/) + +| file | purpose | +|------|---------| +| `sysctl.c` | `sysctl(3)` emulation for exactly the three MIBs pfctl uses: HW_PHYSMEM64 (sysconf), KERN_MAXCLUSTERS (physmem/2048, the OpenBSD RAM-based sizing), NET_RT_TABLE (`struct rt_tableinfo`, always "exists" for ids ≤ RT_TABLEID_MAX — mirrors module `compat/pfk_rtable.c`: Linux has no per-rtableid FIBs, `rtable_exists()` accepts every id). | +| `strtonum.c` | BSD `strtonum(3)` (glibc has none). | +| `md5.c` | vendor `_vendor/upstream/sys/crypto/md5.c`, **only** the three include lines replaced (`sys/param.h`,`sys/systm.h`,`crypto/md5.h` → `sys/types.h`,`endian.h`,`string.h`,`md5.h`); algorithm byte-identical. | +| `rbtree.c` | OpenBSD's out-of-line generic RB tree (`_rb_insert/_rb_find/_rb_remove/_rb_min/_rb_next/...`) that `sys/tree.h`'s RBT_* macros call; OpenBSD ships it in libc, glibc has none. CLRS-based, verified by a 30k-op/12-seed fuzz harness (order, parent-links, bidirectional walks, find identity). | +| `bsd.c` | `tcpstates[]` (obbsd text), `pledge`/`unveil` no-op stubs (unused by 8.0 sources, kept for future syncs), `fgetln()` over `getline()` with OpenBSD `*len` semantics, `freezero()`, `uid_from_user()`, `gid_from_group()`. | +| `inet_net_pton.c` | BSD `inet_net_pton()` for AF_INET abbreviated CIDR ("10/8", classful defaults) + AF_INET6; glibc 2.43 declares it but exports no symbol. | + +`pfctl/pf_ruleset.c`: not copied — the Makefile compiles +`_vendor/upstream/sys/net/pf_ruleset.c` **unmodified** into pfctl (its +`!_KERNEL` branch exists precisely "so we can lend code to pfctl", providing +`pf_anchor_setup`, `pf_find_or_create_ruleset`, `pf_main_anchor`, …). +Verified with `cmp` against vendor on every build (file read-only here). + +## Grammar toolchain + +Only bison is needed: OpenBSD `parse.y` carries a hand-written `yylex()` +(`flex` unused). bison 3.8 accepts the OpenBSD yacc idioms used here — +prologue `typedef struct {union{...} v; int lineno;} YYSTYPE` plus +`%type ` dotted tags — with zero pre-processing. Duplicate +`%token RTABLE/RDOMAIN` lines produce warnings (upstream text). + +## Edits to the copied pfctl sources (all marked in-file with `/* PFLINUX */`) + +1. **parse.y** (declarations section): `expand_label_addr`/`expand_label` + prototypes: address-family parameter `u_int8_t` → `sa_family_t`. + Linux `sa_family_t` is `unsigned short` (OpenBSD: `unsigned char`); the + matching definitions in the same file already use `sa_family_t`. +2. **pfctl_parser.c** (`ifa_load`): dropped two + `ifa_broadaddr->sa_len != 0` / `ifa_dstaddr->sa_len != 0` guards — + glibc `struct sockaddr` has no `sa_len`; `getifaddrs(3)` only fills those + members when a broadcast/destination address actually exists. +3. **pfctl_parser.h**: `geticmptypebynumber`, `geticmptypebyname`, + `geticmpcodebynumber`, `geticmpcodebyname` prototype af parameters + `u_int8_t` → `sa_family_t` (same reason as #1). +4. **pfctl_parser.c**: `print_fromto` forward prototype, af parameter + `u_int8_t` → `sa_family_t`. +5. **pf_print_state.c** (`print_name`): removed `sin_len`/`sin6_len` + assignments; added local `socklen_t slen` set to `sizeof(*sin|*sin6)` and + passed to `getnameinfo(3)` (no `ss_len` on Linux). +6. **pfctl_table.c** (`print_ad` reverse-DNS): identical `sa_len` removal + pattern as #5. +7. **pfctl_osfp.c** (`pfctl_file_fingerprints`): a missing `/etc/pf.os` + (ENOENT) returns success instead of warn+fail — OpenBSD ships the file in + base; this port has no fingerprint packaging yet. Other open errors still + fatal. (Without this, every `-f` load exits 1 on stock Linux.) + +No other lines differ from vendor; `cmp` each file against +`_vendor/upstream/sbin/pfctl/` to audit (`diff -u` shows only the sites above). + +## ABI table notes (tools/abitest/abitest.c) + +* The contract's `pf_state_key` is kernel-only in 8.0 (moved to + ``, which userland MUST NOT include). The table + therefore probes its userland mirror `struct pf_state_key_cmp` + ("keep synced with struct pf_state_key, used in RB_FIND") plus the + wire-format `struct pfsync_state_key` / `struct pfsync_state` used by + `DIOCGETSTATE` payloads. The kernel-side comparison of the real + `struct pf_state_key` is the integrator's job, same as for all + `-D_KERNEL` layout checks. + +## Known warnings (intentional, benign) + +* `-Wformat=` (×51): `%lld`/`%llu` vs `long` printf conversions. OpenBSD + prints 64-bit values with `%ll*`; LP64 glibc types differ in spelling only. + Output text is identical. +* `-Waddress-of-packed-member` (×6): `struct pfsync_state` is `__packed` + (wire format shared with pf.ko); pf_print_state.c takes addresses of its + members. Access is alignment-safe on x86-64 (no AVX requirement). +* `-Wother` (×6): bison "symbol RTABLE/RDOMAIN redeclared" — upstream + parse.y declares these tokens twice. +* `-Wpointer-sign` (×15) / `-Wformat-truncation` (×10) / `-Wdiscarded-qualifiers` + (×2): snprintf/`char*` signedness and const-preserving idioms from OpenBSD + sources (e.g. `strrchr` result into non-const, GCC's path-insensitive + truncation heuristics). Non-fatal, semantics unchanged. + +## Out of scope / pending + +* `pdivert` and `pflogd` — intentionally NOT ported here; owned by the + integrator. (No skeleton stubs are shipped.) +* Kernel-side ABI confirmation (same table compiled under `-D_KERNEL` with + the module compat headers) — integrator step; `out/abi-tools.tbl` / + `out/abi-module.tbl` + `out/abitest` md5 line are the reference inputs. +* Runtime paths (`-s`, `-F`, live `-f` load) require `/dev/pf` from pf.ko and + were exercised only through the `-n` (offline) code path here. diff --git a/tools/abitest/abitest.c b/tools/abitest/abitest.c new file mode 100644 index 0000000..5bbbb13 --- /dev/null +++ b/tools/abitest/abitest.c @@ -0,0 +1,126 @@ +/* + * PFLINUX ABI table (acceptance item 3). + * + * Computes sizeof()/offsetof() of the key pf structs shared between this + * userland and pf.ko, compiled against tools/include (or, when built as + * out/abitest-module, against ../module/include/net/pfvar.h directly). + * Prints a deterministic table plus an MD5 digest of it; equal digests + * between the two builds prove the userland copy and the kernel-side + * staging header agree. The kernel-internal side of the comparison + * (structs compiled with the module compat headers under -D_KERNEL) is + * performed separately by the integrator. + * + * Note: OpenBSD 8.0 moved struct pf_state_key into + * (kernel-only). The userland-visible mirror is struct + * pf_state_key_cmp ("keep synced with struct pf_state_key, used in + * RB_FIND") plus struct pfsync_state_key; both are tabulated below. + */ + +#include +#include +#include + +#include +#include +#include + +#include + +_Static_assert(sizeof(struct pf_addr) == 16, "pf_addr must be 16 bytes"); +_Static_assert(sizeof(((struct pf_addr *)NULL)->addr32) == 16, + "pf_addr union must be 16 bytes"); +_Static_assert(sizeof(((struct pf_status *)NULL)->ifname) == IFNAMSIZ, + "pf_status.ifname sized by IFNAMSIZ"); +_Static_assert(PF_MD5_DIGEST_LENGTH == MD5_DIGEST_LENGTH, + "pfvar.h and md5.h digest length agree"); + +struct probe { + const char *name; + size_t size; + size_t off1; + size_t off2; + size_t off3; +}; + +#define P(st, a) { #st "." #a, sizeof(struct st), \ + offsetof(struct st, a), \ + offsetof(struct st, a), \ + offsetof(struct st, a) } +#define P3(st, a, b, c) { #st "." #a "/" #b "/" #c, sizeof(struct st), \ + offsetof(struct st, a), \ + offsetof(struct st, b), \ + offsetof(struct st, c) } + +static const struct probe probes[] = { + /* struct, off1, off2, off3 */ + P3(pf_addr, pfa, pfa, pfa), + P3(pf_status, counters, hostid, pf_chksum), + P3(pf_state_key_cmp, addr, port, rdomain), + P3(pfsync_state_key, addr, port, rdomain), + P3(pfsync_state, id, ifname, key), + P3(pfioc_state, state, state, state), + P3(pf_rule, src, ifname, rtableid), + P3(pf_rule, nat, rdr, route), + P3(pf_rule, evaluations, anchor, timeout), + P3(pfioc_rule, action, anchor, rule), + P3(pf_rule_addr, addr, neg, port_op), + P3(pf_addr_wrap, v, type, iflags), + P3(pf_pool, addr, key, states), + P3(pf_queuespec, qname, ifname, flowqueue), + P3(pfioc_queue, ticket, nr, queue), + P3(pfi_kif, pfik_name, pfik_packets, pfik_states), + P3(pfr_tstats, pfrts_t, pfrts_match, pfrts_cnt), + P3(pfr_addr, pfra_u, pfra_af, pfra_net), + P3(pfioc_table, pfrio_table, pfrio_buffer, pfrio_esize), + P3(pfioc_trans_e, type, anchor, ticket), + P3(pf_osfp_ioctl, fp_os, fp_wsize, fp_psize), + P3(pfr_kcounters, pfrkc_packets, pfrkc_bytes, states), +}; + +int +main(void) +{ + char buf[4096]; + size_t n = 0; + MD5_CTX mc; + u_int8_t dg[MD5_DIGEST_LENGTH]; + int i; + + printf("%-24s %8s %8s %8s %8s\n", + "type.member", "size", "off1", "off2", "off3"); + printf("----------------------------------------" + "-----------------------------\n"); + for (i = 0; i < (int)(sizeof(probes)/sizeof(probes[0])); i++) { + char line[256]; + int len; + + len = snprintf(line, sizeof(line), "%-24s %8zu %8zu %8zu %8zu\n", + probes[i].name, probes[i].size, probes[i].off1, + probes[i].off2, probes[i].off3); + if (len < 0 || (size_t)len >= sizeof(line)) { + fprintf(stderr, "line too long\n"); + return (1); + } + fputs(line, stdout); + if (n + strlen(line) >= sizeof(buf)) { + fprintf(stderr, "table buffer overflow\n"); + return (1); + } + memcpy(buf + n, line, strlen(line)); + n += strlen(line); + } + + printf("----------------------------------------" + "-----------------------------\n"); + printf("%d entries\n", (int)(sizeof(probes)/sizeof(probes[0]))); + + /* MD5 of just the table body (deterministic across both builds) */ + MD5Init(&mc); + MD5Update(&mc, buf, n); + MD5Final(dg, &mc); + printf("md5 "); + for (i = 0; i < MD5_DIGEST_LENGTH; i++) + printf("%02x", dg[i]); + printf("\n"); + return (0); +} diff --git a/tools/compat-user/bsd.c b/tools/compat-user/bsd.c new file mode 100644 index 0000000..1d4293b --- /dev/null +++ b/tools/compat-user/bsd.c @@ -0,0 +1,117 @@ +/* PFLINUX: small bag of BSD-isms glibc lacks, referenced by the copied + * 8.0 pfctl sources (see tools/UPSTREAM.md). */ + +#include +#include + +/* pf_print_state.c: TCP state names, normally provided by OpenBSD + * under TCPSTATES (array text copied verbatim from + * _vendor/openbsd/obbsd/sys/netinet/tcp_fsm.h). */ +const char *const tcpstates[] = { + "CLOSED", "LISTEN", "SYN_SENT", "SYN_RCVD", + "ESTABLISHED", "CLOSE_WAIT", "FIN_WAIT_1", "CLOSING", + "LAST_ACK", "FIN_WAIT_2", "TIME_WAIT", +}; + +/* + * pledge(2)/unveil(2): BSD-only process sandboxes. OpenBSD 8.0 pfctl + * sources do not call them, but the no-op stubs keep future upstream + * syncs building without changes here. + */ +int +pledge(const char *promises, const char *paths[]) +{ + (void)promises; + (void)paths; + return (0); +} + +int +unveil(const char *path, const char *permissions) +{ + (void)path; + (void)permissions; + return (0); +} + +/* + * fgetln(3): BSD stdio line reader (returns the line *including* the + * trailing newline, sets *len, no NUL terminator). Used by + * pfctl_osfp.c to read fingerprint files. Backed by glibc getline(3); + * the buffer is valid until the next call on the same stream, matching + * the single-file usage in pfctl. + */ +char * +fgetln(FILE *stream, size_t *len) +{ + static char *buf; + static size_t bufsz; + ssize_t n; + + n = getline(&buf, &bufsz, stream); + if (n == -1) + return (NULL); + /* OpenBSD fgetln(): *len INCLUDES the trailing newline (when + * present) and the buffer is NUL-terminated as an extension; + * getline() already gives exactly that. */ + *len = n; + return (buf); +} + +/* + * uid_from_user(3)/gid_from_group(3): OpenBSD / helpers + * used by parse.y for the `user`/`group` filter options. OpenBSD + * returns 0 on success; on failure returns -1 and sets errno (ENOENT + * when the name is unknown). + */ +#include +#include +#include + +int +uid_from_user(const char *name, uid_t *uidp) +{ + struct passwd *pw; + + errno = 0; + if ((pw = getpwnam(name)) == NULL) { + if (errno == 0) + errno = ENOENT; + return (-1); + } + if (uidp != NULL) + *uidp = pw->pw_uid; + return (0); +} + +int +gid_from_group(const char *name, gid_t *gidp) +{ + struct group *gr; + + errno = 0; + if ((gr = getgrnam(name)) == NULL) { + if (errno == 0) + errno = ENOENT; + return (-1); + } + if (gidp != NULL) + *gidp = gr->gr_gid; + return (0); +} + +/* + * freezero(3): OpenBSD secure realloc-free. Used by pf_ruleset.c's + * userland rs_free(). (explicit_bzero is in glibc .) + */ +#include + +void +freezero(void *ptr, size_t siz) +{ + + if (ptr == NULL) + return; + explicit_bzero(ptr, siz); + free(ptr); +} diff --git a/tools/compat-user/inet_net_pton.c b/tools/compat-user/inet_net_pton.c new file mode 100644 index 0000000..c5c511c --- /dev/null +++ b/tools/compat-user/inet_net_pton.c @@ -0,0 +1,155 @@ +/* + * PFLINUX: BSD inet_net_pton(3) for pfctl on glibc. glibc 2.43 declares + * the function in but no longer provides the symbol (the + * resolver dropped it); libc.so.6 exports neither. pfctl_parser.c uses + * it only for AF_INET abbreviated-CIDR parsing ("10/8", "10.0.0.0/255...", + * "10.1.2" classful). Semantics implemented per OpenBSD + * lib/libc/net/inet_net_pton.c: + * - returns prefix length on success, -1 with errno on failure. + * - writes the network address (masked) to pbin. + * - AF_INET6 accepts CIDR via inet_pton (RFC 5952 form), default /128. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int +netmask_bits(uint32_t mask) +{ + int bits = 0; + + while ((mask & 0x80000000u) != 0) { + bits++; + mask <<= 1; + } + return ((mask == 0) ? bits : -1); /* must be contiguous */ +} + +static int +pton4(const char *pres, struct in_addr *pin, size_t binsize) +{ + char copy[128], *sp; + struct in_addr base; + uint32_t hostaddr, mask; + int bits = -1; + + if (strlen(pres) >= sizeof(copy)) { + errno = EMSGSIZE; + return (-1); + } + strcpy(copy, pres); + + sp = strchr(copy, '/'); + if (sp == NULL) { + if (!inet_aton(copy, &base)) + return (-1); + hostaddr = ntohl(base.s_addr); + /* classful defaults, matching BSD */ + if (hostaddr < 0x80000000u) + bits = 8; + else if (hostaddr < 0xC0000000u) + bits = 16; + else + bits = 24; + } else { + *sp++ = '\0'; + if (!inet_aton(copy, &base)) + return (-1); + + if (strchr(sp, '.')) { + struct in_addr m; + if (!inet_aton(sp, &m)) + return (-1); + bits = netmask_bits(ntohl(m.s_addr)); + if (bits < 0) + return (-1); + } else { + char *end; + long v; + + if (*sp == '\0') + return (-1); + v = strtol(sp, &end, 10); + if (end == sp || *end != '\0' || v < 0 || v > 32) + return (-1); + bits = (int)v; + } + } + + hostaddr = ntohl(base.s_addr); + mask = bits == 0 ? 0 : (0xffffffffu << (32 - bits)); + hostaddr &= mask; + + if (binsize < sizeof(base)) { + errno = EMSGSIZE; + return (-1); + } + pin->s_addr = htonl(hostaddr); + return (bits); +} + +static int +pton6(const char *pres, void *pbin, size_t binsize) +{ + char copy[128], *sp; + int bits = 128; + + if (strlen(pres) >= sizeof(copy)) { + errno = EMSGSIZE; + return (-1); + } + strcpy(copy, pres); + sp = strchr(copy, '/'); + if (sp != NULL) { + *sp++ = '\0'; + if (*sp == '\0') + return (-1); + bits = (int)strtol(sp, NULL, 10); + if (bits < 0 || bits > 128) + return (-1); + } + + /* glibc inet_pton accepts bare addresses; mask by prefix by hand */ + if (binsize < sizeof(struct in6_addr)) { + errno = EMSGSIZE; + return (-1); + } + if (inet_pton(AF_INET6, copy, pbin) != 1) + return (-1); + if (bits < 128) { + uint8_t *a = pbin; + int i; + + for (i = bits / 8; i < 16; i++) + a[i] = 0; + a[bits / 8] &= (uint8_t)(0xffu << (8 - (bits % 8))); + } + return (bits); +} + +int +inet_net_pton(int af, const char *pres, void *pbin, size_t binsize) +{ + if (pres == NULL || pbin == NULL) { + errno = EFAULT; + return (-1); + } + switch (af) { + case AF_INET: + return (pton4(pres, pbin, binsize)); + case AF_INET6: + return (pton6(pres, pbin, binsize)); + default: + errno = EAFNOSUPPORT; + return (-1); + } +} diff --git a/tools/compat-user/md5.c b/tools/compat-user/md5.c new file mode 100644 index 0000000..2c2ceeb --- /dev/null +++ b/tools/compat-user/md5.c @@ -0,0 +1,242 @@ +/* $OpenBSD: md5.c,v 1.4 2014/12/28 10:04:35 tedu Exp $ */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* PFLINUX: OpenBSD kernel includes // + * replaced by their glibc/userland equivalents; rest of the file is the + * verbatim vendor MD5 (tools/include/md5.h, string.h, endian.h). */ +#include +#include +#include +#include + +#define PUT_64BIT_LE(cp, value) do { \ + (cp)[7] = (value) >> 56; \ + (cp)[6] = (value) >> 48; \ + (cp)[5] = (value) >> 40; \ + (cp)[4] = (value) >> 32; \ + (cp)[3] = (value) >> 24; \ + (cp)[2] = (value) >> 16; \ + (cp)[1] = (value) >> 8; \ + (cp)[0] = (value); } while (0) + +#define PUT_32BIT_LE(cp, value) do { \ + (cp)[3] = (value) >> 24; \ + (cp)[2] = (value) >> 16; \ + (cp)[1] = (value) >> 8; \ + (cp)[0] = (value); } while (0) + +static u_int8_t PADDING[MD5_BLOCK_LENGTH] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5_CTX *ctx) +{ + ctx->count = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5_CTX *ctx, const void *inputptr, size_t len) +{ + const uint8_t *input = inputptr; + size_t have, need; + + /* Check how many bytes we already have and how many more we need. */ + have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + need = MD5_BLOCK_LENGTH - have; + + /* Update bitcount */ + ctx->count += (u_int64_t)len << 3; + + if (len >= need) { + if (have != 0) { + memcpy(ctx->buffer + have, input, need); + MD5Transform(ctx->state, ctx->buffer); + input += need; + len -= need; + have = 0; + } + + /* Process data in MD5_BLOCK_LENGTH-byte chunks. */ + while (len >= MD5_BLOCK_LENGTH) { + MD5Transform(ctx->state, input); + input += MD5_BLOCK_LENGTH; + len -= MD5_BLOCK_LENGTH; + } + } + + /* Handle any remaining bytes of data. */ + if (len != 0) + memcpy(ctx->buffer + have, input, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx) +{ + u_int8_t count[8]; + size_t padlen; + int i; + + /* Convert count to 8 bytes in little endian order. */ + PUT_64BIT_LE(count, ctx->count); + + /* Pad out to 56 mod 64. */ + padlen = MD5_BLOCK_LENGTH - + ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1)); + if (padlen < 1 + 8) + padlen += MD5_BLOCK_LENGTH; + MD5Update(ctx, PADDING, padlen - 8); /* padlen - 8 <= 64 */ + MD5Update(ctx, count, 8); + + for (i = 0; i < 4; i++) + PUT_32BIT_LE(digest + i * 4, ctx->state[i]); + explicit_bzero(ctx, sizeof(*ctx)); /* in case it's sensitive */ +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(u_int32_t state[4], const u_int8_t block[MD5_BLOCK_LENGTH]) +{ + u_int32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4]; + +#if BYTE_ORDER == LITTLE_ENDIAN + memcpy(in, block, sizeof(in)); +#else + for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) { + in[a] = (u_int32_t)( + (u_int32_t)(block[a * 4 + 0]) | + (u_int32_t)(block[a * 4 + 1]) << 8 | + (u_int32_t)(block[a * 4 + 2]) << 16 | + (u_int32_t)(block[a * 4 + 3]) << 24); + } +#endif + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} diff --git a/tools/compat-user/rbtree.c b/tools/compat-user/rbtree.c new file mode 100644 index 0000000..cdb34a3 --- /dev/null +++ b/tools/compat-user/rbtree.c @@ -0,0 +1,511 @@ +/* + * PFLINUX: userland implementation of the out-of-line generic red-black + * tree API that tools/include/sys/tree.h (copied verbatim from + * _vendor/upstream/sys/sys/tree.h) declares and the RBT_* macros call: + * _rb_insert/_rb_find/_rb_remove/_rb_min/_rb_next/... OpenBSD ships + * these as libc symbols (sys/kern/subr_rbtree.c is linked into libc); + * glibc has none, so pfctl links this file. Behavior follows OpenBSD: + * _rb_insert returns NULL on success, the existing item on duplicate. + * _rb_remove unlinks and returns the removed item. + * Colors use the tree.h RB_RED/RB_BLACK values; struct rb_entry holds + * plain parent/left/right pointers plus an unsigned color. + * t_augment (when non-NULL) is called on ancestors after mutations. + */ + +#include +#include +#include + +#include + +static __inline struct rb_entry * +rbt_entry(const struct rb_type *t, void *item) +{ + return ((struct rb_entry *)((char *)item + t->t_offset)); +} + +static __inline void * +rbt_item(const struct rb_type *t, const struct rb_entry *rbe) +{ + return (rbe == NULL ? NULL : + (void *)((char *)(struct rb_entry *)rbe - t->t_offset)); +} + +#define RBE_PARENT(rbe) ((rbe)->rbt_parent) +#define RBE_LEFT(rbe) ((rbe)->rbt_left) +#define RBE_RIGHT(rbe) ((rbe)->rbt_right) +#define RBE_COLOR(rbe) ((rbe)->rbt_color) + +static void +rbt_augment(const struct rb_type *t, struct rb_entry *rbe) +{ + for (; rbe != NULL && t->t_augment != NULL; rbe = RBE_PARENT(rbe)) + t->t_augment(rbt_item(t, rbe)); +} + +/* + * Replace the subtree rooted at `u' with `v' (CLRS TRANSPLANT). + */ +static void +rbt_transplant(struct rb_entry **root, struct rb_entry *u, + struct rb_entry *v) +{ + if (RBE_PARENT(u) == NULL) + *root = v; + else if (u == RBE_LEFT(RBE_PARENT(u))) + RBE_PARENT(u)->rbt_left = v; + else + RBE_PARENT(u)->rbt_right = v; + if (v != NULL) + v->rbt_parent = RBE_PARENT(u); +} + +/* + * Replace the subtree rooted at `x' with `y'; y takes x's parent link. + * (CLRS RB-INSERT-FIXUP/DELETE-FIXUP rotations.) + */ +static void +rbt_rotate_left(struct rb_entry **root, struct rb_entry *x) +{ + struct rb_entry *y = RBE_RIGHT(x); + + x->rbt_right = RBE_LEFT(y); + if (RBE_LEFT(y) != NULL) + RBE_LEFT(y)->rbt_parent = x; + y->rbt_parent = RBE_PARENT(x); + if (RBE_PARENT(x) == NULL) + *root = y; + else if (x == RBE_LEFT(RBE_PARENT(x))) + RBE_PARENT(x)->rbt_left = y; + else + RBE_PARENT(x)->rbt_right = y; + y->rbt_left = x; + x->rbt_parent = y; +} + +static void +rbt_rotate_right(struct rb_entry **root, struct rb_entry *x) +{ + struct rb_entry *y = RBE_LEFT(x); + + x->rbt_left = RBE_RIGHT(y); + if (RBE_RIGHT(y) != NULL) + RBE_RIGHT(y)->rbt_parent = x; + y->rbt_parent = RBE_PARENT(x); + if (RBE_PARENT(x) == NULL) + *root = y; + else if (x == RBE_RIGHT(RBE_PARENT(x))) + RBE_PARENT(x)->rbt_right = y; + else + RBE_PARENT(x)->rbt_left = y; + y->rbt_right = x; + x->rbt_parent = y; +} + +void * +_rb_insert(const struct rb_type *t, struct rb_tree *rbt, void *item) +{ + struct rb_entry **root = &rbt->rbt_root; + struct rb_entry *rbe = rbt_entry(t, item); + struct rb_entry *current = *root, *parent = NULL; + + /* [1] Find the insertion spot. */ + while (current != NULL) { + int result = t->t_compare(item, rbt_item(t, current)); + + parent = current; + if (result < 0) + current = RBE_LEFT(current); + else if (result > 0) + current = RBE_RIGHT(current); + else + return (rbt_item(t, current)); /* duplicate */ + } + + /* [2] Insert red. */ + rbe->rbt_left = NULL; + rbe->rbt_right = NULL; + rbe->rbt_color = RB_RED; + rbe->rbt_parent = parent; + if (parent == NULL) + *root = rbe; + else if (t->t_compare(item, rbt_item(t, parent)) < 0) + parent->rbt_left = rbe; + else + parent->rbt_right = rbe; + + /* [3] Fix red-black violations (CLRS RB-INSERT-FIXUP). */ + if (parent == NULL) + rbe->rbt_color = RB_BLACK; + else { + struct rb_entry *z = rbe, *p, *g, *u; + + while ((p = RBE_PARENT(z)) != NULL && + RBE_COLOR(p) == RB_RED) { + g = RBE_PARENT(p); + if (p == RBE_LEFT(g)) { + u = RBE_RIGHT(g); + if (u != NULL && RBE_COLOR(u) == RB_RED) { + p->rbt_color = RB_BLACK; + u->rbt_color = RB_BLACK; + g->rbt_color = RB_RED; + z = g; + } else { + if (z == RBE_RIGHT(p)) { + z = p; + rbt_rotate_left(root, p); + p = RBE_PARENT(z); + } + g = RBE_PARENT(p); + p->rbt_color = RB_BLACK; + g->rbt_color = RB_RED; + rbt_rotate_right(root, g); + } + } else { + u = RBE_LEFT(g); + if (u != NULL && RBE_COLOR(u) == RB_RED) { + p->rbt_color = RB_BLACK; + u->rbt_color = RB_BLACK; + g->rbt_color = RB_RED; + z = g; + } else { + if (z == RBE_LEFT(p)) { + z = p; + rbt_rotate_right(root, p); + p = RBE_PARENT(z); + } + g = RBE_PARENT(p); + p->rbt_color = RB_BLACK; + g->rbt_color = RB_RED; + rbt_rotate_left(root, g); + } + } + } + (*root)->rbt_color = RB_BLACK; + } + + /* [4] Augment. */ + rbt_augment(t, RBE_PARENT(rbe)); + + return (NULL); +} + +/* + * CLRS RB-DELETE-FIXUP with x possibly NULL; x_parent threaded explicitly. + */ +static void +rbt_delete_fixup(struct rb_entry **root, struct rb_entry *x, + struct rb_entry *x_parent) +{ + while (x != *root && (x == NULL || RBE_COLOR(x) == RB_BLACK)) { + struct rb_entry *sibling; + + if (x_parent == NULL) + break; + if (x == RBE_LEFT(x_parent)) { + sibling = RBE_RIGHT(x_parent); + if (sibling != NULL && + RBE_COLOR(sibling) == RB_RED) { + sibling->rbt_color = RB_BLACK; + x_parent->rbt_color = RB_RED; + rbt_rotate_left(root, x_parent); + sibling = RBE_RIGHT(x_parent); + } + if (sibling == NULL) { + x = x_parent; + x_parent = RBE_PARENT(x); + continue; + } + if ((RBE_LEFT(sibling) == NULL || + RBE_COLOR(RBE_LEFT(sibling)) == RB_BLACK) && + (RBE_RIGHT(sibling) == NULL || + RBE_COLOR(RBE_RIGHT(sibling)) == RB_BLACK)) { + sibling->rbt_color = RB_RED; + x = x_parent; + x_parent = RBE_PARENT(x); + } else { + if (RBE_RIGHT(sibling) == NULL || + RBE_COLOR(RBE_RIGHT(sibling)) == + RB_BLACK) { + if (RBE_LEFT(sibling) != NULL) + RBE_LEFT(sibling)->rbt_color = + RB_BLACK; + sibling->rbt_color = RB_RED; + rbt_rotate_right(root, sibling); + sibling = RBE_RIGHT(x_parent); + } + sibling->rbt_color = RBE_COLOR(x_parent); + x_parent->rbt_color = RB_BLACK; + if (RBE_RIGHT(sibling) != NULL) + RBE_RIGHT(sibling)->rbt_color = + RB_BLACK; + rbt_rotate_left(root, x_parent); + x = *root; + } + } else { + sibling = RBE_LEFT(x_parent); + if (sibling != NULL && + RBE_COLOR(sibling) == RB_RED) { + sibling->rbt_color = RB_BLACK; + x_parent->rbt_color = RB_RED; + rbt_rotate_right(root, x_parent); + sibling = RBE_LEFT(x_parent); + } + if (sibling == NULL) { + x = x_parent; + x_parent = RBE_PARENT(x); + continue; + } + if ((RBE_RIGHT(sibling) == NULL || + RBE_COLOR(RBE_RIGHT(sibling)) == RB_BLACK) && + (RBE_LEFT(sibling) == NULL || + RBE_COLOR(RBE_LEFT(sibling)) == RB_BLACK)) { + sibling->rbt_color = RB_RED; + x = x_parent; + x_parent = RBE_PARENT(x); + } else { + if (RBE_LEFT(sibling) == NULL || + RBE_COLOR(RBE_LEFT(sibling)) == RB_BLACK) { + if (RBE_RIGHT(sibling) != NULL) + RBE_RIGHT(sibling)->rbt_color = + RB_BLACK; + sibling->rbt_color = RB_RED; + rbt_rotate_left(root, sibling); + sibling = RBE_LEFT(x_parent); + } + sibling->rbt_color = RBE_COLOR(x_parent); + x_parent->rbt_color = RB_BLACK; + if (RBE_LEFT(sibling) != NULL) + RBE_LEFT(sibling)->rbt_color = + RB_BLACK; + rbt_rotate_right(root, x_parent); + x = *root; + } + } + } + + if (x != NULL) + x->rbt_color = RB_BLACK; +} + +void * +_rb_remove(const struct rb_type *t, struct rb_tree *rbt, void *item) +{ + struct rb_entry **root = &rbt->rbt_root; + struct rb_entry *z = rbt_entry(t, item); + struct rb_entry *y = z, *x, *x_parent; + int y_color; + + y_color = RBE_COLOR(y); + + if (RBE_LEFT(z) == NULL) { /* [1] no left child */ + x = RBE_RIGHT(z); + x_parent = RBE_PARENT(z); + rbt_transplant(root, z, x); + } else if (RBE_RIGHT(z) == NULL) { /* [2] no right child */ + x = RBE_LEFT(z); + x_parent = RBE_PARENT(z); + rbt_transplant(root, z, x); + } else { /* [3] two children */ + /* in-order successor: leftmost of z's right subtree */ + for (y = RBE_RIGHT(z); RBE_LEFT(y) != NULL; y = RBE_LEFT(y)) + ; + y_color = RBE_COLOR(y); + x = RBE_RIGHT(y); + if (RBE_PARENT(y) == z) { + x_parent = y; + } else { + x_parent = RBE_PARENT(y); + rbt_transplant(root, y, x); + y->rbt_right = RBE_RIGHT(z); + RBE_RIGHT(z)->rbt_parent = y; + } + rbt_transplant(root, z, y); + y->rbt_left = RBE_LEFT(z); + RBE_LEFT(z)->rbt_parent = y; + y->rbt_color = RBE_COLOR(z); + } + + /* detach z */ + z->rbt_parent = NULL; + z->rbt_left = NULL; + z->rbt_right = NULL; + z->rbt_color = RB_BLACK; + + if (y_color == RB_BLACK) + rbt_delete_fixup(root, x, x_parent); + + /* augment from the changed point up to the root */ + rbt_augment(t, x_parent); + + return (item); +} + +void * +_rb_find(const struct rb_type *t, struct rb_tree *rbt, const void *item) +{ + struct rb_entry *current = rbt->rbt_root; + + while (current != NULL) { + int result = t->t_compare(item, rbt_item(t, current)); + + if (result < 0) + current = RBE_LEFT(current); + else if (result > 0) + current = RBE_RIGHT(current); + else + return (rbt_item(t, current)); + } + return (NULL); +} + +void * +_rb_nfind(const struct rb_type *t, struct rb_tree *rbt, const void *item) +{ + struct rb_entry *current = rbt->rbt_root; + struct rb_entry *best = NULL; + + while (current != NULL) { + if (t->t_compare(item, rbt_item(t, current)) <= 0) { + best = current; + current = RBE_LEFT(current); + } else + current = RBE_RIGHT(current); + } + return (best == NULL ? NULL : rbt_item(t, best)); +} + +void * +_rb_root(const struct rb_type *t, struct rb_tree *rbt) +{ + return (rbt_item(t, rbt->rbt_root)); +} + +void * +_rb_min(const struct rb_type *t, struct rb_tree *rbt) +{ + struct rb_entry *current = rbt->rbt_root; + + if (current == NULL) + return (NULL); + while (RBE_LEFT(current) != NULL) + current = RBE_LEFT(current); + return (rbt_item(t, current)); +} + +void * +_rb_max(const struct rb_type *t, struct rb_tree *rbt) +{ + struct rb_entry *current = rbt->rbt_root; + + if (current == NULL) + return (NULL); + while (RBE_RIGHT(current) != NULL) + current = RBE_RIGHT(current); + return (rbt_item(t, current)); +} + +void * +_rb_next(const struct rb_type *t, void *item) +{ + struct rb_entry *rbe = rbt_entry(t, item); + struct rb_entry *parent; + + if (RBE_RIGHT(rbe) != NULL) { + rbe = RBE_RIGHT(rbe); + while (RBE_LEFT(rbe) != NULL) + rbe = RBE_LEFT(rbe); + return (rbt_item(t, rbe)); + } + for (parent = RBE_PARENT(rbe); parent != NULL; + parent = RBE_PARENT(parent)) { + if (rbe == RBE_LEFT(parent)) + return (rbt_item(t, parent)); + rbe = parent; + } + return (NULL); +} + +void * +_rb_prev(const struct rb_type *t, void *item) +{ + struct rb_entry *rbe = rbt_entry(t, item); + struct rb_entry *parent; + + if (RBE_LEFT(rbe) != NULL) { + rbe = RBE_LEFT(rbe); + while (RBE_RIGHT(rbe) != NULL) + rbe = RBE_RIGHT(rbe); + return (rbt_item(t, rbe)); + } + for (parent = RBE_PARENT(rbe); parent != NULL; + parent = RBE_PARENT(parent)) { + if (rbe == RBE_RIGHT(parent)) + return (rbt_item(t, parent)); + rbe = parent; + } + return (NULL); +} + +void * +_rb_left(const struct rb_type *t, void *item) +{ + return (rbt_item(t, RBE_LEFT(rbt_entry(t, item)))); +} + +void * +_rb_right(const struct rb_type *t, void *item) +{ + return (rbt_item(t, RBE_RIGHT(rbt_entry(t, item)))); +} + +void * +_rb_parent(const struct rb_type *t, void *item) +{ + return (rbt_item(t, RBE_PARENT(rbt_entry(t, item)))); +} + +void +_rb_set_left(const struct rb_type *t, void *item, void *left) +{ + RBE_LEFT(rbt_entry(t, item)) = + left == NULL ? NULL : rbt_entry(t, left); +} + +void +_rb_set_right(const struct rb_type *t, void *item, void *right) +{ + RBE_RIGHT(rbt_entry(t, item)) = + right == NULL ? NULL : rbt_entry(t, right); +} + +void +_rb_set_parent(const struct rb_type *t, void *item, void *parent) +{ + RBE_PARENT(rbt_entry(t, item)) = + parent == NULL ? NULL : rbt_entry(t, parent); +} + +void +_rb_poison(const struct rb_type *t, void *item, unsigned long poison) +{ + struct rb_entry *rbe = rbt_entry(t, item); + + rbe->rbt_parent = (void *)poison; + rbe->rbt_left = (void *)poison; + rbe->rbt_right = (void *)poison; +} + +int +_rb_check(const struct rb_type *t, void *item, unsigned long poison) +{ + struct rb_entry *rbe = rbt_entry(t, item); + + if (rbe->rbt_parent == (void *)poison || + rbe->rbt_left == (void *)poison || + rbe->rbt_right == (void *)poison) { + errno = EINVAL; + return (-1); + } + return (0); +} diff --git a/tools/compat-user/strtonum.c b/tools/compat-user/strtonum.c new file mode 100644 index 0000000..4abaf0b --- /dev/null +++ b/tools/compat-user/strtonum.c @@ -0,0 +1,52 @@ +/* PFLINUX: BSD strtonum(3) for glibc — follows OpenBSD + * lib/libc/stdlib/strtonum.c semantics (public domain). + * Declared in tools/include/pflinux_compat.h. */ + +#include +#include +#include +#include + +#define INVALID 1 +#define TOO_SMALL 2 +#define TOO_LARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + char *str; + ll = strtoll(numstr, &str, 10); + if (numstr == str || str == NULL || *str != '\0') + error = INVALID; + else if ((errno == ERANGE && ll < minval) || ll < minval) + error = TOO_SMALL; + else if ((errno == ERANGE && ll > maxval) || ll > maxval) + error = TOO_LARGE; + } + + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} diff --git a/tools/compat-user/sysctl.c b/tools/compat-user/sysctl.c new file mode 100644 index 0000000..31fbcab --- /dev/null +++ b/tools/compat-user/sysctl.c @@ -0,0 +1,111 @@ +/* PFLINUX: sysctl(3) emulation for pfctl on Linux. + * + * pfctl uses three OpenBSD MIBs; the legacy Linux sysctl(2) syscall this + * replaces them with (/proc, sysconf) covers exactly those, mirroring the + * semantics the pf.ko port provides: + * + * CTL_KERN/KERN_MAXCLUSTERS mbuf-cluster budget feeding the default + * PF_LIMIT_FRAGS limit (pfctl_init_options). + * CTL_HW/HW_PHYSMEM64 physical memory (tiny-host table limit). + * CTL_NET/PF_ROUTE/NET_RT_TABLE/ + * routing-table existence check used by + * parse.y lookup_rtable(). Mirrors + * module/compat/pfk_rtable.c: Linux has no + * per-rtableid FIBs, so every id is accepted + * (ids > RT_TABLEID_MAX never get here: + * callers range-check first, and ENOENT + * answers for completeness). + */ + +#include +#include +#include /* PF_ROUTE (glibc value, see net/route.h note) */ +#include + +#include +#include +#include +#include +#include + +/* + * Physical memory in bytes via sysconf(3); falls back to sysinfo-ish + * estimate of 0 (callers treat it as "tiny host" path). + */ +static int64_t +pflinux_physmem(void) +{ + long pages = sysconf(_SC_PHYS_PAGES); + long psz = sysconf(_SC_PAGESIZE); + + if (pages <= 0 || psz <= 0) + return ((int64_t)32 * 1024 * 1024); + return ((int64_t)pages * psz); +} + +int +sysctl(const int *name, u_int namelen, void *old, size_t *oldlenp, + const void *new, size_t newlen) +{ + (void)new; + (void)newlen; + + if (old == NULL || oldlenp == NULL) { + /* read-only emulation: callers always want values */ + errno = EINVAL; + return (-1); + } + + if (namelen == 2 && name[0] == CTL_HW && name[1] == HW_PHYSMEM64) { + int64_t mem = pflinux_physmem(); + + if (*oldlenp < sizeof(mem)) { + errno = EINVAL; + return (-1); + } + memcpy(old, &mem, sizeof(mem)); + *oldlenp = sizeof(mem); + return (0); + } + + if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_MAXCLUSTERS) { + /* + * PFLINUX: OpenBSD sizes nmbclusters off RAM (roughly + * mem/MCLBYTES, MCLBYTES==2048); Linux has no mbufs, so we + * hand pfctl the same RAM-derived cluster budget its + * defaults were designed against. + */ + int mcl = (int)(pflinux_physmem() / 2048); + + if (*oldlenp < sizeof(mcl)) { + errno = EINVAL; + return (-1); + } + memcpy(old, &mcl, sizeof(mcl)); + *oldlenp = sizeof(mcl); + return (0); + } + + if (namelen == 6 && name[0] == CTL_NET && name[1] == PF_ROUTE && + name[4] == NET_RT_TABLE) { + struct rt_tableinfo info; + + if (name[5] > RT_TABLEID_MAX) { + errno = ENOENT; /* nonexistent table */ + return (-1); + } + memset(&info, 0, sizeof(info)); + info.rti_tableid = name[5]; + info.rti_domainid = 0; + if (*oldlenp < sizeof(info)) { + errno = EINVAL; + return (-1); + } + memcpy(old, &info, sizeof(info)); + *oldlenp = sizeof(info); + return (0); + } + + errno = ENOTSUP; + return (-1); +} diff --git a/tools/include/md5.h b/tools/include/md5.h new file mode 100644 index 0000000..39fa0f3 --- /dev/null +++ b/tools/include/md5.h @@ -0,0 +1,28 @@ +/* PFLINUX: — userland copy of OpenBSD 8.0 + * (from _vendor/upstream/sys/crypto/md5.h) with the kernel-only + * __bounded__ attribute annotations dropped (glibc sys/cdefs.h has no + * __bounded__) and added. Algorithm and constants are + * unchanged; PF_MD5_DIGEST_LENGTH checks in pfvar.h rely on + * MD5_DIGEST_LENGTH == 16. */ +#ifndef _MD5_H_ +#define _MD5_H_ + +#include + +#define MD5_BLOCK_LENGTH 64 +#define MD5_DIGEST_LENGTH 16 + +typedef struct MD5Context { + u_int32_t state[4]; /* state */ + u_int64_t count; /* number of bits, mod 2^64 */ + u_int8_t buffer[MD5_BLOCK_LENGTH]; /* input buffer */ +} MD5_CTX; + +__BEGIN_DECLS +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, const void *, size_t); +void MD5Final(u_int8_t [MD5_DIGEST_LENGTH], MD5_CTX *); +void MD5Transform(u_int32_t [4], const u_int8_t [MD5_BLOCK_LENGTH]); +__END_DECLS + +#endif /* _MD5_H_ */ diff --git a/tools/include/net/if.h b/tools/include/net/if.h new file mode 100644 index 0000000..d2173a5 --- /dev/null +++ b/tools/include/net/if.h @@ -0,0 +1,54 @@ +/* PFLINUX: — glibc's plus OpenBSD's interface-group + * ABI (sys/sockio.h) used by pfctl_parser.c (ifa_exists/ifa_grouplookup). + * Linux has no interface groups: SIOCGIFGMEMB keeps the OpenBSD encoding + * (it is computed with our OpenBSD _IOWR) and every use of it on an AF_INET + * socket returns ENOTTY, so group lookups degrade to "not a group" exactly + * like on an OpenBSD without the group. */ +#ifndef _PFLINUX_NET_IF_H +#define _PFLINUX_NET_IF_H + +#include_next + +#include +#include + +#ifndef IFNAMSIZ +#define IFNAMSIZ IF_NAMESIZE +#endif + +#define IFG_ALL "all" /* group contains all interfaces */ +#define IFG_EGRESS "egress" /* if(s) default route(s) point to */ + +struct ifg_req { + union { + char ifgrqu_group[IFNAMSIZ]; + char ifgrqu_member[IFNAMSIZ]; + } ifgrq_ifgrqu; +#define ifgrq_group ifgrq_ifgrqu.ifgrqu_group +#define ifgrq_member ifgrq_ifgrqu.ifgrqu_member +}; + +struct ifgroupreq { + char ifgr_name[IFNAMSIZ]; + u_int ifgr_len; + union { + char ifgru_group[IFNAMSIZ]; + struct ifg_req *ifgru_groups; + } ifgr_ifgru; +#define ifgr_group ifgr_ifgru.ifgru_group +#define ifgr_groups ifgr_ifgru.ifgru_groups +}; + +#ifndef SIOCGIFGMEMB +#define SIOCGIFGMEMB _IOWR('i', 138, struct ifgroupreq) /* OpenBSD value */ +#endif + +/* PFLINUX: OpenBSD if.h queue-priority range (parse.y `prio` check) */ +#ifndef IFQ_NQUEUES +#define IFQ_NQUEUES 8 +#endif +#ifndef IFQ_MAXPRIO +#define IFQ_MAXPRIO (IFQ_NQUEUES - 1) +#endif + +#endif /* _PFLINUX_NET_IF_H */ diff --git a/tools/include/net/if_dl.h b/tools/include/net/if_dl.h new file mode 100644 index 0000000..1070b39 --- /dev/null +++ b/tools/include/net/if_dl.h @@ -0,0 +1,30 @@ +/* PFLINUX: — BSD link-layer address. Linux has no AF_LINK; + * getifaddrs() reports link-layer addresses as AF_PACKET/struct sockaddr_ll. + * We therefore define AF_LINK as AF_PACKET and lay struct sockaddr_dl out + * exactly like struct sockaddr_ll, so pfctl_parser.c's + * ((struct sockaddr_dl *)ifa->ifa_addr)->sdl_index finds sll_ifindex. + * Everything stays userland-internal: af values tagged AF_LINK never cross + * /dev/pf (rules carry ifname strings, not sockaddr families). */ +#ifndef _PFLINUX_NET_IF_DL_H +#define _PFLINUX_NET_IF_DL_H + +#include +#include + +#ifndef AF_LINK +#define AF_LINK AF_PACKET /* PFLINUX mapping, see header note */ +#endif + +struct sockaddr_dl { + sa_family_t sdl_family; /* sll_family (offset 0) */ + u_short sdl_protocol; /* sll_protocol (offset 2) */ + int sdl_index; /* sll_ifindex (offset 4) */ + u_short sdl_type; /* sll_hatype (offset 8) */ + u_char sdl_pkttype; /* sll_pkttype (offset 10) */ + u_char sdl_alen; /* sll_halen (offset 11) */ + u_char sdl_data[8]; /* sll_addr (offset 12) */ +}; + +#define LLADDR(s) ((caddr_t *)((s)->sdl_data)) + +#endif /* _PFLINUX_NET_IF_DL_H */ diff --git a/tools/include/net/route.h b/tools/include/net/route.h new file mode 100644 index 0000000..d555f19 --- /dev/null +++ b/tools/include/net/route.h @@ -0,0 +1,32 @@ +/* PFLINUX: — the userland-visible subset of OpenBSD's + * / routing constants that net/pfvar.h + * (RTLABEL_LEN) and parse.y (CTL_NET route lookups) rely on. Linux' + * glibc carries only struct rtentry/ioctls, so this shim + * shadows it. Values copied from OpenBSD 8.0 sys/sys/socket.h and + * sys/net/route.h; RT_TABLEID_MAX matches module/include/pfk.h (32), + * the routing-domain ceiling our pf.ko implements. */ +#ifndef _PFLINUX_NET_ROUTE_H +#define _PFLINUX_NET_ROUTE_H + +#include + +/* PFLINUX: AF_ROUTE/PF_ROUTE (== 17) come from glibc + * unmodified; only the routing-table constants pfvar.h/parse.y need + * live here. */ + +#define NET_RT_DUMP 1 +#define NET_RT_FLAGS 2 +#define NET_RT_IFLIST 3 +#define NET_RT_TABLE 5 + +#define RTLABEL_LEN 32 +#define RT_TABLEID_MAX 32 /* keep in sync with module/include/pfk.h */ + +struct rt_tableinfo { + u_short rti_tableid; /* routing table id */ + u_short rti_domainid; /* routing domain id */ +}; + +struct rtentry; /* only referenced via pointers/prototypes */ + +#endif /* _PFLINUX_NET_ROUTE_H */ diff --git a/tools/include/netinet/icmp6.h b/tools/include/netinet/icmp6.h new file mode 100644 index 0000000..8a65a53 --- /dev/null +++ b/tools/include/netinet/icmp6.h @@ -0,0 +1,56 @@ +/* PFLINUX: shadow — glibc's copy lacks the MLD/nodeinfo + * and ND redirect-code constants pfctl_parser.c's icmp6-type keyword + * table references. Values copied verbatim from + * _vendor/openbsd/obbsd/sys/netinet/icmp6.h (OpenBSD 8.0). */ +#ifndef _PFLINUX_NETINET_ICMP6_H +#define _PFLINUX_NETINET_ICMP6_H + +#include_next + +#ifndef ICMP6_MEMBERSHIP_QUERY +#define ICMP6_MEMBERSHIP_QUERY 130 /* group membership query */ +#endif +#ifndef ICMP6_MEMBERSHIP_REPORT +#define ICMP6_MEMBERSHIP_REPORT 131 /* group membership report */ +#endif +#ifndef ICMP6_MEMBERSHIP_REDUCTION +#define ICMP6_MEMBERSHIP_REDUCTION 132 /* membership termination */ +#endif +#ifndef ICMP6_WRUREQUEST +#define ICMP6_WRUREQUEST 139 /* who are you request */ +#endif +#ifndef ICMP6_WRUREPLY +#define ICMP6_WRUREPLY 140 /* who are you reply */ +#endif +#ifndef ICMP6_FQDN_QUERY +#define ICMP6_FQDN_QUERY 139 /* FQDN query */ +#endif +#ifndef ICMP6_FQDN_REPLY +#define ICMP6_FQDN_REPLY 140 /* FQDN reply */ +#endif +#ifndef ICMP6_NI_QUERY +#define ICMP6_NI_QUERY 139 /* node information request */ +#endif +#ifndef ICMP6_NI_REPLY +#define ICMP6_NI_REPLY 140 /* node information reply */ +#endif +#ifndef MLD_LISTENER_DONE +#define MLD_LISTENER_DONE 132 /* multicast listener done */ +#endif +#ifndef MLD_MTRACE_RESP +#define MLD_MTRACE_RESP 200 /* mtrace resp (to sender) */ +#endif +#ifndef MLD_MTRACE +#define MLD_MTRACE 201 /* mtrace messages */ +#endif +#ifndef MLDV2_LISTENER_REPORT +#define MLDV2_LISTENER_REPORT 143 /* RFC3810 listener report */ +#endif +#ifndef ND_REDIRECT_ONLINK +#define ND_REDIRECT_ONLINK 0 /* redirect to on-link node */ +#endif +#ifndef ND_REDIRECT_ROUTER +#define ND_REDIRECT_ROUTER 1 /* redirect to better router */ +#endif + +#endif /* _PFLINUX_NETINET_ICMP6_H */ diff --git a/tools/include/netinet/ip.h b/tools/include/netinet/ip.h new file mode 100644 index 0000000..dbdd5be --- /dev/null +++ b/tools/include/netinet/ip.h @@ -0,0 +1,44 @@ +/* PFLINUX: shadow — glibc's copy lacks the IPTOS_DSCP_CS* + * class-selector codepoints parse.y's `set tos` table references (plus a + * few neighbours); values copied verbatim from + * _vendor/openbsd/obbsd/sys/netinet/ip.h (OpenBSD 8.0). */ +#ifndef _PFLINUX_NETINET_IP_H +#define _PFLINUX_NETINET_IP_H + +#include_next + +#ifndef IPTOS_DSCP_CS0 +#define IPTOS_DSCP_CS0 0x00 +#endif +#ifndef IPTOS_DSCP_LE +#define IPTOS_DSCP_LE 0x04 +#endif +#ifndef IPTOS_DSCP_CS1 +#define IPTOS_DSCP_CS1 0x20 +#endif +#ifndef IPTOS_DSCP_CS2 +#define IPTOS_DSCP_CS2 0x40 +#endif +#ifndef IPTOS_DSCP_CS3 +#define IPTOS_DSCP_CS3 0x60 +#endif +#ifndef IPTOS_DSCP_CS4 +#define IPTOS_DSCP_CS4 0x80 +#endif +#ifndef IPTOS_DSCP_CS5 +#define IPTOS_DSCP_CS5 0xa0 +#endif +#ifndef IPTOS_DSCP_VA +#define IPTOS_DSCP_VA 0xb0 +#endif +#ifndef IPTOS_DSCP_EF +#define IPTOS_DSCP_EF 0xb8 +#endif +#ifndef IPTOS_DSCP_CS6 +#define IPTOS_DSCP_CS6 0xc0 +#endif +#ifndef IPTOS_DSCP_CS7 +#define IPTOS_DSCP_CS7 0xe0 +#endif + +#endif /* _PFLINUX_NETINET_IP_H */ diff --git a/tools/include/netinet/ip_icmp.h b/tools/include/netinet/ip_icmp.h new file mode 100644 index 0000000..4f9ee9b --- /dev/null +++ b/tools/include/netinet/ip_icmp.h @@ -0,0 +1,62 @@ +/* PFLINUX: shadow — glibc's copy lacks the extended + * ICMP type/code constants pfctl_parser.c's icmp-type keyword table + * references. Values copied verbatim from + * _vendor/openbsd/obbsd/sys/netinet/ip_icmp.h (OpenBSD 8.0). */ +#ifndef _PFLINUX_NETINET_IP_ICMP_H +#define _PFLINUX_NETINET_IP_ICMP_H + +#include_next + +#ifndef ICMP_ALTHOSTADDR +#define ICMP_ALTHOSTADDR 6 /* alternate host address */ +#endif +#ifndef ICMP_TRACEROUTE +#define ICMP_TRACEROUTE 30 /* traceroute */ +#endif +#ifndef ICMP_DATACONVERR +#define ICMP_DATACONVERR 31 /* data conversion error */ +#endif +#ifndef ICMP_MOBILE_REDIRECT +#define ICMP_MOBILE_REDIRECT 32 /* mobile host redirect */ +#endif +#ifndef ICMP_IPV6_WHEREAREYOU +#define ICMP_IPV6_WHEREAREYOU 33 /* IPv6 where-are-you */ +#endif +#ifndef ICMP_IPV6_IAMHERE +#define ICMP_IPV6_IAMHERE 34 /* IPv6 i-am-here */ +#endif +#ifndef ICMP_MOBILE_REGREQUEST +#define ICMP_MOBILE_REGREQUEST 35 /* mobile registration req */ +#endif +#ifndef ICMP_MOBILE_REGREPLY +#define ICMP_MOBILE_REGREPLY 36 /* mobile registration reply */ +#endif +#ifndef ICMP_SKIP +#define ICMP_SKIP 39 /* SKIP */ +#endif +#ifndef ICMP_PHOTURIS +#define ICMP_PHOTURIS 40 /* Photuris */ +#endif +#ifndef ICMP_PHOTURIS_UNKNOWN_INDEX +#define ICMP_PHOTURIS_UNKNOWN_INDEX 1 /* unknown sec index */ +#endif +#ifndef ICMP_PHOTURIS_AUTH_FAILED +#define ICMP_PHOTURIS_AUTH_FAILED 2 /* auth failed */ +#endif +#ifndef ICMP_PHOTURIS_DECRYPT_FAILED +#define ICMP_PHOTURIS_DECRYPT_FAILED 3 /* decrypt failed */ +#endif +#ifndef ICMP_PARAMPROB_ERRATPTR +#define ICMP_PARAMPROB_ERRATPTR 0 /* req. opt. absent */ +#endif +#ifndef ICMP_PARAMPROB_LENGTH +#define ICMP_PARAMPROB_LENGTH 2 /* bad length */ +#endif +#ifndef ICMP_ROUTERADVERT_NORMAL +#define ICMP_ROUTERADVERT_NORMAL 0 /* normal advertisement */ +#endif +#ifndef ICMP_ROUTERADVERT_NOROUTE_COMMON +#define ICMP_ROUTERADVERT_NOROUTE_COMMON 16 /* selective routing */ +#endif + +#endif /* _PFLINUX_NETINET_IP_ICMP_H */ diff --git a/tools/include/netinet/tcp_fsm.h b/tools/include/netinet/tcp_fsm.h new file mode 100644 index 0000000..8bc6cfe --- /dev/null +++ b/tools/include/netinet/tcp_fsm.h @@ -0,0 +1,32 @@ +/* PFLINUX: — OpenBSD 8.0 TCP state machine header + * (glibc has no equivalent). Values copied from + * _vendor/openbsd/obbsd/sys/netinet/tcp_fsm.h. Kernel-only bodies + * (TCPOUTFLAGS/TCPSTATES arrays) dropped; tcpstates[] is declared extern + * and defined in tools/compat-user/bsd.c for pf_print_state.c. */ +#ifndef _PFLINUX_NETINET_TCP_FSM_H +#define _PFLINUX_NETINET_TCP_FSM_H + +#define TCP_NSTATES 11 + +#define TCPS_CLOSED 0 /* closed */ +#define TCPS_LISTEN 1 /* listening for connection */ +#define TCPS_SYN_SENT 2 /* active, have sent syn */ +#define TCPS_SYN_RECEIVED 3 /* have sent and received syn */ +/* states < TCPS_ESTABLISHED are those where connections not established */ +#define TCPS_ESTABLISHED 4 /* established */ +#define TCPS_CLOSE_WAIT 5 /* rcvd fin, waiting for close */ +/* states > TCPS_CLOSE_WAIT are those where user has closed */ +#define TCPS_FIN_WAIT_1 6 /* have closed, sent fin */ +#define TCPS_CLOSING 7 /* closed xchd FIN; await FIN ACK */ +#define TCPS_LAST_ACK 8 /* had fin and close; await FIN ACK */ +/* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */ +#define TCPS_FIN_WAIT_2 9 /* have closed, fin is acked */ +#define TCPS_TIME_WAIT 10 /* in 2*msl quiet wait after close */ + +#define TCPS_HAVERCVDSYN(s) ((s) >= TCPS_SYN_RECEIVED) +#define TCPS_HAVEESTABLISHED(s) ((s) >= TCPS_ESTABLISHED) +#define TCPS_HAVERCVDFIN(s) ((s) >= TCPS_TIME_WAIT) + +extern const char *const tcpstates[]; + +#endif /* _PFLINUX_NETINET_TCP_FSM_H */ diff --git a/tools/include/pflinux_compat.h b/tools/include/pflinux_compat.h new file mode 100644 index 0000000..ef0a682 --- /dev/null +++ b/tools/include/pflinux_compat.h @@ -0,0 +1,121 @@ +/* PFLINUX: force-include prelude for the Linux build of OpenBSD 8.0 pfctl + * (see tools/Makefile, CPPFLAGS -include). Keeps the copied pfctl sources + * byte-identical to _vendor except for the edits listed in UPSTREAM.md. */ +#ifndef _PFLINUX_PRELUDE_H +#define _PFLINUX_PRELUDE_H + +#include +#include +#include /* offsetof for sys/tree.h RBT_GENERATE */ +#include +#include /* fgetln(3) declaration below */ +#include +#include +#include /* PFLINUX: BSD bzero/bcopy; OpenBSD + * exposes them, glibc keeps them in */ + +/* PFLINUX: resolves to tools/include/sys/ioctl.h (first -I dir), which + * redefines _IO/_IOW/_IOR/_IOWR to the OpenBSD sys/ioccom.h encoding that + * pf.ko expects (identical to module/include/pfk.h). */ +#include + +/* PFLINUX: BSD AF_LINK. tools/include/net/if_dl.h maps it to AF_PACKET + * with a sockaddr_ll-compatible struct sockaddr_dl; parse.y compares + * n->af against AF_LINK but never includes , so the + * definition lives in this prelude (if_dl.h's own copy is #ifndef'd). */ +#include +#ifndef AF_LINK +#define AF_LINK AF_PACKET +#endif + +/* OpenBSD sys/cdefs.h attribute macros that glibc does not provide. */ +#ifndef __dead +#define __dead __attribute__((__noreturn__)) +#endif +#ifndef __printflike +#define __printflike(f, a) __attribute__((__format__(__printf__, f, a))) +#endif +/* net/pfvar.h marks pfsync structs with __packed (OpenBSD cdefs); recent + * glibc does not define it. */ +#ifndef __packed +#define __packed __attribute__((__packed__)) +#endif +/* sys/tree.h's RBT_* / RB_* generator macros use __unused/__inline; + * recent glibc sys/cdefs.h no longer provides them. */ +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif +#ifndef __inline +#define __inline inline +#endif +/* + * OpenBSD also spells the byte-order helpers + * betoh16/32/64 and letoh16/32/64 (pf_print_state.c uses betoh64); + * glibc only has + * beXXtoh/leXXtoh names. + */ +#include +#ifndef betoh16 +#define betoh16(x) be16toh(x) +#endif +#ifndef betoh32 +#define betoh32(x) be32toh(x) +#endif +#ifndef betoh64 +#define betoh64(x) be64toh(x) +#endif +#ifndef letoh16 +#define letoh16(x) le16toh(x) +#endif +#ifndef letoh32 +#define letoh32(x) le32toh(x) +#endif +#ifndef letoh64 +#define letoh64(x) le64toh(x) +#endif + +/* + * OpenBSD assignment-style host<->net byte-order helpers + * (pfctl.c uses them). htonl/ntohl/htons/ntohs come from + * which the including source pulls in. + */ +#ifndef HTONL +#define HTONL(x) ((x) = htonl(x)) +#endif +#ifndef NTOL +#define NTOL(x) ((x) = ntohl(x)) +#endif +#ifndef HTONS +#define HTONS(x) ((x) = htons(x)) +#endif +#ifndef NOSH +#define NOSH(x) ((x) = ntohs(x)) +#endif + +/* strtonum(3) is BSD (lib/libc/stdlib/strtonum.c); glibc has none. + * Implemented in tools/compat-user/strtonum.c. */ +long long strtonum(const char *numstr, long long minval, + long long maxval, const char **errstrp); + +/* pledge(2)/unveil(2) are BSD-only. The 8.0 pfctl sources do not call + * them, but the stubs are provided (tools/compat-user/bsd.c) for future + * upstream syncs. */ +int pledge(const char *promises, const char *paths[]); +int unveil(const char *path, const char *permissions); + +/* fgetln(3) is BSD stdio (pfctl_osfp.c uses it); implemented in + * tools/compat-user/bsd.c on top of getline(3). */ +char *fgetln(FILE *stream, size_t *len); + +/* freezero(3) is BSD stdlib (pf_ruleset.c userland branch uses it); + * implemented in tools/compat-user/bsd.c. */ +void freezero(void *ptr, size_t siz); + +/* uid_from_user(3)/gid_from_group(3) are BSD / helpers + * (parse.y `user`/`group` keywords); implemented in + * tools/compat-user/bsd.c on getpwnam(3)/getgrnam(3). Return 0 on + * success, non-zero with errno on failure (OpenBSD semantics). */ +int uid_from_user(const char *, uid_t *); +int gid_from_group(const char *, gid_t *); + +#endif /* _PFLINUX_PRELUDE_H */ diff --git a/tools/include/sys/_null.h b/tools/include/sys/_null.h new file mode 100644 index 0000000..4703374 --- /dev/null +++ b/tools/include/sys/_null.h @@ -0,0 +1,14 @@ +/* PFLINUX: — OpenBSD's NULL guard header, required by the + * vendored and . */ +#ifndef _PFLINUX_SYS__NULL_H +#define _PFLINUX_SYS__NULL_H + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#endif /* _PFLINUX_SYS__NULL_H */ diff --git a/tools/include/sys/ioctl.h b/tools/include/sys/ioctl.h new file mode 100644 index 0000000..f582769 --- /dev/null +++ b/tools/include/sys/ioctl.h @@ -0,0 +1,45 @@ +/* PFLINUX: — OpenBSD 8.0 ioctl encoding for userland pfctl. + * + * net/pfvar.h builds every DIOC* number with _IO/_IOW/_IOR/_IOWR. pf.ko + * (see module/include/pfk.h) interprets ioctl commands using the OpenBSD + * sys/ioccom.h bit layout, NOT Linux asm/ioctl.h, so this header must + * reproduce pfk.h's PFKXIOC_* encoding byte-for-byte. We pull in the real + * glibc first (ioctl(2) prototype, Linux SIOC* constants used + * by ) and then redefine the _IO family. + * + * Values copied from module/include/pfk.h lines 135-152. + */ +#ifndef _PFLINUX_SYS_IOCTL_H +#define _PFLINUX_SYS_IOCTL_H + +#include_next + +#include + +#undef _IO +#undef _IOR +#undef _IOW +#undef _IOWR + +#define PFKXIOC_NRBITS 8 +#define PFKXIOC_TYPEBITS 8 +#define PFKXIOC_SIZEBITS 13 +#define PFKXIOC_DIRBITS 2 +#define PFKXIOC_NRSHIFT 0 +#define PFKXIOC_TYPESHIFT (PFKXIOC_NRSHIFT + PFKXIOC_NRBITS) +#define PFKXIOC_SIZESHIFT (PFKXIOC_TYPESHIFT + PFKXIOC_TYPEBITS) +#define PFKXIOC_DIRSHIFT (PFKXIOC_SIZESHIFT + PFKXIOC_SIZEBITS + 1) +#define PFKXIOC_NONE 0U +#define PFKXIOC_WRITE 1U /* OpenBSD IOC_IN (copyin) */ +#define PFKXIOC_READ 2U /* OpenBSD IOC_OUT (copyout) */ + +#define PFKXIOC(dir,type,nr,size) \ + (((dir) << PFKXIOC_DIRSHIFT) | ((type) << PFKXIOC_TYPESHIFT) | \ + ((nr) << PFKXIOC_NRSHIFT) | ((size) << PFKXIOC_SIZESHIFT)) + +#define _IO(t, n) PFKXIOC(PFKXIOC_NONE, (t), (n), 0) +#define _IOWR(t, n, s) PFKXIOC(PFKXIOC_WRITE|PFKXIOC_READ, (t), (n), sizeof(s)) +#define _IOW(t, n, s) PFKXIOC(PFKXIOC_WRITE, (t), (n), sizeof(s)) +#define _IOR(t, n, s) PFKXIOC(PFKXIOC_READ, (t), (n), sizeof(s)) + +#endif /* _PFLINUX_SYS_IOCTL_H */ diff --git a/tools/include/sys/refcnt.h b/tools/include/sys/refcnt.h new file mode 100644 index 0000000..ff0c9d1 --- /dev/null +++ b/tools/include/sys/refcnt.h @@ -0,0 +1,63 @@ +/* PFLINUX: — userland twin of + * module/include/compat/sys/refcnt.h. The kernel side is + * `struct refcnt { refcount_t rc; }` where refcount_t wraps a single + * atomic_t (4 bytes on Linux). Only struct pf_anchor embeds it and + * pf_anchor never crosses /dev/pf, but size/alignment are kept equal. */ +#ifndef _PFLINUX_SYS_REFCNT_H +#define _PFLINUX_SYS_REFCNT_H + +#include + +struct refcnt { + unsigned int rc; +}; + +#define REFCNT_INITIALIZER { .rc = 1 } + +static inline void +refcnt_init(struct refcnt *r, unsigned int v) +{ + r->rc = v ? v : 1; +} + +static inline unsigned int +refcnt_value(const struct refcnt *r) +{ + return (r->rc); +} + +static inline void +refcnt_take(struct refcnt *r) +{ + r->rc++; +} + +static inline int +refcnt_take_not0(struct refcnt *r) +{ + if (r->rc == 0) + return (0); + r->rc++; + return (1); +} + +static inline unsigned int +refcnt_rele(struct refcnt *r) +{ + return (--r->rc); +} + +static inline unsigned int +refcnt_rele_wake(struct refcnt *r) +{ + return (refcnt_rele(r)); +} + +static inline void +refcnt_finalize(struct refcnt *r, const void *unused) +{ + (void)r; + (void)unused; +} + +#endif /* _PFLINUX_SYS_REFCNT_H */ diff --git a/tools/include/sys/sysctl.h b/tools/include/sys/sysctl.h new file mode 100644 index 0000000..1808103 --- /dev/null +++ b/tools/include/sys/sysctl.h @@ -0,0 +1,28 @@ +/* PFLINUX: — OpenBSD sysctl(3) surface used by pfctl. + * Linux removed both and the legacy sysctl(2) syscall; the + * constants below mirror OpenBSD 8.0 (sys/sysctl.h) and sysctl() is + * implemented in tools/compat-user/sysctl.c for exactly the three MIBs + * pfctl queries. */ +#ifndef _PFLINUX_SYS_SYSCTL_H +#define _PFLINUX_SYS_SYSCTL_H + +#include +#include + +#define CTL_UNSPEC 0 +#define CTL_KERN 1 +#define CTL_VM 2 +#define CTL_NET 4 /* PF_ROUTE etc. */ +#define CTL_HW 6 + +#define KERN_MAXCLUSTERS 67 /* int: mbuf cluster limit */ +#define HW_PHYSMEM64 19 /* int64_t: total RAM */ + +struct kinfo_proc; /* forward: nothing pfctl uses, avoid clash */ + +__BEGIN_DECLS +int sysctl(const int *name, u_int namelen, void *old, size_t *oldlenp, + const void *new, size_t newlen); +__END_DECLS + +#endif /* _PFLINUX_SYS_SYSCTL_H */ diff --git a/tools/include/sys/syslimits.h b/tools/include/sys/syslimits.h new file mode 100644 index 0000000..4cf85fa --- /dev/null +++ b/tools/include/sys/syslimits.h @@ -0,0 +1,8 @@ +/* PFLINUX: — glibc ships no such header; PATH_MAX etc. + * live in . */ +#ifndef _PFLINUX_SYS_SYSLIMITS_H +#define _PFLINUX_SYS_SYSLIMITS_H + +#include + +#endif /* _PFLINUX_SYS_SYSLIMITS_H */ diff --git a/tools/include/sys/timeout.h b/tools/include/sys/timeout.h new file mode 100644 index 0000000..1f08994 --- /dev/null +++ b/tools/include/sys/timeout.h @@ -0,0 +1,18 @@ +/* PFLINUX: — pfvar.h embeds struct timeout by value inside + * struct pf_pktdelay. pf_pktdelay is kernel-internal only (it never + * appears in any ioctl payload; the kernel side uses the real + * module/include/compat/sys/timeout.h with linux/timer.h), so userland + * just needs the type to be complete for pfvar.h to compile. */ +#ifndef _PFLINUX_SYS_TIMEOUT_H +#define _PFLINUX_SYS_TIMEOUT_H + +#include + +typedef void (timeout_fn)(void *); + +struct timeout { + timeout_fn *fn; + void *arg; +}; + +#endif /* _PFLINUX_SYS_TIMEOUT_H */ diff --git a/tools/pfctl/parse.y b/tools/pfctl/parse.y new file mode 100644 index 0000000..0281aa4 --- /dev/null +++ b/tools/pfctl/parse.y @@ -0,0 +1,6548 @@ +/* $OpenBSD: parse.y,v 1.726 2026/09/26 15:00:42 deraadt Exp $ */ + +/* + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * Copyright (c) 2002 - 2013 Henning Brauer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 AUTHOR ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +%{ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +#define ISSET(_v, _m) ((_v) & (_m)) + +static struct pfctl *pf = NULL; +static int debug = 0; +static u_int16_t returnicmpdefault = + (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; +static u_int16_t returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; +static int blockpolicy = PFRULE_DROP; +static int default_statelock; + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +int atoul(char *, u_long *); + +struct node_proto { + u_int8_t proto; + struct node_proto *next; + struct node_proto *tail; +}; + +struct node_port { + u_int16_t port[2]; + u_int8_t op; + struct node_port *next; + struct node_port *tail; +}; + +struct node_uid { + uid_t uid[2]; + u_int8_t op; + struct node_uid *next; + struct node_uid *tail; +}; + +struct node_gid { + gid_t gid[2]; + u_int8_t op; + struct node_gid *next; + struct node_gid *tail; +}; + +struct node_icmp { + u_int16_t code; /* aux. value 256 is legit */ + u_int16_t type; /* aux. value 256 is legit */ + u_int8_t proto; + struct node_icmp *next; + struct node_icmp *tail; +}; + +enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, + PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN, + PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, + PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, + PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, + PF_STATE_OPT_PFLOW, + PF_STATE_OPT_STATELIM, PF_STATE_OPT_SOURCELIM }; + +enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; + +struct node_state_opt { + int type; + union { + u_int32_t max_states; + u_int32_t max_src_states; + u_int32_t max_src_conn; + struct { + u_int32_t limit; + u_int32_t seconds; + } max_src_conn_rate; + struct { + u_int8_t flush; + char tblname[PF_TABLE_NAME_SIZE]; + } overload; + u_int32_t max_src_nodes; + u_int8_t src_track; + u_int32_t statelock; + struct { + int number; + u_int32_t seconds; + } timeout; + } data; + struct node_state_opt *next; + struct node_state_opt *tail; +}; + +struct peer { + struct node_host *host; + struct node_port *port; +}; + +struct node_queue { + char queue[PF_QNAME_SIZE]; + char parent[PF_QNAME_SIZE]; + char ifname[IFNAMSIZ]; + int scheduler; + struct node_queue *next; + struct node_queue *tail; +}; + +struct node_qassign { + char *qname; + char *pqname; +}; + +struct range { + int a; + int b; + int t; +}; +struct redirection { + struct node_host *host; + struct range rport; +}; + +struct pool_opts { + int marker; +#define POM_TYPE 0x01 +#define POM_STICKYADDRESS 0x02 + u_int8_t opts; + int type; + int staticport; + struct pf_poolhashkey *key; + +} pool_opts; + +struct divertspec { + struct node_host *addr; + u_int16_t port; + enum pf_divert_types type; +}; + +struct redirspec { + struct redirection *rdr; + struct pool_opts pool_opts; + int binat; + int af; +}; + +struct limiterspec { + u_int32_t id; + int limiter_action; +}; + +struct filter_opts { + int marker; +#define FOM_FLAGS 0x0001 +#define FOM_ICMP 0x0002 +#define FOM_TOS 0x0004 +#define FOM_KEEP 0x0008 +#define FOM_SRCTRACK 0x0010 +#define FOM_MINTTL 0x0020 +#define FOM_MAXMSS 0x0040 +#define FOM_AFTO 0x0080 +#define FOM_SETTOS 0x0100 +#define FOM_SCRUB_TCP 0x0200 +#define FOM_SETPRIO 0x0400 +#define FOM_ONCE 0x1000 +#define FOM_PRIO 0x2000 +#define FOM_SETDELAY 0x4000 + struct node_uid *uid; + struct node_gid *gid; + struct node_if *rcv; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } flags; + struct node_icmp *icmpspec; + u_int32_t tos; + u_int32_t prob; + struct limiterspec statelim; + struct limiterspec sourcelim; + struct { + int action; + struct node_state_opt *options; + } keep; + int fragment; + int allowopts; + char *label; + struct node_qassign queues; + char *tag; + char *match_tag; + u_int8_t match_tag_not; + u_int rtableid; + u_int8_t prio; + u_int8_t set_prio[2]; + u_int16_t delay; + struct divertspec divert; + struct redirspec nat; + struct redirspec rdr; + struct redirspec rroute; + u_int8_t rt; + + /* scrub opts */ + int nodf; + int minttl; + int settos; + int randomid; + int max_mss; + + struct { + u_int32_t limit; + u_int32_t seconds; + } pktrate; +} filter_opts; + +struct antispoof_opts { + char *label; + u_int rtableid; +} antispoof_opts; + +struct scrub_opts { + int marker; + int nodf; + int minttl; + int maxmss; + int randomid; + int reassemble_tcp; +} scrub_opts; + +struct node_sc { + struct node_queue_bw m1; + u_int d; + struct node_queue_bw m2; +}; + +struct node_fq { + u_int flows; + u_int quantum; + u_int target; + u_int interval; +}; + +struct queue_opts { + int marker; +#define QOM_BWSPEC 0x01 +#define QOM_PARENT 0x02 +#define QOM_DEFAULT 0x04 +#define QOM_QLIMIT 0x08 +#define QOM_FLOWS 0x10 +#define QOM_QUANTUM 0x20 + struct node_sc realtime; + struct node_sc linkshare; + struct node_sc upperlimit; + struct node_fq flowqueue; + char *parent; + int flags; + u_int qlimit; +} queue_opts; + +struct table_opts { + int flags; + int init_addr; + struct node_tinithead init_nodes; +} table_opts; + +struct statelim_opts { + unsigned int marker; +#define STATELIM_M_ID 0x01 +#define STATELIM_M_LIMIT 0x02 +#define STATELIM_M_RATE 0x04 + + uint32_t id; + char name[PF_STATELIM_NAME_LEN]; + unsigned int limit; + struct { + unsigned int limit; + unsigned int seconds; + } rate; +}; + +static struct statelim_opts statelim_opts; + +struct sourcelim_opts { + unsigned int marker; +#define SOURCELIM_M_ID 0x01 +#define SOURCELIM_M_ENTRIES 0x02 +#define SOURCELIM_M_LIMIT 0x04 +#define SOURCELIM_M_RATE 0x08 +#define SOURCELIM_M_TABLE 0x10 +#define SOURCELIM_M_INET_MASK 0x20 +#define SOURCELIM_M_INET6_MASK 0x40 + + uint32_t id; + unsigned int entries; + unsigned int limit; + struct { + unsigned int limit; + unsigned int seconds; + } rate; + struct { + char name[PF_TABLE_NAME_SIZE]; + unsigned int above; + unsigned int below; + } table; + unsigned int inet_mask; + unsigned int inet6_mask; +}; + +static struct sourcelim_opts sourcelim_opts; + +struct node_hfsc_opts hfsc_opts; +struct node_state_opt *keep_state_defaults = NULL; +struct pfctl_watermarks syncookie_opts; + +int validate_range(u_int8_t, u_int16_t, u_int16_t); +int disallow_table(struct node_host *, const char *); +int disallow_urpf_failed(struct node_host *, const char *); +int disallow_alias(struct node_host *, const char *); +int rule_consistent(struct pf_rule *); +int process_tabledef(char *, struct table_opts *, int); +void expand_label_str(char *, size_t, const char *, const char *); +void expand_label_if(const char *, char *, size_t, const char *); +void expand_label_addr(const char *, char *, size_t, sa_family_t, + struct node_host *); /* PFLINUX: af is sa_family_t + * (unsigned short on Linux vs u_int8_t on OpenBSD) */ +void expand_label_port(const char *, char *, size_t, + struct node_port *); +void expand_label_proto(const char *, char *, size_t, u_int8_t); +void expand_label(char *, size_t, const char *, sa_family_t, + /* PFLINUX: af parameter, see above */ + struct node_host *, struct node_port *, struct node_host *, + struct node_port *, u_int8_t); +int expand_divertspec(struct pf_rule *, struct divertspec *); +int collapse_redirspec(struct pf_pool *, struct pf_rule *, + struct redirspec *rs, int); +int apply_redirspec(struct pf_pool *, struct pf_rule *, + struct redirspec *, int, struct node_port *); +void expand_rule(struct pf_rule *, int, struct node_if *, + struct redirspec *, struct redirspec *, struct redirspec *, + struct node_proto *, + struct node_os *, struct node_host *, struct node_port *, + struct node_host *, struct node_port *, struct node_uid *, + struct node_gid *, struct node_if *, struct node_icmp *); +int expand_queue(char *, struct node_if *, struct queue_opts *); +int expand_skip_interface(struct node_if *); + +int getservice(char *); +int rule_label(struct pf_rule *, char *); + +void mv_rules(struct pf_ruleset *, struct pf_ruleset *); +void mv_tables(struct pfctl *, struct pfr_ktablehead *, + struct pf_anchor *, struct pf_anchor *); +void decide_address_family(struct node_host *, sa_family_t *); +int invalid_redirect(struct node_host *, sa_family_t); +u_int16_t parseicmpspec(char *, sa_family_t); +int kw_casecmp(const void *, const void *); +int map_tos(char *string, int *); +int lookup_rtable(u_int); +int filteropts_to_rule(struct pf_rule *, struct filter_opts *); + +TAILQ_HEAD(loadanchorshead, loadanchors) + loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); + +struct loadanchors { + TAILQ_ENTRY(loadanchors) entries; + char *anchorname; + char *filename; +}; + +typedef struct { + union { + int64_t number; + double probability; + int i; + char *string; + u_int rtableid; + u_int16_t weight; + struct { + u_int8_t b1; + u_int8_t b2; + u_int16_t w; + u_int16_t w2; + } b; + struct range range; + struct node_if *interface; + struct node_proto *proto; + struct node_icmp *icmp; + struct node_host *host; + struct node_os *os; + struct node_port *port; + struct node_uid *uid; + struct node_gid *gid; + struct node_state_opt *state_opt; + struct peer peer; + struct { + struct peer src, dst; + struct node_os *src_os; + } fromto; + struct redirection *redirection; + struct { + int action; + struct node_state_opt *options; + } keep_state; + struct { + u_int8_t log; + u_int8_t logif; + u_int8_t quick; + } logquick; + struct { + int neg; + char *name; + } tagged; + struct pf_poolhashkey *hashkey; + struct node_queue *queue; + struct node_queue_opt queue_options; + struct node_queue_bw queue_bwspec; + struct node_qassign qassign; + struct node_sc sc; + struct filter_opts filter_opts; + struct antispoof_opts antispoof_opts; + struct queue_opts queue_opts; + struct scrub_opts scrub_opts; + struct table_opts table_opts; + struct pool_opts pool_opts; + struct node_hfsc_opts hfsc_opts; + struct statelim_opts *statelim_opts; + struct sourcelim_opts *sourcelim_opts; + struct pfctl_watermarks *watermarks; + struct limiterspec limiterspec; + } v; + int lineno; +} YYSTYPE; + +#define PPORT_RANGE 1 +#define PPORT_STAR 2 +int parseport(char *, struct range *r, int); + +#define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \ + (!((addr).iflags & PFI_AFLAG_NOALIAS) || \ + !isdigit((unsigned char)(addr).v.ifname[strlen((addr).v.ifname)-1]))) + +%} + +%token PASS BLOCK MATCH SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS +%token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE +%token ICMP6TYPE CODE KEEP MODULATE STATE PORT BINATTO NODF +%token MINTTL ERROR ALLOWOPTS FILENAME ROUTETO DUPTO REPLYTO NO LABEL +%token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE +%token REASSEMBLE ANCHOR SYNCOOKIES +%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY RANDOMID +%token SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID +%token ANTISPOOF FOR INCLUDE MATCHES +%token BITMASK RANDOM SOURCEHASH ROUNDROBIN LEASTSTATES STATICPORT PROBABILITY +%token WEIGHT BANDWIDTH FLOWS QUANTUM +%token QUEUE PRIORITY QLIMIT RTABLE RDOMAIN MINIMUM BURST PARENT +%token LOAD RULESET_OPTIMIZATION RTABLE RDOMAIN PRIO ONCE DEFAULT DELAY +%token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE +%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW MAXPKTRATE +%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE +%token DIVERTTO DIVERTREPLY DIVERTPACKET NATTO AFTO RDRTO RECEIVEDON NE LE GE +%token LIMITER ID RATE SOURCE ENTRIES ABOVE BELOW MASK NOMATCH +%token STRING +%token NUMBER +%token PORTBINARY +%type interface if_list if_item_not if_item +%type number icmptype icmp6type uid gid +%type tos not yesno optnodf sourcelim_opt_below +%type probability +%type optweight +%type dir af optimizer syncookie_val +%type sourcetrack flush unaryop statelock +%type action +%type flags flag blockspec prio +%type portplain portstar portrange +%type hashkey +%type proto proto_list proto_item +%type protoval +%type icmpspec +%type icmp_list icmp_item +%type icmp6_list icmp6_item +%type reticmpspec reticmp6spec +%type fromto +%type ipportspec from to +%type ipspec xhost host dynaddr host_list +%type table_host_list tablespec +%type redir_host_list redirspec +%type os xos os_list +%type portspec port_list port_item +%type uids uid_list uid_item +%type gids gid_list gid_item +%type redirpool +%type label stringall anchorname +%type string varstring numberstring +%type keep +%type state_opt_spec state_opt_list state_opt_item +%type logquick quick log logopts logopt +%type antispoof_ifspc antispoof_iflst antispoof_if +%type qname +%type bandwidth +%type filter_opts filter_opt filter_opts_l +%type filter_sets filter_set filter_sets_l +%type antispoof_opts antispoof_opt antispoof_opts_l +%type queue_opts queue_opt queue_opts_l optscs +%type scspec +%type scrub_opts scrub_opt scrub_opts_l +%type table_opts table_opt table_opts_l +%type pool_opts pool_opt pool_opts_l +%type statelim_nm sourcelim_nm +%type statelim_id sourcelim_id limiter_opt limiter_opt_spec +%type statelim_filter_opt sourcelim_filter_opt +%type statelim_opts +%type sourcelim_opts +%type syncookie_opts +%% + +ruleset : /* empty */ + | ruleset include '\n' + | ruleset '\n' + | ruleset option '\n' + | ruleset statelim '\n' + | ruleset sourcelim '\n' + | ruleset pfrule '\n' + | ruleset anchorrule '\n' + | ruleset loadrule '\n' + | ruleset queuespec '\n' + | ruleset varset '\n' + | ruleset antispoof '\n' + | ruleset tabledef '\n' + | '{' fakeanchor '}' '\n'; + | ruleset error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +/* + * apply to previously specified rule: must be careful to note + * what that is: pf or nat or binat or rdr + */ +fakeanchor : fakeanchor '\n' + | fakeanchor anchorrule '\n' + | fakeanchor pfrule '\n' + | fakeanchor error '\n' + ; + +optimizer : string { + if (!strcmp($1, "none")) + $$ = 0; + else if (!strcmp($1, "basic")) + $$ = PF_OPTIMIZE_BASIC; + else if (!strcmp($1, "profile")) + $$ = PF_OPTIMIZE_BASIC | PF_OPTIMIZE_PROFILE; + else { + yyerror("unknown ruleset-optimization %s", $1); + YYERROR; + } + } + ; + +optnodf : /* empty */ { $$ = 0; } + | NODF { $$ = 1; } + ; + +option : SET REASSEMBLE yesno optnodf { + pfctl_set_reassembly(pf, $3, $4); + } + | SET OPTIMIZATION STRING { + if (pfctl_set_optimization(pf, $3) != 0) { + yyerror("unknown optimization %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET RULESET_OPTIMIZATION optimizer { + if (!(pf->opts & PF_OPT_OPTIMIZE)) { + pf->opts |= PF_OPT_OPTIMIZE; + pf->optimize = $3; + } + } + | SET TIMEOUT timeout_spec + | SET TIMEOUT '{' optnl timeout_list '}' + | SET LIMIT limit_spec + | SET LIMIT '{' optnl limit_list '}' + | SET LOGINTERFACE stringall { + if (pfctl_set_logif(pf, $3) != 0) { + yyerror("error setting loginterface %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET HOSTID number { + if ($3 == 0 || $3 > UINT_MAX) { + yyerror("hostid must be non-zero"); + YYERROR; + } + pfctl_set_hostid(pf, $3); + } + | SET BLOCKPOLICY DROP { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy drop\n"); + blockpolicy = PFRULE_DROP; + } + | SET BLOCKPOLICY RETURN { + if (pf->opts & PF_OPT_VERBOSE) + printf("set block-policy return\n"); + blockpolicy = PFRULE_RETURN; + } + | SET FINGERPRINTS STRING { + if (pf->opts & PF_OPT_VERBOSE) + printf("set fingerprints \"%s\"\n", $3); + if (!pf->anchor->name[0]) { + if (pfctl_file_fingerprints(pf->dev, + pf->opts, $3)) { + yyerror("error loading " + "fingerprints %s", $3); + free($3); + YYERROR; + } + } + free($3); + } + | SET STATEPOLICY statelock { + if (pf->opts & PF_OPT_VERBOSE) + switch ($3) { + case 0: + printf("set state-policy floating\n"); + break; + case PFRULE_IFBOUND: + printf("set state-policy if-bound\n"); + break; + } + default_statelock = $3; + } + | SET DEBUG STRING { + if (pfctl_set_debug(pf, $3) != 0) { + yyerror("error setting debuglevel %s", $3); + free($3); + YYERROR; + } + free($3); + } + | SET DEBUG DEBUG { + if (pfctl_set_debug(pf, "debug") != 0) { + yyerror("error setting debuglevel %s", "debug"); + YYERROR; + } + } + | SET SKIP interface { + if (expand_skip_interface($3) != 0) { + yyerror("error setting skip interface(s)"); + YYERROR; + } + } + | SET STATEDEFAULTS state_opt_list { + if (keep_state_defaults != NULL) { + yyerror("cannot redefine state-defaults"); + YYERROR; + } + keep_state_defaults = $3; + } + | SET SYNCOOKIES syncookie_val syncookie_opts { + if (pfctl_set_syncookies(pf, $3, $4)) { + yyerror("error setting syncookies"); + YYERROR; + } + } + ; + +syncookie_val : STRING { + if (!strcmp($1, "never")) + $$ = PF_SYNCOOKIES_NEVER; + else if (!strcmp($1, "adaptive")) + $$ = PF_SYNCOOKIES_ADAPTIVE; + else if (!strcmp($1, "always")) + $$ = PF_SYNCOOKIES_ALWAYS; + else { + yyerror("illegal value for syncookies"); + YYERROR; + } + } + ; + +syncookie_opts : /* empty */ { $$ = NULL; } + | { + memset(&syncookie_opts, 0, sizeof(syncookie_opts)); + } '(' syncookie_opt_l ')' { $$ = &syncookie_opts; } + ; + +syncookie_opt_l : syncookie_opt_l comma syncookie_opt + | syncookie_opt + ; + +syncookie_opt : STRING STRING { + double val; + char *cp; + + val = strtod($2, &cp); + if (cp == NULL || strcmp(cp, "%")) + YYERROR; + if (val <= 0 || val > 100) { + yyerror("illegal percentage value"); + YYERROR; + } + if (!strcmp($1, "start")) { + syncookie_opts.hi = val; + } else if (!strcmp($1, "end")) { + syncookie_opts.lo = val; + } else { + yyerror("illegal syncookie option"); + YYERROR; + } + } + ; + +stringall : STRING { $$ = $1; } + | ALL { + if (($$ = strdup("all")) == NULL) { + err(1, "stringall: strdup"); + } + } + ; + +string : STRING string { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + err(1, "string: asprintf"); + free($1); + free($2); + } + | STRING + ; + +varstring : numberstring varstring { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + err(1, "string: asprintf"); + free($1); + free($2); + } + | numberstring + ; + +numberstring : NUMBER { + char *s; + if (asprintf(&s, "%lld", $1) == -1) { + yyerror("string: asprintf"); + YYERROR; + } + $$ = s; + } + | STRING + ; + +varset : STRING '=' varstring { + char *s = $1; + if (pf->opts & PF_OPT_VERBOSE) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + err(1, "cannot store variable %s", $1); + free($1); + free($3); + } + ; + +anchorname : STRING { + if ($1[0] == '\0') { + free($1); + yyerror("anchor name must not be empty"); + YYERROR; + } + if (strlen(pf->anchor->path) + 1 + + strlen($1) >= PATH_MAX) { + free($1); + yyerror("anchor name is longer than %u", + PATH_MAX - 1); + YYERROR; + } + if ($1[0] == '_' || strstr($1, "/_") != NULL) { + free($1); + yyerror("anchor names beginning with '_' " + "are reserved for internal use"); + YYERROR; + } + $$ = $1; + } + | /* empty */ { $$ = NULL; } + ; + +pfa_anchorlist : /* empty */ + | pfa_anchorlist '\n' + | pfa_anchorlist tabledef '\n' + | pfa_anchorlist pfrule '\n' + | pfa_anchorlist anchorrule '\n' + | pfa_anchorlist include '\n' + ; + +pfa_anchor : '{' + { + char ta[PF_ANCHOR_NAME_SIZE]; + struct pf_ruleset *rs; + + /* steping into a brace anchor */ + pf->asd++; + if (pf->asd >= PFCTL_ANCHOR_STACK_DEPTH) + errx(1, "pfa_anchor: anchors too deep"); + pf->bn++; + pf->brace = 1; + + /* + * Anchor contents are parsed before the anchor rule + * production completes, so we don't know the real + * location yet. Create a holding ruleset in the root; + * contents will be moved afterwards. + */ + snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); + rs = pf_find_or_create_ruleset(ta); + if (rs == NULL) + err(1, "pfa_anchor: pf_find_or_create_ruleset (%s)", ta); + pf->astack[pf->asd] = rs->anchor; + pf->anchor = rs->anchor; + } '\n' pfa_anchorlist '}' + { + pf->alast = pf->anchor; + pf->asd--; + pf->anchor = pf->astack[pf->asd]; + } + | /* empty */ + ; + +anchorrule : ANCHOR anchorname dir quick interface af proto fromto + filter_opts pfa_anchor + { + struct pf_rule r; + struct node_proto *proto; + char *p; + + memset(&r, 0, sizeof(r)); + if (pf->astack[pf->asd + 1]) { + if ($2 && strchr($2, '/') != NULL) { + free($2); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + YYERROR; + } + + /* Move inline rules into relative location. */ + pf_anchor_setup(&r, + &pf->astack[pf->asd]->ruleset, + $2 ? $2 : pf->alast->name); + + if (r.anchor == NULL) + err(1, "anchorrule: unable to " + "create ruleset"); + + if (pf->alast != r.anchor) { + if (r.anchor->match) { + yyerror("inline anchor '%s' " + "already exists", + r.anchor->name); + YYERROR; + } + mv_rules(&pf->alast->ruleset, + &r.anchor->ruleset); + mv_tables(pf, &pfr_ktables, r.anchor, pf->alast); + } + pf_remove_if_empty_ruleset(&pf->alast->ruleset); + pf->alast = r.anchor; + } else { + if (!$2) { + yyerror("anchors without explicit " + "rules must specify a name"); + YYERROR; + } + + /* + * Don't make non-brace anchors part of the main anchor pool. + */ + if ((r.anchor = calloc(1, sizeof(*r.anchor))) == NULL) { + err(1, "anchorrule: calloc"); + } + pf_init_ruleset(&r.anchor->ruleset); + r.anchor->ruleset.anchor = r.anchor; + if (strlcpy(r.anchor->path, $2, + sizeof(r.anchor->path)) >= sizeof(r.anchor->path)) { + errx(1, "anchorrule: strlcpy"); + } + if ((p = strrchr($2, '/')) != NULL) { + if (strlen(p) == 1) { + yyerror("anchorrule: bad anchor name %s", + $2); + YYERROR; + } + } else + p = $2; + if (strlcpy(r.anchor->name, p, + sizeof(r.anchor->name)) >= sizeof(r.anchor->name)) { + errx(1, "anchorrule: strlcpy"); + } + } + + r.direction = $3; + r.quick = $4.quick; + r.af = $6; + + if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { + for (proto = $7; proto != NULL && + proto->proto != IPPROTO_TCP; + proto = proto->next) + ; /* nothing */ + if (proto == NULL && $7 != NULL) { + if ($9.flags.b1 || $9.flags.b2) + yyerror( + "flags only apply to tcp"); + if ($8.src_os) + yyerror( + "OS fingerprinting only " + "applies to tcp"); + YYERROR; + } + } + + if (filteropts_to_rule(&r, &$9)) + YYERROR; + + if ($9.keep.action) { + yyerror("cannot specify state handling " + "on anchors"); + YYERROR; + } + + if ($9.rt) { + yyerror("cannot specify route handling " + "on anchors"); + YYERROR; + } + + decide_address_family($8.src.host, &r.af); + decide_address_family($8.dst.host, &r.af); + + expand_rule(&r, 0, $5, NULL, NULL, NULL, $7, $8.src_os, + $8.src.host, $8.src.port, $8.dst.host, $8.dst.port, + $9.uid, $9.gid, $9.rcv, $9.icmpspec); + free($2); + pf->astack[pf->asd + 1] = NULL; + } + ; + +loadrule : LOAD ANCHOR anchorname FROM string { + struct loadanchors *loadanchor; + + if ($3 == NULL) { + yyerror("anchor name is missing"); + YYERROR; + } + loadanchor = calloc(1, sizeof(struct loadanchors)); + if (loadanchor == NULL) + err(1, "loadrule: calloc"); + if ((loadanchor->anchorname = malloc(PATH_MAX)) == + NULL) + err(1, "loadrule: malloc"); + if (pf->anchor->name[0]) + snprintf(loadanchor->anchorname, PATH_MAX, + "%s/%s", pf->anchor->path, $3); + else + strlcpy(loadanchor->anchorname, $3, PATH_MAX); + if ((loadanchor->filename = strdup($5)) == NULL) + err(1, "loadrule: strdup"); + + TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor, + entries); + + free($3); + free($5); + }; + +scrub_opts : { + bzero(&scrub_opts, sizeof scrub_opts); + } + scrub_opts_l + { $$ = scrub_opts; } + ; + +scrub_opts_l : scrub_opts_l comma scrub_opt + | scrub_opt + ; + +scrub_opt : NODF { + if (scrub_opts.nodf) { + yyerror("no-df cannot be respecified"); + YYERROR; + } + scrub_opts.nodf = 1; + } + | MINTTL NUMBER { + if (scrub_opts.marker & FOM_MINTTL) { + yyerror("min-ttl cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 255) { + yyerror("illegal min-ttl value %lld", $2); + YYERROR; + } + scrub_opts.marker |= FOM_MINTTL; + scrub_opts.minttl = $2; + } + | MAXMSS NUMBER { + if (scrub_opts.marker & FOM_MAXMSS) { + yyerror("max-mss cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 65535) { + yyerror("illegal max-mss value %lld", $2); + YYERROR; + } + scrub_opts.marker |= FOM_MAXMSS; + scrub_opts.maxmss = $2; + } + | REASSEMBLE STRING { + if (strcasecmp($2, "tcp") != 0) { + yyerror("scrub reassemble supports only tcp, " + "not '%s'", $2); + free($2); + YYERROR; + } + free($2); + if (scrub_opts.reassemble_tcp) { + yyerror("reassemble tcp cannot be respecified"); + YYERROR; + } + scrub_opts.reassemble_tcp = 1; + } + | RANDOMID { + if (scrub_opts.randomid) { + yyerror("random-id cannot be respecified"); + YYERROR; + } + scrub_opts.randomid = 1; + } + ; + +antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { + struct pf_rule r; + struct node_host *h = NULL, *hh; + struct node_if *i, *j; + + for (i = $3; i; i = i->next) { + bzero(&r, sizeof(r)); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.logif = $2.logif; + r.quick = $2.quick; + r.af = $4; + if (rule_label(&r, $5.label)) + YYERROR; + r.rtableid = $5.rtableid; + j = calloc(1, sizeof(struct node_if)); + if (j == NULL) + err(1, "antispoof: calloc"); + if (strlcpy(j->ifname, i->ifname, + sizeof(j->ifname)) >= sizeof(j->ifname)) { + free(j); + yyerror("interface name too long"); + YYERROR; + } + j->not = 1; + if (i->dynamic) { + h = calloc(1, sizeof(*h)); + if (h == NULL) + err(1, "address: calloc"); + h->addr.type = PF_ADDR_DYNIFTL; + set_ipmask(h, 128); + if (strlcpy(h->addr.v.ifname, i->ifname, + sizeof(h->addr.v.ifname)) >= + sizeof(h->addr.v.ifname)) { + free(h); + yyerror( + "interface name too long"); + YYERROR; + } + hh = malloc(sizeof(*hh)); + if (hh == NULL) + err(1, "address: malloc"); + bcopy(h, hh, sizeof(*hh)); + h->addr.iflags = PFI_AFLAG_NETWORK; + } else { + h = ifa_lookup(j->ifname, + PFI_AFLAG_NETWORK); + hh = NULL; + } + + if (h != NULL) + expand_rule(&r, 0, j, NULL, NULL, NULL, + NULL, NULL, h, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + + if ((i->ifa_flags & IFF_LOOPBACK) == 0) { + bzero(&r, sizeof(r)); + + r.action = PF_DROP; + r.direction = PF_IN; + r.log = $2.log; + r.logif = $2.logif; + r.quick = $2.quick; + r.af = $4; + if (rule_label(&r, $5.label)) + YYERROR; + r.rtableid = $5.rtableid; + if (hh != NULL) + h = hh; + else + h = ifa_lookup(i->ifname, 0); + if (h != NULL) + expand_rule(&r, 0, NULL, NULL, + NULL, NULL, NULL, NULL, h, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL); + } else + free(hh); + } + free($5.label); + } + ; + +antispoof_ifspc : FOR antispoof_if { $$ = $2; } + | FOR '{' optnl antispoof_iflst '}' { $$ = $4; } + ; + +antispoof_iflst : antispoof_if optnl { $$ = $1; } + | antispoof_iflst comma antispoof_if optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +antispoof_if : if_item { $$ = $1; } + | '(' if_item ')' { + $2->dynamic = 1; + $$ = $2; + } + ; + +antispoof_opts : { + bzero(&antispoof_opts, sizeof antispoof_opts); + antispoof_opts.rtableid = -1; + } + antispoof_opts_l + { $$ = antispoof_opts; } + | /* empty */ { + bzero(&antispoof_opts, sizeof antispoof_opts); + antispoof_opts.rtableid = -1; + $$ = antispoof_opts; + } + ; + +antispoof_opts_l : antispoof_opts_l antispoof_opt + | antispoof_opt + ; + +antispoof_opt : LABEL label { + if (antispoof_opts.label) { + yyerror("label cannot be redefined"); + YYERROR; + } + antispoof_opts.label = $2; + } + | RTABLE NUMBER { + if ($2 < 0 || $2 > RT_TABLEID_MAX) { + yyerror("invalid rtable id"); + YYERROR; + } else if (!lookup_rtable($2)) { + yyerror("rtable %lld does not exist", $2); + YYERROR; + } + antispoof_opts.rtableid = $2; + } + ; + +not : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + ; + +tabledef : TABLE '<' STRING '>' table_opts { + struct node_host *h, *nh; + struct node_tinit *ti, *nti; + + if (strlen($3) >= PF_TABLE_NAME_SIZE) { + yyerror("table name too long, max %d chars", + PF_TABLE_NAME_SIZE - 1); + free($3); + YYERROR; + } + if (process_tabledef($3, &$5, pf->opts)) { + free($3); + YYERROR; + } + free($3); + for (ti = SIMPLEQ_FIRST(&$5.init_nodes); ti != NULL; + ti = nti) { + if (ti->file) + free(ti->file); + for (h = ti->host; h != NULL; h = nh) { + nh = h->next; + free(h); + } + nti = SIMPLEQ_NEXT(ti, entries); + free(ti); + } + } + ; + +table_opts : { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + } + table_opts_l + { $$ = table_opts; } + | /* empty */ + { + bzero(&table_opts, sizeof table_opts); + SIMPLEQ_INIT(&table_opts.init_nodes); + $$ = table_opts; + } + ; + +table_opts_l : table_opts_l table_opt + | table_opt + ; + +table_opt : STRING { + if (!strcmp($1, "const")) + table_opts.flags |= PFR_TFLAG_CONST; + else if (!strcmp($1, "persist")) + table_opts.flags |= PFR_TFLAG_PERSIST; + else if (!strcmp($1, "counters")) + table_opts.flags |= PFR_TFLAG_COUNTERS; + else { + yyerror("invalid table option '%s'", $1); + free($1); + YYERROR; + } + free($1); + } + | '{' optnl '}' { table_opts.init_addr = 1; } + | '{' optnl table_host_list '}' { + struct node_host *n; + struct node_tinit *ti; + + for (n = $3; n != NULL; n = n->next) { + switch (n->addr.type) { + case PF_ADDR_ADDRMASK: + continue; /* ok */ + case PF_ADDR_RANGE: + yyerror("address ranges are not " + "permitted inside tables"); + break; + case PF_ADDR_DYNIFTL: + yyerror("dynamic addresses are not " + "permitted inside tables"); + break; + case PF_ADDR_TABLE: + yyerror("tables cannot contain tables"); + break; + case PF_ADDR_NOROUTE: + yyerror("\"no-route\" is not permitted " + "inside tables"); + break; + case PF_ADDR_URPFFAILED: + yyerror("\"urpf-failed\" is not " + "permitted inside tables"); + break; + default: + yyerror("unknown address type %d", + n->addr.type); + } + YYERROR; + } + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->host = $3; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + | FILENAME STRING { + struct node_tinit *ti; + + if (!(ti = calloc(1, sizeof(*ti)))) + err(1, "table_opt: calloc"); + ti->file = $2; + SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, + entries); + table_opts.init_addr = 1; + } + ; + +tablespec : xhost optweight { + if ($2 > 0) { + struct node_host *n; + for (n = $1; n != NULL; n = n->next) + n->weight = $2; + } + $$ = $1; + } + | '{' optnl table_host_list '}' { $$ = $3; } + ; + +table_host_list : tablespec optnl { $$ = $1; } + | table_host_list comma tablespec optnl { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + ; + +queuespec : QUEUE STRING interface queue_opts { + struct node_host *n; + + if ($3 == NULL && $4.parent == NULL) { + yyerror("root queue without interface"); + YYERROR; + } + if ($3 != NULL && + ((n = ifa_exists($3->ifname)) == NULL || + n->af != AF_LINK)) { + yyerror("not an interface"); + YYERROR; + } + + expand_queue($2, $3, &$4); + } + ; + +queue_opts : { + bzero(&queue_opts, sizeof queue_opts); + } + queue_opts_l + { $$ = queue_opts; } + ; + +queue_opts_l : queue_opts_l queue_opt + | queue_opt + ; + +queue_opt : BANDWIDTH scspec optscs { + if (queue_opts.marker & QOM_BWSPEC) { + yyerror("bandwidth cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_BWSPEC; + queue_opts.linkshare = $2; + queue_opts.realtime= $3.realtime; + queue_opts.upperlimit = $3.upperlimit; + } + | PARENT STRING { + if (queue_opts.marker & QOM_PARENT) { + yyerror("parent cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_PARENT; + queue_opts.parent = $2; + } + | DEFAULT { + if (queue_opts.marker & QOM_DEFAULT) { + yyerror("default cannot be respecified"); + YYERROR; + } + queue_opts.marker |= QOM_DEFAULT; + queue_opts.flags |= PFQS_DEFAULT; + } + | QLIMIT NUMBER { + if (queue_opts.marker & QOM_QLIMIT) { + yyerror("qlimit cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 65535) { + yyerror("qlimit out of range: max 65535"); + YYERROR; + } + queue_opts.marker |= QOM_QLIMIT; + queue_opts.qlimit = $2; + } + | FLOWS NUMBER { + if (queue_opts.marker & QOM_FLOWS) { + yyerror("number of flows cannot be respecified"); + YYERROR; + } + if ($2 < 1 || $2 > 32767) { + yyerror("number of flows out of range: " + "max 32767"); + YYERROR; + } + queue_opts.marker |= QOM_FLOWS; + queue_opts.flags |= PFQS_FLOWQUEUE; + queue_opts.flowqueue.flows = $2; + } + | QUANTUM NUMBER { + if (queue_opts.marker & QOM_QUANTUM) { + yyerror("quantum cannot be respecified"); + YYERROR; + } + if ($2 < 1 || $2 > 65535) { + yyerror("quantum out of range: max 65535"); + YYERROR; + } + queue_opts.marker |= QOM_QUANTUM; + queue_opts.flowqueue.quantum = $2; + } + ; + +optscs : /* nada */ { + + } + | comma MINIMUM scspec { + $$.realtime = $3; + } + | comma MAXIMUM scspec { + $$.upperlimit = $3; + } + | comma MINIMUM scspec comma MAXIMUM scspec { + $$.realtime = $3; + $$.upperlimit = $6; + } + | comma MAXIMUM scspec comma MINIMUM scspec { + $$.realtime = $6; + $$.upperlimit = $3; + } + ; + +scspec : bandwidth { + $$.m2 = $1; + $$.d = 0; + if ($$.m2.bw_percent) { + yyerror("no bandwidth in %% yet"); + YYERROR; + } + } + | bandwidth BURST bandwidth FOR STRING { + u_long ul; + char *cp; + + ul = strtoul($5, &cp, 10); + if (cp == NULL || strcmp(cp, "ms")) { + yyerror("time in scspec must be in ms"); + YYERROR; + } + + $$.m1 = $3; + $$.d = ul; + $$.m2 = $1; + + if ($$.m1.bw_percent || $$.m2.bw_percent) { + yyerror("no bandwidth in %% yet"); + YYERROR; + } + } + ; + +bandwidth : STRING { + double bps; + char *cp; + + $$.bw_percent = 0; + + bps = strtod($1, &cp); + if (cp != NULL) { + if (strlen(cp) > 1) { + char *cu = cp + 1; + if (!strcmp(cu, "Bit") || + !strcmp(cu, "B") || + !strcmp(cu, "bit") || + !strcmp(cu, "b")) { + *cu = 0; + } + } + if (!strcmp(cp, "b")) + ; /* nothing */ + else if (!strcmp(cp, "K")) + bps *= 1000; + else if (!strcmp(cp, "M")) + bps *= 1000 * 1000; + else if (!strcmp(cp, "G")) + bps *= 1000 * 1000 * 1000; + else if (!strcmp(cp, "%")) { + if (bps < 0 || bps > 100) { + yyerror("bandwidth spec " + "out of range"); + free($1); + YYERROR; + } + $$.bw_percent = bps; + bps = 0; + } else { + yyerror("unknown unit \"%s\"", cp); + free($1); + YYERROR; + } + } + free($1); + if (bps < 0 || bps > (double)LLONG_MAX) { + yyerror("bandwidth number too big"); + YYERROR; + } + $$.bw_absolute = (u_int64_t)bps; + } + | NUMBER { + if ($1 < 0 || $1 > LLONG_MAX) { + yyerror("bandwidth number too big"); + YYERROR; + } + $$.bw_percent = 0; + $$.bw_absolute = $1; + } + ; + +statelim : statelim_nm statelim_opts { + struct pfctl_statelim *stlim; + size_t len; + + if (!ISSET($2->marker, STATELIM_M_ID)) { + yyerror("id not specified"); + free($1); + YYERROR; + } + if (!ISSET($2->marker, STATELIM_M_LIMIT)) { + yyerror("limit not specified"); + free($1); + YYERROR; + } + + stlim = calloc(1, sizeof(*stlim)); + if (stlim == NULL) + err(1, "state limiter: malloc"); + + len = strlcpy(stlim->ioc.name, $1, + sizeof(stlim->ioc.name)); + free($1); + if (len >= sizeof(stlim->ioc.name)) { + /* abort? */ + YYERROR; + } + + stlim->ioc.id = $2->id; + stlim->ioc.limit = $2->limit; + stlim->ioc.rate.limit = $2->rate.limit; + stlim->ioc.rate.seconds = $2->rate.seconds; + + if (pfctl_add_statelim(pf, stlim) != 0) { + yyerror("state limiter %s id %u" + " already exists", + stlim->ioc.name, stlim->ioc.id); + free(stlim); + YYERROR; + } + } + ; + +statelim_nm : STATE LIMITER string { + size_t len = strlen($3); + if (len < 1) { + yyerror("state limiter name is too short"); + free($3); + YYERROR; + } + if (len >= PF_STATELIM_NAME_LEN) { + yyerror("state limiter name is too long"); + free($3); + YYERROR; + } + $$ = $3; + } + ; + +statelim_id : ID NUMBER { + if ($2 < PF_STATELIM_ID_MIN || + $2 > PF_STATELIM_ID_MAX) { + yyerror("state limiter id %lld: " + "invalid identifier", $2); + YYERROR; + } + + $$ = $2; + } + ; + +statelim_opts : /* empty */ { + yyerror("state limiter missing options"); + YYERROR; + } + | { + memset(&statelim_opts, 0, sizeof(statelim_opts)); + } statelim_opts_l { + $$ = &statelim_opts; + } + ; + +statelim_opts_l : statelim_opts_l statelim_opt + | statelim_opt + ; + +statelim_opt : statelim_id { + if (ISSET(statelim_opts.marker, STATELIM_M_ID)) { + yyerror("id cannot be respecified"); + YYERROR; + } + + statelim_opts.id = $1; + + statelim_opts.marker |= STATELIM_M_ID; + } + | LIMIT NUMBER { + if (ISSET(statelim_opts.marker, STATELIM_M_LIMIT)) { + yyerror("limit cannot be respecified"); + YYERROR; + } + + if ($2 < PF_STATELIM_LIMIT_MIN || + $2 > PF_STATELIM_LIMIT_MAX) { + yyerror("invalid state limiter limit"); + YYERROR; + } + + statelim_opts.limit = $2; + + statelim_opts.marker |= STATELIM_M_LIMIT; + } + | RATE NUMBER '/' NUMBER { + if (ISSET(statelim_opts.marker, STATELIM_M_RATE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + if ($2 < 1) { + yyerror("invalid rate limit %lld", $2); + YYERROR; + } + if ($4 < 1) { + yyerror("invalid rate seconds %lld", $4); + YYERROR; + } + + statelim_opts.rate.limit = $2; + statelim_opts.rate.seconds = $4; + + statelim_opts.marker |= STATELIM_M_RATE; + } + ; + +statelim_filter_opt + : STATE LIMITER STRING limiter_opt_spec { + struct pfctl_statelim *stlim; + + stlim = pfctl_get_statelim_nm(pf, $3); + free($3); + if (stlim == NULL) { + yyerror("state limiter not found"); + YYERROR; + } + $$.id = stlim->ioc.id; + $$.limiter_action = $4; + } + | STATE LIMITER statelim_id limiter_opt_spec { + $$.id = $3; + $$.limiter_action = $4; + } + ; + +sourcelim : sourcelim_nm sourcelim_opts { + struct pfctl_sourcelim *srlim; + size_t len; + + if (!ISSET($2->marker, SOURCELIM_M_ID)) { + yyerror("id not specified"); + free($1); + YYERROR; + } + if (!ISSET($2->marker, SOURCELIM_M_ENTRIES)) { + yyerror("entries not specified"); + free($1); + YYERROR; + } + if (!ISSET($2->marker, SOURCELIM_M_LIMIT)) { + yyerror("state limit not specified"); + free($1); + YYERROR; + } + + srlim = calloc(1, sizeof(*srlim)); + if (srlim == NULL) + err(1, "source limiter: malloc"); + + len = strlcpy(srlim->ioc.name, $1, + sizeof(srlim->ioc.name)); + free($1); + if (len >= sizeof(srlim->ioc.name)) { + /* abort? */ + YYERROR; + } + + srlim->ioc.id = $2->id; + srlim->ioc.entries = $2->entries; + srlim->ioc.limit = $2->limit; + srlim->ioc.rate.limit = $2->rate.limit; + srlim->ioc.rate.seconds = $2->rate.seconds; + + if (ISSET($2->marker, SOURCELIM_M_TABLE)) { + if (strlcpy(srlim->ioc.overload_tblname, + $2->table.name, + sizeof(srlim->ioc.overload_tblname)) >= + sizeof(srlim->ioc.overload_tblname)) { + abort(); + } + srlim->ioc.overload_hwm = $2->table.above; + srlim->ioc.overload_lwm = $2->table.below; + } + + srlim->ioc.inet_prefix = $2->inet_mask; + srlim->ioc.inet6_prefix = $2->inet6_mask; + + if (pfctl_add_sourcelim(pf, srlim) != 0) { + yyerror("source limiter %s id %u" + " already exists", + srlim->ioc.name, srlim->ioc.id); + free(srlim); + YYERROR; + } + } + ; + +sourcelim_nm : SOURCE LIMITER string { + size_t len = strlen($3); + if (len < 1) { + yyerror("source limiter name is too short"); + free($3); + YYERROR; + } + if (len >= PF_SOURCELIM_NAME_LEN) { + yyerror("source limiter name is too long"); + free($3); + YYERROR; + } + $$ = $3; + } + ; + +sourcelim_id : ID NUMBER { + if ($2 < PF_SOURCELIM_ID_MIN || + $2 > PF_SOURCELIM_ID_MAX) { + yyerror("source limiter id %lld: " + "invalid identifier", $2); + YYERROR; + } + + $$ = $2; + } + ; + +sourcelim_opts : /* empty */ { + yyerror("source limiter missing options"); + YYERROR; + } + | { + memset(&sourcelim_opts, 0, sizeof(sourcelim_opts)); + sourcelim_opts.inet_mask = 32; + sourcelim_opts.inet6_mask = 128; + } sourcelim_opts_l { + $$ = &sourcelim_opts; + } + ; + +sourcelim_opts_l + : sourcelim_opts_l sourcelim_opt + | sourcelim_opt + ; + +sourcelim_opt : sourcelim_id { + if (ISSET(sourcelim_opts.marker, SOURCELIM_M_ID)) { + yyerror("entries cannot be respecified"); + YYERROR; + } + + sourcelim_opts.id = $1; + + sourcelim_opts.marker |= SOURCELIM_M_ID; + } + | ENTRIES NUMBER { + if (ISSET(sourcelim_opts.marker, SOURCELIM_M_ENTRIES)) { + yyerror("entries cannot be respecified"); + YYERROR; + } + + sourcelim_opts.entries = $2; + + sourcelim_opts.marker |= SOURCELIM_M_ENTRIES; + } + | LIMIT NUMBER { + if (ISSET(sourcelim_opts.marker, SOURCELIM_M_LIMIT)) { + yyerror("state limit cannot be respecified"); + YYERROR; + } + + sourcelim_opts.limit = $2; + + sourcelim_opts.marker |= SOURCELIM_M_LIMIT; + } + | RATE NUMBER '/' NUMBER { + if (ISSET(sourcelim_opts.marker, SOURCELIM_M_RATE)) { + yyerror("rate cannot be respecified"); + YYERROR; + } + + sourcelim_opts.rate.limit = $2; + sourcelim_opts.rate.seconds = $4; + + sourcelim_opts.marker |= SOURCELIM_M_RATE; + } + | TABLE '<' STRING '>' ABOVE NUMBER sourcelim_opt_below { + size_t stringlen; + + if (ISSET(sourcelim_opts.marker, SOURCELIM_M_TABLE)) { + free($3); + yyerror("rate cannot be respecified"); + YYERROR; + } + + stringlen = strlcpy(sourcelim_opts.table.name, + $3, sizeof(sourcelim_opts.table.name)); + free($3); + if (stringlen == 0 || + stringlen >= PF_TABLE_NAME_SIZE) { + yyerror("invalid table name"); + YYERROR; + } + + if ($6 < 0) { + yyerror("above limit is invalid"); + YYERROR; + } + if ($7 > $6) { + yyerror("below limit higher than above limit"); + YYERROR; + } + + sourcelim_opts.table.above = $6; + sourcelim_opts.table.below = $7; + + sourcelim_opts.marker |= SOURCELIM_M_TABLE; + } + | INET MASK NUMBER { + if (ISSET(sourcelim_opts.marker, + SOURCELIM_M_INET_MASK)) { + yyerror("inet mask cannot be respecified"); + YYERROR; + } + + if ($3 < 1 || $3 > 32) { + yyerror("inet mask length out of range"); + YYERROR; + } + + sourcelim_opts.inet_mask = $3; + + sourcelim_opts.marker |= SOURCELIM_M_INET_MASK; + } + | INET6 MASK NUMBER { + if (ISSET(sourcelim_opts.marker, + SOURCELIM_M_INET6_MASK)) { + yyerror("inet6 mask cannot be respecified"); + YYERROR; + } + + if ($3 < 1 || $3 > 128) { + yyerror("inet6 mask length out of range"); + YYERROR; + } + + sourcelim_opts.inet6_mask = $3; + + sourcelim_opts.marker |= SOURCELIM_M_INET6_MASK; + } + ; + +sourcelim_opt_below + : /* empty */ { + $$ = 0; + } + | BELOW NUMBER { + if ($2 < 1) { + yyerror("below limit is invalid"); + YYERROR; + } + $$ = $2; + } + ; + +sourcelim_filter_opt + : SOURCE LIMITER STRING limiter_opt_spec { + struct pfctl_sourcelim *srlim; + + srlim = pfctl_get_sourcelim_nm(pf, $3); + free($3); + if (srlim == NULL) { + yyerror("source limiter not found"); + YYERROR; + } + + $$.id = srlim->ioc.id; + $$.limiter_action = $4; + } + | SOURCE LIMITER sourcelim_id limiter_opt_spec { + $$.id = $3; + $$.limiter_action = $4; + } + ; + +limiter_opt_spec: /* empty */ { $$ = PF_LIMITER_DEFAULT; } + | '(' limiter_opt ')' { $$ = $2; } + ; + +limiter_opt: BLOCK { + $$ = PF_LIMITER_BLOCK; + } + | NOMATCH { + $$ = PF_LIMITER_NOMATCH; + } + ; + +pfrule : action dir logquick interface af proto fromto + filter_opts + { + struct pf_rule r; + struct node_state_opt *o; + struct node_proto *proto; + int srctrack = 0; + int statelock = 0; + int adaptive = 0; + int defaults = 0; + + memset(&r, 0, sizeof(r)); + r.action = $1.b1; + switch ($1.b2) { + case PFRULE_RETURNRST: + r.rule_flag |= PFRULE_RETURNRST; + r.return_ttl = $1.w; + break; + case PFRULE_RETURNICMP: + r.rule_flag |= PFRULE_RETURNICMP; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + case PFRULE_RETURN: + r.rule_flag |= PFRULE_RETURN; + r.return_icmp = $1.w; + r.return_icmp6 = $1.w2; + break; + } + r.direction = $2; + r.log = $3.log; + r.logif = $3.logif; + r.quick = $3.quick; + r.af = $5; + + if (filteropts_to_rule(&r, &$8)) + YYERROR; + + if ($8.flags.b1 || $8.flags.b2 || $7.src_os) { + for (proto = $6; proto != NULL && + proto->proto != IPPROTO_TCP; + proto = proto->next) + ; /* nothing */ + if (proto == NULL && $6 != NULL) { + if ($8.flags.b1 || $8.flags.b2) + yyerror( + "flags only apply to tcp"); + if ($7.src_os) + yyerror( + "OS fingerprinting only " + "apply to tcp"); + YYERROR; + } + } + + r.keep_state = $8.keep.action; + o = $8.keep.options; + + /* 'keep state' by default on pass rules. */ + if (!r.keep_state && !r.action && + !($8.marker & FOM_KEEP)) { + r.keep_state = PF_STATE_NORMAL; + o = keep_state_defaults; + defaults = 1; + } + + while (o) { + struct node_state_opt *p = o; + + switch (o->type) { + case PF_STATE_OPT_MAX: + if (r.max_states) { + yyerror("state option 'max' " + "multiple definitions"); + YYERROR; + } + r.max_states = o->data.max_states; + break; + case PF_STATE_OPT_NOSYNC: + if (r.rule_flag & PFRULE_NOSYNC) { + yyerror("state option 'sync' " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_NOSYNC; + break; + case PF_STATE_OPT_SRCTRACK: + if (srctrack) { + yyerror("state option " + "'source-track' " + "multiple definitions"); + YYERROR; + } + srctrack = o->data.src_track; + r.rule_flag |= PFRULE_SRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_STATES: + if (r.max_src_states) { + yyerror("state option " + "'max-src-states' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_states == 0) { + yyerror("'max-src-states' must " + "be > 0"); + YYERROR; + } + r.max_src_states = + o->data.max_src_states; + r.rule_flag |= PFRULE_SRCTRACK; + break; + case PF_STATE_OPT_OVERLOAD: + if (r.overload_tblname[0]) { + yyerror("multiple 'overload' " + "table definitions"); + YYERROR; + } + if (strlcpy(r.overload_tblname, + o->data.overload.tblname, + PF_TABLE_NAME_SIZE) >= + PF_TABLE_NAME_SIZE) { + yyerror("state option: " + "strlcpy"); + YYERROR; + } + r.flush = o->data.overload.flush; + break; + case PF_STATE_OPT_MAX_SRC_CONN: + if (r.max_src_conn) { + yyerror("state option " + "'max-src-conn' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_conn == 0) { + yyerror("'max-src-conn' " + "must be > 0"); + YYERROR; + } + r.max_src_conn = + o->data.max_src_conn; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_CONN_RATE: + if (r.max_src_conn_rate.limit) { + yyerror("state option " + "'max-src-conn-rate' " + "multiple definitions"); + YYERROR; + } + if (!o->data.max_src_conn_rate.limit || + !o->data.max_src_conn_rate.seconds) { + yyerror("'max-src-conn-rate' " + "values must be > 0"); + YYERROR; + } + if (o->data.max_src_conn_rate.limit > + PF_THRESHOLD_MAX) { + yyerror("'max-src-conn-rate' " + "maximum rate must be < %u", + PF_THRESHOLD_MAX); + YYERROR; + } + r.max_src_conn_rate.limit = + o->data.max_src_conn_rate.limit; + r.max_src_conn_rate.seconds = + o->data.max_src_conn_rate.seconds; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_MAX_SRC_NODES: + if (r.max_src_nodes) { + yyerror("state option " + "'max-src-nodes' " + "multiple definitions"); + YYERROR; + } + if (o->data.max_src_nodes == 0) { + yyerror("'max-src-nodes' must " + "be > 0"); + YYERROR; + } + r.max_src_nodes = + o->data.max_src_nodes; + r.rule_flag |= PFRULE_SRCTRACK | + PFRULE_RULESRCTRACK; + break; + case PF_STATE_OPT_STATELOCK: + if (statelock) { + yyerror("state locking option: " + "multiple definitions"); + YYERROR; + } + statelock = 1; + r.rule_flag |= o->data.statelock; + break; + case PF_STATE_OPT_SLOPPY: + if (r.rule_flag & PFRULE_STATESLOPPY) { + yyerror("state sloppy option: " + "multiple definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_STATESLOPPY; + break; + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow " + "option: multiple " + "definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; + case PF_STATE_OPT_TIMEOUT: + if (o->data.timeout.number == + PFTM_ADAPTIVE_START || + o->data.timeout.number == + PFTM_ADAPTIVE_END) + adaptive = 1; + if (r.timeout[o->data.timeout.number]) { + yyerror("state timeout %s " + "multiple definitions", + pf_timeouts[o->data. + timeout.number].name); + YYERROR; + } + r.timeout[o->data.timeout.number] = + o->data.timeout.seconds; + break; + } + o = o->next; + if (!defaults) + free(p); + } + + /* 'flags S/SA' by default on stateful rules */ + if (!r.action && !r.flags && !r.flagset && + !$8.fragment && !($8.marker & FOM_FLAGS) && + r.keep_state) { + r.flags = parse_flags("S"); + r.flagset = parse_flags("SA"); + } + if (!adaptive && r.max_states) { + r.timeout[PFTM_ADAPTIVE_START] = + (r.max_states / 10) * 6; + r.timeout[PFTM_ADAPTIVE_END] = + (r.max_states / 10) * 12; + } + if (r.rule_flag & PFRULE_SRCTRACK) { + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_nodes) { + yyerror("'max-src-nodes' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_conn) { + yyerror("'max-src-conn' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (srctrack == PF_SRCTRACK_GLOBAL && + r.max_src_conn_rate.seconds) { + yyerror("'max-src-conn-rate' is " + "incompatible with " + "'source-track global'"); + YYERROR; + } + if (r.timeout[PFTM_SRC_NODE] < + r.max_src_conn_rate.seconds) + r.timeout[PFTM_SRC_NODE] = + r.max_src_conn_rate.seconds; + r.rule_flag |= PFRULE_SRCTRACK; + if (srctrack == PF_SRCTRACK_RULE) + r.rule_flag |= PFRULE_RULESRCTRACK; + } + if (r.keep_state && !statelock) + r.rule_flag |= default_statelock; + + decide_address_family($7.src.host, &r.af); + decide_address_family($7.dst.host, &r.af); + + if ($8.rt) { + if ($8.rt != PF_DUPTO && !r.direction) { + yyerror("direction must be explicit " + "with rules that specify routing"); + YYERROR; + } + r.rt = $8.rt; + } + + if (expand_divertspec(&r, &$8.divert)) + YYERROR; + + expand_rule(&r, 0, $4, &$8.nat, &$8.rdr, &$8.rroute, $6, + $7.src_os, + $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, + $8.uid, $8.gid, $8.rcv, $8.icmpspec); + } + ; + +filter_opts : { + bzero(&filter_opts, sizeof filter_opts); + filter_opts.statelim.id = PF_STATELIM_ID_NONE; + filter_opts.statelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.sourcelim.id = PF_SOURCELIM_ID_NONE; + filter_opts.sourcelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.rtableid = -1; + } + filter_opts_l + { $$ = filter_opts; } + | /* empty */ { + bzero(&filter_opts, sizeof filter_opts); + filter_opts.statelim.id = PF_STATELIM_ID_NONE; + filter_opts.statelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.sourcelim.id = PF_SOURCELIM_ID_NONE; + filter_opts.sourcelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.rtableid = -1; + $$ = filter_opts; + } + ; + +filter_opts_l : filter_opts_l filter_opt + | filter_opt + ; + +filter_opt : USER uids { + if (filter_opts.uid) + $2->tail->next = filter_opts.uid; + filter_opts.uid = $2; + } + | GROUP gids { + if (filter_opts.gid) + $2->tail->next = filter_opts.gid; + filter_opts.gid = $2; + } + | flags { + if (filter_opts.marker & FOM_FLAGS) { + yyerror("flags cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_FLAGS; + filter_opts.flags.b1 |= $1.b1; + filter_opts.flags.b2 |= $1.b2; + filter_opts.flags.w |= $1.w; + filter_opts.flags.w2 |= $1.w2; + } + | icmpspec { + if (filter_opts.marker & FOM_ICMP) { + yyerror("icmp-type cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_ICMP; + filter_opts.icmpspec = $1; + } + | PRIO NUMBER { + if (filter_opts.marker & FOM_PRIO) { + yyerror("prio cannot be redefined"); + YYERROR; + } + if ($2 < 0 || $2 > IFQ_MAXPRIO) { + yyerror("prio must be 0 - %u", IFQ_MAXPRIO); + YYERROR; + } + filter_opts.marker |= FOM_PRIO; + filter_opts.prio = $2; + } + | TOS tos { + if (filter_opts.marker & FOM_TOS) { + yyerror("tos cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_TOS; + filter_opts.tos = $2; + } + | keep { + if (filter_opts.marker & FOM_KEEP) { + yyerror("modulate or keep cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_KEEP; + filter_opts.keep.action = $1.action; + filter_opts.keep.options = $1.options; + } + | FRAGMENT { + filter_opts.fragment = 1; + } + | ALLOWOPTS { + filter_opts.allowopts = 1; + } + | LABEL label { + if (filter_opts.label) { + yyerror("label cannot be redefined"); + YYERROR; + } + filter_opts.label = $2; + } + | QUEUE qname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $2; + } + | TAG string { + filter_opts.tag = $2; + } + | not TAGGED string { + filter_opts.match_tag = $3; + filter_opts.match_tag_not = $1; + } + | PROBABILITY probability { + double p; + + p = floor($2 * UINT_MAX + 0.5); + if (p < 0.0 || p > UINT_MAX) { + yyerror("invalid probability: %g%%", $2 * 100); + YYERROR; + } + filter_opts.prob = (u_int32_t)p; + if (filter_opts.prob == 0) + filter_opts.prob = 1; + } + | statelim_filter_opt { + if (filter_opts.statelim.id != PF_STATELIM_ID_NONE) { + yyerror("state limiter already specified"); + YYERROR; + } + filter_opts.statelim = $1; + } + | sourcelim_filter_opt { + if (filter_opts.sourcelim.id != PF_SOURCELIM_ID_NONE) { + yyerror("source limiter already specified"); + YYERROR; + } + filter_opts.sourcelim = $1; + } + | RTABLE NUMBER { + if ($2 < 0 || $2 > RT_TABLEID_MAX) { + yyerror("invalid rtable id"); + YYERROR; + } else if (!lookup_rtable($2)) { + yyerror("rtable %lld does not exist", $2); + YYERROR; + } + filter_opts.rtableid = $2; + } + | DIVERTTO STRING PORT portplain { + if (filter_opts.divert.type != PF_DIVERT_NONE) { + yyerror("more than one divert option"); + YYERROR; + } + filter_opts.divert.type = PF_DIVERT_TO; + if ((filter_opts.divert.addr = host($2, pf->opts)) == NULL) { + yyerror("could not parse divert address: %s", + $2); + free($2); + YYERROR; + } + free($2); + filter_opts.divert.port = $4.a; + if (!filter_opts.divert.port) { + yyerror("invalid divert port: %u", ntohs($4.a)); + YYERROR; + } + } + | DIVERTREPLY { + if (filter_opts.divert.type != PF_DIVERT_NONE) { + yyerror("more than one divert option"); + YYERROR; + } + filter_opts.divert.type = PF_DIVERT_REPLY; + } + | DIVERTPACKET PORT portplain { + if (filter_opts.divert.type != PF_DIVERT_NONE) { + yyerror("more than one divert option"); + YYERROR; + } + filter_opts.divert.type = PF_DIVERT_PACKET; + /* + * If IP reassembly was not turned off, also + * forcibly enable TCP reassembly by default. + */ + if (pf->reassemble & PF_REASS_ENABLED) + filter_opts.marker |= FOM_SCRUB_TCP; + + filter_opts.divert.port = $3.a; + if (!filter_opts.divert.port) { + yyerror("invalid divert port: %u", ntohs($3.a)); + YYERROR; + } + } + | SCRUB '(' scrub_opts ')' { + filter_opts.nodf = $3.nodf; + filter_opts.minttl = $3.minttl; + filter_opts.randomid = $3.randomid; + filter_opts.max_mss = $3.maxmss; + if ($3.reassemble_tcp) + filter_opts.marker |= FOM_SCRUB_TCP; + filter_opts.marker |= $3.marker; + } + | NATTO redirpool pool_opts { + if (filter_opts.nat.rdr) { + yyerror("cannot respecify nat-to/binat-to"); + YYERROR; + } + filter_opts.nat.rdr = $2; + memcpy(&filter_opts.nat.pool_opts, &$3, + sizeof(filter_opts.nat.pool_opts)); + } + | AFTO af FROM redirpool pool_opts { + if (filter_opts.nat.rdr) { + yyerror("cannot respecify af-to"); + YYERROR; + } + if ($2 == 0) { + yyerror("no target address family specified"); + YYERROR; + } + filter_opts.nat.af = $2; + filter_opts.nat.rdr = $4; + memcpy(&filter_opts.nat.pool_opts, &$5, + sizeof(filter_opts.nat.pool_opts)); + filter_opts.rdr.rdr = + calloc(1, sizeof(struct redirection)); + bzero(&filter_opts.rdr.pool_opts, + sizeof(filter_opts.rdr.pool_opts)); + filter_opts.marker |= FOM_AFTO; + } + | AFTO af FROM redirpool pool_opts TO redirpool pool_opts { + if (filter_opts.nat.rdr) { + yyerror("cannot respecify af-to"); + YYERROR; + } + if ($2 == 0) { + yyerror("no address family specified"); + YYERROR; + } + if (($4->host->af && $4->host->af != $2) || + ($7->host->af && $7->host->af != $2)) { + yyerror("af-to addresses must be in the " + "target address family"); + YYERROR; + } + filter_opts.nat.af = $2; + filter_opts.nat.rdr = $4; + memcpy(&filter_opts.nat.pool_opts, &$5, + sizeof(filter_opts.nat.pool_opts)); + filter_opts.rdr.af = $2; + filter_opts.rdr.rdr = $7; + memcpy(&filter_opts.rdr.pool_opts, &$8, + sizeof(filter_opts.rdr.pool_opts)); + filter_opts.marker |= FOM_AFTO; + } + | RDRTO redirpool pool_opts { + if (filter_opts.rdr.rdr) { + yyerror("cannot respecify rdr-to"); + YYERROR; + } + filter_opts.rdr.rdr = $2; + memcpy(&filter_opts.rdr.pool_opts, &$3, + sizeof(filter_opts.rdr.pool_opts)); + } + | BINATTO redirpool pool_opts { + if (filter_opts.nat.rdr) { + yyerror("cannot respecify nat-to/binat-to"); + YYERROR; + } + filter_opts.nat.rdr = $2; + filter_opts.nat.binat = 1; + memcpy(&filter_opts.nat.pool_opts, &$3, + sizeof(filter_opts.nat.pool_opts)); + filter_opts.nat.pool_opts.staticport = 1; + } + | ROUTETO routespec { + filter_opts.rt = PF_ROUTETO; + } + | REPLYTO routespec { + filter_opts.rt = PF_REPLYTO; + } + | DUPTO routespec { + filter_opts.rt = PF_DUPTO; + } + | not RECEIVEDON if_item { + if (filter_opts.rcv) { + yyerror("cannot respecify received-on"); + YYERROR; + } + filter_opts.rcv = $3; + filter_opts.rcv->not = $1; + } + | ONCE { + filter_opts.marker |= FOM_ONCE; + } + | MAXPKTRATE NUMBER '/' NUMBER { + if ($2 < 0 || $2 > UINT_MAX || + $4 < 0 || $4 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (filter_opts.pktrate.limit) { + yyerror("cannot respecify max-pkt-rate"); + YYERROR; + } + filter_opts.pktrate.limit = $2; + filter_opts.pktrate.seconds = $4; + } + | filter_sets + ; + +filter_sets : SET '(' filter_sets_l ')' { $$ = filter_opts; } + | SET filter_set { $$ = filter_opts; } + ; + +filter_sets_l : filter_sets_l comma filter_set + | filter_set + ; + +filter_set : prio { + if (filter_opts.marker & FOM_SETPRIO) { + yyerror("prio cannot be redefined"); + YYERROR; + } + filter_opts.marker |= FOM_SETPRIO; + filter_opts.set_prio[0] = $1.b1; + filter_opts.set_prio[1] = $1.b2; + } + | QUEUE qname { + if (filter_opts.queues.qname) { + yyerror("queue cannot be redefined"); + YYERROR; + } + filter_opts.queues = $2; + } + | TOS tos { + if (filter_opts.marker & FOM_SETTOS) { + yyerror("tos cannot be respecified"); + YYERROR; + } + filter_opts.marker |= FOM_SETTOS; + filter_opts.settos = $2; + } + | DELAY NUMBER { + if (filter_opts.delay) { + yyerror("delay cannot be respecified"); + YYERROR; + } + if ($2 < 0 || $2 > 0xffff) { + yyerror("illegal delay value %lld (0-%u)", $2, + 0xffff); + YYERROR; + } + filter_opts.marker |= FOM_SETDELAY; + filter_opts.delay = $2; + } + ; + +prio : PRIO NUMBER { + if ($2 < 0 || $2 > IFQ_MAXPRIO) { + yyerror("prio must be 0 - %u", IFQ_MAXPRIO); + YYERROR; + } + $$.b1 = $$.b2 = $2; + } + | PRIO '(' NUMBER comma NUMBER ')' { + if ($3 < 0 || $3 > IFQ_MAXPRIO || + $5 < 0 || $5 > IFQ_MAXPRIO) { + yyerror("prio must be 0 - %u", IFQ_MAXPRIO); + YYERROR; + } + $$.b1 = $3; + $$.b2 = $5; + } + ; + +probability : STRING { + char *e; + double p = strtod($1, &e); + + if (*e == '%') { + p *= 0.01; + e++; + } + if (*e) { + yyerror("invalid probability: %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = p; + } + | NUMBER { + $$ = (double)$1; + } + ; + + +action : PASS { $$.b1 = PF_PASS; $$.b2 = $$.w = 0; } + | MATCH { $$.b1 = PF_MATCH; $$.b2 = $$.w = 0; } + | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; } + ; + +blockspec : /* empty */ { + $$.b2 = blockpolicy; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | DROP { + $$.b2 = PFRULE_DROP; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST { + $$.b2 = PFRULE_RETURNRST; + $$.w = 0; + $$.w2 = 0; + } + | RETURNRST '(' TTL NUMBER ')' { + if ($4 < 0 || $4 > 255) { + yyerror("illegal ttl value %lld", $4); + YYERROR; + } + $$.b2 = PFRULE_RETURNRST; + $$.w = $4; + $$.w2 = 0; + } + | RETURNICMP { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP6 { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + | RETURNICMP '(' reticmpspec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = $3; + $$.w2 = returnicmpdefault; + } + | RETURNICMP6 '(' reticmp6spec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = returnicmpdefault; + $$.w2 = $3; + } + | RETURNICMP '(' reticmpspec comma reticmp6spec ')' { + $$.b2 = PFRULE_RETURNICMP; + $$.w = $3; + $$.w2 = $5; + } + | RETURN { + $$.b2 = PFRULE_RETURN; + $$.w = returnicmpdefault; + $$.w2 = returnicmp6default; + } + ; + +reticmpspec : STRING { + if (!($$ = parseicmpspec($1, AF_INET))) { + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + u_int8_t icmptype; + + if ($1 < 0 || $1 > 255) { + yyerror("invalid icmp code %lld", $1); + YYERROR; + } + icmptype = returnicmpdefault >> 8; + $$ = (icmptype << 8 | $1); + } + ; + +reticmp6spec : STRING { + if (!($$ = parseicmpspec($1, AF_INET6))) { + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + u_int8_t icmptype; + + if ($1 < 0 || $1 > 255) { + yyerror("invalid icmp code %lld", $1); + YYERROR; + } + icmptype = returnicmp6default >> 8; + $$ = (icmptype << 8 | $1); + } + ; + +dir : /* empty */ { $$ = PF_INOUT; } + | IN { $$ = PF_IN; } + | OUT { $$ = PF_OUT; } + ; + +quick : /* empty */ { $$.quick = 0; } + | QUICK { $$.quick = 1; } + ; + +logquick : /* empty */ { $$.log = 0; $$.quick = 0; $$.logif = 0; } + | log { $$ = $1; $$.quick = 0; } + | QUICK { $$.quick = 1; $$.log = 0; $$.logif = 0; } + | log QUICK { $$ = $1; $$.quick = 1; } + | QUICK log { $$ = $2; $$.quick = 1; } + ; + +log : LOG { $$.log = PF_LOG; $$.logif = 0; } + | LOG '(' logopts ')' { + $$.log = PF_LOG | $3.log; + $$.logif = $3.logif; + } + ; + +logopts : logopt { $$ = $1; } + | logopts comma logopt { + $$.log = $1.log | $3.log; + $$.logif = $3.logif; + if ($$.logif == 0) + $$.logif = $1.logif; + } + ; + +logopt : ALL { $$.log = PF_LOG_ALL; $$.logif = 0; } + | MATCHES { $$.log = PF_LOG_MATCHES; $$.logif = 0; } + | USER { $$.log = PF_LOG_USER; $$.logif = 0; } + | TO string { + const char *errstr; + u_int i; + + $$.log = 0; + if (strncmp($2, "pflog", 5)) { + yyerror("%s: should be a pflog interface", $2); + free($2); + YYERROR; + } + i = strtonum($2 + 5, 0, 255, &errstr); + if (errstr) { + yyerror("%s: %s", $2, errstr); + free($2); + YYERROR; + } + free($2); + $$.logif = i; + } + ; + +interface : /* empty */ { $$ = NULL; } + | ON if_item_not { $$ = $2; } + | ON '{' optnl if_list '}' { $$ = $4; } + ; + +if_list : if_item_not optnl { $$ = $1; } + | if_list comma if_item_not optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +if_item_not : not if_item { $$ = $2; $$->not = $1; } + ; + +if_item : STRING { + struct node_host *n; + + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >= + sizeof($$->ifname)) { + free($1); + free($$); + yyerror("interface name too long"); + YYERROR; + } + + if ((n = ifa_exists($1)) != NULL) + $$->ifa_flags = n->ifa_flags; + + free($1); + $$->not = 0; + $$->next = NULL; + $$->tail = $$; + } + | ANY { + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + strlcpy($$->ifname, "any", sizeof($$->ifname)); + $$->not = 0; + $$->next = NULL; + $$->tail = $$; + } + | RDOMAIN NUMBER { + if ($2 < 0 || $2 > RT_TABLEID_MAX) + yyerror("rdomain %lld outside range", $2); + + $$ = calloc(1, sizeof(struct node_if)); + if ($$ == NULL) + err(1, "if_item: calloc"); + $$->not = 0; + $$->use_rdomain = 1; + $$->rdomain = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +af : /* empty */ { $$ = 0; } + | INET { $$ = AF_INET; } + | INET6 { $$ = AF_INET6; } + ; + +proto : /* empty */ { $$ = NULL; } + | PROTO proto_item { $$ = $2; } + | PROTO '{' optnl proto_list '}' { $$ = $4; } + ; + +proto_list : proto_item optnl { $$ = $1; } + | proto_list comma proto_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +proto_item : protoval { + u_int8_t pr; + + pr = (u_int8_t)$1; + if (pr == 0) { + yyerror("proto 0 cannot be used"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_proto)); + if ($$ == NULL) + err(1, "proto_item: calloc"); + $$->proto = pr; + $$->next = NULL; + $$->tail = $$; + } + ; + +protoval : STRING { + struct protoent *p; + + p = getprotobyname($1); + if (p == NULL) { + yyerror("unknown protocol %s", $1); + free($1); + YYERROR; + } + $$ = p->p_proto; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("protocol outside range"); + YYERROR; + } + } + ; + +fromto : ALL { + $$.src.host = NULL; + $$.src.port = NULL; + $$.dst.host = NULL; + $$.dst.port = NULL; + $$.src_os = NULL; + } + | from os to { + $$.src = $1; + $$.src_os = $2; + $$.dst = $3; + } + ; + +os : /* empty */ { $$ = NULL; } + | OS xos { $$ = $2; } + | OS '{' optnl os_list '}' { $$ = $4; } + ; + +xos : STRING { + $$ = calloc(1, sizeof(struct node_os)); + if ($$ == NULL) + err(1, "os: calloc"); + $$->os = $1; + $$->tail = $$; + } + ; + +os_list : xos optnl { $$ = $1; } + | os_list comma xos optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +from : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | FROM ipportspec { + $$ = $2; + } + ; + +to : /* empty */ { + $$.host = NULL; + $$.port = NULL; + } + | TO ipportspec { + if (disallow_urpf_failed($2.host, "\"urpf-failed\" is " + "not permitted in a destination address")) + YYERROR; + $$ = $2; + } + ; + +ipportspec : ipspec { + $$.host = $1; + $$.port = NULL; + } + | ipspec PORT portspec { + $$.host = $1; + $$.port = $3; + } + | PORT portspec { + $$.host = NULL; + $$.port = $2; + } + ; + +optnl : '\n' optnl + | /* empty */ + ; + +ipspec : ANY { $$ = NULL; } + | xhost { $$ = $1; } + | '{' optnl host_list '}' { $$ = $3; } + ; + + +host_list : ipspec optnl { $$ = $1; } + | host_list comma ipspec optnl { + if ($1 == NULL) { + freehostlist($3); + $$ = $1; + } else if ($3 == NULL) { + freehostlist($1); + $$ = $3; + } else { + $1->tail->next = $3; + $1->tail = $3->tail; + $$ = $1; + } + } + ; + +xhost : not host { + struct node_host *n; + + for (n = $2; n != NULL; n = n->next) + n->not = $1; + $$ = $2; + } + | not NOROUTE { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "xhost: calloc"); + $$->addr.type = PF_ADDR_NOROUTE; + $$->next = NULL; + $$->not = $1; + $$->tail = $$; + } + | not URPFFAILED { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "xhost: calloc"); + $$->addr.type = PF_ADDR_URPFFAILED; + $$->next = NULL; + $$->not = $1; + $$->tail = $$; + } + ; + +optweight : WEIGHT NUMBER { + if ($2 < 1 || $2 > USHRT_MAX) { + yyerror("weight out of range"); + YYERROR; + } + $$ = $2; + } + | /* empty */ { $$ = 0; } + ; + +host : STRING { + if (($$ = host($1, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free($1); + yyerror("could not parse host specification"); + YYERROR; + } + free($1); + + } + | STRING '-' STRING { + struct node_host *b, *e; + + if ((b = host($1, pf->opts)) == NULL || + (e = host($3, pf->opts)) == NULL) { + free($1); + free($3); + yyerror("could not parse host specification"); + YYERROR; + } + if (b->af != e->af || + b->addr.type != PF_ADDR_ADDRMASK || + e->addr.type != PF_ADDR_ADDRMASK || + unmask(&b->addr.v.a.mask) != + (b->af == AF_INET ? 32 : 128) || + unmask(&e->addr.v.a.mask) != + (e->af == AF_INET ? 32 : 128) || + b->next != NULL || b->not || + e->next != NULL || e->not) { + free(b); + free(e); + free($1); + free($3); + yyerror("invalid address range"); + YYERROR; + } + memcpy(&b->addr.v.a.mask, &e->addr.v.a.addr, + sizeof(b->addr.v.a.mask)); + b->addr.type = PF_ADDR_RANGE; + $$ = b; + free(e); + free($1); + free($3); + } + | STRING '/' NUMBER { + char *buf; + + if (asprintf(&buf, "%s/%lld", $1, $3) == -1) + err(1, "host: asprintf"); + free($1); + if (($$ = host(buf, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + | NUMBER '/' NUMBER { + char *buf; + + /* ie. for 10/8 parsing */ + if (asprintf(&buf, "%lld/%lld", $1, $3) == -1) + err(1, "host: asprintf"); + if (($$ = host(buf, pf->opts)) == NULL) { + /* error. "any" is handled elsewhere */ + free(buf); + yyerror("could not parse host specification"); + YYERROR; + } + free(buf); + } + | dynaddr + | dynaddr '/' NUMBER { + struct node_host *n; + + if ($3 < 0 || $3 > 128) { + yyerror("bit number too big"); + YYERROR; + } + $$ = $1; + for (n = $1; n != NULL; n = n->next) + set_ipmask(n, $3); + } + | '<' STRING '>' { + if (strlen($2) >= PF_TABLE_NAME_SIZE) { + yyerror("table name '%s' too long", $2); + free($2); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "host: calloc"); + $$->addr.type = PF_ADDR_TABLE; + if (strlcpy($$->addr.v.tblname, $2, + sizeof($$->addr.v.tblname)) >= + sizeof($$->addr.v.tblname)) + errx(1, "host: strlcpy"); + free($2); + $$->next = NULL; + $$->tail = $$; + } + | ROUTE STRING { + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) { + free($2); + err(1, "host: calloc"); + } + $$->addr.type = PF_ADDR_RTLABEL; + if (strlcpy($$->addr.v.rtlabelname, $2, + sizeof($$->addr.v.rtlabelname)) >= + sizeof($$->addr.v.rtlabelname)) { + yyerror("route label too long, max %zu chars", + sizeof($$->addr.v.rtlabelname) - 1); + free($2); + free($$); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + free($2); + } + ; + +number : NUMBER + | STRING { + u_long ulval; + + if (atoul($1, &ulval) == -1) { + yyerror("%s is not a number", $1); + free($1); + YYERROR; + } else + $$ = ulval; + free($1); + } + ; + +dynaddr : '(' STRING ')' { + int flags = 0; + char *p, *op; + + op = $2; + if (!isalpha((unsigned char)op[0])) { + yyerror("invalid interface name '%s'", op); + free(op); + YYERROR; + } + while ((p = strrchr($2, ':')) != NULL) { + if (!strcmp(p+1, "network")) + flags |= PFI_AFLAG_NETWORK; + else if (!strcmp(p+1, "broadcast")) + flags |= PFI_AFLAG_BROADCAST; + else if (!strcmp(p+1, "peer")) + flags |= PFI_AFLAG_PEER; + else if (!strcmp(p+1, "0")) + flags |= PFI_AFLAG_NOALIAS; + else { + yyerror("interface %s has bad modifier", + $2); + free(op); + YYERROR; + } + *p = '\0'; + } + if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { + free(op); + yyerror("illegal combination of " + "interface modifiers"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_host)); + if ($$ == NULL) + err(1, "address: calloc"); + $$->af = 0; + set_ipmask($$, 128); + $$->addr.type = PF_ADDR_DYNIFTL; + $$->addr.iflags = flags; + if (strlcpy($$->addr.v.ifname, $2, + sizeof($$->addr.v.ifname)) >= + sizeof($$->addr.v.ifname)) { + free(op); + free($$); + yyerror("interface name too long"); + YYERROR; + } + free(op); + $$->next = NULL; + $$->tail = $$; + } + ; + +portspec : port_item { $$ = $1; } + | '{' optnl port_list '}' { $$ = $3; } + ; + +port_list : port_item optnl { $$ = $1; } + | port_list comma port_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +port_item : portrange { + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $1.b; + if ($1.t) { + $$->op = PF_OP_RRG; + + if (validate_range($$->op, $$->port[0], + $$->port[1])) { + yyerror("invalid port range"); + YYERROR; + } + } else + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop portrange { + if ($2.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $2.a; + $$->port[1] = $2.b; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | portrange PORTBINARY portrange { + if ($1.t || $3.t) { + yyerror("':' cannot be used with an other " + "port operator"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_port)); + if ($$ == NULL) + err(1, "port_item: calloc"); + $$->port[0] = $1.a; + $$->port[1] = $3.a; + $$->op = $2; + if (validate_range($$->op, $$->port[0], $$->port[1])) { + yyerror("invalid port range"); + YYERROR; + } + $$->next = NULL; + $$->tail = $$; + } + ; + +portplain : numberstring { + if (parseport($1, &$$, 0) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +portrange : numberstring { + if (parseport($1, &$$, PPORT_RANGE) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +uids : uid_item { $$ = $1; } + | '{' optnl uid_list '}' { $$ = $3; } + ; + +uid_list : uid_item optnl { $$ = $1; } + | uid_list comma uid_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +uid_item : uid { + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop uid { + if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $2; + $$->uid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | uid PORTBINARY uid { + if ($1 == -1 || $3 == -1) { + yyerror("user unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_uid)); + if ($$ == NULL) + err(1, "uid_item: calloc"); + $$->uid[0] = $1; + $$->uid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +uid : STRING { + if (!strcmp($1, "unknown")) + $$ = -1; + else { + uid_t uid; + + if (uid_from_user($1, &uid) == -1) { + yyerror("unknown user %s", $1); + free($1); + YYERROR; + } + $$ = uid; + } + free($1); + } + | NUMBER { + if ($1 < 0 || $1 >= UINT_MAX) { + yyerror("illegal uid value %lld", $1); + YYERROR; + } + $$ = $1; + } + ; + +gids : gid_item { $$ = $1; } + | '{' optnl gid_list '}' { $$ = $3; } + ; + +gid_list : gid_item optnl { $$ = $1; } + | gid_list comma gid_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +gid_item : gid { + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $1; + $$->op = PF_OP_EQ; + $$->next = NULL; + $$->tail = $$; + } + | unaryop gid { + if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $2; + $$->gid[1] = $2; + $$->op = $1; + $$->next = NULL; + $$->tail = $$; + } + | gid PORTBINARY gid { + if ($1 == -1 || $3 == -1) { + yyerror("group unknown requires operator = or " + "!="); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_gid)); + if ($$ == NULL) + err(1, "gid_item: calloc"); + $$->gid[0] = $1; + $$->gid[1] = $3; + $$->op = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +gid : STRING { + if (!strcmp($1, "unknown")) + $$ = -1; + else { + gid_t gid; + + if (gid_from_group($1, &gid) == -1) { + yyerror("unknown group %s", $1); + free($1); + YYERROR; + } + $$ = gid; + } + free($1); + } + | NUMBER { + if ($1 < 0 || $1 >= UINT_MAX) { + yyerror("illegal gid value %lld", $1); + YYERROR; + } + $$ = $1; + } + ; + +flag : STRING { + int f; + + if ((f = parse_flags($1)) < 0) { + yyerror("bad flags %s", $1); + free($1); + YYERROR; + } + free($1); + $$.b1 = f; + } + ; + +flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } + | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } + | FLAGS ANY { $$.b1 = 0; $$.b2 = 0; } + ; + +icmpspec : ICMPTYPE icmp_item { $$ = $2; } + | ICMPTYPE '{' optnl icmp_list '}' { $$ = $4; } + | ICMP6TYPE icmp6_item { $$ = $2; } + | ICMP6TYPE '{' optnl icmp6_list '}' { $$ = $4; } + ; + +icmp_list : icmp_item optnl { $$ = $1; } + | icmp_list comma icmp_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp6_list : icmp6_item optnl { $$ = $1; } + | icmp6_list comma icmp6_item optnl { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +icmp_item : icmptype { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE STRING { + const struct icmpcodeent *p; + + if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) { + yyerror("unknown icmp-code %s", $3); + free($3); + YYERROR; + } + + free($3); + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = p->code + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + | icmptype CODE NUMBER { + if ($3 < 0 || $3 > 255) { + yyerror("illegal icmp-code %lld", $3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = $3 + 1; + $$->proto = IPPROTO_ICMP; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmp6_item : icmp6type { + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = 0; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE STRING { + const struct icmpcodeent *p; + + if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) { + yyerror("unknown icmp6-code %s", $3); + free($3); + YYERROR; + } + free($3); + + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = p->code + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + | icmp6type CODE NUMBER { + if ($3 < 0 || $3 > 255) { + yyerror("illegal icmp-code %lld", $3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_icmp)); + if ($$ == NULL) + err(1, "icmp_item: calloc"); + $$->type = $1; + $$->code = $3 + 1; + $$->proto = IPPROTO_ICMPV6; + $$->next = NULL; + $$->tail = $$; + } + ; + +icmptype : STRING { + const struct icmptypeent *p; + + if ((p = geticmptypebyname($1, AF_INET)) == NULL) { + yyerror("unknown icmp-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("illegal icmp-type %lld", $1); + YYERROR; + } + $$ = $1 + 1; + } + ; + +icmp6type : STRING { + const struct icmptypeent *p; + + if ((p = geticmptypebyname($1, AF_INET6)) == + NULL) { + yyerror("unknown icmp6-type %s", $1); + free($1); + YYERROR; + } + $$ = p->type + 1; + free($1); + } + | NUMBER { + if ($1 < 0 || $1 > 255) { + yyerror("illegal icmp6-type %lld", $1); + YYERROR; + } + $$ = $1 + 1; + } + ; + +tos : STRING { + int val; + char *end; + + if (map_tos($1, &val)) + $$ = val; + else if ($1[0] == '0' && $1[1] == 'x') { + errno = 0; + $$ = strtoul($1, &end, 16); + if (errno || *end != '\0') + $$ = 256; + } else + $$ = 256; /* flag bad argument */ + if ($$ < 0 || $$ > 255) { + yyerror("illegal tos value %s", $1); + free($1); + YYERROR; + } + free($1); + } + | NUMBER { + $$ = $1; + if ($$ < 0 || $$ > 255) { + yyerror("illegal tos value %lld", $1); + YYERROR; + } + } + ; + +sourcetrack : /* empty */ { $$ = PF_SRCTRACK; } + | GLOBAL { $$ = PF_SRCTRACK_GLOBAL; } + | RULE { $$ = PF_SRCTRACK_RULE; } + ; + +statelock : IFBOUND { + $$ = PFRULE_IFBOUND; + } + | FLOATING { + $$ = 0; + } + ; + +keep : NO STATE { + $$.action = 0; + $$.options = NULL; + } + | KEEP STATE state_opt_spec { + $$.action = PF_STATE_NORMAL; + $$.options = $3; + } + | MODULATE STATE state_opt_spec { + $$.action = PF_STATE_MODULATE; + $$.options = $3; + } + | SYNPROXY STATE state_opt_spec { + $$.action = PF_STATE_SYNPROXY; + $$.options = $3; + } + ; + +flush : /* empty */ { $$ = 0; } + | FLUSH { $$ = PF_FLUSH; } + | FLUSH GLOBAL { + $$ = PF_FLUSH | PF_FLUSH_GLOBAL; + } + ; + +state_opt_spec : '(' state_opt_list ')' { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +state_opt_list : state_opt_item { $$ = $1; } + | state_opt_list comma state_opt_item { + $1->tail->next = $3; + $1->tail = $3; + $$ = $1; + } + ; + +state_opt_item : MAXIMUM NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX; + $$->data.max_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | NOSYNC { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_NOSYNC; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCSTATES NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_STATES; + $$->data.max_src_states = $2; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCCONN NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_CONN; + $$->data.max_src_conn = $2; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCCONNRATE NUMBER '/' NUMBER { + if ($2 < 0 || $2 > UINT_MAX || + $4 < 0 || $4 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_CONN_RATE; + $$->data.max_src_conn_rate.limit = $2; + $$->data.max_src_conn_rate.seconds = $4; + $$->next = NULL; + $$->tail = $$; + } + | OVERLOAD '<' STRING '>' flush { + if (strlen($3) >= PF_TABLE_NAME_SIZE) { + yyerror("table name '%s' too long", $3); + free($3); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + if (strlcpy($$->data.overload.tblname, $3, + PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE) + errx(1, "state_opt_item: strlcpy"); + free($3); + $$->type = PF_STATE_OPT_OVERLOAD; + $$->data.overload.flush = $5; + $$->next = NULL; + $$->tail = $$; + } + | MAXSRCNODES NUMBER { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_MAX_SRC_NODES; + $$->data.max_src_nodes = $2; + $$->next = NULL; + $$->tail = $$; + } + | SOURCETRACK sourcetrack { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SRCTRACK; + $$->data.src_track = $2; + $$->next = NULL; + $$->tail = $$; + } + | statelock { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_STATELOCK; + $$->data.statelock = $1; + $$->next = NULL; + $$->tail = $$; + } + | SLOPPY { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_SLOPPY; + $$->next = NULL; + $$->tail = $$; + } + | PFLOW { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PFLOW; + $$->next = NULL; + $$->tail = $$; + } + | STRING NUMBER { + int i; + + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + for (i = 0; pf_timeouts[i].name && + strcmp(pf_timeouts[i].name, $1); ++i) + ; /* nothing */ + if (!pf_timeouts[i].name) { + yyerror("illegal timeout name %s", $1); + free($1); + YYERROR; + } + if (strchr(pf_timeouts[i].name, '.') == NULL) { + yyerror("illegal state timeout %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_TIMEOUT; + $$->data.timeout.number = pf_timeouts[i].timeout; + $$->data.timeout.seconds = $2; + $$->next = NULL; + $$->tail = $$; + } + ; + +label : STRING { + $$ = $1; + } + ; + +qname : STRING { + struct pfctl_qsitem *qsi; + + if ((qsi = pfctl_find_queue($1, &qspecs)) == NULL) { + yyerror("queue %s is not defined", $1); + YYERROR; + } + $$.qname = $1; + $$.pqname = NULL; + } + | '(' STRING ')' { + struct pfctl_qsitem *qsi; + + if ((qsi = pfctl_find_queue($2, &qspecs)) == NULL) { + yyerror("queue %s is not defined", $2); + YYERROR; + } + $$.qname = $2; + $$.pqname = NULL; + } + | '(' STRING comma STRING ')' { + struct pfctl_qsitem *qsi, *pqsi; + + if ((qsi = pfctl_find_queue($2, &qspecs)) == NULL) { + yyerror("queue %s is not defined", $2); + YYERROR; + } + if ((pqsi = pfctl_find_queue($4, &qspecs)) == NULL) { + yyerror("queue %s is not defined", $4); + YYERROR; + } + $$.qname = $2; + $$.pqname = $4; + } + ; + +portstar : numberstring { + if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) { + free($1); + YYERROR; + } + free($1); + } + ; + +redirspec : host optweight { + if ($2 > 0) { + struct node_host *n; + for (n = $1; n != NULL; n = n->next) + n->weight = $2; + } + $$ = $1; + } + | '{' optnl redir_host_list '}' { $$ = $3; } + ; + +redir_host_list : host optweight optnl { + if ($1->addr.type != PF_ADDR_ADDRMASK) { + free($1); + yyerror("only addresses can be listed for " + "redirection pools "); + YYERROR; + } + if ($2 > 0) { + struct node_host *n; + for (n = $1; n != NULL; n = n->next) + n->weight = $2; + } + $$ = $1; + } + | redir_host_list comma host optweight optnl { + $1->tail->next = $3; + $1->tail = $3->tail; + if ($4 > 0) { + struct node_host *n; + for (n = $3; n != NULL; n = n->next) + n->weight = $4; + } + $$ = $1; + } + ; + +redirpool : redirspec { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $1; + $$->rport.a = $$->rport.b = $$->rport.t = 0; + } + | redirspec PORT portstar { + $$ = calloc(1, sizeof(struct redirection)); + if ($$ == NULL) + err(1, "redirection: calloc"); + $$->host = $1; + $$->rport = $3; + } + ; + +hashkey : /* empty */ + { + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + $$->key32[0] = arc4random(); + $$->key32[1] = arc4random(); + $$->key32[2] = arc4random(); + $$->key32[3] = arc4random(); + } + | string + { + if (!strncmp($1, "0x", 2)) { + if (strlen($1) != 34) { + free($1); + yyerror("hex key must be 128 bits " + "(32 hex digits) long"); + YYERROR; + } + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + + if (sscanf($1, "0x%8x%8x%8x%8x", + &$$->key32[0], &$$->key32[1], + &$$->key32[2], &$$->key32[3]) != 4) { + free($$); + free($1); + yyerror("invalid hex key"); + YYERROR; + } + } else { + MD5_CTX context; + + $$ = calloc(1, sizeof(struct pf_poolhashkey)); + if ($$ == NULL) + err(1, "hashkey: calloc"); + MD5Init(&context); + MD5Update(&context, (unsigned char *)$1, + strlen($1)); + MD5Final((unsigned char *)$$, &context); + HTONL($$->key32[0]); + HTONL($$->key32[1]); + HTONL($$->key32[2]); + HTONL($$->key32[3]); + } + free($1); + } + ; + +pool_opts : { bzero(&pool_opts, sizeof pool_opts); } + pool_opts_l + { $$ = pool_opts; } + | /* empty */ { + bzero(&pool_opts, sizeof pool_opts); + $$ = pool_opts; + } + ; + +pool_opts_l : pool_opts_l pool_opt + | pool_opt + ; + +pool_opt : BITMASK { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_BITMASK; + } + | RANDOM { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_RANDOM; + } + | SOURCEHASH hashkey { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_SRCHASH; + pool_opts.key = $2; + } + | ROUNDROBIN { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_ROUNDROBIN; + } + | LEASTSTATES { + if (pool_opts.type) { + yyerror("pool type cannot be redefined"); + YYERROR; + } + pool_opts.type = PF_POOL_LEASTSTATES; + } + | STATICPORT { + if (pool_opts.staticport) { + yyerror("static-port cannot be redefined"); + YYERROR; + } + pool_opts.staticport = 1; + } + | STICKYADDRESS { + if (pool_opts.marker & POM_STICKYADDRESS) { + yyerror("sticky-address cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_STICKYADDRESS; + pool_opts.opts |= PF_POOL_STICKYADDR; + } + ; + +routespec : redirspec pool_opts { + struct redirection *redir; + if (filter_opts.rt != PF_NOPFROUTE) { + yyerror("cannot respecify " + "route-to/reply-to/dup-to"); + YYERROR; + } + redir = calloc(1, sizeof(*redir)); + if (redir == NULL) + err(1, "routespec calloc"); + redir->host = $1; + filter_opts.rroute.rdr = redir; + memcpy(&filter_opts.rroute.pool_opts, &$2, + sizeof(filter_opts.rroute.pool_opts)); + } + ; + +timeout_spec : STRING NUMBER + { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (pfctl_set_timeout(pf, $1, $2, 0) != 0) { + yyerror("unknown timeout %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +timeout_list : timeout_list comma timeout_spec optnl + | timeout_spec optnl + ; + +limit_spec : STRING NUMBER + { + if ($2 < 0 || $2 > UINT_MAX) { + yyerror("only positive values permitted"); + YYERROR; + } + if (pfctl_set_limit(pf, $1, $2) != 0) { + yyerror("unable to set limit %s %lld", $1, $2); + free($1); + YYERROR; + } + free($1); + } + ; + +limit_list : limit_list comma limit_spec optnl + | limit_spec optnl + ; + +comma : ',' + | /* empty */ + ; + +yesno : NO { $$ = 0; } + | STRING { + if (!strcmp($1, "yes")) + $$ = 1; + else { + yyerror("invalid value '%s', expected 'yes' " + "or 'no'", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +unaryop : '=' { $$ = PF_OP_EQ; } + | NE { $$ = PF_OP_NE; } + | LE { $$ = PF_OP_LE; } + | '<' { $$ = PF_OP_LT; } + | GE { $$ = PF_OP_GE; } + | '>' { $$ = PF_OP_GT; } + ; + +%% + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +validate_range(u_int8_t op, u_int16_t p1, u_int16_t p2) +{ + u_int16_t a = ntohs(p1); + u_int16_t b = ntohs(p2); + + if ((op == PF_OP_RRG && a > b) || /* 34:12, i.e. none */ + (op == PF_OP_IRG && a >= b) || /* 34><12, i.e. none */ + (op == PF_OP_XRG && a > b)) /* 34<>22, i.e. all */ + return 1; + return 0; +} + +int +disallow_table(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_TABLE) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +disallow_urpf_failed(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (h->addr.type == PF_ADDR_URPFFAILED) { + yyerror("%s", fmt); + return (1); + } + return (0); +} + +int +disallow_alias(struct node_host *h, const char *fmt) +{ + for (; h != NULL; h = h->next) + if (DYNIF_MULTIADDR(h->addr)) { + yyerror(fmt, h->addr.v.tblname); + return (1); + } + return (0); +} + +int +rule_consistent(struct pf_rule *r) +{ + int problems = 0; + + if (r->proto != IPPROTO_TCP && r->os_fingerprint != PF_OSFP_ANY) { + yyerror("os only applies to tcp"); + problems++; + } + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + (r->src.port_op || r->dst.port_op)) { + yyerror("port only applies to tcp/udp"); + problems++; + } + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->uid.op) { + yyerror("user only applies to tcp/udp"); + problems++; + } + if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && + r->gid.op) { + yyerror("group only applies to tcp/udp"); + problems++; + } + if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && + (r->type || r->code)) { + yyerror("icmp-type/code only applies to icmp"); + problems++; + } + if (!r->af && (r->type || r->code)) { + yyerror("must indicate address family with icmp-type/code"); + problems++; + } + if (r->rule_flag & PFRULE_AFTO && r->af == r->naf) { + yyerror("must indicate different address family with af-to"); + problems++; + } + if (r->overload_tblname[0] && + r->max_src_conn == 0 && r->max_src_conn_rate.seconds == 0) { + yyerror("'overload' requires 'max-src-conn' " + "or 'max-src-conn-rate'"); + problems++; + } + if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || + (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { + yyerror("proto %s doesn't match address family %s", + r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", + r->af == AF_INET ? "inet" : "inet6"); + problems++; + } + if (r->allow_opts && r->action != PF_PASS) { + yyerror("allow-opts can only be specified for pass rules"); + problems++; + } + if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || + r->dst.port_op || r->flagset || r->type || r->code)) { + yyerror("fragments can be filtered only on IP header fields"); + problems++; + } + if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { + yyerror("return-rst can only be applied to TCP rules"); + problems++; + } + if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) { + yyerror("max-src-nodes requires 'source-track rule'"); + problems++; + } + if (r->action != PF_PASS && r->keep_state) { + yyerror("keep state is great, but only for pass rules"); + problems++; + } + if (r->rt && !r->keep_state) { + yyerror("route-to, reply-to and dup-to require keep state"); + problems++; + } + if (r->rule_flag & PFRULE_STATESLOPPY && + (r->keep_state == PF_STATE_MODULATE || + r->keep_state == PF_STATE_SYNPROXY)) { + yyerror("sloppy state matching cannot be used with " + "synproxy state or modulate state"); + problems++; + } + + if ((r->keep_state == PF_STATE_SYNPROXY) && (r->direction != PF_IN)) + fprintf(stderr, "%s:%d: warning: " + "synproxy used for inbound rules only, " + "ignored for outbound\n", file->name, yylval.lineno); + + if ((r->nat.addr.type != PF_ADDR_NONE || + r->rdr.addr.type != PF_ADDR_NONE) && + r->action != PF_MATCH && !r->keep_state) { + yyerror("nat-to and rdr-to require keep state"); + problems++; + } + if (r->direction == PF_INOUT && (r->nat.addr.type != PF_ADDR_NONE || + r->rdr.addr.type != PF_ADDR_NONE)) { + yyerror("nat-to and rdr-to require a direction"); + problems++; + } + if (r->af == AF_INET6 && (r->scrub_flags & + (PFSTATE_NODF|PFSTATE_RANDOMID))) { + yyerror("address family inet6 does not support scrub options " + "no-df, random-id"); + problems++; + } + + /* Basic rule sanity check. */ + switch (r->action) { + case PF_MATCH: + if (r->divert.type != PF_DIVERT_NONE) { + yyerror("divert is not supported on match rules"); + problems++; + } + if (r->rt) { + yyerror("route-to, reply-to and dup-to " + "are not supported on match rules"); + problems++; + } + if (r->rule_flag & PFRULE_AFTO) { + yyerror("af-to is not supported on match rules"); + problems++; + } + break; + case PF_DROP: + if (r->rt) { + yyerror("route-to, reply-to and dup-to " + "are not supported on block rules"); + problems++; + } + break; + default:; + } + return (-problems); +} + +int +process_tabledef(char *name, struct table_opts *opts, int popts) +{ + struct pfr_buffer ab; + struct node_tinit *ti; + struct pfr_uktable *ukt; + + bzero(&ab, sizeof(ab)); + ab.pfrb_type = PFRB_ADDRS; + SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { + if (ti->file) + if (pfr_buf_load(&ab, ti->file, 0, popts)) { + if (errno) + yyerror("cannot load \"%s\": %s", + ti->file, strerror(errno)); + else + yyerror("file \"%s\" contains bad data", + ti->file); + goto _error; + } + if (ti->host) + if (append_addr_host(&ab, ti->host, 0, 0)) { + yyerror("cannot create address buffer: %s", + strerror(errno)); + goto _error; + } + } + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(name, opts->flags, opts->init_addr, + &opts->init_nodes); + if (!(pf->opts & PF_OPT_NOACTION) || + (pf->opts & PF_OPT_DUMMYACTION)) + warn_duplicate_tables(name, pf->anchor->path); + else if (pf->opts & PF_OPT_VERBOSE) + fprintf(stderr, "%s:%d: skipping duplicate table checks" + " for <%s>\n", file->name, yylval.lineno, name); + + /* + * postpone definition of non-root tables to moment + * when path is fully resolved. + */ + if (pf->asd > 0) { + ukt = calloc(1, sizeof(struct pfr_uktable)); + if (ukt == NULL) { + DBGPRINT( + "%s:%d: not enough memory for <%s>\n", file->name, + yylval.lineno, name); + goto _error; + } + } else + ukt = NULL; + + if (!(pf->opts & PF_OPT_NOACTION) && + pfctl_define_table(name, opts->flags, opts->init_addr, + pf->anchor->path, &ab, pf->anchor->ruleset.tticket, ukt)) { + yyerror("cannot define table %s: %s", name, + pf_strerror(errno)); + goto _error; + } + + if (ukt != NULL) { + ukt->pfrukt_init_addr = opts->init_addr; + if (RB_INSERT(pfr_ktablehead, &pfr_ktables, + &ukt->pfrukt_kt) != NULL) { + /* + * I think this should not happen, because + * pfctl_define_table() above does the same check + * effectively. + */ + DBGPRINT( + "%s:%d table %s already exists in %s\n", + file->name, yylval.lineno, + ukt->pfrukt_name, pf->anchor->path); + free(ukt); + goto _error; + } + DBGPRINT("%s %s@%s inserted to tree\n", + __func__, ukt->pfrukt_name, pf->anchor->path); + + } else + DBGPRINT("%s ukt is null\n", __func__); + + pf->tdirty = 1; + pfr_buf_clear(&ab); + return (0); +_error: + pfr_buf_clear(&ab); + return (-1); +} + +struct keywords { + const char *k_name; + int k_val; +}; + +/* macro gore, but you should've seen the prior indentation nightmare... */ + +#define FREE_LIST(T,r) \ + do { \ + T *p, *node = r; \ + while (node != NULL) { \ + p = node; \ + node = node->next; \ + free(p); \ + } \ + } while (0) + +#define LOOP_THROUGH(T,n,r,C) \ + do { \ + T *n; \ + if (r == NULL) { \ + r = calloc(1, sizeof(T)); \ + if (r == NULL) \ + err(1, "LOOP: calloc"); \ + r->next = NULL; \ + } \ + n = r; \ + while (n != NULL) { \ + do { \ + C; \ + } while (0); \ + n = n->next; \ + } \ + } while (0) + +void +expand_label_str(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) + err(1, "%s", __func__); + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) + errx(1, "expand_label: label too long"); + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) + errx(1, "expand_label: label too long"); + strlcpy(label, tmp, len); /* always fits */ + free(tmp); +} + +void +expand_label_if(const char *name, char *label, size_t len, const char *ifname) +{ + if (strstr(label, name) != NULL) { + if (!*ifname) + expand_label_str(label, len, name, "any"); + else + expand_label_str(label, len, name, ifname); + } +} + +void +expand_label_addr(const char *name, char *label, size_t len, sa_family_t af, + struct node_host *h) +{ + char tmp[64], tmp_not[66]; + + if (strstr(label, name) != NULL) { + switch (h->addr.type) { + case PF_ADDR_DYNIFTL: + snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname); + break; + case PF_ADDR_TABLE: + snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname); + break; + case PF_ADDR_NOROUTE: + snprintf(tmp, sizeof(tmp), "no-route"); + break; + case PF_ADDR_URPFFAILED: + snprintf(tmp, sizeof(tmp), "urpf-failed"); + break; + case PF_ADDR_ADDRMASK: + if (!af || (PF_AZERO(&h->addr.v.a.addr, af) && + PF_AZERO(&h->addr.v.a.mask, af))) + snprintf(tmp, sizeof(tmp), "any"); + else { + char a[48]; + int bits; + + if (inet_ntop(af, &h->addr.v.a.addr, a, + sizeof(a)) == NULL) + snprintf(tmp, sizeof(tmp), "?"); + else { + bits = unmask(&h->addr.v.a.mask); + if ((af == AF_INET && bits < 32) || + (af == AF_INET6 && bits < 128)) + snprintf(tmp, sizeof(tmp), + "%s/%d", a, bits); + else + snprintf(tmp, sizeof(tmp), + "%s", a); + } + } + break; + default: + snprintf(tmp, sizeof(tmp), "?"); + break; + } + + if (h->not) { + snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); + expand_label_str(label, len, name, tmp_not); + } else + expand_label_str(label, len, name, tmp); + } +} + +void +expand_label_port(const char *name, char *label, size_t len, + struct node_port *port) +{ + char a1[6], a2[6], op[13] = ""; + + if (strstr(label, name) != NULL) { + snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0])); + snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1])); + if (!port->op) + ; + else if (port->op == PF_OP_IRG) + snprintf(op, sizeof(op), "%s><%s", a1, a2); + else if (port->op == PF_OP_XRG) + snprintf(op, sizeof(op), "%s<>%s", a1, a2); + else if (port->op == PF_OP_EQ) + snprintf(op, sizeof(op), "%s", a1); + else if (port->op == PF_OP_NE) + snprintf(op, sizeof(op), "!=%s", a1); + else if (port->op == PF_OP_LT) + snprintf(op, sizeof(op), "<%s", a1); + else if (port->op == PF_OP_LE) + snprintf(op, sizeof(op), "<=%s", a1); + else if (port->op == PF_OP_GT) + snprintf(op, sizeof(op), ">%s", a1); + else if (port->op == PF_OP_GE) + snprintf(op, sizeof(op), ">=%s", a1); + expand_label_str(label, len, name, op); + } +} + +void +expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto) +{ + struct protoent *pe; + char n[4]; + + if (strstr(label, name) != NULL) { + pe = getprotobynumber(proto); + if (pe != NULL) + expand_label_str(label, len, name, pe->p_name); + else { + snprintf(n, sizeof(n), "%u", proto); + expand_label_str(label, len, name, n); + } + } +} + +void +pfctl_expand_label_nr(struct pf_rule *r, unsigned int rno) +{ + char n[11]; + + snprintf(n, sizeof(n), "%u", rno); + + if (strstr(r->label, "$nr") != NULL) + expand_label_str(r->label, PF_RULE_LABEL_SIZE, "$nr", n); + + if (strstr(r->tagname, "$nr") != NULL) + expand_label_str(r->tagname, PF_TAG_NAME_SIZE, "$nr", n); + + if (strstr(r->match_tagname, "$nr") != NULL) + expand_label_str(r->match_tagname, PF_TAG_NAME_SIZE, "$nr", n); +} + +void +expand_label(char *label, size_t len, const char *ifname, sa_family_t af, + struct node_host *src_host, struct node_port *src_port, + struct node_host *dst_host, struct node_port *dst_port, + u_int8_t proto) +{ + expand_label_if("$if", label, len, ifname); + expand_label_addr("$srcaddr", label, len, af, src_host); + expand_label_addr("$dstaddr", label, len, af, dst_host); + expand_label_port("$srcport", label, len, src_port); + expand_label_port("$dstport", label, len, dst_port); + expand_label_proto("$proto", label, len, proto); + /* rule number, '$nr', gets expanded after optimizer */ +} + +int +expand_queue(char *qname, struct node_if *interfaces, struct queue_opts *opts) +{ + struct pf_queuespec qspec; + + LOOP_THROUGH(struct node_if, interface, interfaces, + bzero(&qspec, sizeof(qspec)); + if (!opts->parent && (opts->marker & QOM_BWSPEC)) + opts->flags |= PFQS_ROOTCLASS; + if (!(opts->marker & QOM_BWSPEC) && + !(opts->marker & QOM_FLOWS)) { + yyerror("no bandwidth or flow specification"); + return (1); + } + if (strlcpy(qspec.qname, qname, sizeof(qspec.qname)) >= + sizeof(qspec.qname)) { + yyerror("queuename too long"); + return (1); + } + if (opts->parent && strlcpy(qspec.parent, opts->parent, + sizeof(qspec.parent)) >= sizeof(qspec.parent)) { + yyerror("parent too long"); + return (1); + } + if (strlcpy(qspec.ifname, interface->ifname, + sizeof(qspec.ifname)) >= sizeof(qspec.ifname)) { + yyerror("interface too long"); + return (1); + } + qspec.realtime.m1.absolute = opts->realtime.m1.bw_absolute; + qspec.realtime.m1.percent = opts->realtime.m1.bw_percent; + qspec.realtime.m2.absolute = opts->realtime.m2.bw_absolute; + qspec.realtime.m2.percent = opts->realtime.m2.bw_percent; + qspec.realtime.d = opts->realtime.d; + + qspec.linkshare.m1.absolute = opts->linkshare.m1.bw_absolute; + qspec.linkshare.m1.percent = opts->linkshare.m1.bw_percent; + qspec.linkshare.m2.absolute = opts->linkshare.m2.bw_absolute; + qspec.linkshare.m2.percent = opts->linkshare.m2.bw_percent; + qspec.linkshare.d = opts->linkshare.d; + + qspec.upperlimit.m1.absolute = opts->upperlimit.m1.bw_absolute; + qspec.upperlimit.m1.percent = opts->upperlimit.m1.bw_percent; + qspec.upperlimit.m2.absolute = opts->upperlimit.m2.bw_absolute; + qspec.upperlimit.m2.percent = opts->upperlimit.m2.bw_percent; + qspec.upperlimit.d = opts->upperlimit.d; + + qspec.flowqueue.flows = opts->flowqueue.flows; + qspec.flowqueue.quantum = opts->flowqueue.quantum; + qspec.flowqueue.interval = opts->flowqueue.interval; + qspec.flowqueue.target = opts->flowqueue.target; + + qspec.flags = opts->flags; + qspec.qlimit = opts->qlimit; + + if (pfctl_add_queue(pf, &qspec)) { + yyerror("cannot add queue"); + return (1); + } + ); + + FREE_LIST(struct node_if, interfaces); + return (0); +} + +int +expand_divertspec(struct pf_rule *r, struct divertspec *ds) +{ + struct node_host *n; + + switch (ds->type) { + case PF_DIVERT_NONE: + return (0); + case PF_DIVERT_TO: + if (r->direction == PF_OUT) { + yyerror("divert-to used with outgoing rule"); + return (1); + } + if (r->af) { + for (n = ds->addr; n != NULL; n = n->next) + if (n->af == r->af) + break; + if (n == NULL) { + yyerror("divert-to address family mismatch"); + return (1); + } + r->divert.addr = n->addr.v.a.addr; + } else { + r->af = ds->addr->af; + r->divert.addr = ds->addr->addr.v.a.addr; + } + r->divert.port = ds->port; + r->divert.type = ds->type; + return (0); + case PF_DIVERT_REPLY: + if (r->direction == PF_IN) { + yyerror("divert-reply used with incoming rule"); + return (1); + } + r->divert.type = ds->type; + return (0); + case PF_DIVERT_PACKET: + r->divert.port = ds->port; + r->divert.type = ds->type; + return (0); + } + return (1); +} + +int +collapse_redirspec(struct pf_pool *rpool, struct pf_rule *r, + struct redirspec *rs, int routing) +{ + struct pf_opt_tbl *tbl = NULL; + struct node_host *h, *hprev = NULL; + struct pf_rule_addr ra; + int af = 0, naddr = 0; + + if (!rs || !rs->rdr || rs->rdr->host == NULL) { + rpool->addr.type = PF_ADDR_NONE; + return (0); + } + + if (r->rule_flag & PFRULE_AFTO) + r->naf = rs->af; + + for (h = rs->rdr->host; h != NULL; h = h->next) { + if (routing) { + if (h->addr.type == PF_ADDR_DYNIFTL && + h->addr.iflags != PFI_AFLAG_PEER) { + yyerror("route spec requires :peer with " + "dynamic interface addresses"); + return (1); + } + } + + /* set rule address family if redirect spec has one */ + if (rs->af && !r->af && !af) { + /* swap address families for af-to */ + if (r->naf == AF_INET6) + af = AF_INET; + else if (r->naf == AF_INET) + af = AF_INET6; + else + af = rs->af; + } + if (h->af && !r->naf) { /* nat-to/rdr-to case */ + /* skip if the rule af doesn't match redirect af */ + if (r->af && r->af != h->af) + continue; + /* + * fail if the chosen af is not universal for + * all addresses in the redirect address pool + */ + if (!r->af && af && af != h->af) { + yyerror("%s spec contains addresses with " + "different address families", + routing ? "routing" : "translation"); + return (1); + } + } else if (h->af) { /* af-to case */ + /* + * fail if the redirect spec af is not universal + * for all addresses in the redirect address pool + */ + if (rs->af && rs->af != h->af) { + yyerror("%s spec contains addresses that " + "don't match target address family", + routing ? "routing" : "translation"); + return (1); + } + } + /* else if (!h->af): + * we silently allow any not af-specific host specs, + * e.g. (em0) and let the kernel deal with them + */ + + /* if we haven't selected the rule af yet, now it's time */ + if (!r->af && !af) + af = h->af; + + if (naddr == 0) { /* the first host */ + rpool->addr = h->addr; + if (h->ifname) { + yyerror("@if not permitted for %s", + routing ? "routing" : "translation"); + return (1); + } + if (h->ifname && strlcpy(rpool->ifname, h->ifname, + sizeof(rpool->ifname)) >= sizeof(rpool->ifname)) + errx(1, "collapse_redirspec: strlcpy"); + hprev = h; /* in case we need to conver to a table */ + } else { /* multiple hosts */ + if (rs->pool_opts.type && + !PF_POOL_DYNTYPE(rs->pool_opts.type)) { + yyerror("pool type is not valid for multiple " + "translation or routing addresses"); + return (1); + } + if ((hprev && hprev->addr.type != PF_ADDR_ADDRMASK) && + (hprev && hprev->addr.type != PF_ADDR_NONE) && + h->addr.type != PF_ADDR_ADDRMASK && + h->addr.type != PF_ADDR_NONE) { + yyerror("multiple tables or dynamic interfaces " + "not supported for translation or routing"); + return (1); + } + if (h->ifname) { + yyerror("@if not permitted for %s", + routing ? "routing" : "translation"); + return (1); + } + if (hprev) { + /* + * undo some damage and convert the single + * host pool to the table + */ + memset(&ra, 0, sizeof(ra)); + memset(rpool->ifname, 0, sizeof(rpool->ifname)); + ra.addr = hprev->addr; + ra.weight = hprev->weight; + if (add_opt_table(pf, &tbl, + hprev->af, &ra, hprev->ifname)) + return (1); + hprev = NULL; + } + memset(&ra, 0, sizeof(ra)); + ra.addr = h->addr; + ra.weight = h->weight; + if (add_opt_table(pf, &tbl, + h->af, &ra, h->ifname)) + return (1); + } + naddr++; + } + /* set rule af to the one chosen above */ + if (!r->af && af) + r->af = af; + if (!naddr) { + yyerror("af mismatch in %s spec", + routing ? "routing" : "translation"); + return (1); + } + if (tbl) { + if ((pf->opts & PF_OPT_NOACTION) == 0 && + pf_opt_create_table(pf, tbl)) + return (1); + + pf->tdirty = 1; + + if (pf->opts & PF_OPT_VERBOSE) + print_tabledef(tbl->pt_name, + PFR_TFLAG_CONST | tbl->pt_flags, + 1, &tbl->pt_nodes); + + memset(&rpool->addr, 0, sizeof(rpool->addr)); + rpool->addr.type = PF_ADDR_TABLE; + strlcpy(rpool->addr.v.tblname, tbl->pt_name, + sizeof(rpool->addr.v.tblname)); + + pfr_buf_clear(tbl->pt_buf); + free(tbl->pt_buf); + tbl->pt_buf = NULL; + free(tbl); + } + return (0); +} + + +int +apply_redirspec(struct pf_pool *rpool, struct pf_rule *r, struct redirspec *rs, + int isrdr, struct node_port *np) +{ + if (!rs || !rs->rdr) + return (0); + + rpool->proxy_port[0] = ntohs(rs->rdr->rport.a); + + if (isrdr) { + if (!rs->rdr->rport.b && rs->rdr->rport.t) { + rpool->proxy_port[1] = ntohs(rs->rdr->rport.a) + + (ntohs(np->port[1]) - ntohs(np->port[0])); + } else { + if (validate_range(rs->rdr->rport.t, rs->rdr->rport.a, + rs->rdr->rport.b)) { + yyerror("invalid rdr-to port range"); + return (1); + } + + rpool->port_op = rs->rdr->rport.t; + rpool->proxy_port[1] = ntohs(rs->rdr->rport.b); + } + } else { + rpool->proxy_port[1] = ntohs(rs->rdr->rport.b); + if (!rpool->proxy_port[0] && !rpool->proxy_port[1]) { + rpool->proxy_port[0] = PF_NAT_PROXY_PORT_LOW; + rpool->proxy_port[1] = PF_NAT_PROXY_PORT_HIGH; + } else if (!rpool->proxy_port[1]) + rpool->proxy_port[1] = rpool->proxy_port[0]; + } + + rpool->opts = rs->pool_opts.type; + if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_NONE && + (rpool->addr.type == PF_ADDR_TABLE || + DYNIF_MULTIADDR(rpool->addr))) + rpool->opts |= PF_POOL_ROUNDROBIN; + + if (!PF_POOL_DYNTYPE(rpool->opts) && + (disallow_table(rs->rdr->host, + "tables are not supported by pool type") || + disallow_alias(rs->rdr->host, + "interface (%s) is not supported by pool type"))) + return (1); + + if (rs->pool_opts.key != NULL) + memcpy(&rpool->key, rs->pool_opts.key, + sizeof(struct pf_poolhashkey)); + + if (rs->pool_opts.opts) + rpool->opts |= rs->pool_opts.opts; + + if (rs->pool_opts.staticport) { + if (isrdr) { + yyerror("the 'static-port' option is only valid with " + "nat rules"); + return (1); + } + if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW && + rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) { + yyerror("the 'static-port' option can't be used when " + "specifying a port range"); + return (1); + } + rpool->proxy_port[0] = 0; + rpool->proxy_port[1] = 0; + } + + return (0); +} + + +void +expand_rule(struct pf_rule *r, int keeprule, struct node_if *interfaces, + struct redirspec *nat, struct redirspec *rdr, struct redirspec *rroute, + struct node_proto *protos, struct node_os *src_oses, + struct node_host *src_hosts, struct node_port *src_ports, + struct node_host *dst_hosts, struct node_port *dst_ports, + struct node_uid *uids, struct node_gid *gids, struct node_if *rcv, + struct node_icmp *icmp_types) +{ + sa_family_t af = r->af; + int added = 0, error = 0; + char ifname[IF_NAMESIZE]; + char label[PF_RULE_LABEL_SIZE]; + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + u_int8_t flags, flagset, keep_state; + struct node_host *srch, *dsth, *osrch, *odsth; + struct redirspec binat; + struct pf_rule rb; + int dir = r->direction; + + if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= + sizeof(match_tagname)) + errx(1, "expand_rule: strlcpy"); + flags = r->flags; + flagset = r->flagset; + keep_state = r->keep_state; + + r->src.addr.type = r->dst.addr.type = PF_ADDR_ADDRMASK; + + LOOP_THROUGH(struct node_if, interface, interfaces, + LOOP_THROUGH(struct node_proto, proto, protos, + LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, + LOOP_THROUGH(struct node_host, src_host, src_hosts, + LOOP_THROUGH(struct node_host, dst_host, dst_hosts, + LOOP_THROUGH(struct node_port, src_port, src_ports, + LOOP_THROUGH(struct node_port, dst_port, dst_ports, + LOOP_THROUGH(struct node_os, src_os, src_oses, + LOOP_THROUGH(struct node_uid, uid, uids, + LOOP_THROUGH(struct node_gid, gid, gids, + + r->af = af; + + error += collapse_redirspec(&r->rdr, r, rdr, 0); + error += collapse_redirspec(&r->nat, r, nat, 0); + error += collapse_redirspec(&r->route, r, rroute, 1); + + /* disallow @if in from or to for the time being */ + if ((src_host->addr.type == PF_ADDR_ADDRMASK && + src_host->ifname) || + (dst_host->addr.type == PF_ADDR_ADDRMASK && + dst_host->ifname)) { + yyerror("@if syntax not permitted in from or to"); + error++; + } + /* for link-local IPv6 address, interface must match up */ + if ((r->af && src_host->af && r->af != src_host->af) || + (r->af && dst_host->af && r->af != dst_host->af) || + (src_host->af && dst_host->af && + src_host->af != dst_host->af) || + (src_host->ifindex && dst_host->ifindex && + src_host->ifindex != dst_host->ifindex) || + (src_host->ifindex && *interface->ifname && + src_host->ifindex != ifa_nametoindex(interface->ifname)) || + (dst_host->ifindex && *interface->ifname && + dst_host->ifindex != ifa_nametoindex(interface->ifname))) + continue; + if (!r->af && src_host->af) + r->af = src_host->af; + else if (!r->af && dst_host->af) + r->af = dst_host->af; + + if (*interface->ifname) + strlcpy(r->ifname, interface->ifname, + sizeof(r->ifname)); + else if (ifa_indextoname(src_host->ifindex, ifname)) + strlcpy(r->ifname, ifname, sizeof(r->ifname)); + else if (ifa_indextoname(dst_host->ifindex, ifname)) + strlcpy(r->ifname, ifname, sizeof(r->ifname)); + else + memset(r->ifname, '\0', sizeof(r->ifname)); + + if (interface->use_rdomain) + r->onrdomain = interface->rdomain; + else + r->onrdomain = -1; + if (strlcpy(r->label, label, sizeof(r->label)) >= + sizeof(r->label)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= + sizeof(r->tagname)) + errx(1, "expand_rule: strlcpy"); + if (strlcpy(r->match_tagname, match_tagname, + sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) + errx(1, "expand_rule: strlcpy"); + expand_label(r->label, PF_RULE_LABEL_SIZE, r->ifname, r->af, + src_host, src_port, dst_host, dst_port, proto->proto); + expand_label(r->tagname, PF_TAG_NAME_SIZE, r->ifname, r->af, + src_host, src_port, dst_host, dst_port, proto->proto); + expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r->ifname, + r->af, src_host, src_port, dst_host, dst_port, + proto->proto); + + osrch = odsth = NULL; + if (src_host->addr.type == PF_ADDR_DYNIFTL) { + osrch = src_host; + if ((src_host = gen_dynnode(src_host, r->af)) == NULL) + err(1, "%s", __func__); + } + if (dst_host->addr.type == PF_ADDR_DYNIFTL) { + odsth = dst_host; + if ((dst_host = gen_dynnode(dst_host, r->af)) == NULL) + err(1, "%s", __func__); + } + + error += check_netmask(src_host, r->af); + error += check_netmask(dst_host, r->af); + + r->ifnot = interface->not; + r->proto = proto->proto; + r->src.addr = src_host->addr; + r->src.neg = src_host->not; + r->src.port[0] = src_port->port[0]; + r->src.port[1] = src_port->port[1]; + r->src.port_op = src_port->op; + r->dst.addr = dst_host->addr; + r->dst.neg = dst_host->not; + r->dst.port[0] = dst_port->port[0]; + r->dst.port[1] = dst_port->port[1]; + r->dst.port_op = dst_port->op; + r->uid.op = uid->op; + r->uid.uid[0] = uid->uid[0]; + r->uid.uid[1] = uid->uid[1]; + r->gid.op = gid->op; + r->gid.gid[0] = gid->gid[0]; + r->gid.gid[1] = gid->gid[1]; + if (rcv) { + strlcpy(r->rcv_ifname, rcv->ifname, + sizeof(r->rcv_ifname)); + r->rcvifnot = rcv->not; + } + r->type = icmp_type->type; + r->code = icmp_type->code; + + if ((keep_state == PF_STATE_MODULATE || + keep_state == PF_STATE_SYNPROXY) && + r->proto && r->proto != IPPROTO_TCP) + r->keep_state = PF_STATE_NORMAL; + else + r->keep_state = keep_state; + + if (r->proto && r->proto != IPPROTO_TCP) { + r->flags = 0; + r->flagset = 0; + } else { + r->flags = flags; + r->flagset = flagset; + } + if (icmp_type->proto && r->proto != icmp_type->proto) { + yyerror("icmp-type mismatch"); + error++; + } + + if (src_os && src_os->os) { + r->os_fingerprint = pfctl_get_fingerprint(src_os->os); + if ((pf->opts & PF_OPT_VERBOSE2) && + r->os_fingerprint == PF_OSFP_NOMATCH) + fprintf(stderr, + "warning: unknown '%s' OS fingerprint\n", + src_os->os); + } else { + r->os_fingerprint = PF_OSFP_ANY; + } + + if (nat && nat->rdr && nat->binat) { + if (disallow_table(src_host, "invalid use of table " + "<%s> as the source address of a binat-to rule") || + disallow_alias(src_host, "invalid use of interface " + "(%s) as the source address of a binat-to rule")) { + error++; + } else if ((r->src.addr.type != PF_ADDR_ADDRMASK && + r->src.addr.type != PF_ADDR_DYNIFTL) || + (r->nat.addr.type != PF_ADDR_ADDRMASK && + r->nat.addr.type != PF_ADDR_DYNIFTL)) { + yyerror("binat-to requires a specified " + "source and redirect address"); + error++; + } + if (DYNIF_MULTIADDR(r->src.addr) || + DYNIF_MULTIADDR(r->nat.addr)) { + yyerror ("dynamic interfaces must be used with " + ":0 in a binat-to rule"); + error++; + } + if (PF_AZERO(&r->src.addr.v.a.mask, af) || + PF_AZERO(&r->nat.addr.v.a.mask, af)) { + yyerror ("source and redir addresess must have " + "a matching network mask in binat-rule"); + error++; + } + if (r->nat.addr.type == PF_ADDR_TABLE) { + yyerror ("tables cannot be used as the redirect " + "address of a binat-to rule"); + error++; + } + if (r->direction != PF_INOUT) { + yyerror("binat-to cannot be specified " + "with a direction"); + error++; + } + + /* first specify outbound NAT rule */ + r->direction = PF_OUT; + } + + error += apply_redirspec(&r->nat, r, nat, 0, dst_port); + error += apply_redirspec(&r->rdr, r, rdr, 1, dst_port); + error += apply_redirspec(&r->route, r, rroute, 2, dst_port); + + if (rule_consistent(r) < 0 || error) + yyerror("skipping rule due to errors"); + else { + r->nr = pf->astack[pf->asd]->match++; + pfctl_add_rule(pf, r); + added++; + } + r->direction = dir; + + /* Generate binat's matching inbound rule */ + if (!error && nat && nat->rdr && nat->binat) { + bcopy(r, &rb, sizeof(rb)); + + /* now specify inbound rdr rule */ + rb.direction = PF_IN; + + if ((srch = calloc(1, sizeof(*srch))) == NULL) + err(1, "%s", __func__); + bcopy(src_host, srch, sizeof(*srch)); + srch->ifname = NULL; + srch->next = NULL; + srch->tail = NULL; + + if ((dsth = calloc(1, sizeof(*dsth))) == NULL) + err(1, "%s", __func__); + bcopy(&rb.nat.addr, &dsth->addr, sizeof(dsth->addr)); + dsth->ifname = NULL; + dsth->next = NULL; + dsth->tail = NULL; + + bzero(&binat, sizeof(binat)); + if ((binat.rdr = + calloc(1, sizeof(*binat.rdr))) == NULL) + err(1, "%s", __func__); + bcopy(nat->rdr, binat.rdr, sizeof(*binat.rdr)); + bcopy(&nat->pool_opts, &binat.pool_opts, + sizeof(binat.pool_opts)); + binat.pool_opts.staticport = 0; + binat.rdr->host = srch; + + expand_rule(&rb, 1, interface, NULL, &binat, NULL, + proto, + src_os, dst_host, dst_port, dsth, src_port, + uid, gid, rcv, icmp_type); + } + + if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) { + free(src_host); + src_host = osrch; + } + if (odsth && dst_host->addr.type == PF_ADDR_DYNIFTL) { + free(dst_host); + dst_host = odsth; + } + )))))))))); + + if (!keeprule) { + FREE_LIST(struct node_if, interfaces); + FREE_LIST(struct node_proto, protos); + FREE_LIST(struct node_host, src_hosts); + FREE_LIST(struct node_port, src_ports); + FREE_LIST(struct node_os, src_oses); + FREE_LIST(struct node_host, dst_hosts); + FREE_LIST(struct node_port, dst_ports); + FREE_LIST(struct node_uid, uids); + FREE_LIST(struct node_gid, gids); + FREE_LIST(struct node_icmp, icmp_types); + if (nat && nat->rdr) + FREE_LIST(struct node_host, nat->rdr->host); + if (rdr && rdr->rdr) + FREE_LIST(struct node_host, rdr->rdr->host); + + } + + if (!added) + yyerror("rule expands to no valid combination"); +} + +int +expand_skip_interface(struct node_if *interfaces) +{ + int errs = 0; + + if (!interfaces || (!interfaces->next && !interfaces->not && + !strcmp(interfaces->ifname, "none"))) { + if (pf->opts & PF_OPT_VERBOSE) + printf("set skip on none\n"); + errs = pfctl_set_interface_flags(pf, "", PFI_IFLAG_SKIP, 0); + return (errs); + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set skip on {"); + LOOP_THROUGH(struct node_if, interface, interfaces, + if (pf->opts & PF_OPT_VERBOSE) + printf(" %s", interface->ifname); + if (interface->not) { + yyerror("skip on ! is not supported"); + errs++; + } else if (interface->use_rdomain) { + yyerror("skip on rdomain is not supported"); + errs++; + } else + errs += pfctl_set_interface_flags(pf, + interface->ifname, PFI_IFLAG_SKIP, 1); + ); + if (pf->opts & PF_OPT_VERBOSE) + printf(" }\n"); + + FREE_LIST(struct node_if, interfaces); + + if (errs) + return (1); + else + return (0); +} + +void +freehostlist(struct node_host *h) +{ + struct node_host *n; + + for (n = h; n != NULL; n = n->next) + if (n->ifname) + free(n->ifname); + FREE_LIST(struct node_host, h); +} + +#undef FREE_LIST +#undef LOOP_THROUGH + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "above", ABOVE}, + { "af-to", AFTO}, + { "all", ALL}, + { "allow-opts", ALLOWOPTS}, + { "anchor", ANCHOR}, + { "antispoof", ANTISPOOF}, + { "any", ANY}, + { "bandwidth", BANDWIDTH}, + { "below", BELOW}, + { "binat-to", BINATTO}, + { "bitmask", BITMASK}, + { "block", BLOCK}, + { "block-policy", BLOCKPOLICY}, + { "burst", BURST}, + { "code", CODE}, + { "debug", DEBUG}, + { "default", DEFAULT}, + { "delay", DELAY}, + { "divert-packet", DIVERTPACKET}, + { "divert-reply", DIVERTREPLY}, + { "divert-to", DIVERTTO}, + { "drop", DROP}, + { "dup-to", DUPTO}, + { "entries", ENTRIES}, + { "file", FILENAME}, + { "fingerprints", FINGERPRINTS}, + { "flags", FLAGS}, + { "floating", FLOATING}, + { "flows", FLOWS}, + { "flush", FLUSH}, + { "for", FOR}, + { "fragment", FRAGMENT}, + { "from", FROM}, + { "global", GLOBAL}, + { "group", GROUP}, + { "hostid", HOSTID}, + { "icmp-type", ICMPTYPE}, + { "icmp6-type", ICMP6TYPE}, + { "id", ID}, + { "if-bound", IFBOUND}, + { "in", IN}, + { "include", INCLUDE}, + { "inet", INET}, + { "inet6", INET6}, + { "keep", KEEP}, + { "label", LABEL}, + { "least-states", LEASTSTATES}, + { "limit", LIMIT}, + { "limiter", LIMITER}, + { "load", LOAD}, + { "log", LOG}, + { "loginterface", LOGINTERFACE}, + { "mask", MASK}, + { "match", MATCH}, + { "matches", MATCHES}, + { "max", MAXIMUM}, + { "max-mss", MAXMSS}, + { "max-pkt-rate", MAXPKTRATE}, + { "max-src-conn", MAXSRCCONN}, + { "max-src-conn-rate", MAXSRCCONNRATE}, + { "max-src-nodes", MAXSRCNODES}, + { "max-src-states", MAXSRCSTATES}, + { "min", MINIMUM}, + { "min-ttl", MINTTL}, + { "modulate", MODULATE}, + { "nat-to", NATTO}, + { "no", NO}, + { "no-df", NODF}, + { "no-match", NOMATCH}, + { "no-route", NOROUTE}, + { "no-sync", NOSYNC}, + { "on", ON}, + { "once", ONCE}, + { "optimization", OPTIMIZATION}, + { "os", OS}, + { "out", OUT}, + { "overload", OVERLOAD}, + { "parent", PARENT}, + { "pass", PASS}, + { "pflow", PFLOW}, + { "port", PORT}, + { "prio", PRIO}, + { "probability", PROBABILITY}, + { "proto", PROTO}, + { "qlimit", QLIMIT}, + { "quantum", QUANTUM}, + { "queue", QUEUE}, + { "quick", QUICK}, + { "random", RANDOM}, + { "random-id", RANDOMID}, + { "rate", RATE}, + { "rdomain", RDOMAIN}, + { "rdr-to", RDRTO}, + { "reassemble", REASSEMBLE}, + { "received-on", RECEIVEDON}, + { "reply-to", REPLYTO}, + { "return", RETURN}, + { "return-icmp", RETURNICMP}, + { "return-icmp6", RETURNICMP6}, + { "return-rst", RETURNRST}, + { "round-robin", ROUNDROBIN}, + { "route", ROUTE}, + { "route-to", ROUTETO}, + { "rtable", RTABLE}, + { "rule", RULE}, + { "ruleset-optimization", RULESET_OPTIMIZATION}, + { "scrub", SCRUB}, + { "set", SET}, + { "skip", SKIP}, + { "sloppy", SLOPPY}, + { "source", SOURCE}, + { "source-hash", SOURCEHASH}, + { "source-track", SOURCETRACK}, + { "state", STATE}, + { "state-defaults", STATEDEFAULTS}, + { "state-policy", STATEPOLICY}, + { "static-port", STATICPORT}, + { "sticky-address", STICKYADDRESS}, + { "syncookies", SYNCOOKIES}, + { "synproxy", SYNPROXY}, + { "table", TABLE}, + { "tag", TAG}, + { "tagged", TAGGED}, + { "timeout", TIMEOUT}, + { "to", TO}, + { "tos", TOS}, + { "ttl", TTL}, + { "urpf-failed", URPFFAILED}, + { "user", USER}, + { "weight", WEIGHT}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) { + if (debug > 1) + fprintf(stderr, "%s: %d\n", s, p->k_val); + return (p->k_val); + } else { + if (debug > 1) + fprintf(stderr, "string: %s\n", s); + return (STRING); + } +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "%s", __func__); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc((unsigned char)*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s", __func__); + return (STRING); + case '!': + next = lgetc(0); + if (next == '=') + return (NE); + lungetc(next); + break; + case '<': + next = lgetc(0); + if (next == '>') { + yylval.v.i = PF_OP_XRG; + return (PORTBINARY); + } else if (next == '=') + return (LE); + lungetc(next); + break; + case '>': + next = lgetc(0); + if (next == '<') { + yylval.v.i = PF_OP_IRG; + return (PORTBINARY); + } else if (next == '=') + return (GE); + lungetc(next); + break; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "%s", __func__); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL || + (nfile->name = strdup(name)) == NULL) { + warn("%s", __func__); + if (nfile) + free(nfile); + return (NULL); + } + if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) { + nfile->stream = stdin; + free(nfile->name); + if ((nfile->name = strdup("stdin")) == NULL) { + warn("%s", __func__); + free(nfile); + return (NULL); + } + } else if ((nfile->stream = pfctl_fopen(nfile->name, "r")) == NULL) { + warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + warn("%s", __func__); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + + return (file ? 0 : EOF); +} + +int +parse_config(char *filename, struct pfctl *xpf) +{ + int errors = 0; + struct sym *sym; + + pf = xpf; + returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; + returnicmp6default = + (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; + blockpolicy = PFRULE_DROP; + + if ((file = pushfile(filename, 0)) == NULL) { + warn("cannot open the main config file!"); + return (-1); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + while ((sym = TAILQ_FIRST(&symhead))) { + if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + + return (errors ? -1 : 0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +pfctl_cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + sym = strndup(s, val - s); + if (sym == NULL) + err(1, "%s", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +void +mv_rules(struct pf_ruleset *src, struct pf_ruleset *dst) +{ + struct pf_rule *r; + + TAILQ_FOREACH(r, src->rules.active.ptr, entries) + dst->anchor->match++; + TAILQ_CONCAT(dst->rules.active.ptr, src->rules.active.ptr, entries); + src->anchor->match = 0; + TAILQ_CONCAT(dst->rules.inactive.ptr, src->rules.inactive.ptr, entries); +} + +void +mv_tables(struct pfctl *pf, struct pfr_ktablehead *ktables, + struct pf_anchor *a, struct pf_anchor *alast) +{ + + struct pfr_ktable *kt, *kt_safe; + char new_path[PF_ANCHOR_MAXPATH]; + char *path_cut; + int sz; + struct pfr_uktable *ukt; + SLIST_HEAD(, pfr_uktable) ukt_list; + + /* + * Here we need to rename anchor path from temporal names such as + * _1/_2/foo to _1/bar/foo etc. + * + * This also means we need to remove and insert table to ktables + * tree as anchor path is being updated. + */ + SLIST_INIT(&ukt_list); + DBGPRINT("%s [ %s ] (%s)\n", __func__, a->path, alast->path); + RB_FOREACH_SAFE(kt, pfr_ktablehead, ktables, kt_safe) { + path_cut = strstr(kt->pfrkt_anchor, alast->path); + if (path_cut != NULL) { + path_cut += strlen(alast->path); + if (*path_cut) + sz = snprintf(new_path, sizeof (new_path), + "%s%s", a->path, path_cut); + else + sz = snprintf(new_path, sizeof (new_path), + "%s", a->path); + if (sz >= sizeof (new_path)) + errx(1, "new path is too long for %s@%s\n", + kt->pfrkt_name, kt->pfrkt_anchor); + + DBGPRINT("%s %s@%s -> %s@%s\n", __func__, + kt->pfrkt_name, kt->pfrkt_anchor, + kt->pfrkt_name, new_path); + RB_REMOVE(pfr_ktablehead, ktables, kt); + strlcpy(kt->pfrkt_anchor, new_path, + sizeof(kt->pfrkt_anchor)); + SLIST_INSERT_HEAD(&ukt_list, (struct pfr_uktable *)kt, + pfrukt_entry); + } + } + + while ((ukt = SLIST_FIRST(&ukt_list)) != NULL) { + SLIST_REMOVE_HEAD(&ukt_list, pfrukt_entry); + if (RB_INSERT(pfr_ktablehead, ktables, + (struct pfr_ktable *)ukt) != NULL) + errx(1, "%s@%s exists already\n", + ukt->pfrukt_name, + ukt->pfrukt_anchor); + } +} + +void +decide_address_family(struct node_host *n, sa_family_t *af) +{ + if (*af != 0 || n == NULL) + return; + *af = n->af; + while ((n = n->next) != NULL) { + if (n->af != *af) { + *af = 0; + return; + } + } +} + +int +invalid_redirect(struct node_host *nh, sa_family_t af) +{ + if (!af) { + struct node_host *n; + + /* tables and dyniftl are ok without an address family */ + for (n = nh; n != NULL; n = n->next) { + if (n->addr.type != PF_ADDR_TABLE && + n->addr.type != PF_ADDR_DYNIFTL) { + yyerror("address family not given and " + "translation address expands to multiple " + "address families"); + return (1); + } + } + } + if (nh == NULL) { + yyerror("no translation address with matching address family " + "found."); + return (1); + } + return (0); +} + +int +atoul(char *s, u_long *ulvalp) +{ + u_long ulval; + char *ep; + + errno = 0; + ulval = strtoul(s, &ep, 0); + if (s[0] == '\0' || *ep != '\0') + return (-1); + if (errno == ERANGE && ulval == ULONG_MAX) + return (-1); + *ulvalp = ulval; + return (0); +} + +int +getservice(char *n) +{ + struct servent *s; + u_long ulval; + + if (atoul(n, &ulval) == 0) { + if (ulval > 65535) { + yyerror("illegal port value %lu", ulval); + return (-1); + } + return (htons(ulval)); + } else { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } +} + +int +rule_label(struct pf_rule *r, char *s) +{ + if (s) { + if (strlcpy(r->label, s, sizeof(r->label)) >= + sizeof(r->label)) { + yyerror("rule label too long (max %zu chars)", + sizeof(r->label)-1); + return (-1); + } + } + return (0); +} + +u_int16_t +parseicmpspec(char *w, sa_family_t af) +{ + const struct icmpcodeent *p; + u_long ulval; + u_int8_t icmptype; + + if (af == AF_INET) + icmptype = returnicmpdefault >> 8; + else + icmptype = returnicmp6default >> 8; + + if (atoul(w, &ulval) == -1) { + if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { + yyerror("unknown icmp code %s", w); + return (0); + } + ulval = p->code; + } + if (ulval > 255) { + yyerror("invalid icmp code %lu", ulval); + return (0); + } + return (icmptype << 8 | ulval); +} + +int +parseport(char *port, struct range *r, int extensions) +{ + char *p = strchr(port, ':'); + + if (p == NULL) { + if ((r->a = getservice(port)) == -1) + return (-1); + r->b = 0; + r->t = PF_OP_NONE; + return (0); + } + if ((extensions & PPORT_STAR) && !strcmp(p+1, "*")) { + *p = 0; + if ((r->a = getservice(port)) == -1) + return (-1); + r->b = 0; + r->t = PF_OP_IRG; + return (0); + } + if ((extensions & PPORT_RANGE)) { + *p++ = 0; + if ((r->a = getservice(port)) == -1 || + (r->b = getservice(p)) == -1) + return (-1); + if (r->a == r->b) { + r->b = 0; + r->t = PF_OP_NONE; + } else + r->t = PF_OP_RRG; + return (0); + } + yyerror("port is invalid: %s", port); + return (-1); +} + +int +pfctl_load_anchors(int dev, struct pfctl *pf) +{ + struct loadanchors *la; + + TAILQ_FOREACH(la, &loadanchorshead, entries) { + if (pf->opts & PF_OPT_VERBOSE) + fprintf(stderr, "\nLoading anchor %s from %s\n", + la->anchorname, la->filename); + if (pfctl_rules(dev, la->filename, pf->opts, pf->optimize, + la->anchorname, pf->trans) == -1) + return (-1); + } + + return (0); +} + +int +kw_casecmp(const void *k, const void *e) +{ + return (strcasecmp(k, ((const struct keywords *)e)->k_name)); +} + +int +map_tos(char *s, int *val) +{ + /* DiffServ Codepoints and other TOS mappings */ + const struct keywords toswords[] = { + { "af11", IPTOS_DSCP_AF11 }, + { "af12", IPTOS_DSCP_AF12 }, + { "af13", IPTOS_DSCP_AF13 }, + { "af21", IPTOS_DSCP_AF21 }, + { "af22", IPTOS_DSCP_AF22 }, + { "af23", IPTOS_DSCP_AF23 }, + { "af31", IPTOS_DSCP_AF31 }, + { "af32", IPTOS_DSCP_AF32 }, + { "af33", IPTOS_DSCP_AF33 }, + { "af41", IPTOS_DSCP_AF41 }, + { "af42", IPTOS_DSCP_AF42 }, + { "af43", IPTOS_DSCP_AF43 }, + { "critical", IPTOS_PREC_CRITIC_ECP }, + { "cs0", IPTOS_DSCP_CS0 }, + { "cs1", IPTOS_DSCP_CS1 }, + { "cs2", IPTOS_DSCP_CS2 }, + { "cs3", IPTOS_DSCP_CS3 }, + { "cs4", IPTOS_DSCP_CS4 }, + { "cs5", IPTOS_DSCP_CS5 }, + { "cs6", IPTOS_DSCP_CS6 }, + { "cs7", IPTOS_DSCP_CS7 }, + { "ef", IPTOS_DSCP_EF }, + { "inetcontrol", IPTOS_PREC_INTERNETCONTROL }, + { "lowdelay", IPTOS_LOWDELAY }, + { "netcontrol", IPTOS_PREC_NETCONTROL }, + { "reliability", IPTOS_RELIABILITY }, + { "throughput", IPTOS_THROUGHPUT }, + { "va", IPTOS_DSCP_VA }, + }; + const struct keywords *p; + + p = bsearch(s, toswords, sizeof(toswords)/sizeof(toswords[0]), + sizeof(toswords[0]), kw_casecmp); + + if (p) { + *val = p->k_val; + return (1); + } + return (0); +} + +int +lookup_rtable(u_int rtableid) +{ + size_t len; + struct rt_tableinfo info; + int mib[6]; + static u_int found[RT_TABLEID_MAX+1]; + + if (found[rtableid]) + return found[rtableid]; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_TABLE; + mib[5] = rtableid; + + len = sizeof(info); + if (sysctl(mib, 6, &info, &len, NULL, 0) == -1) { + if (errno == ENOENT) { + /* table nonexistent */ + found[rtableid] = 0; + return 0; + } + err(1, "%s", __func__); + } + found[rtableid] = 1; + return 1; +} + +int +filteropts_to_rule(struct pf_rule *r, struct filter_opts *opts) +{ + if (opts->marker & FOM_ONCE) { + if ((r->action != PF_PASS && r->action != PF_DROP) || r->anchor) { + yyerror("'once' only applies to pass/block rules"); + return (1); + } + r->rule_flag |= PFRULE_ONCE; + } + + if (opts->statelim.id != PF_STATELIM_ID_NONE && r->action != PF_PASS) { + yyerror("state limiter only applies to pass rules"); + return (1); + } + if (opts->sourcelim.id != PF_SOURCELIM_ID_NONE && r->action != PF_PASS) { + yyerror("source limiter only applies to pass rules"); + return (1); + } + + r->keep_state = opts->keep.action; + r->pktrate.limit = opts->pktrate.limit; + r->pktrate.seconds = opts->pktrate.seconds; + r->prob = opts->prob; + r->statelim.id = opts->statelim.id; + r->statelim.limiter_action = opts->statelim.limiter_action; + r->sourcelim.id = opts->sourcelim.id; + r->sourcelim.limiter_action = opts->sourcelim.limiter_action; + r->rtableid = opts->rtableid; + r->tos = opts->tos; + + if (opts->nodf) + r->scrub_flags |= PFSTATE_NODF; + if (opts->randomid) + r->scrub_flags |= PFSTATE_RANDOMID; + if (opts->minttl) + r->min_ttl = opts->minttl; + if (opts->max_mss) + r->max_mss = opts->max_mss; + + if (opts->tag) + if (strlcpy(r->tagname, opts->tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + return (1); + } + if (opts->match_tag) + if (strlcpy(r->match_tagname, opts->match_tag, + PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { + yyerror("tag too long, max %u chars", + PF_TAG_NAME_SIZE - 1); + return (1); + } + r->match_tag_not = opts->match_tag_not; + + if (rule_label(r, opts->label)) + return (1); + free(opts->label); + + if (opts->marker & FOM_AFTO) + r->rule_flag |= PFRULE_AFTO; + if ((opts->marker & FOM_AFTO) && r->direction != PF_IN) { + yyerror("af-to can only be used with direction in"); + return (1); + } + if ((opts->marker & FOM_AFTO) && opts->rt) { + yyerror("af-to cannot be used together with " + "route-to, reply-to, dup-to"); + return (1); + } + if (opts->marker & FOM_SCRUB_TCP) + r->scrub_flags |= PFSTATE_SCRUB_TCP; + if (opts->marker & FOM_SETDELAY) { + r->delay = opts->delay; + r->rule_flag |= PFRULE_SETDELAY; + } + if (opts->marker & FOM_SETTOS) { + r->scrub_flags |= PFSTATE_SETTOS; + r->set_tos = opts->settos; + } + if (opts->marker & FOM_PRIO) + r->prio = opts->prio ? opts->prio : PF_PRIO_ZERO; + if (opts->marker & FOM_SETPRIO) { + r->set_prio[0] = opts->set_prio[0]; + r->set_prio[1] = opts->set_prio[1]; + r->scrub_flags |= PFSTATE_SETPRIO; + } + + r->flags = opts->flags.b1; + r->flagset = opts->flags.b2; + if ((opts->flags.b1 & opts->flags.b2) != opts->flags.b1) { + yyerror("flags always false"); + return (1); + } + + if (opts->queues.qname != NULL) { + if (strlcpy(r->qname, opts->queues.qname, + sizeof(r->qname)) >= sizeof(r->qname)) { + yyerror("rule qname too long (max " + "%zu chars)", sizeof(r->qname)-1); + return (1); + } + free(opts->queues.qname); + } + if (opts->queues.pqname != NULL) { + if (strlcpy(r->pqname, opts->queues.pqname, + sizeof(r->pqname)) >= sizeof(r->pqname)) { + yyerror("rule pqname too long (max " + "%zu chars)", sizeof(r->pqname)-1); + return (1); + } + free(opts->queues.pqname); + } + + if (opts->fragment) + r->rule_flag |= PFRULE_FRAGMENT; + r->allow_opts = opts->allowopts; + + return (0); +} diff --git a/tools/pfctl/pf_print_state.c b/tools/pfctl/pf_print_state.c new file mode 100644 index 0000000..980e6d1 --- /dev/null +++ b/tools/pfctl/pf_print_state.c @@ -0,0 +1,381 @@ +/* $OpenBSD: pf_print_state.c,v 1.69 2019/02/02 15:43:18 yasuoka Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#define TCPSTATES +#include +#include +#include +#include +#include + +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +void print_name(struct pf_addr *, sa_family_t); + +void +print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose) +{ + switch (addr->type) { + case PF_ADDR_DYNIFTL: + printf("(%s", addr->v.ifname); + if (addr->iflags & PFI_AFLAG_NETWORK) + printf(":network"); + if (addr->iflags & PFI_AFLAG_BROADCAST) + printf(":broadcast"); + if (addr->iflags & PFI_AFLAG_PEER) + printf(":peer"); + if (addr->iflags & PFI_AFLAG_NOALIAS) + printf(":0"); + if (verbose) { + if (addr->p.dyncnt <= 0) + printf(":*"); + else + printf(":%d", addr->p.dyncnt); + } + printf(")"); + break; + case PF_ADDR_TABLE: + if (verbose) + if (addr->p.tblcnt == -1) + printf("<%s:*>", addr->v.tblname); + else + printf("<%s:%d>", addr->v.tblname, + addr->p.tblcnt); + else + printf("<%s>", addr->v.tblname); + return; + case PF_ADDR_RANGE: { + print_addr_str(af, &addr->v.a.addr); + printf(" - "); + print_addr_str(af, &addr->v.a.mask); + break; + } + case PF_ADDR_ADDRMASK: + if (PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6)) + printf("any"); + else + print_addr_str(af, &addr->v.a.addr); + break; + case PF_ADDR_NOROUTE: + printf("no-route"); + return; + case PF_ADDR_URPFFAILED: + printf("urpf-failed"); + return; + case PF_ADDR_RTLABEL: + printf("route \"%s\"", addr->v.rtlabelname); + return; + default: + printf("?"); + return; + } + + /* mask if not _both_ address and mask are zero */ + if (addr->type != PF_ADDR_RANGE && + !(PF_AZERO(&addr->v.a.addr, AF_INET6) && + PF_AZERO(&addr->v.a.mask, AF_INET6))) { + int bits = unmask(&addr->v.a.mask); + + if (bits < (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } +} + +void +print_addr_str(sa_family_t af, struct pf_addr *addr) +{ + static char buf[48]; + + if (inet_ntop(af, addr, buf, sizeof(buf)) == NULL) + printf("?"); + else + printf("%s", buf); +} + +void +print_name(struct pf_addr *addr, sa_family_t af) +{ + struct sockaddr_storage ss; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char host[NI_MAXHOST]; + socklen_t slen; /* PFLINUX: sa_len replacement */ + + memset(&ss, 0, sizeof(ss)); + ss.ss_family = af; + if (ss.ss_family == AF_INET) { + sin = (struct sockaddr_in *)&ss; + /* PFLINUX: Linux struct sockaddr(_in) has no sa_len; + * getnameinfo(3) takes an explicit length instead. */ + sin->sin_addr = addr->v4; + slen = sizeof(*sin); + } else { + sin6 = (struct sockaddr_in6 *)&ss; + sin6->sin6_addr = addr->v6; + slen = sizeof(*sin6); + } + + if (getnameinfo((struct sockaddr *)&ss, slen, host, sizeof(host), + NULL, 0, NI_NOFQDN) != 0) + printf("?"); + else + printf("%s", host); +} + +void +print_host(struct pf_addr *addr, u_int16_t port, sa_family_t af, u_int16_t rdom, + const char *proto, int opts) +{ + struct pf_addr_wrap aw; + struct servent *s = NULL; + char ps[6]; + + if (rdom) + printf("(%u) ", ntohs(rdom)); + + if (opts & PF_OPT_USEDNS) + print_name(addr, af); + else { + memset(&aw, 0, sizeof(aw)); + aw.v.a.addr = *addr; + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + print_addr(&aw, af, opts & PF_OPT_VERBOSE2); + } + + if (port) { + snprintf(ps, sizeof(ps), "%u", ntohs(port)); + if (opts & PF_OPT_PORTNAMES) + s = getservbyport(port, proto); + if (af == AF_INET) + printf(":%s", s ? s->s_name : ps); + else + printf("[%s]", s ? s->s_name : ps); + } +} + +void +print_seq(struct pfsync_state_peer *p) +{ + if (p->seqdiff) + printf("[%u + %u](+%u)", ntohl(p->seqlo), + ntohl(p->seqhi) - ntohl(p->seqlo), ntohl(p->seqdiff)); + else + printf("[%u + %u]", ntohl(p->seqlo), + ntohl(p->seqhi) - ntohl(p->seqlo)); +} + +void +print_state(struct pfsync_state *s, int opts) +{ + struct pfsync_state_peer *src, *dst; + struct pfsync_state_key *sk, *nk; + struct protoent *p; + char *pn = NULL; + int min, sec; + int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af); + int idx; + + if (s->direction == PF_OUT) { + src = &s->src; + dst = &s->dst; + sk = &s->key[PF_SK_STACK]; + nk = &s->key[PF_SK_WIRE]; + if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6) + sk->port[0] = nk->port[0]; + } else { + src = &s->dst; + dst = &s->src; + sk = &s->key[PF_SK_WIRE]; + nk = &s->key[PF_SK_STACK]; + if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6) + sk->port[1] = nk->port[1]; + } + printf("%s ", s->ifname); + if ((p = getprotobynumber(s->proto)) != NULL) { + pn = p->p_name; + printf("%s ", pn); + } else + printf("%u ", s->proto); + + print_host(&nk->addr[1], nk->port[1], nk->af, nk->rdomain, pn, opts); + if (nk->af != sk->af || PF_ANEQ(&nk->addr[1], &sk->addr[1], nk->af) || + nk->port[1] != sk->port[1] || + nk->rdomain != sk->rdomain) { + idx = afto ? 0 : 1; + printf(" ("); + print_host(&sk->addr[idx], sk->port[idx], sk->af, + sk->rdomain, pn, opts); + printf(")"); + } + if (s->direction == PF_IN && !PF_AZERO(&s->rt_addr, sk->af)) { + printf(" {"); + print_addr_str(sk->af, &s->rt_addr); + printf("}"); + } + if (s->direction == PF_OUT || (afto && s->direction == PF_IN)) + printf(" -> "); + else + printf(" <- "); + print_host(&nk->addr[0], nk->port[0], nk->af, nk->rdomain, pn, opts); + if (nk->af != sk->af || PF_ANEQ(&nk->addr[0], &sk->addr[0], nk->af) || + nk->port[0] != sk->port[0] || + nk->rdomain != sk->rdomain) { + idx = afto ? 1 : 0; + printf(" ("); + print_host(&sk->addr[idx], sk->port[idx], sk->af, + sk->rdomain, pn, opts); + printf(")"); + } + if (s->direction == PF_OUT && !PF_AZERO(&s->rt_addr, nk->af)) { + printf(" {"); + print_addr_str(nk->af, &s->rt_addr); + printf("}"); + } + + printf(" "); + if (s->proto == IPPROTO_TCP) { + if (src->state <= TCPS_TIME_WAIT && + dst->state <= TCPS_TIME_WAIT) + printf(" %s:%s\n", tcpstates[src->state], + tcpstates[dst->state]); + else if (src->state == PF_TCPS_PROXY_SRC || + dst->state == PF_TCPS_PROXY_SRC) + printf(" PROXY:SRC\n"); + else if (src->state == PF_TCPS_PROXY_DST || + dst->state == PF_TCPS_PROXY_DST) + printf(" PROXY:DST\n"); + else + printf(" \n", + src->state, dst->state); + if (opts & PF_OPT_VERBOSE) { + printf(" "); + print_seq(src); + if (src->wscale && dst->wscale) + printf(" wscale %u", + src->wscale & PF_WSCALE_MASK); + printf(" "); + print_seq(dst); + if (src->wscale && dst->wscale) + printf(" wscale %u", + dst->wscale & PF_WSCALE_MASK); + printf("\n"); + } + } else if (s->proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES && + dst->state < PFUDPS_NSTATES) { + const char *states[] = PFUDPS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else if (s->proto != IPPROTO_ICMP && s->proto != IPPROTO_ICMPV6 && + src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) { + /* XXX ICMP doesn't really have state levels */ + const char *states[] = PFOTHERS_NAMES; + + printf(" %s:%s\n", states[src->state], states[dst->state]); + } else { + printf(" %u:%u\n", src->state, dst->state); + } + + if (opts & PF_OPT_VERBOSE) { + u_int64_t packets[2]; + u_int64_t bytes[2]; + u_int32_t creation = ntohl(s->creation); + u_int32_t expire = ntohl(s->expire); + + sec = creation % 60; + creation /= 60; + min = creation % 60; + creation /= 60; + printf(" age %.2u:%.2u:%.2u", creation, min, sec); + sec = expire % 60; + expire /= 60; + min = expire % 60; + expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", expire, min, sec); + + bcopy(s->packets[0], &packets[0], sizeof(u_int64_t)); + bcopy(s->packets[1], &packets[1], sizeof(u_int64_t)); + bcopy(s->bytes[0], &bytes[0], sizeof(u_int64_t)); + bcopy(s->bytes[1], &bytes[1], sizeof(u_int64_t)); + printf(", %llu:%llu pkts, %llu:%llu bytes", + betoh64(packets[0]), + betoh64(packets[1]), + betoh64(bytes[0]), + betoh64(bytes[1])); + if (ntohl(s->anchor) != -1) + printf(", anchor %u", ntohl(s->anchor)); + if (ntohl(s->rule) != -1) + printf(", rule %u", ntohl(s->rule)); + if (ntohs(s->state_flags) & PFSTATE_SLOPPY) + printf(", sloppy"); + if (ntohs(s->state_flags) & PFSTATE_PFLOW) + printf(", pflow"); + if (s->sync_flags & PFSYNC_FLAG_SRCNODE) + printf(", source-track"); + if (s->sync_flags & PFSYNC_FLAG_NATSRCNODE) + printf(", sticky-address"); + printf("\n"); + } + if (opts & PF_OPT_VERBOSE2) { + u_int64_t id; + + bcopy(&s->id, &id, sizeof(u_int64_t)); + printf(" id: %016llx creatorid: %08x", + betoh64(id), ntohl(s->creatorid)); + printf("\n"); + } +} + +int +unmask(struct pf_addr *m) +{ + int i = 31, j = 0, b = 0; + u_int32_t tmp; + + while (j < 4 && m->addr32[j] == 0xffffffff) { + b += 32; + j++; + } + if (j < 4) { + tmp = ntohl(m->addr32[j]); + for (i = 31; tmp & (1 << i); --i) + b++; + } + return (b); +} diff --git a/tools/pfctl/pfctl.c b/tools/pfctl/pfctl.c new file mode 100644 index 0000000..666945e --- /dev/null +++ b/tools/pfctl/pfctl.c @@ -0,0 +1,3683 @@ +/* $OpenBSD: pfctl.c,v 1.406 2026/08/30 12:27:27 jsg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * Copyright (c) 2002 - 2013 Henning Brauer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +struct pfctl_opt_id; + +void usage(void); +int pfctl_enable(int, int); +int pfctl_disable(int, int); +void pfctl_clear_queues(struct pf_qihead *); +void pfctl_clear_stats(int, const char *, int); +void pfctl_clear_interface_flags(int, int); +int pfctl_clear_rules(int, int, char *); +void pfctl_clear_src_nodes(int, int); +void pfctl_clear_states(int, const char *, int); +struct addrinfo * + pfctl_addrprefix(char *, struct pf_addr *, int); +void pfctl_kill_src_nodes(int, int); +void pfctl_net_kill_states(int, const char *, int, int); +void pfctl_label_kill_states(int, const char *, int, int); +void pfctl_id_kill_states(int, int); +void pfctl_key_kill_states(int, const char *, int, int); +void pfctl_kill_source(int, const char *, const char *, int); +int pfctl_parse_host(char *, struct pf_rule_addr *); +void pfctl_init_options(struct pfctl *); +int pfctl_load_options(struct pfctl *); +int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); +int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); +int pfctl_load_debug(struct pfctl *, unsigned int); +int pfctl_load_logif(struct pfctl *, char *); +int pfctl_load_hostid(struct pfctl *, unsigned int); +int pfctl_load_reassembly(struct pfctl *, u_int32_t); +int pfctl_load_syncookies(struct pfctl *, u_int8_t); +int pfctl_set_synflwats(struct pfctl *, u_int32_t, u_int32_t); +void pfctl_print_rule_counters(struct pf_rule *, int); +int pfctl_show_statelims(int, enum pfctl_show); +int pfctl_show_sourcelims(int, enum pfctl_show, int, const char *); +void pfctl_init_show_rules(struct pfctl *, int, int); +int pfctl_show_rules(struct pfctl *, char *, enum pfctl_show, char *, int, int, + long); +int pfctl_show_src_nodes(int, int); +int pfctl_show_states(int, const char *, int, long); +int pfctl_show_status(int, int); +int pfctl_show_timeouts(int, int); +int pfctl_show_limits(int, int); +void pfctl_read_limits(int); +void pfctl_restore_limits(void); +void pfctl_debug(int, u_int32_t, int); +int pfctl_show_anchors(int, int, char *); +int pfctl_ruleset_trans(struct pfctl *, char *, struct pf_anchor *); +u_int pfctl_find_childqs(struct pfctl_qsitem *); +void pfctl_load_queue(struct pfctl *, u_int32_t, struct pfctl_qsitem *); +int pfctl_load_queues(struct pfctl *); +u_int pfctl_leafqueue_check(char *); +u_int pfctl_check_qassignments(struct pf_ruleset *); +void pfctl_load_statelims(struct pfctl *); +void pfctl_load_statelim(struct pfctl *, struct pfctl_statelim *); +void pfctl_load_sourcelims(struct pfctl *); +void pfctl_load_sourcelim(struct pfctl *, struct pfctl_sourcelim *); +int pfctl_load_ruleset(struct pfctl *, char *, struct pf_ruleset *, int); +int pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int); +const char *pfctl_lookup_option(char *, const char **); +int pfctl_lookup_id(const char *, const struct pfctl_opt_id *); +void pfctl_state_store(int, const char *); +void pfctl_state_load(int, const char *); +void pfctl_reset(int, int); +int pfctl_walk_show(int, struct pfioc_ruleset *, void *); +int pfctl_walk_get(int, struct pfioc_ruleset *, void *); +int pfctl_walk_anchors(int, int, const char *, + int(*)(int, struct pfioc_ruleset *, void *), void *); +struct pfr_anchors * + pfctl_get_anchors(int, const char *, int); +int pfctl_recurse(int, int, const char *, + int(*)(int, int, struct pfr_anchoritem *)); +int pfctl_call_clearrules(int, int, struct pfr_anchoritem *); +int pfctl_call_cleartables(int, int, struct pfr_anchoritem *); +int pfctl_call_clearanchors(int, int, struct pfr_anchoritem *); +int pfctl_call_showtables(int, int, struct pfr_anchoritem *); + +RBT_PROTOTYPE(pfctl_statelim_ids, pfctl_statelim, id_entry, + pfctl_statelim_id_cmp); +RBT_PROTOTYPE(pfctl_statelim_nms, pfctl_statelim, nm_entry, + pfctl_statelim_nm_cmp); +RBT_PROTOTYPE(pfctl_sourcelim_ids, pfctl_sourcelim, id_entry, + pfctl_sourcelim_id_cmp); +RBT_PROTOTYPE(pfctl_sourcelim_nms, pfctl_sourcelim, nm_entry, + pfctl_sourcelim_nm_cmp); + +enum showopt_id { + SHOWOPT_NONE = 0, + SHOWOPT_QUEUE, + SHOWOPT_RULES, + SHOWOPT_ANCHORS, + SHOWOPT_SOURCES, + SHOWOPT_STATES, + SHOWOPT_INFO, + SHOWOPT_IFACES, + SHOWOPT_LABELS, + SHOWOPT_TIMEOUTS, + SHOWOPT_MEMORY, + SHOWOPT_TABLES, + SHOWOPT_OSFP, + SHOWOPT_STATELIMS, + SHOWOPT_SOURCELIMS, + SHOWOPT_ALL, +}; + +const char *clearopt; +char *rulesopt; +enum showopt_id showopt; +const char *debugopt; +char *anchoropt; +const char *optiopt = NULL; +char *pf_device = "/dev/pf"; +char *ifaceopt; +char *tableopt; +const char *tblcmdopt; +int src_node_killers; +char *src_node_kill[2]; +int state_killers; +char *state_kill[2]; + +int dev = -1; +int first_title = 1; +int labels = 0; +int exit_val = 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 }, + { "tables", PF_LIMIT_TABLES }, + { "table-entries", PF_LIMIT_TABLE_ENTRIES }, + { "pktdelay-pkts", PF_LIMIT_PKTDELAY_PKTS }, + { "anchors", PF_LIMIT_ANCHORS }, + { NULL, 0 } +}; + +static unsigned int limit_curr[PF_LIMIT_MAX]; + +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 *clearopt_list[] = { + "rules", "Sources", "states", "info", "Tables", "osfp", "Reset", + "all", NULL +}; + +struct pfctl_opt_id { + const char *name; + int id; +}; + +static const struct pfctl_opt_id showopt_list[] = { + { "queue", SHOWOPT_QUEUE }, + { "rules", SHOWOPT_RULES }, + { "Anchors", SHOWOPT_ANCHORS }, + { "Sources", SHOWOPT_SOURCES }, + { "states", SHOWOPT_STATES }, + { "info", SHOWOPT_INFO }, + { "Interfaces", SHOWOPT_IFACES }, + { "labels", SHOWOPT_LABELS }, + { "timeouts", SHOWOPT_TIMEOUTS }, + { "memory", SHOWOPT_MEMORY }, + { "Tables", SHOWOPT_TABLES }, + { "osfp", SHOWOPT_OSFP }, + { "Stlimiters", SHOWOPT_STATELIMS }, + { "Srclimiters", SHOWOPT_SOURCELIMS }, + { "all", SHOWOPT_ALL }, + + { NULL, SHOWOPT_NONE }, +}; + +static const char *tblcmdopt_list[] = { + "kill", "flush", "add", "delete", "replace", "show", + "test", "zero", "expire", NULL +}; + +static const char *debugopt_list[] = { + "debug", "info", "notice", "warning", + "error", "crit", "alert", "emerg", + NULL +}; + +static const char *optiopt_list[] = { + "none", "basic", "profile", NULL +}; + +struct pf_qihead qspecs = TAILQ_HEAD_INITIALIZER(qspecs); +struct pf_qihead rootqs = TAILQ_HEAD_INITIALIZER(rootqs); + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-deghNnPqrvz] ", __progname); + fprintf(stderr, "[-a anchor] [-D macro=value] [-F modifier]"); + fprintf(stderr, " [-f file]\n"); + fprintf(stderr, "\t[-i interface] [-K key] [-k key] [-L statefile]"); + fprintf(stderr, " [-o level]\n"); + fprintf(stderr, "\t[-p device] [-S statefile] [-s modifier [-R id]]\n"); + fprintf(stderr, "\t[-t table -T command [address ...]]"); + fprintf(stderr, " [-V rdomain] [-x level]\n"); + exit(1); +} + +void +pfctl_err(int opts, int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if ((opts & PF_OPT_IGNFAIL) == 0) + verr(eval, fmt, ap); + else + vwarn(fmt, ap); + + va_end(ap); + + exit_val = eval; +} + +void +pfctl_errx(int opts, int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if ((opts & PF_OPT_IGNFAIL) == 0) + verrx(eval, fmt, ap); + else + vwarnx(fmt, ap); + + va_end(ap); + + exit_val = eval; +} + +int +pfctl_enable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTART) == -1) { + if (errno == EEXIST) + errx(1, "pf already enabled"); + else + err(1, "DIOCSTART"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf enabled\n"); + + return (0); +} + +int +pfctl_disable(int dev, int opts) +{ + if (ioctl(dev, DIOCSTOP) == -1) { + if (errno == ENOENT) + errx(1, "pf not enabled"); + else + err(1, "DIOCSTOP"); + } + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf disabled\n"); + + return (0); +} + +void +pfctl_clear_stats(int dev, const char *iface, int opts) +{ + struct pfioc_iface pi; + + memset(&pi, 0, sizeof(pi)); + if (iface != NULL && strlcpy(pi.pfiio_name, iface, + sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (ioctl(dev, DIOCCLRSTATUS, &pi) == -1) + pfctl_err(opts, 1, "DIOCCLRSTATUS"); + if ((opts & PF_OPT_QUIET) == 0) { + fprintf(stderr, "pf: statistics cleared"); + if (iface != NULL) + fprintf(stderr, " for interface %s", iface); + fprintf(stderr, "\n"); + } +} + +void +pfctl_clear_interface_flags(int dev, int opts) +{ + struct pfioc_iface pi; + + if ((opts & PF_OPT_NOACTION) == 0) { + bzero(&pi, sizeof(pi)); + pi.pfiio_flags = PFI_IFLAG_SKIP; + + if (ioctl(dev, DIOCCLRIFFLAG, &pi) == -1) + pfctl_err(opts, 1, "DIOCCLRIFFLAG"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "pf: interface flags reset\n"); + } +} + +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_TRANS_RULESET, anchorname) || + pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) { + pfctl_err(opts, 1, "%s", __func__); + return (1); + } else if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "rules cleared\n"); + + return (0); +} + +void +pfctl_clear_src_nodes(int dev, int opts) +{ + if (ioctl(dev, DIOCCLRSRCNODES) == -1) + pfctl_err(opts, 1, "DIOCCLRSRCNODES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "source tracking entries cleared\n"); +} + +void +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)) + pfctl_errx(opts, 1, "invalid interface: %s", iface); + + if (ioctl(dev, DIOCCLRSTATES, &psk) == -1) + pfctl_err(opts, 1, "DIOCCLRSTATES"); + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "%d states cleared\n", psk.psk_killed); +} + +struct addrinfo * +pfctl_addrprefix(char *addr, struct pf_addr *mask, int numeric) +{ + char *p; + const char *errstr; + int prefix, ret_ga, q, r; + struct addrinfo hints, *res; + + bzero(&hints, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + if (numeric) + hints.ai_flags = AI_NUMERICHOST; + + if ((p = strchr(addr, '/')) != NULL) { + *p++ = '\0'; + /* 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 (p == NULL) + return res; + + prefix = strtonum(p, 0, res->ai_family == AF_INET6 ? 128 : 32, &errstr); + if (errstr) + errx(1, "prefix is %s: %s", errstr, p); + + 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; + } + + return res; +} + +void +pfctl_kill_src_nodes(int dev, int opts) +{ + struct pfioc_src_node_kill psnk; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + int killed, sources, dests; + + 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)); + + res[0] = pfctl_addrprefix(src_node_kill[0], + &psnk.psnk_src.addr.v.a.mask, (opts & PF_OPT_NODNS)); + + 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++; + + copy_satopfaddr(&psnk.psnk_src.addr.v.a.addr, resp[0]->ai_addr); + + 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)); + res[1] = pfctl_addrprefix(src_node_kill[1], + &psnk.psnk_dst.addr.v.a.mask, + (opts & PF_OPT_NODNS)); + 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++; + + copy_satopfaddr(&psnk.psnk_dst.addr.v.a.addr, + resp[1]->ai_addr); + + if (ioctl(dev, DIOCKILLSRCNODES, &psnk) == -1) + err(1, "DIOCKILLSRCNODES"); + killed += psnk.psnk_killed; + } + freeaddrinfo(res[1]); + } else { + if (ioctl(dev, DIOCKILLSRCNODES, &psnk) == -1) + err(1, "DIOCKILLSRCNODES"); + killed += psnk.psnk_killed; + } + } + + freeaddrinfo(res[0]); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d src nodes from %d sources and %d " + "destinations\n", killed, sources, dests); +} + +void +pfctl_net_kill_states(int dev, const char *iface, int opts, int rdomain) +{ + struct pfioc_state_kill psk; + struct addrinfo *res[2], *resp[2]; + struct sockaddr last_src, last_dst; + int killed, sources, dests; + + 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); + + psk.psk_rdomain = rdomain; + + res[0] = pfctl_addrprefix(state_kill[0], + &psk.psk_src.addr.v.a.mask, (opts & PF_OPT_NODNS)); + + 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++; + + copy_satopfaddr(&psk.psk_src.addr.v.a.addr, resp[0]->ai_addr); + + 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)); + res[1] = pfctl_addrprefix(state_kill[1], + &psk.psk_dst.addr.v.a.mask, + (opts & PF_OPT_NODNS)); + 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++; + + copy_satopfaddr(&psk.psk_dst.addr.v.a.addr, + resp[1]->ai_addr); + + if (ioctl(dev, DIOCKILLSTATES, &psk) == -1) + err(1, "DIOCKILLSTATES"); + killed += psk.psk_killed; + } + freeaddrinfo(res[1]); + } else { + if (ioctl(dev, DIOCKILLSTATES, &psk) == -1) + 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); +} + +void +pfctl_label_kill_states(int dev, const char *iface, int opts, int rdomain) +{ + 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]); + + psk.psk_rdomain = rdomain; + + if (ioctl(dev, DIOCKILLSTATES, &psk) == -1) + err(1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", psk.psk_killed); +} + +void +pfctl_id_kill_states(int dev, 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], "%llx/%x", + &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2) + HTONL(psk.psk_pfcmp.creatorid); + else if ((sscanf(state_kill[1], "%llx", &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) == -1) + err(1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", psk.psk_killed); +} + +void +pfctl_key_kill_states(int dev, const char *iface, int opts, int rdomain) +{ + struct pfioc_state_kill psk; + char *s, *token, *tokens[4]; + struct protoent *p; + u_int i, sidx, didx; + + if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { + warnx("no key 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); + + psk.psk_rdomain = rdomain; + + s = strdup(state_kill[1]); + if (!s) + errx(1, "pfctl_key_kill_states: strdup"); + i = 0; + while ((token = strsep(&s, " \t")) != NULL) + if (*token != '\0') { + if (i < 4) + tokens[i] = token; + i++; + } + if (i != 4) + errx(1, "pfctl_key_kill_states: key must be " + "\"protocol host1:port1 direction host2:port2\" format"); + + if ((p = getprotobyname(tokens[0])) == NULL) + errx(1, "invalid protocol: %s", tokens[0]); + psk.psk_proto = p->p_proto; + + if (strcmp(tokens[2], "->") == 0) { + sidx = 1; + didx = 3; + } else if (strcmp(tokens[2], "<-") == 0) { + sidx = 3; + didx = 1; + } else + errx(1, "invalid direction: %s", tokens[2]); + + if (pfctl_parse_host(tokens[sidx], &psk.psk_src) == -1) + errx(1, "invalid host: %s", tokens[sidx]); + if (pfctl_parse_host(tokens[didx], &psk.psk_dst) == -1) + errx(1, "invalid host: %s", tokens[didx]); + + if (ioctl(dev, DIOCKILLSTATES, &psk) == -1) + err(1, "DIOCKILLSTATES"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "killed %d states\n", psk.psk_killed); +} + +int +pfctl_parse_host(char *str, struct pf_rule_addr *addr) +{ + char *s = NULL, *sbs, *sbe; + struct addrinfo hints, *ai; + + s = strdup(str); + if (!s) + errx(1, "pfctl_parse_host: strdup"); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = AI_NUMERICHOST; + + if ((sbs = strchr(s, '[')) != NULL && (sbe = strrchr(s, ']')) != NULL) { + hints.ai_family = AF_INET6; + *(sbs++) = *sbe = '\0'; + } else if ((sbs = strchr(s, ':')) != NULL) { + hints.ai_family = AF_INET; + *(sbs++) = '\0'; + } else + goto error; + + if (getaddrinfo(s, sbs, &hints, &ai) != 0) + goto error; + + copy_satopfaddr(&addr->addr.v.a.addr, ai->ai_addr); + addr->port[0] = ai->ai_family == AF_INET6 ? + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port : + ((struct sockaddr_in *)ai->ai_addr)->sin_port; + + freeaddrinfo(ai); + free(s); + + memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr)); + addr->port_op = PF_OP_EQ; + addr->addr.type = PF_ADDR_ADDRMASK; + + return (0); + + error: + free(s); + return (-1); +} + +void +pfctl_print_rule_counters(struct pf_rule *rule, int opts) +{ + if ((rule->rule_flag & PFRULE_EXPIRED) && + !(opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG))) + return; + + if (opts & PF_OPT_DEBUG) { + const char *t[PF_SKIP_COUNT] = { "i", "d", "r", "f", + "p", "sa", "da", "sp", "dp" }; + int i; + + printf(" [ Skip steps: "); + for (i = 0; i < PF_SKIP_COUNT; ++i) { + if (rule->skip[i].nr == rule->nr + 1) + continue; + printf("%s=", t[i]); + if (rule->skip[i].nr == -1) + printf("end "); + else + printf("%u ", rule->skip[i].nr); + } + printf("]\n"); + + printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", + rule->qname, rule->qid, rule->pqname, rule->pqid); + } + if (opts & PF_OPT_VERBOSE) { + printf(" [ Evaluations: %-8llu Packets: %-8llu " + "Bytes: %-10llu States: %-6u]\n", + (unsigned long long)rule->evaluations, + (unsigned long long)(rule->packets[0] + + rule->packets[1]), + (unsigned long long)(rule->bytes[0] + + rule->bytes[1]), rule->states_cur); + if (!(opts & PF_OPT_DEBUG)) + printf(" [ Inserted: uid %lu pid %lu " + "State Creations: %-6u]\n", + (unsigned long)rule->cuid, (unsigned long)rule->cpid, + rule->states_tot); + } +} + +void +pfctl_print_title(char *title) +{ + if (!first_title) + printf("\n"); + first_title = 0; + printf("%s\n", title); +} + +const char * +pfctl_statelim_id2name(struct pfctl *pf, u_int8_t id) +{ + struct pfctl_statelim *stlim; + + stlim = pfctl_get_statelim_id(pf, id); + if (stlim == NULL) { + stlim = malloc(sizeof(struct pfctl_statelim)); + if (stlim == NULL) + err(1, NULL); + memset(stlim, 0, sizeof(struct pfctl_statelim)); + stlim->ioc.id = id; + if (ioctl(pf->dev, DIOCGETSTATELIM, &stlim->ioc) == -1) { + if (errno != ESRCH) + err(1, "DIOCGETSTATELIM %u", id); + } + pfctl_add_statelim(pf, stlim); + } + + return stlim->ioc.name; +} + +int +pfctl_show_statelims(int dev, enum pfctl_show format) +{ + struct pfioc_statelim stlim; + uint32_t id = PF_STATELIM_ID_MIN; + + if (format == PFCTL_SHOW_LABELS) { + printf("%3s %8s/%-8s %5s/%-5s %8s %8s %8s\n", "ID", + "USE", "LIMIT", "RATE", "SECS", + "ADMIT", "HARDLIM", "RATELIM"); + } + + for (;;) { + memset(&stlim, 0, sizeof(stlim)); + stlim.id = id; + + if (ioctl(dev, DIOCGETNSTATELIM, &stlim) == -1) { + if (errno == ENOENT) { + /* we're done */ + return (0); + } + warn("DIOCGETNSTATELIM %u", stlim.id); + return (-1); + } + + switch (format) { + case PFCTL_SHOW_RULES: + print_statelim(&stlim); + break; + case PFCTL_SHOW_LABELS: + printf("%3u %8u/%-8u ", stlim.id, + stlim.inuse, stlim.limit); + if (stlim.rate.limit != 0) { + printf("%5u/%-5u ", + stlim.rate.limit, stlim.rate.seconds); + } else + printf("%5s/%-5s ", "nil", "nil"); + printf("%8llu %8llu %8llu\n", + stlim.admitted, stlim.hardlimited, stlim.ratelimited); + break; + default: + errx(1, "%s: unexpected format %d", __func__, format); + /* NOTREACHED */ + } + + id = stlim.id + 1; + } +} + +static inline int +pf_addr_inc(struct pf_addr *addr) +{ + int i; + uint32_t val, inc; + + for (i = 3; i >= 0; i--) { + val = ntohl(addr->addr32[i]); + inc = val + 1; + addr->addr32[i] = htonl(inc); + if (inc > val) + return (0); + } + + return (1); +} + +static int +pfctl_show_sources(int dev, const struct pfioc_sourcelim *srlim, + enum pfctl_show format, int opts) +{ + struct pfioc_source sr = { .id = srlim->id }; + struct pfioc_source_entry *entries, *e; + unsigned int nentries; + size_t len, used; + + if (format != PFCTL_SHOW_LABELS) + errx(1, "%s format is not PFCTL_SHOW_LABELS", __func__); + + nentries = srlim->nentries; + if (nentries == 0) + return (0); + if (nentries > 128) /* arbitrary */ + nentries = 128; + + entries = reallocarray(NULL, nentries, sizeof(*entries)); + if (entries == NULL) + err(1, "alloc %u source limiter entries", nentries); + + len = nentries * sizeof(*entries); + + e = entries; + + /* start from af 0 address 0 */ + memset(e, 0, sizeof(*e)); + + sr.entry_size = sizeof(*e); + sr.key = e; + + for (;;) { + sr.entries = entries; + sr.entrieslen = len; + + if (ioctl(dev, DIOCGETNSOURCE, &sr) == -1) { + switch (errno) { + case ESRCH: /* can't find the sourcelim */ + case ENOENT: /* no more sources */ + return (0); /* we're done */ + } + warn("DIOCGETNSOURCE %u", sr.id); + return (-1); + } + + used = 0; + if (sr.entrieslen > len) + errx(1, "DIOCGETNSOURCE used too much buffer"); + + e = entries; + for (;;) { + if (used > sr.entrieslen) + errx(1, "DIOCGETNSOURCE weird entrieslen"); + + print_addr_str(e->af, &e->addr); + switch (e->af) { + case AF_INET: + printf("/%u ", sr.inet_prefix); + break; + case AF_INET6: + printf("/%u ", sr.inet6_prefix); + break; + default: + printf("/af? "); + break; + } + printf("rdomain %u ", e->rdomain); + + printf("inuse %u/%u ", e->inuse, sr.limit); + printf("admit %llu hardlim %llu ratelim %llu\n", + e->admitted, e->hardlimited, e->ratelimited); + + used += sizeof(*e); + if (used == sr.entrieslen) + break; + + e++; + } + + /* reuse the last entry as the next key */ + e->af += pf_addr_inc(&e->addr); + sr.key = e; + } + + return (0); +} + +const char * +pfctl_sourcelim_id2name(struct pfctl *pf, u_int8_t id) +{ + struct pfctl_sourcelim *srlim; + + srlim = pfctl_get_sourcelim_id(pf, id); + if (srlim == NULL) { + srlim = malloc(sizeof(struct pfctl_sourcelim)); + if (srlim == NULL) + err(1, NULL); + memset(srlim, 0, sizeof(struct pfctl_sourcelim)); + srlim->ioc.id = id; + if (ioctl(pf->dev, DIOCGETSOURCELIM, &srlim->ioc) == -1) { + if (errno != ESRCH) + err(1, "DIOCGETSTATELIM %u", id); + } + pfctl_add_sourcelim(pf, srlim); + } + + return srlim->ioc.name; +} + +int +pfctl_show_sourcelims(int dev, enum pfctl_show format, int opts, + const char *idopt) +{ + struct pfioc_sourcelim srlim; + uint32_t id = PF_SOURCELIM_ID_MIN; + unsigned long cmd = DIOCGETNSOURCELIM; + + if (idopt != NULL) { + const char *errstr; + + id = strtonum(idopt, PF_SOURCELIM_ID_MIN, PF_SOURCELIM_ID_MAX, + &errstr); + if (errstr != NULL) + errx(1, "source limiter id: %s", errstr); + + cmd = DIOCGETSOURCELIM; + } + + if (format == PFCTL_SHOW_LABELS) { + printf("%3s %8s/%-8s %5s %5s/%-5s %8s %8s %8s %8s\n", "ID", + "USE", "ADDRS", "LIMIT", "RATE", "SECS", + "ADMIT", "ADDRLIM", "HARDLIM", "RATELIM"); + } + + for (;;) { + memset(&srlim, 0, sizeof(srlim)); + srlim.id = id; + + if (ioctl(dev, cmd, &srlim) == -1) { + if (errno == ESRCH) { + /* we're done */ + return (0); + } + warn("DIOCGETNSOURCELIM %u", srlim.id); + return (-1); + } + + switch (format) { + case PFCTL_SHOW_RULES: + print_sourcelim(&srlim); + break; + + case PFCTL_SHOW_LABELS: + printf("%3u %8u/%-8u %5u ", srlim.id, + srlim.nentries, srlim.entries, srlim.limit); + if (srlim.rate.limit != 0) { + printf("%5u/%-5u ", + srlim.rate.limit, srlim.rate.seconds); + } else + printf("%5s/%-5s ", "nil", "nil"); + printf("%8llu %8llu %8llu %8llu\n", + srlim.admitted, srlim.addrlimited, + srlim.hardlimited, srlim.ratelimited); + + if (opts & PF_OPT_VERBOSE) + if (pfctl_show_sources(dev, &srlim, + format, opts) != 0) + return (-1); + break; + + default: + errx(1, "%s: unexpected format %d", __func__, format); + /* NOTREACHED */ + } + + id = srlim.id + 1; + } + + return (0); +} + +void +pfctl_kill_source(int dev, const char *idopt, const char *source, int opts) +{ + struct pfioc_source_kill ioc; + unsigned int id; + const char *errstr; + struct addrinfo hints, *res; + int error; + + if (idopt == NULL) + errx(1, "source limiter id unspecified"); + if (source == NULL) + errx(1, "source limiter address unspecified"); + + id = strtonum(idopt, PF_SOURCELIM_ID_MIN, PF_SOURCELIM_ID_MAX, &errstr); + if (errstr != NULL) + errx(1, "source limiter id: %s", errstr); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = AI_NUMERICHOST; + + error = getaddrinfo(source, NULL, &hints, &res); + if (error != 0) + errx(1, "source limiter address: %s", gai_strerror(error)); + + ioc.id = id; + ioc.af = res->ai_family; + copy_satopfaddr(&ioc.addr, res->ai_addr); + ioc.rmstates = 0; + + freeaddrinfo(res); + + if (ioctl(dev, DIOCCLRSOURCE, &ioc) == -1) { + switch (errno) { + case ESRCH: + errx(1, "source limiter %u not found", id); + case ENOENT: + errx(1, "source limiter %u: %s not found", id, source); + default: + err(1, "kill source limiter %u entry %s", id, source); + } + } +} + +void +pfctl_init_show_rules(struct pfctl *pf, int dev, int opts) +{ + memset(pf, 0, sizeof(*pf)); + pf->dev = dev; + pf->opts = opts; + RBT_INIT(pfctl_statelim_ids, &pf->statelim_ids); + RBT_INIT(pfctl_statelim_nms, &pf->statelim_nms); + RBT_INIT(pfctl_sourcelim_ids, &pf->sourcelim_ids); + RBT_INIT(pfctl_sourcelim_nms, &pf->sourcelim_nms); +} + +int +pfctl_show_rules(struct pfctl *pf, char *path, enum pfctl_show format, + char *anchorname, int depth, int wildcard, long shownr) +{ + struct pfioc_rule pr; + u_int32_t header = 0; + int len = strlen(path), ret = 0; + char *npath = NULL, *p; + + if (depth > PF_ANCHOR_STACK_MAX) { + warnx("%s: max stack depth exceeded for %s", __func__, path); + return (-1); + } + + if (anchorname[0] == '\0') { + ret = pfctl_show_statelims(pf->dev, format); + if (ret != 0) + goto error; + ret = pfctl_show_sourcelims(pf->dev, format, pf->opts, NULL); + if (ret != 0) + goto error; + } + + /* + * Truncate a trailing / and * on an anchorname before searching for + * the ruleset, this is syntactic sugar that doesn't actually make it + * to the kernel. + */ + if ((p = strrchr(anchorname, '/')) != NULL && + p[1] == '*' && p[2] == '\0') { + p[0] = '\0'; + } + + memset(&pr, 0, sizeof(pr)); + if (anchorname[0] == '/') { + if ((npath = calloc(1, PATH_MAX)) == NULL) + err(1, "calloc"); + strlcpy(npath, anchorname, PATH_MAX); + } else { + if (path[0]) + snprintf(&path[len], PATH_MAX - len, "/%s", anchorname); + else + snprintf(&path[len], PATH_MAX - len, "%s", anchorname); + npath = path; + } + + memcpy(pr.anchor, npath, sizeof(pr.anchor)); + if (pf->opts & PF_OPT_SHOWALL) { + pr.rule.action = PF_PASS; + if (ioctl(pf->dev, DIOCGETRULES, &pr) == -1) { + warnx("%s", pf_strerror(errno)); + ret = -1; + goto error; + } + header++; + 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:"); + } + if (pf->opts & PF_OPT_CLRRULECTRS) + pr.action = PF_GET_CLR_CNTR; + + pr.rule.action = PF_PASS; + if (ioctl(pf->dev, DIOCGETRULES, &pr) == -1) { + warnx("%s", pf_strerror(errno)); + ret = -1; + goto error; + } + + while (ioctl(pf->dev, DIOCGETRULE, &pr) != -1) { + if (shownr != -1 && shownr != pr.nr) + continue; + + /* anchor is the same for all rules in it */ + if (pr.rule.anchor_wildcard == 0) + wildcard = 0; + + switch (format) { + case PFCTL_SHOW_LABELS: + if (pr.rule.label[0]) { + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE)); + printf("%s %llu %llu %llu %llu" + " %llu %llu %llu %llu\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], + (unsigned long long)pr.rule.states_tot); + } + break; + case PFCTL_SHOW_RULES: + if (pr.rule.label[0] && (pf->opts & PF_OPT_SHOWALL)) + labels = 1; + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE)); + print_rule(pf, &pr.rule, pr.anchor_call, pf->opts); + + /* + * If this is an 'unnamed' brace notation anchor OR + * the user has explicitly requested recursion, + * print it recursively. + */ + if (pr.anchor_call[0] && + (((p = strrchr(pr.anchor_call, '/')) ? + p[1] == '_' : pr.anchor_call[0] == '_') || + pf->opts & PF_OPT_RECURSE)) { + printf(" {\n"); + pfctl_print_rule_counters(&pr.rule, pf->opts); + pfctl_show_rules(pf, npath, format, + pr.anchor_call, depth + 1, + pr.rule.anchor_wildcard, -1); + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } else { + if ((pr.rule.rule_flag & PFRULE_EXPIRED) && + !(pf->opts & + (PF_OPT_VERBOSE2|PF_OPT_DEBUG))) + break; + printf("\n"); + pfctl_print_rule_counters(&pr.rule, pf->opts); + } + break; + case PFCTL_SHOW_NOTHING: + break; + } + errno = 0; + } + + if (errno != 0 && errno != ENOENT) { + warn("DIOCGETRULE"); + ret = -1; + goto error; + } + + /* + * If this anchor was called with a wildcard path, go through + * the rulesets in the anchor rather than the rules. + */ + if (wildcard && (pf->opts & PF_OPT_RECURSE)) { + struct pfioc_ruleset prs; + u_int32_t mnr, nr; + + memset(&prs, 0, sizeof(prs)); + memcpy(prs.path, npath, sizeof(prs.path)); + if (ioctl(pf->dev, DIOCGETRULESETS, &prs) == -1) + errx(1, "%s", pf_strerror(errno)); + mnr = prs.nr; + + for (nr = 0; nr < mnr; ++nr) { + prs.nr = nr; + if (ioctl(pf->dev, DIOCGETRULESET, &prs) == -1) + errx(1, "%s", pf_strerror(errno)); + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE)); + printf("anchor \"%s\" all {\n", prs.name); + pfctl_show_rules(pf, npath, format, + prs.name, depth + 1, 0, shownr); + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + return (0); + } + + error: + if (path != npath) + free(npath); + path[len] = '\0'; + return (ret); +} + +int +pfctl_show_src_nodes(int dev, int opts) +{ + struct pfioc_src_nodes psn; + struct pf_src_node *p; + char *inbuf = NULL, *newinbuf = NULL; + size_t i, len = 0; + + 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) == -1) { + 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, long shownr) +{ + struct pfioc_states ps; + struct pfsync_state *p; + char *inbuf = NULL, *newinbuf = NULL; + size_t i, len = 0; + int 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) == -1) { + 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; + } + if (shownr < 0 || ntohl(p->rule) == shownr) + print_state(p, opts); + } +done: + free(inbuf); + return (0); +} + +int +pfctl_show_status(int dev, int opts) +{ + struct pf_status status; + struct pfctl_watermarks wats; + struct pfioc_synflwats iocwats; + + if (ioctl(dev, DIOCGETSTATUS, &status) == -1) { + warn("DIOCGETSTATUS"); + return (-1); + } + if (ioctl(dev, DIOCGETSYNFLWATS, &iocwats) == -1) { + warn("DIOCGETSYNFLWATS"); + return (-1); + } + wats.hi = iocwats.hiwat; + wats.lo = iocwats.lowat; + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INFO:"); + print_status(&status, &wats, opts); + return (0); +} + +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) == -1) + 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) == -1) + 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); +} + +void +pfctl_read_limits(int dev) +{ + struct pfioc_limit pl; + int i; + + for (i = 0; pf_limits[i].name; i++) { + pl.index = pf_limits[i].index; + if (ioctl(dev, DIOCGETLIMIT, &pl) == -1) + err(1, "DIOCGETLIMIT"); + limit_curr[i] = pl.limit; + } +} + +void +pfctl_restore_limits(void) +{ + struct pfioc_limit pl; + int i; + + if (dev == -1) + return; + + for (i = 0; pf_limits[i].name; i++) { + pl.index = pf_limits[i].index; + pl.limit = limit_curr[i]; + if (ioctl(dev, DIOCSETLIMIT, &pl) == -1) + warn("DIOCSETLIMIT (%s)", pf_limits[i].name); + } +} + +/* callbacks for rule/nat/rdr/addr */ +void +pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) +{ + struct pf_rule *rule; + struct pf_ruleset *rs; + + rs = &pf->anchor->ruleset; + + if ((rule = calloc(1, sizeof(*rule))) == NULL) + err(1, "calloc"); + bcopy(r, rule, sizeof(*rule)); + + TAILQ_INSERT_TAIL(rs->rules.active.ptr, rule, entries); +} + +int +pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pf_anchor *a) +{ + int osize = pf->trans->pfrb_size; + + if (pfctl_add_trans(pf->trans, PF_TRANS_RULESET, path)) + return (3); + if (pfctl_add_trans(pf->trans, PF_TRANS_TABLE, path)) + return (4); + if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) + return (5); + + return (0); +} + +int +pfctl_add_queue(struct pfctl *pf, struct pf_queuespec *q) +{ + struct pfctl_qsitem *qi; + + if (pf->anchor->name[0]) { + printf("must not have queue definitions in an anchor\n"); + return (1); + } + + if (q->parent[0] == '\0') { + TAILQ_FOREACH(qi, &rootqs, entries) { + if (strcmp(q->ifname, qi->qs.ifname)) + continue; + printf("A root queue is already defined on %s\n", + qi->qs.ifname); + return (1); + } + } + + if ((qi = calloc(1, sizeof(*qi))) == NULL) + err(1, "calloc"); + bcopy(q, &qi->qs, sizeof(qi->qs)); + TAILQ_INIT(&qi->children); + + if (qi->qs.parent[0]) + TAILQ_INSERT_TAIL(&qspecs, qi, entries); + else + TAILQ_INSERT_TAIL(&rootqs, qi, entries); + + return (0); +} + +struct pfctl_qsitem * +pfctl_find_queue(char *what, struct pf_qihead *where) +{ + struct pfctl_qsitem *q; + + TAILQ_FOREACH(q, where, entries) + if (strcmp(q->qs.qname, what) == 0) + return (q); + + return (NULL); +} + +u_int +pfctl_find_childqs(struct pfctl_qsitem *qi) +{ + struct pfctl_qsitem *n, *p, *q; + u_int flags = qi->qs.flags; + + TAILQ_FOREACH(p, &qspecs, entries) { + if (strcmp(p->qs.parent, qi->qs.qname)) + continue; + if (p->qs.ifname[0] && strcmp(p->qs.ifname, qi->qs.ifname)) + continue; + if (++p->matches > 10000) + errx(1, "pfctl_find_childqs: excessive matches, loop?"); + + if ((q = pfctl_find_queue(p->qs.qname, &qi->children)) == NULL) { + /* insert */ + if ((n = calloc(1, sizeof(*n))) == NULL) + err(1, "calloc"); + TAILQ_INIT(&n->children); + bcopy(&p->qs, &n->qs, sizeof(n->qs)); + TAILQ_INSERT_TAIL(&qi->children, n, entries); + } else { + if ((q->qs.ifname[0] && p->qs.ifname[0])) + errx(1, "queue %s on %s respecified", + q->qs.qname, q->qs.ifname); + if (!q->qs.ifname[0] && !p->qs.ifname[0]) + errx(1, "queue %s respecified", + q->qs.qname); + /* ifbound beats floating */ + if (!q->qs.ifname[0]) + bcopy(&p->qs, &q->qs, sizeof(q->qs)); + } + } + + TAILQ_FOREACH(p, &qi->children, entries) + flags |= pfctl_find_childqs(p); + + if (!TAILQ_EMPTY(&qi->children)) { + if (qi->qs.flags & PFQS_DEFAULT) + errx(1, "default queue %s is not a leaf queue", + qi->qs.qname); + if (qi->qs.flags & PFQS_FLOWQUEUE) + errx(1, "flow queue %s is not a leaf queue", + qi->qs.qname); + } + + return (flags); +} + +void +pfctl_load_queue(struct pfctl *pf, u_int32_t ticket, struct pfctl_qsitem *qi) +{ + struct pfioc_queue q; + struct pfctl_qsitem *p; + + q.ticket = ticket; + bcopy(&qi->qs, &q.queue, sizeof(q.queue)); + if ((pf->opts & PF_OPT_NOACTION) == 0) + if (ioctl(pf->dev, DIOCADDQUEUE, &q) == -1) + err(1, "DIOCADDQUEUE"); + if (pf->opts & PF_OPT_VERBOSE) + print_queuespec(&qi->qs); + + TAILQ_FOREACH(p, &qi->children, entries) { + strlcpy(p->qs.ifname, qi->qs.ifname, IFNAMSIZ); + pfctl_load_queue(pf, ticket, p); + } +} + +int +pfctl_load_queues(struct pfctl *pf) +{ + struct pfctl_qsitem *qi, *tempqi; + struct pf_queue_scspec *rtsc, *lssc, *ulsc; + u_int32_t ticket; + + TAILQ_FOREACH(qi, &qspecs, entries) { + if (qi->matches == 0) + errx(1, "queue %s: parent %s not found", qi->qs.qname, + qi->qs.parent); + + rtsc = &qi->qs.realtime; + lssc = &qi->qs.linkshare; + ulsc = &qi->qs.upperlimit; + + if (rtsc->m1.percent || rtsc->m2.percent || + lssc->m1.percent || lssc->m2.percent || + ulsc->m1.percent || ulsc->m2.percent) + errx(1, "only absolute bandwidth specs for now"); + + /* Link sharing policy must be specified for child classes */ + if (qi->qs.parent[0] != '\0' && + lssc->m1.absolute == 0 && lssc->m2.absolute == 0) + errx(1, "queue %s: no bandwidth was specified", + qi->qs.qname); + } + + if ((pf->opts & PF_OPT_NOACTION) == 0) + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, ""); + else + ticket = 0; + + TAILQ_FOREACH_SAFE(qi, &rootqs, entries, tempqi) { + TAILQ_REMOVE(&rootqs, qi, entries); + pfctl_load_queue(pf, ticket, qi); + TAILQ_INSERT_HEAD(&rootqs, qi, entries); + } + + return (0); +} + +void +pfctl_clear_queues(struct pf_qihead *head) +{ + struct pfctl_qsitem *qi; + + while ((qi = TAILQ_FIRST(head)) != NULL) { + TAILQ_REMOVE(head, qi, entries); + pfctl_clear_queues(&qi->children); + free(qi); + } +} + +u_int +pfctl_leafqueue_check(char *qname) +{ + struct pfctl_qsitem *qi; + if (qname == NULL || qname[0] == 0) + return (0); + + TAILQ_FOREACH(qi, &rootqs, entries) { + if (strcmp(qname, qi->qs.qname)) + continue; + if (!TAILQ_EMPTY(&qi->children)) { + printf("queue %s: packets must be assigned to leaf " + "queues only\n", qname); + return (1); + } + } + TAILQ_FOREACH(qi, &qspecs, entries) { + if (strcmp(qname, qi->qs.qname)) + continue; + if (!TAILQ_EMPTY(&qi->children)) { + printf("queue %s: packets must be assigned to leaf " + "queues only\n", qname); + return (1); + } + } + return (0); +} + +u_int +pfctl_check_qassignments(struct pf_ruleset *rs) +{ + struct pf_rule *r; + struct pfctl_qsitem *qi; + u_int flags, errs = 0; + + /* main ruleset: need find_childqs to populate qi->children */ + if (rs->anchor->path[0] == 0) { + TAILQ_FOREACH(qi, &rootqs, entries) { + flags = pfctl_find_childqs(qi); + if (!(qi->qs.flags & PFQS_ROOTCLASS) && + !TAILQ_EMPTY(&qi->children)) { + if (qi->qs.flags & PFQS_FLOWQUEUE) + errx(1, "root queue %s doesn't " + "support hierarchy", + qi->qs.qname); + else + errx(1, "no bandwidth was specified " + "for root queue %s", qi->qs.qname); + } + if ((qi->qs.flags & PFQS_ROOTCLASS) && + !(flags & PFQS_DEFAULT)) + errx(1, "no default queue specified"); + } + } + + TAILQ_FOREACH(r, rs->rules.active.ptr, entries) { + if (r->anchor) + errs += pfctl_check_qassignments(&r->anchor->ruleset); + if (pfctl_leafqueue_check(r->qname) || + pfctl_leafqueue_check(r->pqname)) + errs++; + } + return (errs); +} + +void +pfctl_load_statelim(struct pfctl *pf, struct pfctl_statelim *stlim) +{ + if (pf->opts & PF_OPT_VERBOSE) + print_statelim(&stlim->ioc); + + if (pf->opts & PF_OPT_NOACTION) + return; + + if (ioctl(pf->dev, DIOCADDSTATELIM, &stlim->ioc) == -1) { + err(1, "DIOCADDSTATELIM %s id %u", + stlim->ioc.name, stlim->ioc.id); + } +} + +void +pfctl_load_statelims(struct pfctl *pf) +{ + struct pfctl_statelim *stlim; + u_int32_t ticket = 0; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, ""); + + RBT_FOREACH(stlim, pfctl_statelim_ids, &pf->statelim_ids) { + stlim->ioc.ticket = ticket; + pfctl_load_statelim(pf, stlim); + } + + /* Don't free the statelims because we're about to exit anyway. */ +} + +void +pfctl_load_sourcelim(struct pfctl *pf, struct pfctl_sourcelim *srlim) +{ + if (pf->opts & PF_OPT_VERBOSE) + print_sourcelim(&srlim->ioc); + + if (pf->opts & PF_OPT_NOACTION) + return; + + if (ioctl(pf->dev, DIOCADDSOURCELIM, &srlim->ioc) == -1) { + err(1, "DIOCADDSOURCELIM %s id %u", + srlim->ioc.name, srlim->ioc.id); + } +} + +void +pfctl_load_sourcelims(struct pfctl *pf) +{ + struct pfctl_sourcelim *srlim; + u_int32_t ticket = 0; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, ""); + + RBT_FOREACH(srlim, pfctl_sourcelim_ids, &pf->sourcelim_ids) { + srlim->ioc.ticket = ticket; + pfctl_load_sourcelim(pf, srlim); + } + + /* Don't free the sourcelims because we're about to exit anyway. */ +} + +static int +pfctl_load_tables(struct pfctl *pf, char *path, struct pf_anchor *a) +{ + struct pfr_ktable *kt, *ktw; + struct pfr_uktable *ukt; + uint32_t ticket; + char anchor_path[PF_ANCHOR_MAXPATH]; + int e; + + RB_FOREACH_SAFE(kt, pfr_ktablehead, &pfr_ktables, ktw) { + if (strcmp(kt->pfrkt_anchor, a->path) != 0) + continue; + + if (path != NULL && *path) { + strlcpy(anchor_path, kt->pfrkt_anchor, + sizeof (anchor_path)); + snprintf(kt->pfrkt_anchor, PF_ANCHOR_MAXPATH, "%s/%s", + path, anchor_path); + } + ukt = (struct pfr_uktable *) kt; + ticket = pfctl_get_ticket(pf->trans, PF_TRANS_TABLE, path); + e = pfr_ina_define(&ukt->pfrukt_t, ukt->pfrukt_addrs.pfrb_caddr, + ukt->pfrukt_addrs.pfrb_size, NULL, NULL, ticket, + ukt->pfrukt_init_addr ? PFR_FLAG_ADDRSTOO : 0); + if (e != 0) + err(1, "%s pfr_ina_define() %s@%s", __func__, + kt->pfrkt_name, kt->pfrkt_anchor); + RB_REMOVE(pfr_ktablehead, &pfr_ktables, kt); + pfr_buf_clear(&ukt->pfrukt_addrs); + free(ukt); + } + + return (0); +} + +int +pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs, + int depth) +{ + struct pf_rule *r; + int error, len = strlen(path); + int brace = 0; + unsigned int rno = 0; + + pf->anchor = rs->anchor; + + if (path[0]) + snprintf(&path[len], PATH_MAX - len, "/%s", pf->anchor->name); + else + snprintf(&path[len], PATH_MAX - len, "%s", pf->anchor->path); + + if (depth) { + if (TAILQ_FIRST(rs->rules.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_ruleset: " + "pfctl_ruleset_trans %d\n", error); + goto error; + } + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + } + + if (pf->optimize && (error = pfctl_optimize_ruleset(pf, rs)) != 0) + goto error; + + while ((r = TAILQ_FIRST(rs->rules.active.ptr)) != NULL) { + TAILQ_REMOVE(rs->rules.active.ptr, r, entries); + pfctl_expand_label_nr(r, rno); + rno++; + if ((error = pfctl_load_rule(pf, path, r, depth))) + goto error; + if (r->anchor) { + if ((error = pfctl_load_ruleset(pf, path, + &r->anchor->ruleset, depth + 1))) + goto error; + if ((error = pfctl_load_tables(pf, path, r->anchor))) + goto error; + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + free(r); + } + if (brace && pf->opts & PF_OPT_VERBOSE) { + INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + return (0); + + error: + path[len] = '\0'; + return (error); + +} + +int +pfctl_load_rule(struct pfctl *pf, char *path, struct pf_rule *r, int depth) +{ + char *name; + struct pfioc_rule pr; + int len = strlen(path); + + bzero(&pr, sizeof(pr)); + /* set up anchor before adding to path for anchor_call */ + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (pf->trans == NULL) + errx(1, "pfctl_load_rule: no transaction"); + pr.ticket = pfctl_get_ticket(pf->trans, PF_TRANS_RULESET, path); + } + if (strlcpy(pr.anchor, path, sizeof(pr.anchor)) >= sizeof(pr.anchor)) + errx(1, "pfctl_load_rule: strlcpy"); + + if (r->anchor) { + if (r->anchor->match) { + if (path[0]) + snprintf(&path[len], PATH_MAX - len, + "/%s", r->anchor->name); + else + snprintf(&path[len], PATH_MAX - len, + "%s", r->anchor->name); + name = r->anchor->name; + } else + name = r->anchor->path; + } else + name = ""; + + if ((pf->opts & PF_OPT_NOACTION) == 0) { + memcpy(&pr.rule, r, sizeof(pr.rule)); + if (r->anchor && strlcpy(pr.anchor_call, name, + sizeof(pr.anchor_call)) >= sizeof(pr.anchor_call)) + errx(1, "pfctl_load_rule: strlcpy"); + if (ioctl(pf->dev, DIOCADDRULE, &pr) == -1) + err(1, "DIOCADDRULE"); + } + + if (pf->opts & PF_OPT_VERBOSE) { + INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); + print_rule(pf, r, name, pf->opts); + } + path[len] = '\0'; + return (0); +} + +int +pfctl_rules(int dev, char *filename, int opts, int optimize, + char *anchorname, struct pfr_buffer *trans) +{ +#define ERR(...) do { warn(__VA_ARGS__); goto _error; } while(0) +#define ERRX(...) do { warnx(__VA_ARGS__); goto _error; } while(0) + + struct pfr_buffer *t, buf; + struct pfctl pf; + struct pf_ruleset *rs; + struct pfr_table trs; + char *path = NULL; + int osize; + char *p; + + RB_INIT(&pf_anchors); + memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); + pf_init_ruleset(&pf_main_anchor.ruleset); + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); + + if (trans == NULL) { + bzero(&buf, sizeof(buf)); + buf.pfrb_type = PFRB_TRANS; + pf.trans = &buf; + t = &buf; + osize = 0; + } else { + t = trans; + osize = t->pfrb_size; + } + + if ((path = calloc(1, PATH_MAX)) == NULL) + ERR("%s: calloc", __func__); + if (strlcpy(trs.pfrt_anchor, anchorname, + sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) + ERRX("%s: strlcpy", __func__); + pf.dev = dev; + pf.opts = opts; + pf.optimize = optimize; + + RBT_INIT(pfctl_statelim_ids, &pf.statelim_ids); + RBT_INIT(pfctl_statelim_nms, &pf.statelim_nms); + RBT_INIT(pfctl_sourcelim_ids, &pf.sourcelim_ids); + RBT_INIT(pfctl_sourcelim_nms, &pf.sourcelim_nms); + + /* non-brace anchor, create without resolving the path */ + if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) + ERR("%s: calloc", __func__); + rs = &pf.anchor->ruleset; + pf_init_ruleset(rs); + rs->anchor = pf.anchor; + if (strlcpy(pf.anchor->path, anchorname, + sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) + errx(1, "%s: strlcpy", __func__); + + if ((p = strrchr(anchorname, '/')) != NULL) { + if (strlen(p) == 1) + errx(1, "%s: bad anchor name %s", __func__, anchorname); + } else + p = anchorname; + + if (strlcpy(pf.anchor->name, p, + sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) + errx(1, "%s: strlcpy", __func__); + + pf.astack[0] = pf.anchor; + pf.asd = 0; + 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"); + pf.astack[0]->ruleset.tticket = + pfctl_get_ticket(t, PF_TRANS_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 (anchorname[0] == '\0') { + if (pfctl_check_qassignments(&pf.anchor->ruleset) || + pfctl_load_queues(&pf)) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Unable to load queues into kernel"); + else + goto _error; + } + + pfctl_load_statelims(&pf); + pfctl_load_sourcelims(&pf); + } + + if (pfctl_load_ruleset(&pf, path, rs, 0)) { + if ((opts & PF_OPT_NOACTION) == 0) + ERRX("Unable to load rules into kernel"); + else + goto _error; + } + + free(path); + path = NULL; + + if (trans == NULL) { + /* + * process "load anchor" directives that might have used queues + */ + if (pfctl_load_anchors(dev, &pf) == -1) + ERRX("load anchors"); + pfctl_clear_queues(&qspecs); + pfctl_clear_queues(&rootqs); + + if ((opts & PF_OPT_NOACTION) == 0) { + if (!anchorname[0] && pfctl_load_options(&pf)) + goto _error; + if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) + ERR("DIOCXCOMMIT"); + } + } + 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) == -1) { + 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) +{ + int64_t mem; + int mib[2], mcl; + size_t size; + + 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->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT; + pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT; + + /* + * limit_curr is populated by pfctl_read_limits() after main() opens + * /dev/pf. + */ + mib[0] = CTL_KERN; + mib[1] = KERN_MAXCLUSTERS; + size = sizeof(mcl); + if (sysctl(mib, 2, &mcl, &size, NULL, 0) == -1) + err(1, "sysctl"); + pf->limit[PF_LIMIT_FRAGS] = (limit_curr[PF_LIMIT_FRAGS] == 0) ? + mcl / 4 : limit_curr[PF_LIMIT_FRAGS]; + + pf->limit[PF_LIMIT_SRC_NODES] = (limit_curr[PF_LIMIT_SRC_NODES] == 0) ? + PFSNODE_HIWAT : limit_curr[PF_LIMIT_SRC_NODES]; + pf->limit[PF_LIMIT_TABLES] = (limit_curr[PF_LIMIT_TABLES] == 0) ? + PFR_KTABLE_HIWAT : limit_curr[PF_LIMIT_TABLES]; + pf->limit[PF_LIMIT_TABLE_ENTRIES] = + (limit_curr[PF_LIMIT_TABLE_ENTRIES] == 0) ? + PFR_KENTRY_HIWAT : limit_curr[PF_LIMIT_TABLE_ENTRIES]; + pf->limit[PF_LIMIT_PKTDELAY_PKTS] = + (limit_curr[PF_LIMIT_PKTDELAY_PKTS] == 0) ? + PF_PKTDELAY_MAXPKTS : limit_curr[PF_LIMIT_PKTDELAY_PKTS]; + pf->limit[PF_LIMIT_ANCHORS] = (limit_curr[PF_LIMIT_ANCHORS] == 0) ? + PF_ANCHOR_HIWAT : limit_curr[PF_LIMIT_ANCHORS]; + + mib[0] = CTL_HW; + mib[1] = HW_PHYSMEM64; + size = sizeof(mem); + if (sysctl(mib, 2, &mem, &size, NULL, 0) == -1) + err(1, "sysctl"); + if (mem <= 100*1024*1024) + pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT_SMALL; + + pf->debug = LOG_ERR; + pf->debug_set = 0; + pf->reassemble = PF_REASS_ENABLED; +} + +int +pfctl_load_options(struct pfctl *pf) +{ + int i, error = 0; + + /* load limits */ + for (i = 0; i < PF_LIMIT_MAX; i++) + if (pfctl_load_limit(pf, i, pf->limit[i])) + error = 1; + + /* + * If we've set the states limit, but haven't explicitly set adaptive + * timeouts, do it now with a start of 60% and end of 120%. + */ + if (pf->limit_set[PF_LIMIT_STATES] && + !pf->timeout_set[PFTM_ADAPTIVE_START] && + !pf->timeout_set[PFTM_ADAPTIVE_END]) { + pf->timeout[PFTM_ADAPTIVE_START] = + (pf->limit[PF_LIMIT_STATES] / 10) * 6; + pf->timeout_set[PFTM_ADAPTIVE_START] = 1; + pf->timeout[PFTM_ADAPTIVE_END] = + (pf->limit[PF_LIMIT_STATES] / 10) * 12; + pf->timeout_set[PFTM_ADAPTIVE_END] = 1; + } + + /* load timeouts */ + for (i = 0; i < PFTM_MAX; i++) + if (pfctl_load_timeout(pf, i, pf->timeout[i])) + error = 1; + + /* load debug */ + if (pf->debug_set && pfctl_load_debug(pf, pf->debug)) + error = 1; + + /* load logif */ + if (pf->ifname_set && pfctl_load_logif(pf, pf->ifname)) + error = 1; + + /* load hostid */ + if (pf->hostid_set && pfctl_load_hostid(pf, pf->hostid)) + error = 1; + + /* load reassembly settings */ + if (pf->reass_set && pfctl_load_reassembly(pf, pf->reassemble)) + error = 1; + + /* load syncookies settings */ + if (pf->syncookies_set && pfctl_load_syncookies(pf, pf->syncookies)) + error = 1; + if (pf->syncookieswat_set) { + struct pfioc_limit pl; + unsigned curlim; + + if (pf->limit_set[PF_LIMIT_STATES]) + curlim = pf->limit[PF_LIMIT_STATES]; + else { + memset(&pl, 0, sizeof(pl)); + pl.index = pf_limits[PF_LIMIT_STATES].index; + if (ioctl(dev, DIOCGETLIMIT, &pl) == -1) + err(1, "DIOCGETLIMIT"); + curlim = pl.limit; + } + if (pfctl_set_synflwats(pf, curlim * pf->syncookieswat[0]/100, + curlim * pf->syncookieswat[1]/100)) + 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); + + if ((pf->opts & PF_OPT_NOACTION) == 0) + pfctl_load_options(pf); + + return (0); +} + +int +pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) +{ + struct pfioc_limit pl; + static int restore_limit_handler_armed = 0; + + memset(&pl, 0, sizeof(pl)); + pl.index = index; + pl.limit = limit; + if (ioctl(pf->dev, DIOCSETLIMIT, &pl) == -1) { + if (errno == EBUSY) + warnx("Current pool size exceeds requested %s limit %u", + pf_limits[index].name, limit); + else + warnx("Cannot set %s limit to %u", + pf_limits[index].name, limit); + return (1); + } else if (restore_limit_handler_armed == 0) { + atexit(pfctl_restore_limits); + restore_limit_handler_armed = 1; + } + return (0); +} + +int +pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) +{ + int i; + + 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) == -1) { + warnx("DIOCSETTIMEOUT"); + return (1); + } + return (0); +} + +int +pfctl_set_synflwats(struct pfctl *pf, u_int32_t lowat, u_int32_t hiwat) +{ + struct pfioc_synflwats ps; + + memset(&ps, 0, sizeof(ps)); + ps.hiwat = hiwat; + ps.lowat = lowat; + + if (ioctl(pf->dev, DIOCSETSYNFLWATS, &ps) == -1) { + warnx("Cannot set synflood detection watermarks"); + return (1); + } + return (0); +} + +int +pfctl_set_reassembly(struct pfctl *pf, int on, int nodf) +{ + pf->reass_set = 1; + if (on) { + pf->reassemble = PF_REASS_ENABLED; + if (nodf) + pf->reassemble |= PF_REASS_NODF; + } else { + pf->reassemble = 0; + } + + if (pf->opts & PF_OPT_VERBOSE) + printf("set reassemble %s %s\n", on ? "yes" : "no", + nodf ? "no-df" : ""); + + return (0); +} + +int +pfctl_set_syncookies(struct pfctl *pf, u_int8_t val, struct pfctl_watermarks *w) +{ + if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) { + warnx("syncookies start/end only apply to adaptive"); + return (1); + } + if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) { + if (!w->hi) + w->hi = PF_SYNCOOKIES_HIWATPCT; + if (!w->lo) + w->lo = w->hi / 2; + if (w->lo >= w->hi) { + warnx("start must be higher than end"); + return (1); + } + pf->syncookieswat[0] = w->lo; + pf->syncookieswat[1] = w->hi; + pf->syncookieswat_set = 1; + } + + if (pf->opts & PF_OPT_VERBOSE) { + if (val == PF_SYNCOOKIES_NEVER) + printf("set syncookies never\n"); + else if (val == PF_SYNCOOKIES_ALWAYS) + printf("set syncookies always\n"); + else if (val == PF_SYNCOOKIES_ADAPTIVE) { + if (pf->syncookieswat_set) + printf("set syncookies adaptive (start %u%%, " + "end %u%%)\n", pf->syncookieswat[1], + pf->syncookieswat[0]); + else + printf("set syncookies adaptive\n"); + } else { /* cannot happen */ + warnx("king bula ate all syncookies"); + return (1); + } + } + + pf->syncookies_set = 1; + pf->syncookies = val; + return (0); +} + +int +pfctl_set_optimization(struct pfctl *pf, const char *opt) +{ + const struct pf_hint *hint; + int i, r; + + 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 (!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_iface pi; + + memset(&pi, 0, sizeof(pi)); + if (ifname && strlcpy(pi.pfiio_name, ifname, + sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) { + warnx("pfctl_load_logif: strlcpy"); + return (1); + } + if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi) == -1) { + warnx("DIOCSETSTATUSIF"); + return (1); + } + return (0); +} + +void +pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) +{ + HTONL(hostid); + + pf->hostid = hostid; + pf->hostid_set = 1; + + if (pf->opts & PF_OPT_VERBOSE) + printf("set hostid 0x%08x\n", ntohl(hostid)); +} + +int +pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) +{ + if (ioctl(dev, DIOCSETHOSTID, &hostid) == -1) { + warnx("DIOCSETHOSTID"); + return (1); + } + return (0); +} + +int +pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly) +{ + if (ioctl(dev, DIOCSETREASS, &reassembly) == -1) { + warnx("DIOCSETREASS"); + return (1); + } + return (0); +} + +int +pfctl_load_syncookies(struct pfctl *pf, u_int8_t val) +{ + if (ioctl(dev, DIOCSETSYNCOOKIES, &val) == -1) { + warnx("DIOCSETSYNCOOKIES"); + return (1); + } + return (0); +} + +int +pfctl_set_debug(struct pfctl *pf, char *d) +{ + u_int32_t level; + int loglevel; + + if ((loglevel = string_to_loglevel(d)) >= 0) + level = loglevel; + else { + warnx("unknown debug level \"%s\"", d); + return (-1); + } + pf->debug = level; + pf->debug_set = 1; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + if (ioctl(dev, DIOCSETDEBUG, &level) == -1) + 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) == -1) { + warnx("DIOCSETDEBUG"); + return (1); + } + return (0); +} + +int +pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) +{ + struct pfioc_iface pi; + + bzero(&pi, sizeof(pi)); + + pi.pfiio_flags = flags; + + 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) == -1) + err(1, "DIOCCLRIFFLAG"); + } else { + if (ioctl(pf->dev, DIOCSETIFFLAG, &pi) == -1) + err(1, "DIOCSETIFFLAG"); + } + } + return (0); +} + +void +pfctl_debug(int dev, u_int32_t level, int opts) +{ + struct pfr_buffer t; + + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_trans(dev, &t, DIOCXBEGIN, 0) || + ioctl(dev, DIOCSETDEBUG, &level) == -1|| + pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "pfctl_debug ioctl"); + + if ((opts & PF_OPT_QUIET) == 0) + fprintf(stderr, "debug level set to '%s'\n", + loglevel_to_string(level)); +} + +int +pfctl_walk_show(int opts, struct pfioc_ruleset *pr, void *warg) +{ + if (pr->path[0]) { + if (pr->path[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s/%s\n", pr->path, pr->name); + } else if (pr->name[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s\n", pr->name); + + return (0); +} + +int +pfctl_walk_get(int opts, struct pfioc_ruleset *pr, void *warg) +{ + struct pfr_anchoritem *pfra; + struct pfr_anchors *anchors; + int e; + + anchors = (struct pfr_anchors *) warg; + + pfra = malloc(sizeof(*pfra)); + if (pfra == NULL) + err(1, "%s", __func__); + + if (pr->path[0]) + e = asprintf(&pfra->pfra_anchorname, "%s/%s", pr->path, + pr->name); + else + e = asprintf(&pfra->pfra_anchorname, "%s", pr->name); + + if (e == -1) + err(1, "%s", __func__); + + + SLIST_INSERT_HEAD(anchors, pfra, pfra_sle); + + return (0); +} + +int +pfctl_walk_anchors(int dev, int opts, const char *anchor, + int(walkf)(int, struct pfioc_ruleset *, void *), void *warg) +{ + struct pfioc_ruleset pr; + u_int32_t mnr, nr; + + memset(&pr, 0, sizeof(pr)); + strlcpy(pr.path, anchor, sizeof(pr.path)); + if (ioctl(dev, DIOCGETRULESETS, &pr) == -1) + errx(1, "%s", pf_strerror(errno)); + mnr = pr.nr; + for (nr = 0; nr < mnr; ++nr) { + char sub[PATH_MAX]; + + pr.nr = nr; + if (ioctl(dev, DIOCGETRULESET, &pr) == -1) + errx(1, "%s", pf_strerror(errno)); + if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) + continue; + sub[0] = '\0'; + + if (walkf(opts, &pr, warg)) + return (-1); + + if (pr.path[0]) + snprintf(sub, sizeof(sub), "%s/%s", + pr.path, pr.name); + else + snprintf(sub, sizeof(sub), "%s", + pr.name); + if (pfctl_walk_anchors(dev, opts, sub, walkf, warg)) + return (-1); + } + return (0); +} + +int +pfctl_show_anchors(int dev, int opts, char *anchor) +{ + return ( + pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_show, NULL)); +} + +struct pfr_anchors * +pfctl_get_anchors(int dev, const char *anchor, int opts) +{ + struct pfioc_ruleset pr; + static struct pfr_anchors anchors; + char anchorbuf[PATH_MAX]; + char *n; + + SLIST_INIT(&anchors); + + memset(&pr, 0, sizeof(pr)); + if (*anchor != '\0') { + strlcpy(anchorbuf, anchor, sizeof(anchorbuf)); + n = dirname(anchorbuf); + if (n[0] != '.' && n[1] != '\0') + strlcpy(pr.path, n, sizeof(pr.path)); + strlcpy(anchorbuf, anchor, sizeof(anchorbuf)); + n = basename(anchorbuf); + if (n != NULL) + strlcpy(pr.name, n, sizeof(pr.name)); + } + + /* insert a root anchor first. */ + pfctl_walk_get(opts, &pr, &anchors); + + if (pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_get, &anchors)) + errx(1, + "%s failed to retrieve list of anchors, can't continue", + __func__); + + return (&anchors); +} + +int +pfctl_call_cleartables(int dev, int opts, struct pfr_anchoritem *pfra) +{ + /* + * PF_OPT_QUIET makes pfctl_clear_tables() to stop printing number of + * tables cleared for given anchor. + */ + opts |= PF_OPT_QUIET; + return ((pfctl_clear_tables(pfra->pfra_anchorname, opts) == -1) ? + 1 : 0); +} + +int +pfctl_call_clearrules(int dev, int opts, struct pfr_anchoritem *pfra) +{ + /* + * PF_OPT_QUIET makes pfctl_clear_rules() to stop printing a 'rules + * cleared' message for every anchor it deletes. + */ + opts |= PF_OPT_QUIET; + return (pfctl_clear_rules(dev, opts, pfra->pfra_anchorname)); +} + +int +pfctl_call_showtables(int dev, int opts, struct pfr_anchoritem *pfra) +{ + pfctl_show_tables(pfra->pfra_anchorname, opts); + return (0); +} + +int +pfctl_call_clearanchors(int dev, int opts, struct pfr_anchoritem *pfra) +{ + int rv = 0; + + rv |= pfctl_call_cleartables(dev, opts, pfra); + rv |= pfctl_call_clearrules(dev, opts, pfra); + + return (rv); +} + +int +pfctl_recurse(int dev, int opts, const char *anchorname, + int(*walkf)(int, int, struct pfr_anchoritem *)) +{ + int rv = 0; + struct pfr_anchors *anchors; + struct pfr_anchoritem *pfra, *pfra_save; + + anchors = pfctl_get_anchors(dev, anchorname, opts); + /* + * While traversing the list, pfctl_clear_*() must always return + * so that failures on one anchor do not prevent clearing others. + */ + opts |= PF_OPT_IGNFAIL; + if ((opts & PF_OPT_CALLSHOW) == 0) + printf("Removing:\n"); + SLIST_FOREACH_SAFE(pfra, anchors, pfra_sle, pfra_save) { + if ((opts & PF_OPT_CALLSHOW) == 0) + printf(" %s\n", (*pfra->pfra_anchorname == '\0') ? + "/" : pfra->pfra_anchorname); + rv |= walkf(dev, opts, pfra); + SLIST_REMOVE(anchors, pfra, pfr_anchoritem, pfra_sle); + free(pfra->pfra_anchorname); + free(pfra); + } + + return (rv); +} + +const char * +pfctl_lookup_option(char *cmd, const char **list) +{ + const char *item = NULL; + + if (cmd != NULL && *cmd) + for (; *list; list++) + if (!strncmp(cmd, *list, strlen(cmd))) { + if (item == NULL) + item = *list; + else + errx(1, "%s is ambiguous", cmd); + } + + return (item); +} + +int +pfctl_lookup_id(const char *cmd, const struct pfctl_opt_id *opt_ids) +{ + const struct pfctl_opt_id *opt_id; + int id = 0; + size_t cmdlen = strlen(cmd); + + for (opt_id = opt_ids; opt_id->id != 0; opt_id++) { + if (strncmp(cmd, opt_id->name, cmdlen) == 0) { + if (id != 0) + errx(1, "%s is ambiguous", cmd); + + id = opt_id->id; + } + } + + return (id); +} + +void +pfctl_state_store(int dev, const char *file) +{ + FILE *f; + struct pfioc_states ps; + char *inbuf = NULL, *newinbuf = NULL; + size_t n, len = 0; + + f = fopen(file, "w"); + if (f == NULL) + err(1, "open: %s", file); + + 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) == -1) + err(1, "DIOCGETSTATES"); + + 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; + } + + n = ps.ps_len / sizeof(struct pfsync_state); + if (fwrite(inbuf, sizeof(struct pfsync_state), n, f) < n) + err(1, "fwrite"); + +done: + free(inbuf); + fclose(f); +} + +void +pfctl_state_load(int dev, const char *file) +{ + FILE *f; + struct pfioc_state ps; + + f = fopen(file, "r"); + if (f == NULL) + err(1, "open: %s", file); + + while (fread(&ps.state, sizeof(ps.state), 1, f) == 1) { + if (ioctl(dev, DIOCADDSTATE, &ps) == -1) { + switch (errno) { + case EEXIST: + case EINVAL: + break; + default: + err(1, "DIOCADDSTATE"); + } + } + } + + fclose(f); +} + +void +pfctl_reset(int dev, int opts) +{ + struct pfctl pf; + struct pfr_buffer t; + int i; + + memset(&pf, 0, sizeof(pf)); + pf.dev = dev; + pfctl_init_options(&pf); + + /* Force reset upon pfctl_load_options() */ + pf.debug_set = 1; + pf.reass_set = 1; + pf.syncookieswat_set = 1; + pf.syncookies_set = 1; + pf.ifname = strdup("none"); + if (pf.ifname == NULL) + err(1, "%s: strdup", __func__); + pf.ifname_set = 1; + + memset(&t, 0, sizeof(t)); + t.pfrb_type = PFRB_TRANS; + if (pfctl_trans(dev, &t, DIOCXBEGIN, 0)) + err(1, "%s: DIOCXBEGIN", __func__); + + for (i = 0; pf_limits[i].name; i++) + pf.limit_set[pf_limits[i].index] = 1; + + for (i = 0; pf_timeouts[i].name; i++) + pf.timeout_set[pf_timeouts[i].timeout] = 1; + + pfctl_load_options(&pf); + + if (pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) + err(1, "%s: DIOCXCOMMIT", __func__); + + pfctl_clear_interface_flags(dev, opts); +} + +#ifndef REGRESS_NOMAIN +int +main(int argc, char *argv[]) +{ + int ch; + int mode = O_RDONLY; + int opts = 0; + int optimize = PF_OPTIMIZE_BASIC; + int level; + int rdomain = 0; + char anchorname[PATH_MAX]; + int anchor_wildcard = 0; + char *path; + char *lfile = NULL, *sfile = NULL; + const char *idopt = NULL; + const char *errstr; + long shownr = -1; + struct pfctl show_rules_pf; + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "a:dD:eqf:F:ghi:I:k:K:L:Nno:Pp:R:rS:s:t:T:vV:x: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 'I': + idopt = 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 'N': + opts |= PF_OPT_NODNS; + break; + case 'n': + opts |= PF_OPT_NOACTION; + break; + case 'r': + opts |= PF_OPT_USEDNS; + break; + case 'R': + shownr = strtonum(optarg, -1, LONG_MAX, &errstr); + if (errstr) { + warnx("invalid rule id: %s", errstr); + usage(); + } + break; + case 'f': + rulesopt = optarg; + mode = O_RDWR; + break; + case 'g': + opts |= PF_OPT_DEBUG; + 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 'P': + opts |= PF_OPT_PORTNAMES; + break; + case 'p': + pf_device = optarg; + break; + case 's': + showopt = pfctl_lookup_id(optarg, showopt_list); + if (showopt == 0) { + 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 'V': + rdomain = strtonum(optarg, 0, RT_TABLEID_MAX, &errstr); + if (errstr) { + warnx("Invalid rdomain: %s", errstr); + usage(); + } + 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 'S': + sfile = optarg; + break; + case 'L': + mode = O_RDWR; + lfile = optarg; + break; + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + + if ((opts & PF_OPT_NODNS) && (opts & PF_OPT_USEDNS)) + errx(1, "-N and -r are mutually exclusive"); + + if ((tblcmdopt == NULL) ^ (tableopt == NULL)) + usage(); + + if (tblcmdopt != NULL) { + argc -= optind; + argv += optind; + ch = *tblcmdopt; + mode = strchr("st", ch) ? O_RDONLY : O_RDWR; + } else if (argc != optind) { + warnx("unknown command line argument: %s ...", argv[optind]); + usage(); + /* NOTREACHED */ + } + + memset(anchorname, 0, sizeof(anchorname)); + if (anchoropt != NULL) { + if (anchoropt[0] == '\0') + errx(1, "anchor name must not be empty"); + if (mode == O_RDONLY && showopt == 0 && tblcmdopt == NULL) { + warnx("anchors apply to -f, -F, -s, and -T only"); + usage(); + } + if (mode == O_RDWR && tblcmdopt == NULL && + (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL)) + errx(1, "anchor names beginning with '_' cannot " + "be modified from the command line"); + int len = strlen(anchoropt); + + if (anchoropt[len - 1] == '*') { + if (len >= 2 && anchoropt[len - 2] == '/') { + anchoropt[len - 2] = '\0'; + anchor_wildcard = 1; + } 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); + } + + if ((opts & PF_OPT_NOACTION) == 0) { + dev = open(pf_device, mode); + if (dev == -1) + err(1, "%s", pf_device); + pfctl_read_limits(dev); + } else { + dev = open(pf_device, O_RDONLY); + if (dev >= 0) { + opts |= PF_OPT_DUMMYACTION; + pfctl_read_limits(dev); + } + /* turn off options */ + opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); + clearopt = debugopt = NULL; + showopt = 0; + } + + if (opts & PF_OPT_DISABLE) + if (pfctl_disable(dev, opts)) + exit_val = 1; + + if ((path = calloc(1, PATH_MAX)) == NULL) + errx(1, "%s: calloc", __func__); + + switch (showopt) { + case SHOWOPT_NONE: + break; + + case SHOWOPT_ANCHORS: + pfctl_show_anchors(dev, opts, anchorname); + break; + case SHOWOPT_RULES: + pfctl_init_show_rules(&show_rules_pf, dev, opts); + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(&show_rules_pf, path, PFCTL_SHOW_RULES, + anchorname, 0, anchor_wildcard, shownr); + break; + case SHOWOPT_LABELS: + pfctl_init_show_rules(&show_rules_pf, dev, opts); + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(&show_rules_pf, path, PFCTL_SHOW_LABELS, + anchorname, 0, anchor_wildcard, shownr); + break; + case SHOWOPT_QUEUE: + pfctl_show_queues(dev, ifaceopt, opts, + opts & PF_OPT_VERBOSE2); + break; + case SHOWOPT_STATES: + pfctl_show_states(dev, ifaceopt, opts, shownr); + break; + case SHOWOPT_SOURCES: + pfctl_show_src_nodes(dev, opts); + break; + case SHOWOPT_INFO: + pfctl_show_status(dev, opts); + break; + case SHOWOPT_TIMEOUTS: + pfctl_show_timeouts(dev, opts); + break; + case SHOWOPT_MEMORY: + pfctl_show_limits(dev, opts); + break; + case SHOWOPT_ALL: + opts |= PF_OPT_SHOWALL; + pfctl_init_show_rules(&show_rules_pf, dev, opts); + pfctl_load_fingerprints(dev, opts); + pfctl_show_rules(&show_rules_pf, path, PFCTL_SHOW_RULES, + anchorname, 0, 0, -1); + pfctl_show_queues(dev, ifaceopt, opts, + opts & PF_OPT_VERBOSE2); + pfctl_show_states(dev, ifaceopt, opts, -1); + pfctl_show_src_nodes(dev, opts); + pfctl_show_status(dev, opts); + pfctl_show_rules(&show_rules_pf, path, PFCTL_SHOW_LABELS, + anchorname, 0, 0, -1); + pfctl_show_timeouts(dev, opts); + pfctl_show_limits(dev, opts); + pfctl_show_tables(anchorname, opts); + pfctl_show_fingerprints(opts); + break; + case SHOWOPT_TABLES: + if (opts & PF_OPT_RECURSE) { + opts |= PF_OPT_CALLSHOW; + pfctl_recurse(dev, opts, anchorname, + pfctl_call_showtables); + } else + pfctl_show_tables(anchorname, opts); + break; + case SHOWOPT_OSFP: + pfctl_load_fingerprints(dev, opts); + pfctl_show_fingerprints(opts); + break; + case SHOWOPT_IFACES: + pfctl_show_ifaces(ifaceopt, opts); + break; + case SHOWOPT_STATELIMS: + pfctl_show_statelims(dev, PFCTL_SHOW_LABELS); + break; + case SHOWOPT_SOURCELIMS: + pfctl_show_sourcelims(dev, PFCTL_SHOW_LABELS, opts, idopt); + break; + } + + if ((opts & PF_OPT_CLRRULECTRS) && showopt == 0) { + pfctl_init_show_rules(&show_rules_pf, dev, opts); + pfctl_show_rules(&show_rules_pf, path, PFCTL_SHOW_NOTHING, + anchorname, 0, 0, -1); + } + + if (clearopt != NULL) { + switch (*clearopt) { + case 'r': + if (opts & PF_OPT_RECURSE) + pfctl_recurse(dev, opts, anchorname, + pfctl_call_clearrules); + else + pfctl_clear_rules(dev, opts, anchorname); + 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, ifaceopt, opts); + break; + case 'a': + if (ifaceopt) { + warnx("don't specify an interface with -Fall"); + usage(); + /* NOTREACHED */ + } + if (opts & PF_OPT_RECURSE) + pfctl_recurse(dev, opts, anchorname, + pfctl_call_clearanchors); + else { + pfctl_clear_tables(anchorname, opts); + pfctl_clear_rules(dev, opts, anchorname); + } + + if (!*anchorname) { + pfctl_clear_states(dev, ifaceopt, opts); + pfctl_clear_src_nodes(dev, opts); + pfctl_clear_stats(dev, ifaceopt, opts); + pfctl_clear_fingerprints(dev, opts); + pfctl_reset(dev, opts); + } + break; + case 'o': + pfctl_clear_fingerprints(dev, opts); + break; + case 'T': + if ((opts & PF_OPT_RECURSE) == 0) + pfctl_clear_tables(anchorname, opts); + else + pfctl_recurse(dev, opts, anchorname, + pfctl_call_cleartables); + break; + case 'R': + pfctl_reset(dev, opts); + break; + } + } + if (state_killers) { + if (!strcmp(state_kill[0], "label")) + pfctl_label_kill_states(dev, ifaceopt, opts, rdomain); + else if (!strcmp(state_kill[0], "id")) + pfctl_id_kill_states(dev, opts); + else if (!strcmp(state_kill[0], "key")) + pfctl_key_kill_states(dev, ifaceopt, opts, rdomain); + else if (!strcmp(state_kill[0], "source")) + pfctl_kill_source(dev, idopt, state_kill[1], opts); + else + pfctl_net_kill_states(dev, ifaceopt, opts, rdomain); + } + + if (src_node_killers) + pfctl_kill_src_nodes(dev, opts); + + if (tblcmdopt != NULL) { + exit_val = pfctl_table(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 && !anchorname[0]) { + pfctl_clear_interface_flags(dev, opts | PF_OPT_QUIET); + if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) + exit_val = 1; + } + + if (rulesopt != NULL) { + if (pfctl_rules(dev, rulesopt, opts, optimize, + anchorname, NULL)) + exit_val = 1; + } + + if (opts & PF_OPT_ENABLE) + if (pfctl_enable(dev, opts)) + exit_val = 1; + + if (debugopt != NULL) { + if ((level = string_to_loglevel((char *)debugopt)) < 0) { + switch (*debugopt) { + case 'n': + level = LOG_CRIT; + break; + case 'u': + level = LOG_ERR; + break; + case 'm': + level = LOG_NOTICE; + break; + case 'l': + level = LOG_DEBUG; + break; + } + } + if (level >= 0) + pfctl_debug(dev, level, opts); + } + + if (sfile != NULL) + pfctl_state_store(dev, sfile); + if (lfile != NULL) + pfctl_state_load(dev, lfile); + + /* + * prevent pfctl_restore_limits() exit handler from restoring + * pf(4) options settings on successful exit. + */ + if (exit_val == 0) { + close(dev); + dev = -1; + } + + return exit_val; +} +#endif /* REGRESS_NOMAIN */ + +char * +pf_strerror(int errnum) +{ + switch (errnum) { + case ESRCH: + return "Table does not exist"; + case EINVAL: + case ENOENT: + return "Anchor does not exist"; + default: + return strerror(errnum); + } +} + +static inline int +pfctl_statelim_id_cmp(const struct pfctl_statelim *a, + const struct pfctl_statelim *b) +{ + uint32_t ida = a->ioc.id; + uint32_t idb = b->ioc.id; + + if (ida > idb) + return (1); + if (ida < idb) + return (-1); + + return (0); +} + +RBT_GENERATE(pfctl_statelim_ids, pfctl_statelim, id_entry, + pfctl_statelim_id_cmp); + +static inline int +pfctl_statelim_nm_cmp(const struct pfctl_statelim *a, + const struct pfctl_statelim *b) +{ + return (strcmp(a->ioc.name, b->ioc.name)); +} + +RBT_GENERATE(pfctl_statelim_nms, pfctl_statelim, nm_entry, + pfctl_statelim_nm_cmp); + +int +pfctl_add_statelim(struct pfctl *pf, struct pfctl_statelim *stlim) +{ + struct pfctl_statelim *ostlim; + + ostlim = RBT_INSERT(pfctl_statelim_ids, &pf->statelim_ids, stlim); + if (ostlim != NULL) + return (-1); + + ostlim = RBT_INSERT(pfctl_statelim_nms, &pf->statelim_nms, stlim); + if (ostlim != NULL) { + RBT_REMOVE(pfctl_statelim_ids, &pf->statelim_ids, stlim); + return (-1); + } + + return (0); +} + +struct pfctl_statelim * +pfctl_get_statelim_id(struct pfctl *pf, uint32_t id) +{ + struct pfctl_statelim key; + + key.ioc.id = id; + + return (RBT_FIND(pfctl_statelim_ids, &pf->statelim_ids, &key)); +} + +struct pfctl_statelim * +pfctl_get_statelim_nm(struct pfctl *pf, const char *name) +{ + struct pfctl_statelim key; + + if (strlcpy(key.ioc.name, name, sizeof(key.ioc.name)) >= + sizeof(key.ioc.name)) + return (NULL); + + return (RBT_FIND(pfctl_statelim_nms, &pf->statelim_nms, &key)); +} + +static inline int +pfctl_sourcelim_id_cmp(const struct pfctl_sourcelim *a, + const struct pfctl_sourcelim *b) +{ + uint32_t ida = a->ioc.id; + uint32_t idb = b->ioc.id; + + if (ida > idb) + return (1); + if (ida < idb) + return (-1); + + return (0); +} + +RBT_GENERATE(pfctl_sourcelim_ids, pfctl_sourcelim, id_entry, + pfctl_sourcelim_id_cmp); + +static inline int +pfctl_sourcelim_nm_cmp(const struct pfctl_sourcelim *a, + const struct pfctl_sourcelim *b) +{ + return (strcmp(a->ioc.name, b->ioc.name)); +} + +RBT_GENERATE(pfctl_sourcelim_nms, pfctl_sourcelim, nm_entry, + pfctl_sourcelim_nm_cmp); + +int +pfctl_add_sourcelim(struct pfctl *pf, struct pfctl_sourcelim *srlim) +{ + struct pfctl_sourcelim *osrlim; + + osrlim = RBT_INSERT(pfctl_sourcelim_ids, &pf->sourcelim_ids, srlim); + if (osrlim != NULL) { + return (-1); + } + + osrlim = RBT_INSERT(pfctl_sourcelim_nms, &pf->sourcelim_nms, srlim); + if (osrlim != NULL) { + RBT_REMOVE(pfctl_sourcelim_ids, &pf->sourcelim_ids, srlim); + return (-1); + } + + return (0); +} + +struct pfctl_sourcelim * +pfctl_get_sourcelim_id(struct pfctl *pf, uint32_t id) +{ + struct pfctl_sourcelim key; + + key.ioc.id = id; + + return (RBT_FIND(pfctl_sourcelim_ids, &pf->sourcelim_ids, &key)); +} + +struct pfctl_sourcelim * +pfctl_get_sourcelim_nm(struct pfctl *pf, const char *name) +{ + struct pfctl_sourcelim key; + + if (strlcpy(key.ioc.name, name, sizeof(key.ioc.name)) >= + sizeof(key.ioc.name)) + return (NULL); + + return (RBT_FIND(pfctl_sourcelim_nms, &pf->sourcelim_nms, &key)); +} diff --git a/tools/pfctl/pfctl.h b/tools/pfctl/pfctl.h new file mode 100644 index 0000000..2c159b7 --- /dev/null +++ b/tools/pfctl/pfctl.h @@ -0,0 +1,137 @@ +/* $OpenBSD: pfctl.h,v 1.66 2026/02/03 10:25:28 sashan Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * 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. + * + */ + +#ifndef _PFCTL_H_ +#define _PFCTL_H_ + +#ifdef PFCTL_DEBUG +#define DBGPRINT(...) fprintf(stderr, __VA_ARGS__) +#else +#define DBGPRINT(...) (void)(0) +#endif + +enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; + +enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, + PFRB_IFACES, PFRB_TRANS, PFRB_MAX }; +struct pfr_buffer { + int pfrb_type; /* type of content, see enum above */ + int pfrb_size; /* number of objects in buffer */ + int pfrb_msize; /* maximum number of objects in buffer */ + void *pfrb_caddr; /* malloc'ated memory area */ +}; +#define PFRB_FOREACH(var, buf) \ + for ((var) = pfr_buf_next((buf), NULL); \ + (var) != NULL; \ + (var) = pfr_buf_next((buf), (var))) + + +struct pfr_anchoritem { + SLIST_ENTRY(pfr_anchoritem) pfra_sle; + char *pfra_anchorname; +}; + +struct pfr_uktable { + struct pfr_ktable pfrukt_kt; + struct pfr_buffer pfrukt_addrs; + int pfrukt_init_addr; + SLIST_ENTRY(pfr_uktable) + pfrukt_entry; +}; + +#define pfrukt_t pfrukt_kt.pfrkt_ts.pfrts_t +#define pfrukt_name pfrukt_kt.pfrkt_t.pfrt_name +#define pfrukt_anchor pfrukt_kt.pfrkt_t.pfrt_anchor + +extern struct pfr_ktablehead pfr_ktables; + +SLIST_HEAD(pfr_anchors, pfr_anchoritem); + +struct pfctl; + +int pfr_clr_tables(struct pfr_table *, int *, int); +int pfr_add_tables(struct pfr_table *, int, int *, int); +int pfr_del_tables(struct pfr_table *, int, int *, int); +int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); +int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); +int pfr_clr_tstats(struct pfr_table *, int, int *, int); +int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_clr_addrs(struct pfr_table *, int *, int); +int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int *, int *, int); +int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); +int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); +int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); +int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, + int *, int, int); +void pfr_buf_clear(struct pfr_buffer *); +int pfr_buf_add(struct pfr_buffer *, const void *); +void *pfr_buf_next(struct pfr_buffer *, const void *); +int pfr_buf_grow(struct pfr_buffer *, int); +int pfr_buf_load(struct pfr_buffer *, char *, int, int); +char *pf_strerror(int); +int pfi_get_ifaces(const char *, struct pfi_kif *, int *); + +void pfctl_print_title(char *); +int pfctl_clear_tables(const char *, int); +void pfctl_show_tables(const char *, int); +int pfctl_table(int, char *[], char *, const char *, char *, + const char *, int); +void warn_duplicate_tables(const char *, const char *); +void pfctl_show_ifaces(const char *, int); +FILE *pfctl_fopen(const char *, const char *); + +void print_addr(struct pf_addr_wrap *, sa_family_t, int); +void print_addr_str(sa_family_t, struct pf_addr *); +void print_host(struct pf_addr *, u_int16_t p, sa_family_t, u_int16_t, const char *, int); +void print_seq(struct pfsync_state_peer *); +void print_state(struct pfsync_state *, int); + +int pfctl_cmdline_symset(char *); +int pfctl_add_trans(struct pfr_buffer *, int, const char *); +u_int32_t + pfctl_get_ticket(struct pfr_buffer *, int, const char *); +int pfctl_trans(int, struct pfr_buffer *, u_long, int); + +int pfctl_show_queues(int, const char *, int, int); + +void pfctl_err(int, int, const char *, ...); +void pfctl_errx(int, int, const char *, ...); + +const char + *pfctl_statelim_id2name(struct pfctl *, u_int8_t); +const char + *pfctl_sourcelim_id2name(struct pfctl *, u_int8_t); + +#endif /* _PFCTL_H_ */ diff --git a/tools/pfctl/pfctl_optimize.c b/tools/pfctl/pfctl_optimize.c new file mode 100644 index 0000000..81d1bd0 --- /dev/null +++ b/tools/pfctl/pfctl_optimize.c @@ -0,0 +1,1632 @@ +/* $OpenBSD: pfctl_optimize.c,v 1.52 2026/05/17 14:11:57 sashan 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 +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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?!? */ +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(rdr, BREAK), + PF_RULE_FIELD(nat, BREAK), + PF_RULE_FIELD(logif, BREAK), + PF_RULE_FIELD(route, BREAK), + PF_RULE_FIELD(rtableid, 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), + PF_RULE_FIELD(onrdomain, NOMERGE), + PF_RULE_FIELD(naf, 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 XXX fix*/ + PF_RULE_FIELD(max_mss, NEVER), + PF_RULE_FIELD(min_ttl, NEVER), + PF_RULE_FIELD(set_tos, NEVER), +}; + + + +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 *); +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_rdom(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 *); +struct pf_opt_tbl *pf_opt_table_ref(struct pf_opt_tbl *); +void pf_opt_table_unref(struct pf_opt_tbl *); + + +int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *, struct pf_rule *); +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 }, \ + { "rdomain", PF_SKIP_RDOM, skip_cmp_rdom }, \ + { "af", PF_SKIP_AF, skip_cmp_af }, \ + { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \ + { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \ + { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \ + { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \ + { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \ +} + +struct pfr_buffer table_buffer; +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; + + if (TAILQ_EMPTY(rs->rules.active.ptr)) + return (0); + + DEBUG("optimizing ruleset \"%s\"", rs->anchor->path); + memset(&table_buffer, 0, sizeof(table_buffer)); + skip_init(); + TAILQ_INIT(&opt_queue); + + old_rules = rs->rules.active.ptr; + rs->rules.active.ptr = rs->rules.inactive.ptr; + rs->rules.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.inactive.ptr)) != NULL) { + TAILQ_REMOVE(rs->rules.inactive.ptr, r, entries); + if ((por = calloc(1, sizeof(*por))) == NULL) + err(1, "calloc"); + memcpy(&por->por_rule, r, sizeof(*r)); + + 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_INSERT_TAIL(rs->rules.active.ptr, r, entries); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(por->por_dst_tbl); + free(por); + } + superblock_free(pf, block); + } + + return (0); + +error: + while ((por = TAILQ_FIRST(&opt_queue))) { + TAILQ_REMOVE(&opt_queue, por, por_entry); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(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(pf, &por->por_rule, por->por_rule.anchor ? + por->por_rule.anchor->name : "", PF_OPT_DEBUG); + } +#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; + + /* 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, NULL)) + return (1); + if (add_opt_table(pf, &p1->por_dst_tbl, + p1->por_rule.af, &p2->por_rule.dst, NULL)) + return (1); + if (p1->por_dst_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + TAILQ_REMOVE(&block->sb_rules, p2, + por_entry); + free(p2); + } else + p2->por_dst_tbl = + pf_opt_table_ref(p1->por_dst_tbl); + } 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, NULL)) + return (1); + if (add_opt_table(pf, &p1->por_src_tbl, + p1->por_rule.af, &p2->por_rule.src, NULL)) + return (1); + if (p1->por_src_tbl->pt_rulecount >= + TABLE_THRESHOLD) { + TAILQ_REMOVE(&block->sb_rules, p2, + por_entry); + free(p2); + } else + p2->por_src_tbl = + pf_opt_table_ref(p1->por_src_tbl); + } + } + } + + + /* + * Then we make a final pass to create a valid table name and + * insert the name into the rules. + * Convert translation/routing mapping pools to tables as well. + */ + 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); + TAILQ_CONCAT(&head, &block->sb_rules, por_entry); + + while (!TAILQ_EMPTY(&head)) { + largest = 1; + largest_list = 0; + + /* + * 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); + TAILQ_CONCAT(&block->sb_rules, &head, 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; + 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); + 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); + TAILQ_CONCAT(&queue, &block->sb_rules, 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; + } + } + if (por2 == NULL) + 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) == -1) { + warnx("%s", pf_strerror(errno)); + 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) == -1) { + warnx("%s", pf_strerror(errno)); + free(por); + 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; + TAILQ_INSERT_TAIL(&queue, por, por_entry); + } + + 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 ON RDOMAIN field for skiplist construction */ +int +skip_cmp_rdom(struct pf_rule *a, struct pf_rule *b) +{ + if (a->onrdomain == -1 || a->onrdomain != b->onrdomain) + return (1); + return (a->ifnot != b->ifnot); +} + +/* 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, char *ifname) +{ +#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_refcnt = 1; + (*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_OPTIMIZER_TABLE_PFX, tablenum++); + DEBUG("creating table <%s>", (*tbl)->pt_name); + } + + memset(&node_host, 0, sizeof(node_host)); + node_host.af = af; + node_host.addr = addr->addr; + node_host.ifname = ifname; + node_host.weight = addr->weight; + + 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)); + + 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_OPTIMIZER_TABLE_PFX, table_identifier, tablenum); + snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d", + PF_OPTIMIZER_TABLE_PFX, 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 | tbl->pt_flags, 1, + pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket, + NULL)) { + 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+%lxh", + 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) +{ + if (ifname == NULL || !ifname[0]) + return (0); + + /* Real interfaces must end in a number, interface groups do not */ + if (isdigit((unsigned char)ifname[strlen(ifname) - 1])) + return (0); + else + 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) < + unmask(&sub->src.addr.v.a.mask) && + 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) < + unmask(&sub->dst.addr.v.a.mask) && + 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); + pf_opt_table_unref(por->por_src_tbl); + pf_opt_table_unref(por->por_dst_tbl); + free(por); + } + if (block->sb_profiled_block) + superblock_free(pf, block->sb_profiled_block); + free(block); +} + +struct pf_opt_tbl * +pf_opt_table_ref(struct pf_opt_tbl *pt) +{ + /* parser does not run concurrently, we don't need atomic ops. */ + if (pt != NULL) + pt->pt_refcnt++; + + return (pt); +} + +void +pf_opt_table_unref(struct pf_opt_tbl *pt) +{ + if ((pt != NULL) && ((--pt->pt_refcnt) == 0)) { + if (pt->pt_buf != NULL) { + pfr_buf_clear(pt->pt_buf); + free(pt->pt_buf); + } + free(pt); + } +} diff --git a/tools/pfctl/pfctl_osfp.c b/tools/pfctl/pfctl_osfp.c new file mode 100644 index 0000000..cfed976 --- /dev/null +++ b/tools/pfctl/pfctl_osfp.c @@ -0,0 +1,1103 @@ +/* $OpenBSD: pfctl_osfp.c,v 1.27 2020/01/15 11:52:50 sashan Exp $ */ + +/* + * Copyright (c) 2003 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) + +/* #define OSFP_DEBUG 1 */ +#ifdef OSFP_DEBUG +# define DEBUG(fp, str, v...) \ + fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \ + (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v); +#else +# define DEBUG(fp, str, v...) ((void)0) +#endif + + +struct name_entry; +LIST_HEAD(name_list, name_entry); +struct name_entry { + LIST_ENTRY(name_entry) nm_entry; + int nm_num; + char nm_name[PF_OSFP_LEN]; + + struct name_list nm_sublist; + int nm_sublist_num; +}; +struct name_list classes = LIST_HEAD_INITIALIZER(&classes); +int class_count; +int fingerprint_count; + +void add_fingerprint(int, int, struct pf_osfp_ioctl *); +struct name_entry *fingerprint_name_entry(struct name_list *, char *); +void pfctl_flush_my_fingerprints(struct name_list *); +char *get_field(char **, size_t *, int *); +int get_int(char **, size_t *, int *, int *, const char *, + int, int, const char *, int); +int get_str(char **, size_t *, char **, const char *, int, + const char *, int); +int get_tcpopts(const char *, int, const char *, + pf_tcpopts_t *, int *, int *, int *, int *, int *, + int *); +void import_fingerprint(struct pf_osfp_ioctl *); +#ifdef OSFP_DEBUG +const char *print_ioctl(struct pf_osfp_ioctl *); +#endif +void print_name_list(int, struct name_list *, const char *); +void sort_name_list(int, struct name_list *); +struct name_entry *lookup_name_list(struct name_list *, const char *); + +/* Load fingerprints from a file */ +int +pfctl_file_fingerprints(int dev, int opts, const char *fp_filename) +{ + FILE *in; + char *line; + size_t len; + int i, lineno = 0; + int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale, + wscale_mod, optcnt, ts0; + pf_tcpopts_t packed_tcpopts; + char *class, *version, *subtype, *desc, *tcpopts; + struct pf_osfp_ioctl fp; + + pfctl_flush_my_fingerprints(&classes); + + if ((in = pfctl_fopen(fp_filename, "r")) == NULL) { + /* PFLINUX: OpenBSD ships /etc/pf.os as part of base; the + * Linux port has no fingerprint packaging yet. A missing + * file behaves as "no fingerprints" instead of failing the + * whole -f load (ENOENT only; other errors still fatal). */ + if (errno == ENOENT) + return (0); + warn("%s", fp_filename); + return (1); + } + class = version = subtype = desc = tcpopts = NULL; + + if ((opts & PF_OPT_NOACTION) == 0) + pfctl_clear_fingerprints(dev, opts); + + while ((line = fgetln(in, &len)) != NULL) { + lineno++; + free(class); + free(version); + free(subtype); + free(desc); + free(tcpopts); + class = version = subtype = desc = tcpopts = NULL; + memset(&fp, 0, sizeof(fp)); + + /* Chop off comment */ + for (i = 0; i < len; i++) + if (line[i] == '#') { + len = i; + break; + } + /* Chop off whitespace */ + while (len > 0 && isspace((unsigned char)line[len - 1])) + len--; + while (len > 0 && isspace((unsigned char)line[0])) { + len--; + line++; + } + if (len == 0) + continue; + +#define T_DC 0x01 /* Allow don't care */ +#define T_MSS 0x02 /* Allow MSS multiple */ +#define T_MTU 0x04 /* Allow MTU multiple */ +#define T_MOD 0x08 /* Allow modulus */ + +#define GET_INT(v, mod, n, ty, mx) \ + get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno) +#define GET_STR(v, n, mn) \ + get_str(&line, &len, &v, n, mn, fp_filename, lineno) + + if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU| + T_MOD, 0xffff) || + GET_INT(ttl, NULL, "ttl", 0, 0xff) || + GET_INT(df, NULL, "don't fragment frag", 0, 1) || + GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC, + 8192) || + GET_STR(tcpopts, "TCP Options", 1) || + GET_STR(class, "OS class", 1) || + GET_STR(version, "OS version", 0) || + GET_STR(subtype, "OS subtype", 0) || + GET_STR(desc, "OS description", 2)) + continue; + if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts, + &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0)) + continue; + if (len != 0) { + fprintf(stderr, "%s:%d excess field\n", fp_filename, + lineno); + continue; + } + + fp.fp_ttl = ttl; + if (df) + fp.fp_flags |= PF_OSFP_DF; + switch (w_mod) { + case 0: + break; + case T_DC: + fp.fp_flags |= PF_OSFP_WSIZE_DC; + break; + case T_MSS: + fp.fp_flags |= PF_OSFP_WSIZE_MSS; + break; + case T_MTU: + fp.fp_flags |= PF_OSFP_WSIZE_MTU; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSIZE_MOD; + break; + } + fp.fp_wsize = window; + + switch (p_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_PSIZE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_PSIZE_MOD; + } + fp.fp_psize = psize; + + + switch (wscale_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_WSCALE_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_WSCALE_MOD; + } + fp.fp_wscale = wscale; + + switch (mss_mod) { + case T_DC: + fp.fp_flags |= PF_OSFP_MSS_DC; + break; + case T_MOD: + fp.fp_flags |= PF_OSFP_MSS_MOD; + break; + } + fp.fp_mss = mss; + + fp.fp_tcpopts = packed_tcpopts; + fp.fp_optcnt = optcnt; + if (ts0) + fp.fp_flags |= PF_OSFP_TS0; + + if (class[0] == '@') + fp.fp_os.fp_enflags |= PF_OSFP_GENERIC; + if (class[0] == '*') + fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL; + + if (class[0] == '@' || class[0] == '*') + strlcpy(fp.fp_os.fp_class_nm, class + 1, + sizeof(fp.fp_os.fp_class_nm)); + else + strlcpy(fp.fp_os.fp_class_nm, class, + sizeof(fp.fp_os.fp_class_nm)); + strlcpy(fp.fp_os.fp_version_nm, version, + sizeof(fp.fp_os.fp_version_nm)); + strlcpy(fp.fp_os.fp_subtype_nm, subtype, + sizeof(fp.fp_os.fp_subtype_nm)); + + add_fingerprint(dev, opts, &fp); + + fp.fp_flags |= (PF_OSFP_DF | PF_OSFP_INET6); + fp.fp_psize += sizeof(struct ip6_hdr) - sizeof(struct ip); + add_fingerprint(dev, opts, &fp); + } + + free(class); + free(version); + free(subtype); + free(desc); + free(tcpopts); + + fclose(in); + + if (opts & PF_OPT_VERBOSE2) + printf("Loaded %d passive OS fingerprints\n", + fingerprint_count); + return (0); +} + +/* flush the kernel's fingerprints */ +void +pfctl_clear_fingerprints(int dev, int opts) +{ + if (ioctl(dev, DIOCOSFPFLUSH) == -1) + pfctl_err(opts, 1, "DIOCOSFPFLUSH"); +} + +/* flush pfctl's view of the fingerprints */ +void +pfctl_flush_my_fingerprints(struct name_list *list) +{ + struct name_entry *nm; + + while ((nm = LIST_FIRST(list)) != NULL) { + LIST_REMOVE(nm, nm_entry); + pfctl_flush_my_fingerprints(&nm->nm_sublist); + free(nm); + } + fingerprint_count = 0; + class_count = 0; +} + +/* Fetch the active fingerprints from the kernel */ +int +pfctl_load_fingerprints(int dev, int opts) +{ + struct pf_osfp_ioctl io; + int i; + + pfctl_flush_my_fingerprints(&classes); + + for (i = 0; i >= 0; i++) { + memset(&io, 0, sizeof(io)); + io.fp_getnum = i; + if (ioctl(dev, DIOCOSFPGET, &io) == -1) { + if (errno == EBUSY) + break; + warn("DIOCOSFPGET"); + return (1); + } + import_fingerprint(&io); + } + return (0); +} + +/* List the fingerprints */ +void +pfctl_show_fingerprints(int opts) +{ + if (LIST_FIRST(&classes) != NULL) { + if (opts & PF_OPT_SHOWALL) { + pfctl_print_title("OS FINGERPRINTS:"); + printf("%u fingerprints loaded\n", fingerprint_count); + } else { + printf("Class\tVersion\tSubtype(subversion)\n"); + printf("-----\t-------\t-------------------\n"); + sort_name_list(opts, &classes); + print_name_list(opts, &classes, ""); + } + } +} + +/* Lookup a fingerprint */ +pf_osfp_t +pfctl_get_fingerprint(const char *name) +{ + struct name_entry *nm, *class_nm, *version_nm, *subtype_nm; + pf_osfp_t ret = PF_OSFP_NOMATCH; + int class, version, subtype; + int unp_class, unp_version, unp_subtype; + int wr_len, version_len, subtype_len; + char *ptr, *wr_name; + + if (strcasecmp(name, "unknown") == 0) + return (PF_OSFP_UNKNOWN); + + /* Try most likely no version and no subtype */ + if ((nm = lookup_name_list(&classes, name))) { + class = nm->nm_num; + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + goto found; + } else { + + /* Chop it up into class/version/subtype */ + + if ((wr_name = strdup(name)) == NULL) + err(1, "malloc"); + if ((ptr = strchr(wr_name, ' ')) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + *ptr++ = '\0'; + + /* The class is easy to find since it is delimited by a space */ + if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) { + free(wr_name); + return (PF_OSFP_NOMATCH); + } + class = class_nm->nm_num; + + /* Try no subtype */ + if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr))) + { + version = version_nm->nm_num; + subtype = PF_OSFP_ANY; + free(wr_name); + goto found; + } + + + /* + * There must be a version and a subtype. + * We'll do some fuzzy matching to pick up things like: + * Linux 2.2.14 (version=2.2 subtype=14) + * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE) + * Windows 2000 SP2 (version=2000 subtype=SP2) + */ +#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-') + wr_len = strlen(ptr); + LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) { + version_len = strlen(version_nm->nm_name); + if (wr_len < version_len + 2 || + !CONNECTOR(ptr[version_len])) + continue; + /* first part of the string must be version */ + if (strncasecmp(ptr, version_nm->nm_name, + version_len)) + continue; + + LIST_FOREACH(subtype_nm, &version_nm->nm_sublist, + nm_entry) { + subtype_len = strlen(subtype_nm->nm_name); + if (wr_len != version_len + subtype_len + 1) + continue; + + /* last part of the string must be subtype */ + if (strcasecmp(&ptr[version_len+1], + subtype_nm->nm_name) != 0) + continue; + + /* Found it!! */ + version = version_nm->nm_num; + subtype = subtype_nm->nm_num; + free(wr_name); + goto found; + } + } + + free(wr_name); + return (PF_OSFP_NOMATCH); + } + +found: + PF_OSFP_PACK(ret, class, version, subtype); + if (ret != PF_OSFP_NOMATCH) { + PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype); + if (class != unp_class) { + fprintf(stderr, "warning: fingerprint table overflowed " + "classes\n"); + return (PF_OSFP_NOMATCH); + } + if (version != unp_version) { + fprintf(stderr, "warning: fingerprint table overflowed " + "versions\n"); + return (PF_OSFP_NOMATCH); + } + if (subtype != unp_subtype) { + fprintf(stderr, "warning: fingerprint table overflowed " + "subtypes\n"); + return (PF_OSFP_NOMATCH); + } + } + if (ret == PF_OSFP_ANY) { + /* should never happen */ + fprintf(stderr, "warning: fingerprint packed to 'any'\n"); + return (PF_OSFP_NOMATCH); + } + + return (ret); +} + +/* Lookup a fingerprint name by ID */ +char * +pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len) +{ + int class, version, subtype; + struct name_list *list; + struct name_entry *nm; + + char *class_name, *version_name, *subtype_name; + class_name = version_name = subtype_name = NULL; + + if (fp == PF_OSFP_UNKNOWN) { + strlcpy(buf, "unknown", len); + return (buf); + } + if (fp == PF_OSFP_ANY) { + strlcpy(buf, "any", len); + return (buf); + } + + PF_OSFP_UNPACK(fp, class, version, subtype); + if (class >= (1 << _FP_CLASS_BITS) || + version >= (1 << _FP_VERSION_BITS) || + subtype >= (1 << _FP_SUBTYPE_BITS)) { + warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp); + strlcpy(buf, "nomatch", len); + return (buf); + } + + LIST_FOREACH(nm, &classes, nm_entry) { + if (nm->nm_num == class) { + class_name = nm->nm_name; + if (version == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == version) { + version_name = nm->nm_name; + if (subtype == PF_OSFP_ANY) + goto found; + list = &nm->nm_sublist; + LIST_FOREACH(nm, list, nm_entry) { + if (nm->nm_num == subtype) { + subtype_name = + nm->nm_name; + goto found; + } + } /* foreach subtype */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach version */ + strlcpy(buf, "nomatch", len); + return (buf); + } + } /* foreach class */ + + strlcpy(buf, "nomatch", len); + return (buf); + +found: + snprintf(buf, len, "%s", class_name); + if (version_name) { + strlcat(buf, " ", len); + strlcat(buf, version_name, len); + if (subtype_name) { + if (strchr(version_name, ' ')) + strlcat(buf, " ", len); + else if (strchr(version_name, '.') && + isdigit((unsigned char)*subtype_name)) + strlcat(buf, ".", len); + else + strlcat(buf, " ", len); + strlcat(buf, subtype_name, len); + } + } + return (buf); +} + +/* lookup a name in a list */ +struct name_entry * +lookup_name_list(struct name_list *list, const char *name) +{ + struct name_entry *nm; + LIST_FOREACH(nm, list, nm_entry) + if (strcasecmp(name, nm->nm_name) == 0) + return (nm); + + return (NULL); +} + + +void +add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp) +{ + struct pf_osfp_ioctl fptmp; + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + +/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */ +#define EXPAND(field) do { \ + int _dot = -1, _start = -1, _end = -1, _i = 0; \ + /* pick major version out of #.# */ \ + if (isdigit((unsigned char)fp->field[_i]) && \ + fp->field[_i+1] == '.') { \ + _dot = fp->field[_i] - '0'; \ + _i += 2; \ + } \ + if (isdigit((unsigned char)fp->field[_i])) \ + _start = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit((unsigned char)fp->field[_i])) \ + _start = (_start * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i++] != '-') \ + break; \ + if (isdigit((unsigned char)fp->field[_i]) && \ + fp->field[_i+1] == '.' && fp->field[_i] - '0' == _dot) \ + _i += 2; \ + else if (_dot != -1) \ + break; \ + if (isdigit((unsigned char)fp->field[_i])) \ + _end = fp->field[_i++] - '0'; \ + else \ + break; \ + if (isdigit((unsigned char)fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (isdigit((unsigned char)fp->field[_i])) \ + _end = (_end * 10) + fp->field[_i++] - '0'; \ + if (fp->field[_i] != '\0') \ + break; \ + memcpy(&fptmp, fp, sizeof(fptmp)); \ + for (;_start <= _end; _start++) { \ + memset(fptmp.field, 0, sizeof(fptmp.field)); \ + fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \ + if (_dot == -1) \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d", _start); \ + else \ + snprintf(fptmp.field, sizeof(fptmp.field), \ + "%d.%d", _dot, _start); \ + add_fingerprint(dev, opts, &fptmp); \ + } \ +} while(0) + + /* We allow "#-#" as a version or subtype and we'll expand it */ + EXPAND(fp_os.fp_version_nm); + EXPAND(fp_os.fp_subtype_nm); + + if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0) + errx(1, "fingerprint class \"nomatch\" is reserved"); + + version = PF_OSFP_ANY; + subtype = PF_OSFP_ANY; + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) + nm_class->nm_num = ++class_count; + class = nm_class->nm_num; + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) + nm_version->nm_num = ++nm_class->nm_sublist_num; + version = nm_version->nm_num; + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) + nm_subtype->nm_num = + ++nm_version->nm_sublist_num; + subtype = nm_subtype->nm_num; + } + } + + + DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype, + print_ioctl(fp)); + + PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype); + fingerprint_count++; + +#ifdef FAKE_PF_KERNEL + /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */ + if ((errno = pf_osfp_add(fp))) +#else + if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp) == -1) +#endif /* FAKE_PF_KERNEL */ + { + if (errno == EEXIST) { + warn("Duplicate signature for %s %s %s", + fp->fp_os.fp_class_nm, + fp->fp_os.fp_version_nm, + fp->fp_os.fp_subtype_nm); + + } else { + err(1, "DIOCOSFPADD"); + } + } +} + +/* import a fingerprint from the kernel */ +void +import_fingerprint(struct pf_osfp_ioctl *fp) +{ + struct name_entry *nm_class, *nm_version, *nm_subtype; + int class, version, subtype; + + PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype); + + nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm); + if (nm_class->nm_num == 0) { + nm_class->nm_num = class; + class_count = MAXIMUM(class_count, class); + } + + nm_version = fingerprint_name_entry(&nm_class->nm_sublist, + fp->fp_os.fp_version_nm); + if (nm_version) { + if (nm_version->nm_num == 0) { + nm_version->nm_num = version; + nm_class->nm_sublist_num = MAXIMUM(nm_class->nm_sublist_num, + version); + } + nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist, + fp->fp_os.fp_subtype_nm); + if (nm_subtype) { + if (nm_subtype->nm_num == 0) { + nm_subtype->nm_num = subtype; + nm_version->nm_sublist_num = + MAXIMUM(nm_version->nm_sublist_num, subtype); + } + } + } + + + fingerprint_count++; + DEBUG(fp, "import signature %d:%d:%d", class, version, subtype); +} + +/* Find an entry for a fingerprints class/version/subtype */ +struct name_entry * +fingerprint_name_entry(struct name_list *list, char *name) +{ + struct name_entry *nm_entry; + + if (name == NULL || strlen(name) == 0) + return (NULL); + + LIST_FOREACH(nm_entry, list, nm_entry) { + if (strcasecmp(nm_entry->nm_name, name) == 0) { + /* We'll move this to the front of the list later */ + LIST_REMOVE(nm_entry, nm_entry); + break; + } + } + if (nm_entry == NULL) { + nm_entry = calloc(1, sizeof(*nm_entry)); + if (nm_entry == NULL) + err(1, "calloc"); + LIST_INIT(&nm_entry->nm_sublist); + strlcpy(nm_entry->nm_name, name, sizeof(nm_entry->nm_name)); + } + LIST_INSERT_HEAD(list, nm_entry, nm_entry); + return (nm_entry); +} + + +void +print_name_list(int opts, struct name_list *nml, const char *prefix) +{ + char newprefix[32]; + struct name_entry *nm; + + LIST_FOREACH(nm, nml, nm_entry) { + snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix, + nm->nm_name); + printf("%s\n", newprefix); + print_name_list(opts, &nm->nm_sublist, newprefix); + } +} + +void +sort_name_list(int opts, struct name_list *nml) +{ + struct name_list new; + struct name_entry *nm, *nmsearch, *nmlast; + + /* yes yes, it's a very slow sort. so sue me */ + + LIST_INIT(&new); + + while ((nm = LIST_FIRST(nml)) != NULL) { + LIST_REMOVE(nm, nm_entry); + nmlast = NULL; + LIST_FOREACH(nmsearch, &new, nm_entry) { + if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) { + LIST_INSERT_BEFORE(nmsearch, nm, nm_entry); + break; + } + nmlast = nmsearch; + } + if (nmsearch == NULL) { + if (nmlast) + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + else + LIST_INSERT_HEAD(&new, nm, nm_entry); + } + + sort_name_list(opts, &nm->nm_sublist); + } + nmlast = NULL; + while ((nm = LIST_FIRST(&new)) != NULL) { + LIST_REMOVE(nm, nm_entry); + if (nmlast == NULL) + LIST_INSERT_HEAD(nml, nm, nm_entry); + else + LIST_INSERT_AFTER(nmlast, nm, nm_entry); + nmlast = nm; + } +} + +/* parse the next integer in a formatted config file line */ +int +get_int(char **line, size_t *len, int *var, int *mod, + const char *name, int flags, int max, const char *filename, int lineno) +{ + int fieldlen, i; + char *field; + long val = 0; + + if (mod) + *mod = 0; + *var = 0; + + field = get_field(line, len, &fieldlen); + if (field == NULL) + return (1); + if (fieldlen == 0) { + fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name); + return (1); + } + + i = 0; + if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*') + && fieldlen >= 1) { + switch (*field) { + case 'S': + if (mod && (flags & T_MSS)) + *mod = T_MSS; + if (fieldlen == 1) + return (0); + break; + case 'T': + if (mod && (flags & T_MTU)) + *mod = T_MTU; + if (fieldlen == 1) + return (0); + break; + case '*': + if (fieldlen != 1) { + fprintf(stderr, "%s:%d long '%c' %s\n", + filename, lineno, *field, name); + return (1); + } + if (mod && (flags & T_DC)) { + *mod = T_DC; + return (0); + } + case '%': + if (mod && (flags & T_MOD)) + *mod = T_MOD; + if (fieldlen == 1) { + fprintf(stderr, "%s:%d modulus %s must have a " + "value\n", filename, lineno, name); + return (1); + } + break; + } + if (mod == NULL || *mod == 0) { + fprintf(stderr, "%s:%d does not allow %c' %s\n", + filename, lineno, *field, name); + return (1); + } + i++; + } + + for (; i < fieldlen; i++) { + if (field[i] < '0' || field[i] > '9') { + fprintf(stderr, "%s:%d non-digit character in %s\n", + filename, lineno, name); + return (1); + } + val = val * 10 + field[i] - '0'; + if (val < 0) { + fprintf(stderr, "%s:%d %s overflowed\n", filename, + lineno, name); + return (1); + } + } + + if (val > max) { + fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno, + name, val, max); + return (1); + } + *var = (int)val; + + return (0); +} + +/* parse the next string in a formatted config file line */ +int +get_str(char **line, size_t *len, char **v, const char *name, int minlen, + const char *filename, int lineno) +{ + int fieldlen; + char *ptr; + + ptr = get_field(line, len, &fieldlen); + if (ptr == NULL) + return (1); + if (fieldlen < minlen) { + fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name); + return (1); + } + if ((*v = malloc(fieldlen + 1)) == NULL) { + perror("malloc()"); + return (1); + } + memcpy(*v, ptr, fieldlen); + (*v)[fieldlen] = '\0'; + + return (0); +} + +/* Parse out the TCP opts */ +int +get_tcpopts(const char *filename, int lineno, const char *tcpopts, + pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale, + int *wscale_mod, int *ts0) +{ + int i, opt; + + *packed = 0; + *optcnt = 0; + *wscale = 0; + *wscale_mod = T_DC; + *mss = 0; + *mss_mod = T_DC; + *ts0 = 0; + if (strcmp(tcpopts, ".") == 0) + return (0); + + for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) { + switch ((opt = toupper((unsigned char)tcpopts[i++]))) { + case 'N': /* FALLTHROUGH */ + case 'S': + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'N' ? PF_OSFP_TCPOPT_NOP : + PF_OSFP_TCPOPT_SACK); + break; + case 'W': /* FALLTHROUGH */ + case 'M': { + int *this_mod, *this; + + if (opt == 'W') { + this = wscale; + this_mod = wscale_mod; + } else { + this = mss; + this_mod = mss_mod; + } + *this = 0; + *this_mod = 0; + + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE : + PF_OSFP_TCPOPT_MSS); + if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' || + tcpopts[i + 1] == ',')) { + *this_mod = T_DC; + i++; + break; + } + + if (tcpopts[i] == '%') { + *this_mod = T_MOD; + i++; + } + do { + if (!isdigit((unsigned char)tcpopts[i])) { + fprintf(stderr, "%s:%d unknown " + "character '%c' in %c TCP opt\n", + filename, lineno, tcpopts[i], opt); + return (1); + } + *this = (*this * 10) + tcpopts[i++] - '0'; + } while(tcpopts[i] != ',' && tcpopts[i] != '\0'); + break; + } + case 'T': + if (tcpopts[i] == '0') { + *ts0 = 1; + i++; + } + *packed = (*packed << PF_OSFP_TCPOPT_BITS) | + PF_OSFP_TCPOPT_TS; + break; + } + (*optcnt) ++; + if (tcpopts[i] == '\0') + break; + if (tcpopts[i] != ',') { + fprintf(stderr, "%s:%d unknown option to %c TCP opt\n", + filename, lineno, opt); + return (1); + } + i++; + } + + return (0); +} + +/* rip the next field out of a formatted config file line */ +char * +get_field(char **line, size_t *len, int *fieldlen) +{ + char *ret, *ptr = *line; + size_t plen = *len; + + + while (plen && isspace((unsigned char)*ptr)) { + plen--; + ptr++; + } + ret = ptr; + *fieldlen = 0; + + for (; plen > 0 && *ptr != ':'; plen--, ptr++) + (*fieldlen)++; + if (plen) { + *line = ptr + 1; + *len = plen - 1; + } else { + *len = 0; + } + while (*fieldlen && isspace((unsigned char)ret[*fieldlen - 1])) + (*fieldlen)--; + return (ret); +} + + +#ifdef OSFP_DEBUG +const char * +print_ioctl(struct pf_osfp_ioctl *fp) +{ + static char buf[1024]; + char tmp[32]; + int i, opt; + + *buf = '\0'; + if (fp->fp_flags & PF_OSFP_WSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MSS) + strlcat(buf, "S", sizeof(buf)); + else if (fp->fp_flags & PF_OSFP_WSIZE_MTU) + strlcat(buf, "T", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl); + strlcat(buf, tmp, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_DF) + strlcat(buf, "1", sizeof(buf)); + else + strlcat(buf, "0", sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_flags & PF_OSFP_PSIZE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_PSIZE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize); + strlcat(buf, tmp, sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + if (fp->fp_optcnt == 0) + strlcat(buf, ".", sizeof(buf)); + for (i = fp->fp_optcnt - 1; i >= 0; i--) { + opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS); + opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1; + switch (opt) { + case PF_OSFP_TCPOPT_NOP: + strlcat(buf, "N", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_SACK: + strlcat(buf, "S", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_TS: + strlcat(buf, "T", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_TS0) + strlcat(buf, "0", sizeof(buf)); + break; + case PF_OSFP_TCPOPT_MSS: + strlcat(buf, "M", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_MSS_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_MSS_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss); + strlcat(buf, tmp, sizeof(buf)); + } + break; + case PF_OSFP_TCPOPT_WSCALE: + strlcat(buf, "W", sizeof(buf)); + if (fp->fp_flags & PF_OSFP_WSCALE_DC) + strlcat(buf, "*", sizeof(buf)); + else { + if (fp->fp_flags & PF_OSFP_WSCALE_MOD) + strlcat(buf, "%", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale); + strlcat(buf, tmp, sizeof(buf)); + } + break; + } + + if (i != 0) + strlcat(buf, ",", sizeof(buf)); + } + strlcat(buf, ":", sizeof(buf)); + + strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf)); + strlcat(buf, ":", sizeof(buf)); + + snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt, + (long long int)fp->fp_tcpopts); + strlcat(buf, tmp, sizeof(buf)); + + return (buf); +} +#endif diff --git a/tools/pfctl/pfctl_parser.c b/tools/pfctl/pfctl_parser.c new file mode 100644 index 0000000..c482084 --- /dev/null +++ b/tools/pfctl/pfctl_parser.c @@ -0,0 +1,2032 @@ +/* $OpenBSD: pfctl_parser.c,v 1.359 2026/03/20 09:37:23 sthen Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * Copyright (c) 2002 - 2013 Henning Brauer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SYSLOG_NAMES +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +void print_op (u_int8_t, const char *, const char *); +void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int); +void print_ugid (u_int8_t, id_t, id_t, const char *); +void print_flags (u_int8_t); +void print_fromto(struct pf_rule_addr *, pf_osfp_t, + struct pf_rule_addr *, sa_family_t, u_int8_t, int); + /* PFLINUX: af parameter is sa_family_t (unsigned short + * on Linux), see definition at line ~421 */ +void print_bwspec(const char *index, struct pf_queue_bwspec *); +void print_scspec(const char *, struct pf_queue_scspec *); +int ifa_skip_if(const char *filter, struct node_host *p); + +struct node_host *ifa_grouplookup(const char *, int); +struct node_host *host_if(const char *, int); +struct node_host *host_ip(const char *, int); +struct node_host *host_dns(const char *, int, int); + +const char *tcpflags = "FSRPAUEW"; + +static const struct icmptypeent icmp_type[] = { + { "echoreq", ICMP_ECHO }, + { "echorep", ICMP_ECHOREPLY }, + { "unreach", ICMP_UNREACH }, + { "squench", ICMP_SOURCEQUENCH }, + { "redir", ICMP_REDIRECT }, + { "althost", ICMP_ALTHOSTADDR }, + { "routeradv", ICMP_ROUTERADVERT }, + { "routersol", ICMP_ROUTERSOLICIT }, + { "timex", ICMP_TIMXCEED }, + { "paramprob", ICMP_PARAMPROB }, + { "timereq", ICMP_TSTAMP }, + { "timerep", ICMP_TSTAMPREPLY }, + { "inforeq", ICMP_IREQ }, + { "inforep", ICMP_IREQREPLY }, + { "maskreq", ICMP_MASKREQ }, + { "maskrep", ICMP_MASKREPLY }, + { "trace", ICMP_TRACEROUTE }, + { "dataconv", ICMP_DATACONVERR }, + { "mobredir", ICMP_MOBILE_REDIRECT }, + { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, + { "ipv6-here", ICMP_IPV6_IAMHERE }, + { "mobregreq", ICMP_MOBILE_REGREQUEST }, + { "mobregrep", ICMP_MOBILE_REGREPLY }, + { "skip", ICMP_SKIP }, + { "photuris", ICMP_PHOTURIS } +}; + +static const struct icmptypeent icmp6_type[] = { + { "unreach", ICMP6_DST_UNREACH }, + { "toobig", ICMP6_PACKET_TOO_BIG }, + { "timex", ICMP6_TIME_EXCEEDED }, + { "paramprob", ICMP6_PARAM_PROB }, + { "echoreq", ICMP6_ECHO_REQUEST }, + { "echorep", ICMP6_ECHO_REPLY }, + { "groupqry", ICMP6_MEMBERSHIP_QUERY }, + { "listqry", MLD_LISTENER_QUERY }, + { "grouprep", ICMP6_MEMBERSHIP_REPORT }, + { "listenrep", MLD_LISTENER_REPORT }, + { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, + { "listendone", MLD_LISTENER_DONE }, + { "routersol", ND_ROUTER_SOLICIT }, + { "routeradv", ND_ROUTER_ADVERT }, + { "neighbrsol", ND_NEIGHBOR_SOLICIT }, + { "neighbradv", ND_NEIGHBOR_ADVERT }, + { "redir", ND_REDIRECT }, + { "routrrenum", ICMP6_ROUTER_RENUMBERING }, + { "wrureq", ICMP6_WRUREQUEST }, + { "wrurep", ICMP6_WRUREPLY }, + { "fqdnreq", ICMP6_FQDN_QUERY }, + { "fqdnrep", ICMP6_FQDN_REPLY }, + { "niqry", ICMP6_NI_QUERY }, + { "nirep", ICMP6_NI_REPLY }, + { "mtraceresp", MLD_MTRACE_RESP }, + { "mtrace", MLD_MTRACE }, + { "listenrepv2", MLDV2_LISTENER_REPORT }, +}; + +static const struct icmpcodeent icmp_code[] = { + { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, + { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, + { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, + { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, + { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, + { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, + { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, + { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, + { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, + { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, + { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, + { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, + { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, + { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, + { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, + { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, + { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, + { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, + { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, + { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, + { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, + { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, + { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, + { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } +}; + +static const struct icmpcodeent icmp6_code[] = { + { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, + { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, + { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, + { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, + { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, + { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, + { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, + { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, + { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, + { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, + { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } +}; + +const struct pf_timeout pf_timeouts[] = { + { "tcp.first", PFTM_TCP_FIRST_PACKET }, + { "tcp.opening", PFTM_TCP_OPENING }, + { "tcp.established", PFTM_TCP_ESTABLISHED }, + { "tcp.closing", PFTM_TCP_CLOSING }, + { "tcp.finwait", PFTM_TCP_FIN_WAIT }, + { "tcp.closed", PFTM_TCP_CLOSED }, + { "tcp.tsdiff", PFTM_TS_DIFF }, + { "udp.first", PFTM_UDP_FIRST_PACKET }, + { "udp.single", PFTM_UDP_SINGLE }, + { "udp.multiple", PFTM_UDP_MULTIPLE }, + { "icmp.first", PFTM_ICMP_FIRST_PACKET }, + { "icmp.error", PFTM_ICMP_ERROR_REPLY }, + { "other.first", PFTM_OTHER_FIRST_PACKET }, + { "other.single", PFTM_OTHER_SINGLE }, + { "other.multiple", PFTM_OTHER_MULTIPLE }, + { "frag", PFTM_FRAG }, + { "interval", PFTM_INTERVAL }, + { "adaptive.start", PFTM_ADAPTIVE_START }, + { "adaptive.end", PFTM_ADAPTIVE_END }, + { "src.track", PFTM_SRC_NODE }, + { NULL, 0 } +}; + +enum { PF_POOL_ROUTE, PF_POOL_NAT, PF_POOL_RDR }; + +void +copy_satopfaddr(struct pf_addr *pfa, struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET6) + pfa->v6 = ((struct sockaddr_in6 *)sa)->sin6_addr; + else if (sa->sa_family == AF_INET) + pfa->v4 = ((struct sockaddr_in *)sa)->sin_addr; + else + warnx("unhandled af %d", sa->sa_family); +} + +const struct icmptypeent * +geticmptypebynumber(u_int8_t type, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i = 0; i < nitems(icmp_type); i++) { + if (type == icmp_type[i].type) + return (&icmp_type[i]); + } + } else { + for (i = 0; i < nitems(icmp6_type); i++) { + if (type == icmp6_type[i].type) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmptypeent * +geticmptypebyname(char *w, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i = 0; i < nitems(icmp_type); i++) { + if (!strcmp(w, icmp_type[i].name)) + return (&icmp_type[i]); + } + } else { + for (i = 0; i < nitems(icmp6_type); i++) { + if (!strcmp(w, icmp6_type[i].name)) + return (&icmp6_type[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i = 0; i < nitems(icmp_code); i++) { + if (type == icmp_code[i].type && + code == icmp_code[i].code) + return (&icmp_code[i]); + } + } else { + for (i = 0; i < nitems(icmp6_code); i++) { + if (type == icmp6_code[i].type && + code == icmp6_code[i].code) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +const struct icmpcodeent * +geticmpcodebyname(u_long type, char *w, sa_family_t af) +{ + size_t i; + + if (af != AF_INET6) { + for (i = 0; i < nitems(icmp_code); i++) { + if (type == icmp_code[i].type && + !strcmp(w, icmp_code[i].name)) + return (&icmp_code[i]); + } + } else { + for (i = 0; i < nitems(icmp6_code); i++) { + if (type == icmp6_code[i].type && + !strcmp(w, icmp6_code[i].name)) + return (&icmp6_code[i]); + } + } + return (NULL); +} + +/* + * Decode a symbolic name to a numeric value. + * From syslogd. + */ +int +string_to_loglevel(const char *name) +{ + CODE *c; + char *p, buf[40]; + + if (isdigit((unsigned char)*name)) { + const char *errstr; + int val; + + val = strtonum(name, 0, LOG_DEBUG, &errstr); + if (errstr) + return -1; + return val; + } + + for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) { + if (isupper((unsigned char)*name)) + *p = tolower((unsigned char)*name); + else + *p = *name; + } + *p = '\0'; + for (c = prioritynames; c->c_name; c++) + if (!strcmp(buf, c->c_name) && c->c_val != INTERNAL_NOPRI) + return (c->c_val); + + return (-1); +} + +const char * +loglevel_to_string(int level) +{ + CODE *c; + + for (c = prioritynames; c->c_name; c++) + if (c->c_val == level) + return (c->c_name); + + return ("unknown"); +} + +void +print_op(u_int8_t op, const char *a1, const char *a2) +{ + if (op == PF_OP_IRG) + printf(" %s >< %s", a1, a2); + else if (op == PF_OP_XRG) + printf(" %s <> %s", a1, a2); + else if (op == PF_OP_EQ) + printf(" = %s", a1); + else if (op == PF_OP_NE) + printf(" != %s", a1); + else if (op == PF_OP_LT) + printf(" < %s", a1); + else if (op == PF_OP_LE) + printf(" <= %s", a1); + else if (op == PF_OP_GT) + printf(" > %s", a1); + else if (op == PF_OP_GE) + printf(" >= %s", a1); + else if (op == PF_OP_RRG) + printf(" %s:%s", a1, a2); +} + +void +print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int opts) +{ + char a1[6], a2[6]; + struct servent *s = NULL; + + if (opts & PF_OPT_PORTNAMES) + s = getservbyport(p1, proto); + p1 = ntohs(p1); + p2 = ntohs(p2); + snprintf(a1, sizeof(a1), "%u", p1); + snprintf(a2, sizeof(a2), "%u", p2); + printf(" port"); + if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, s->s_name, a2); + else + print_op(op, a1, a2); +} + +void +print_ugid(u_int8_t op, id_t i1, id_t i2, const char *t) +{ + char a1[11], a2[11]; + + snprintf(a1, sizeof(a1), "%u", i1); + snprintf(a2, sizeof(a2), "%u", i2); + printf(" %s", t); + if (i1 == -1 && (op == PF_OP_EQ || op == PF_OP_NE)) + print_op(op, "unknown", a2); + else + print_op(op, a1, a2); +} + +void +print_flags(u_int8_t f) +{ + int i; + + for (i = 0; tcpflags[i]; ++i) + if (f & (1 << i)) + printf("%c", tcpflags[i]); +} + +void +print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, + sa_family_t af, u_int8_t proto, int opts) +{ + char buf[PF_OSFP_LEN*3]; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + if (src->addr.type == PF_ADDR_ADDRMASK && + dst->addr.type == PF_ADDR_ADDRMASK && + PF_AZERO(&src->addr.v.a.addr, AF_INET6) && + PF_AZERO(&src->addr.v.a.mask, AF_INET6) && + PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && + PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && + !src->neg && !dst->neg && + !src->port_op && !dst->port_op && + osfp == PF_OSFP_ANY) + printf(" all"); + else { + printf(" from "); + if (src->neg) + printf("! "); + print_addr(&src->addr, af, verbose); + if (src->port_op) + print_port(src->port_op, src->port[0], + src->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp", opts); + if (osfp != PF_OSFP_ANY) + printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, + sizeof(buf))); + + printf(" to "); + if (dst->neg) + printf("! "); + print_addr(&dst->addr, af, verbose); + if (dst->port_op) + print_port(dst->port_op, dst->port[0], + dst->port[1], + proto == IPPROTO_TCP ? "tcp" : "udp", opts); + } +} + +void +print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2, + sa_family_t af, int id, int verbose) +{ + if (pool->ifname[0]) { + if (!PF_AZERO(&pool->addr.v.a.addr, af)) { + print_addr(&pool->addr, af, verbose); + printf("@"); + } + printf("%s", pool->ifname); + } else + print_addr(&pool->addr, af, verbose); + switch (id) { + case PF_POOL_NAT: + if ((p1 != PF_NAT_PROXY_PORT_LOW || + p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { + if (p1 == p2) + printf(" port %u", p1); + else + printf(" port %u:%u", p1, p2); + } + break; + case PF_POOL_RDR: + if (p1) { + printf(" port %u", p1); + if (p2 && (p2 != p1)) + printf(":%u", p2); + } + break; + default: + break; + } + switch (pool->opts & PF_POOL_TYPEMASK) { + case PF_POOL_NONE: + break; + case PF_POOL_BITMASK: + printf(" bitmask"); + break; + case PF_POOL_RANDOM: + printf(" random"); + break; + case PF_POOL_SRCHASH: + printf(" source-hash 0x%08x%08x%08x%08x", + pool->key.key32[0], pool->key.key32[1], + pool->key.key32[2], pool->key.key32[3]); + break; + case PF_POOL_ROUNDROBIN: + printf(" round-robin"); + break; + case PF_POOL_LEASTSTATES: + printf(" least-states"); + break; + } + if (pool->opts & PF_POOL_STICKYADDR) + printf(" sticky-address"); + if (id == PF_POOL_NAT && p1 == 0 && p2 == 0) + printf(" static-port"); +} + +const char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; +const char *pf_lcounters[LCNT_MAX+1] = LCNT_NAMES; +const char *pf_fcounters[FCNT_MAX+1] = FCNT_NAMES; +const char *pf_scounters[SCNT_MAX+1] = FCNT_NAMES; +const char *pf_ncounters[NCNT_MAX+1] = FCNT_NAMES; + +void +print_status(struct pf_status *s, struct pfctl_watermarks *synflwats, int opts) +{ + char statline[80], *running, *debug; + time_t runtime = 0; + struct timespec uptime; + int i; + char buf[PF_MD5_DIGEST_LENGTH * 2 + 1]; + static const char hex[] = "0123456789abcdef"; + + if (!clock_gettime(CLOCK_BOOTTIME, &uptime)) + runtime = uptime.tv_sec - s->since; + running = s->running ? "Enabled" : "Disabled"; + + if (runtime) { + unsigned int sec, min, hrs; + time_t day = runtime; + + sec = day % 60; + day /= 60; + min = day % 60; + day /= 60; + hrs = day % 24; + day /= 24; + snprintf(statline, sizeof(statline), + "Status: %s for %lld days %.2u:%.2u:%.2u", + running, (long long)day, hrs, min, sec); + } else + snprintf(statline, sizeof(statline), "Status: %s", running); + printf("%-44s", statline); + if (asprintf(&debug, "Debug: %s", loglevel_to_string(s->debug)) != -1) { + printf("%15s\n\n", debug); + free(debug); + } + + if (opts & PF_OPT_VERBOSE) { + printf("Hostid: 0x%08x\n", ntohl(s->hostid)); + + for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) { + buf[i + i] = hex[s->pf_chksum[i] >> 4]; + buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f]; + } + buf[i + i] = '\0'; + printf("Checksum: 0x%s\n\n", buf); + } + + if (s->ifname[0] != 0) { + printf("Interface Stats for %-16s %5s %16s\n", + s->ifname, "IPv4", "IPv6"); + printf(" %-25s %14llu %16llu\n", "Bytes In", + (unsigned long long)s->bcounters[0][0], + (unsigned long long)s->bcounters[1][0]); + printf(" %-25s %14llu %16llu\n", "Bytes Out", + (unsigned long long)s->bcounters[0][1], + (unsigned long long)s->bcounters[1][1]); + printf(" Packets In\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][0][PF_PASS], + (unsigned long long)s->pcounters[1][0][PF_PASS]); + printf(" %-23s %14llu %16llu\n", "Blocked", + (unsigned long long)s->pcounters[0][0][PF_DROP], + (unsigned long long)s->pcounters[1][0][PF_DROP]); + printf(" Packets Out\n"); + printf(" %-23s %14llu %16llu\n", "Passed", + (unsigned long long)s->pcounters[0][1][PF_PASS], + (unsigned long long)s->pcounters[1][1][PF_PASS]); + printf(" %-23s %14llu %16llu\n\n", "Blocked", + (unsigned long long)s->pcounters[0][1][PF_DROP], + (unsigned long long)s->pcounters[1][1][PF_DROP]); + } + printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); + printf(" %-25s %14u %14s\n", "current entries", s->states, ""); + printf(" %-25s %14u %14s\n", "half-open tcp", s->states_halfopen, ""); + for (i = 0; i < FCNT_MAX; i++) { + printf(" %-25s %14llu ", pf_fcounters[i], + (unsigned long long)s->fcounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->fcounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + if (opts & PF_OPT_VERBOSE) { + printf("Source Tracking Table\n"); + printf(" %-25s %14u %14s\n", "current entries", + s->src_nodes, ""); + for (i = 0; i < SCNT_MAX; i++) { + printf(" %-25s %14lld ", pf_scounters[i], + s->scounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->scounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + } + if (opts & PF_OPT_VERBOSE) { + printf("Fragments\n"); + printf(" %-25s %14u %14s\n", "current entries", + s->fragments, ""); + for (i = 0; i < NCNT_MAX; i++) { + printf(" %-25s %14lld ", pf_ncounters[i], + s->ncounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->ncounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + } + printf("Counters\n"); + for (i = 0; i < PFRES_MAX; i++) { + printf(" %-25s %14llu ", pf_reasons[i], + (unsigned long long)s->counters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->counters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + if (opts & PF_OPT_VERBOSE) { + printf("Limit Counters\n"); + for (i = 0; i < LCNT_MAX; i++) { + printf(" %-25s %14lld ", pf_lcounters[i], + s->lcounters[i]); + if (runtime > 0) + printf("%14.1f/s\n", + (double)s->lcounters[i] / (double)runtime); + else + printf("%14s\n", ""); + } + } + if (opts & PF_OPT_VERBOSE) { + printf("Adaptive Syncookies Watermarks\n"); + printf(" %-25s %14d states\n", "start", synflwats->hi); + printf(" %-25s %14d states\n", "end", synflwats->lo); + } +} + +void +print_src_node(struct pf_src_node *sn, int opts) +{ + struct pf_addr_wrap aw; + int min, sec; + + memset(&aw, 0, sizeof(aw)); + if (sn->af == AF_INET) + aw.v.a.mask.addr32[0] = 0xffffffff; + else + memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); + + aw.v.a.addr = sn->addr; + print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); + + if (!PF_AZERO(&sn->raddr, sn->af)) { + if (sn->type == PF_SN_NAT) + printf(" nat-to "); + else if (sn->type == PF_SN_RDR) + printf(" rdr-to "); + else if (sn->type == PF_SN_ROUTE) + printf(" route-to "); + else + printf(" ??? (%u) ", sn->type); + aw.v.a.addr = sn->raddr; + print_addr(&aw, sn->naf ? sn->naf : sn->af, + opts & PF_OPT_VERBOSE2); + } + + printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states, + sn->conn, sn->conn_rate.count / 1000, + (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds); + if (opts & PF_OPT_VERBOSE) { + sec = sn->creation % 60; + sn->creation /= 60; + min = sn->creation % 60; + sn->creation /= 60; + printf(" age %.2u:%.2u:%.2u", sn->creation, min, sec); + if (sn->states == 0) { + sec = sn->expire % 60; + sn->expire /= 60; + min = sn->expire % 60; + sn->expire /= 60; + printf(", expires in %.2u:%.2u:%.2u", + sn->expire, min, sec); + } + printf(", %llu pkts, %llu bytes", + sn->packets[0] + sn->packets[1], + sn->bytes[0] + sn->bytes[1]); + if (sn->rule.nr != -1) + printf(", rule %u", sn->rule.nr); + printf("\n"); + } +} + +void +print_statelim(const struct pfioc_statelim *ioc) +{ + printf("state limiter \"%s\" id %u limit %u", + ioc->name, ioc->id, ioc->limit); + if (ioc->rate.limit != 0) + printf(" rate %u/%u", ioc->rate.limit, ioc->rate.seconds); + + printf("\n"); +} + +void +print_sourcelim(const struct pfioc_sourcelim *ioc) +{ + printf("source limiter \"%s\" id %u entries %u limit %u", + ioc->name, ioc->id, ioc->entries, ioc->limit); + if (ioc->rate.limit != 0) + printf(" rate %u/%u", ioc->rate.limit, ioc->rate.seconds); + if (ioc->overload_tblname[0] != '\0') { + printf(" table <%s> above %u", + ioc->overload_tblname, ioc->overload_hwm); + if (ioc->overload_lwm) + printf(" below %u", ioc->overload_lwm); + } + if (ioc->inet_prefix < 32) + printf(" inet mask %u", ioc->inet_prefix); + if (ioc->inet6_prefix < 128) + printf(" inet6 mask %u", ioc->inet6_prefix); + + printf("\n"); +} + +void +print_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call, + int opts) +{ + static const char *actiontypes[] = { "pass", "block", "scrub", + "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr", + "", "", "match"}; + static const char *anchortypes[] = { "anchor", "anchor", "anchor", + "anchor", "nat-anchor", "nat-anchor", "binat-anchor", + "binat-anchor", "rdr-anchor", "rdr-anchor" }; + int i, ropts; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + char *p; + + if ((r->rule_flag & PFRULE_EXPIRED) && (!verbose)) + return; + + if (verbose) + printf("@%d ", r->nr); + + if (anchor_call[0]) { + if (r->action >= nitems(anchortypes)) { + printf("anchor(%d)", r->action); + } else { + p = strrchr(anchor_call, '/'); + if (p ? p[1] == '_' : anchor_call[0] == '_') + printf("%s", anchortypes[r->action]); + else + printf("%s \"%s\"", anchortypes[r->action], + anchor_call); + } + } else { + if (r->action >= nitems(actiontypes)) + printf("action(%d)", r->action); + else + printf("%s", actiontypes[r->action]); + } + if (r->action == PF_DROP) { + if (r->rule_flag & PFRULE_RETURN) + printf(" return"); + else if (r->rule_flag & PFRULE_RETURNRST) { + if (!r->return_ttl) + printf(" return-rst"); + else + printf(" return-rst(ttl %d)", r->return_ttl); + } else if (r->rule_flag & PFRULE_RETURNICMP) { + const struct icmpcodeent *ic, *ic6; + + ic = geticmpcodebynumber(r->return_icmp >> 8, + r->return_icmp & 255, AF_INET); + ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, + r->return_icmp6 & 255, AF_INET6); + + switch (r->af) { + case AF_INET: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u)", r->return_icmp & 255); + else + printf("(%s)", ic->name); + break; + case AF_INET6: + printf(" return-icmp6"); + if (ic6 == NULL) + printf("(%u)", r->return_icmp6 & 255); + else + printf("(%s)", ic6->name); + break; + default: + printf(" return-icmp"); + if (ic == NULL) + printf("(%u, ", r->return_icmp & 255); + else + printf("(%s, ", ic->name); + if (ic6 == NULL) + printf("%u)", r->return_icmp6 & 255); + else + printf("%s)", ic6->name); + break; + } + } else + printf(" drop"); + } + if (r->direction == PF_IN) + printf(" in"); + else if (r->direction == PF_OUT) + printf(" out"); + if (r->log) { + printf(" log"); + if (r->log & ~PF_LOG || r->logif) { + int count = 0; + + printf(" ("); + if (r->log & PF_LOG_ALL) + printf("%sall", count++ ? ", " : ""); + if (r->log & PF_LOG_MATCHES) + printf("%smatches", count++ ? ", " : ""); + if (r->log & PF_LOG_USER) + printf("%suser", count++ ? ", " : ""); + if (r->logif) + printf("%sto pflog%u", count++ ? ", " : "", + r->logif); + printf(")"); + } + } + if (r->quick) + printf(" quick"); + if (r->ifname[0]) { + if (r->ifnot) + printf(" on ! %s", r->ifname); + else + printf(" on %s", r->ifname); + } + if (r->onrdomain >= 0) { + if (r->ifnot) + printf(" on ! rdomain %d", r->onrdomain); + else + printf(" on rdomain %d", r->onrdomain); + } + if (r->af) { + if (r->af == AF_INET) + printf(" inet"); + else + printf(" inet6"); + } + if (r->proto) { + struct protoent *p; + + if ((p = getprotobynumber(r->proto)) != NULL) + printf(" proto %s", p->p_name); + else + printf(" proto %u", r->proto); + } + print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, + pf->opts | opts); + if (r->rcv_ifname[0]) + printf(" %sreceived-on %s", r->rcvifnot ? "!" : "", + r->rcv_ifname); + if (r->uid.op) + print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user"); + if (r->gid.op) + print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group"); + if (r->flags || r->flagset) { + printf(" flags "); + print_flags(r->flags); + printf("/"); + print_flags(r->flagset); + } else if ((r->action == PF_PASS || r->action == PF_MATCH) && + (!r->proto || r->proto == IPPROTO_TCP) && + !(r->rule_flag & PFRULE_FRAGMENT) && + !anchor_call[0] && r->keep_state) + printf(" flags any"); + if (r->type) { + const struct icmptypeent *it; + + it = geticmptypebynumber(r->type-1, r->af); + if (r->af != AF_INET6) + printf(" icmp-type"); + else + printf(" icmp6-type"); + if (it != NULL) + printf(" %s", it->name); + else + printf(" %u", r->type-1); + if (r->code) { + const struct icmpcodeent *ic; + + ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); + if (ic != NULL) + printf(" code %s", ic->name); + else + printf(" code %u", r->code-1); + } + } + if (r->tos) + printf(" tos 0x%2.2x", r->tos); + if (r->prio) + printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio); + if (r->pktrate.limit) + printf(" max-pkt-rate %u/%u", r->pktrate.limit, + r->pktrate.seconds); + + if (r->scrub_flags & PFSTATE_SETMASK || r->qname[0] || + r->rule_flag & PFRULE_SETDELAY) { + char *comma = ""; + printf(" set ("); + if (r->scrub_flags & PFSTATE_SETPRIO) { + if (r->set_prio[0] == r->set_prio[1]) + printf("%sprio %u", comma, r->set_prio[0]); + else + printf("%sprio(%u, %u)", comma, r->set_prio[0], + r->set_prio[1]); + comma = ", "; + } + if (r->qname[0]) { + if (r->pqname[0]) + printf("%squeue(%s, %s)", comma, r->qname, + r->pqname); + else + printf("%squeue %s", comma, r->qname); + comma = ", "; + } + if (r->scrub_flags & PFSTATE_SETTOS) { + printf("%stos 0x%2.2x", comma, r->set_tos); + comma = ", "; + } + if (r->rule_flag & PFRULE_SETDELAY) { + printf("%sdelay %u", comma, r->delay); + comma = ", "; + } + printf(")"); + } + + ropts = 0; + if (r->max_states || r->max_src_nodes || r->max_src_states) + ropts = 1; + if (r->rule_flag & PFRULE_NOSYNC) + ropts = 1; + if (r->rule_flag & PFRULE_SRCTRACK) + ropts = 1; + if (r->rule_flag & PFRULE_IFBOUND) + ropts = 1; + if (r->rule_flag & PFRULE_STATESLOPPY) + ropts = 1; + if (r->rule_flag & PFRULE_PFLOW) + ropts = 1; + for (i = 0; !ropts && i < PFTM_MAX; ++i) + if (r->timeout[i]) + ropts = 1; + + if (!r->keep_state && r->action == PF_PASS && !anchor_call[0]) + printf(" no state"); + else if (r->keep_state == PF_STATE_NORMAL && ropts) + printf(" keep state"); + else if (r->keep_state == PF_STATE_MODULATE) + printf(" modulate state"); + else if (r->keep_state == PF_STATE_SYNPROXY) + printf(" synproxy state"); + if (r->prob) { + char buf[20]; + + snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0)); + for (i = strlen(buf)-1; i > 0; i--) { + if (buf[i] == '0') + buf[i] = '\0'; + else { + if (buf[i] == '.') + buf[i] = '\0'; + break; + } + } + printf(" probability %s%%", buf); + } + if (r->statelim.id != PF_STATELIM_ID_NONE) { + const char *nm = pfctl_statelim_id2name(pf, r->statelim.id); + printf(" state limiter"); + if (nm[0] != '\0') + printf(" \"%s\"", nm); + else + printf(" id %u", r->statelim.id); + printf(" (%s)", + (r->statelim.limiter_action == PF_LIMITER_BLOCK) ? + "block" : "no-match"); + } + if (r->sourcelim.id != PF_SOURCELIM_ID_NONE) { + const char *nm = pfctl_sourcelim_id2name(pf, r->sourcelim.id); + printf(" source limiter"); + if (nm[0] != '\0') + printf(" \"%s\"", nm); + else + printf(" id %u", r->sourcelim.id); + printf(" (%s)", + (r->sourcelim.limiter_action == PF_LIMITER_BLOCK) ? + "block" : "no-match"); + } + if (ropts) { + printf(" ("); + if (r->max_states) { + printf("max %u", r->max_states); + ropts = 0; + } + if (r->rule_flag & PFRULE_NOSYNC) { + if (!ropts) + printf(", "); + printf("no-sync"); + ropts = 0; + } + if (r->rule_flag & PFRULE_SRCTRACK) { + if (!ropts) + printf(", "); + printf("source-track"); + if (r->rule_flag & PFRULE_RULESRCTRACK) + printf(" rule"); + else + printf(" global"); + ropts = 0; + } + if (r->max_src_states) { + if (!ropts) + printf(", "); + printf("max-src-states %u", r->max_src_states); + ropts = 0; + } + if (r->max_src_conn) { + if (!ropts) + printf(", "); + printf("max-src-conn %u", r->max_src_conn); + ropts = 0; + } + if (r->max_src_conn_rate.limit) { + if (!ropts) + printf(", "); + printf("max-src-conn-rate %u/%u", + r->max_src_conn_rate.limit, + r->max_src_conn_rate.seconds); + ropts = 0; + } + if (r->max_src_nodes) { + if (!ropts) + printf(", "); + printf("max-src-nodes %u", r->max_src_nodes); + ropts = 0; + } + if (r->overload_tblname[0]) { + if (!ropts) + printf(", "); + printf("overload <%s>", r->overload_tblname); + if (r->flush) + printf(" flush"); + if (r->flush & PF_FLUSH_GLOBAL) + printf(" global"); + } + if (r->rule_flag & PFRULE_IFBOUND) { + if (!ropts) + printf(", "); + printf("if-bound"); + ropts = 0; + } + if (r->rule_flag & PFRULE_STATESLOPPY) { + if (!ropts) + printf(", "); + printf("sloppy"); + ropts = 0; + } + if (r->rule_flag & PFRULE_PFLOW) { + if (!ropts) + printf(", "); + printf("pflow"); + ropts = 0; + } + for (i = 0; i < PFTM_MAX; ++i) + if (r->timeout[i]) { + int j; + + if (!ropts) + printf(", "); + ropts = 0; + for (j = 0; pf_timeouts[j].name != NULL; + ++j) + if (pf_timeouts[j].timeout == i) + break; + printf("%s %u", pf_timeouts[j].name == NULL ? + "inv.timeout" : pf_timeouts[j].name, + r->timeout[i]); + } + printf(")"); + } + + if (r->rule_flag & PFRULE_FRAGMENT) + printf(" fragment"); + + if (r->scrub_flags & PFSTATE_SCRUBMASK || r->min_ttl || r->max_mss) { + printf(" scrub ("); + ropts = 1; + if (r->scrub_flags & PFSTATE_NODF) { + printf("no-df"); + ropts = 0; + } + if (r->scrub_flags & PFSTATE_RANDOMID) { + if (!ropts) + printf(" "); + printf("random-id"); + ropts = 0; + } + if (r->min_ttl) { + if (!ropts) + printf(" "); + printf("min-ttl %d", r->min_ttl); + ropts = 0; + } + if (r->scrub_flags & PFSTATE_SCRUB_TCP) { + if (!ropts) + printf(" "); + printf("reassemble tcp"); + ropts = 0; + } + if (r->max_mss) { + if (!ropts) + printf(" "); + printf("max-mss %d", r->max_mss); + ropts = 0; + } + printf(")"); + } + + if (r->allow_opts) + printf(" allow-opts"); + if (r->label[0]) + printf(" label \"%s\"", r->label); + if (r->rule_flag & PFRULE_ONCE) + printf(" once"); + if (r->tagname[0]) + printf(" tag %s", r->tagname); + if (r->match_tagname[0]) { + if (r->match_tag_not) + printf(" !"); + printf(" tagged %s", r->match_tagname); + } + if (r->rtableid != -1) + printf(" rtable %u", r->rtableid); + switch (r->divert.type) { + case PF_DIVERT_NONE: + break; + case PF_DIVERT_TO: { + printf(" divert-to "); + print_addr_str(r->af, &r->divert.addr); + printf(" port %u", ntohs(r->divert.port)); + break; + } + case PF_DIVERT_REPLY: + printf(" divert-reply"); + break; + case PF_DIVERT_PACKET: + printf(" divert-packet port %u", ntohs(r->divert.port)); + break; + default: + printf(" divert ???"); + break; + } + + if (!anchor_call[0] && r->nat.addr.type != PF_ADDR_NONE && + r->rule_flag & PFRULE_AFTO) { + printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6"); + print_pool(&r->nat, r->nat.proxy_port[0], + r->nat.proxy_port[1], r->naf ? r->naf : r->af, + PF_POOL_NAT, verbose); + if (r->rdr.addr.type != PF_ADDR_NONE) { + printf(" to "); + print_pool(&r->rdr, r->rdr.proxy_port[0], + r->rdr.proxy_port[1], r->naf ? r->naf : r->af, + PF_POOL_RDR, verbose); + } + } else { + if (!anchor_call[0] && r->nat.addr.type != PF_ADDR_NONE) { + printf (" nat-to "); + print_pool(&r->nat, r->nat.proxy_port[0], + r->nat.proxy_port[1], r->naf ? r->naf : r->af, + PF_POOL_NAT, verbose); + } + if (!anchor_call[0] && r->rdr.addr.type != PF_ADDR_NONE) { + printf (" rdr-to "); + print_pool(&r->rdr, r->rdr.proxy_port[0], + r->rdr.proxy_port[1], r->af, PF_POOL_RDR, verbose); + } + } + if (r->rt) { + if (r->rt == PF_ROUTETO) + printf(" route-to"); + else if (r->rt == PF_REPLYTO) + printf(" reply-to"); + else if (r->rt == PF_DUPTO) + printf(" dup-to"); + printf(" "); + print_pool(&r->route, 0, 0, r->af, PF_POOL_ROUTE, verbose); + } + + if (r->rule_flag & PFRULE_EXPIRED) + printf(" # expired"); +} + +void +print_tabledef(const char *name, int flags, int addrs, + struct node_tinithead *nodes) +{ + struct node_tinit *ti, *nti; + struct node_host *h; + + printf("table <%s>", name); + if (flags & PFR_TFLAG_CONST) + printf(" const"); + if (flags & PFR_TFLAG_PERSIST) + printf(" persist"); + if (flags & PFR_TFLAG_COUNTERS) + printf(" counters"); + SIMPLEQ_FOREACH(ti, nodes, entries) { + if (ti->file) { + printf(" file \"%s\"", ti->file); + continue; + } + printf(" {"); + for (;;) { + for (h = ti->host; h != NULL; h = h->next) { + printf(h->not ? " !" : " "); + print_addr(&h->addr, h->af, 0); + if (h->ifname) + printf("@%s", h->ifname); + } + nti = SIMPLEQ_NEXT(ti, entries); + if (nti != NULL && nti->file == NULL) + ti = nti; /* merge lists */ + else + break; + } + printf(" }"); + } + if (addrs && SIMPLEQ_EMPTY(nodes)) + printf(" { }"); + printf("\n"); +} + +void +print_bwspec(const char *prefix, struct pf_queue_bwspec *bw) +{ + uint64_t rate; + int i; + static const char unit[] = " KMG"; + + if (bw->percent) + printf("%s%u%%", prefix, bw->percent); + else if (bw->absolute) { + rate = bw->absolute; + for (i = 0; rate >= 1000 && i < 3 && (rate % 1000 == 0); i++) + rate /= 1000; + printf("%s%llu%c", prefix, rate, unit[i]); + } +} + +void +print_scspec(const char *prefix, struct pf_queue_scspec *sc) +{ + print_bwspec(prefix, &sc->m2); + if (sc->d) { + printf(" burst "); + print_bwspec("", &sc->m1); + printf(" for %ums", sc->d); + } +} + +void +print_queuespec(struct pf_queuespec *q) +{ + printf("queue %s", q->qname); + if (q->parent[0]) + printf(" parent %s", q->parent); + else if (q->ifname[0]) + printf(" on %s", q->ifname); + if (q->flags & PFQS_FLOWQUEUE) { + printf(" flows %u", q->flowqueue.flows); + if (q->flowqueue.quantum > 0) + printf(" quantum %u", q->flowqueue.quantum); + if (q->flowqueue.interval > 0) + printf(" interval %ums", + q->flowqueue.interval / 1000000); + if (q->flowqueue.target > 0) + printf(" target %ums", + q->flowqueue.target / 1000000); + } + if (q->linkshare.m1.absolute || q->linkshare.m2.absolute) { + print_scspec(" bandwidth ", &q->linkshare); + print_scspec(", min ", &q->realtime); + print_scspec(", max ", &q->upperlimit); + } + if (q->flags & PFQS_DEFAULT) + printf(" default"); + if (q->qlimit) + printf(" qlimit %u", q->qlimit); + printf("\n"); +} + +int +parse_flags(char *s) +{ + char *p, *q; + u_int8_t f = 0; + + for (p = s; *p; p++) { + if ((q = strchr(tcpflags, *p)) == NULL) + return -1; + else + f |= 1 << (q - tcpflags); + } + return (f ? f : PF_TH_ALL); +} + +void +set_ipmask(struct node_host *h, int bb) +{ + struct pf_addr *m, *n; + int i, j = 0; + u_int8_t b; + + m = &h->addr.v.a.mask; + memset(m, 0, sizeof(*m)); + + if (bb == -1) + b = h->af == AF_INET ? 32 : 128; + else + b = bb; + + while (b >= 32) { + m->addr32[j++] = 0xffffffff; + b -= 32; + } + for (i = 31; i > 31-b; --i) + m->addr32[j] |= (1 << i); + if (b) + m->addr32[j] = htonl(m->addr32[j]); + + /* Mask off bits of the address that will never be used. */ + n = &h->addr.v.a.addr; + if (h->addr.type == PF_ADDR_ADDRMASK) + for (i = 0; i < 4; i++) + n->addr32[i] = n->addr32[i] & m->addr32[i]; +} + +int +check_netmask(struct node_host *h, sa_family_t af) +{ + struct node_host *n = NULL; + struct pf_addr *m; + + for (n = h; n != NULL; n = n->next) { + if (h->addr.type == PF_ADDR_TABLE) + continue; + m = &h->addr.v.a.mask; + /* netmasks > 32 bit are invalid on v4 */ + if (af == AF_INET && + (m->addr32[1] || m->addr32[2] || m->addr32[3])) { + fprintf(stderr, "netmask %u invalid for IPv4 address\n", + unmask(m)); + return (1); + } + } + return (0); +} + +struct node_host * +gen_dynnode(struct node_host *h, sa_family_t af) +{ + struct node_host *n; + + if (h->addr.type != PF_ADDR_DYNIFTL) + return (NULL); + + if ((n = calloc(1, sizeof(*n))) == NULL) + return (NULL); + bcopy(h, n, sizeof(*n)); + n->ifname = NULL; + n->next = NULL; + n->tail = NULL; + + /* fix up netmask */ + if (af == AF_INET && unmask(&n->addr.v.a.mask) > 32) + set_ipmask(n, 32); + + return (n); +} + +/* interface lookup routines */ + +struct node_host *iftab; + +void +ifa_load(void) +{ + struct ifaddrs *ifap, *ifa; + struct node_host *n = NULL, *h = NULL; + + if (getifaddrs(&ifap) == -1) + err(1, "getifaddrs"); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || + !(ifa->ifa_addr->sa_family == AF_INET || + ifa->ifa_addr->sa_family == AF_INET6 || + ifa->ifa_addr->sa_family == AF_LINK)) + continue; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "%s: calloc", __func__); + n->af = ifa->ifa_addr->sa_family; + n->ifa_flags = ifa->ifa_flags; +#ifdef __KAME__ + if (n->af == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_addr) && + ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == + 0) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | + sin6->sin6_addr.s6_addr[3]; + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +#endif + n->ifindex = 0; + if (n->af == AF_LINK) + n->ifindex = ((struct sockaddr_dl *) + ifa->ifa_addr)->sdl_index; + else { + copy_satopfaddr(&n->addr.v.a.addr, ifa->ifa_addr); + ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->addr.v.a.mask, ifa->ifa_netmask); + if (ifa->ifa_flags & IFF_BROADCAST && + ifa->ifa_broadaddr != NULL + /* PFLINUX: Linux struct sockaddr has no sa_len; + * getifaddrs() only fills ifa_broadaddr when a + * broadcast address actually exists. */ + ) { + ifa->ifa_broadaddr->sa_family = + ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->bcast, ifa->ifa_broadaddr); + } else if (ifa->ifa_flags & IFF_POINTOPOINT && + ifa->ifa_dstaddr != NULL) { + ifa->ifa_dstaddr->sa_family = + ifa->ifa_addr->sa_family; + copy_satopfaddr(&n->peer, ifa->ifa_dstaddr); + } + if (n->af == AF_INET6) + n->ifindex = ((struct sockaddr_in6 *) + ifa->ifa_addr)->sin6_scope_id; + } + if ((n->ifname = strdup(ifa->ifa_name)) == NULL) + err(1, "%s: strdup", __func__); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + + iftab = h; + freeifaddrs(ifap); +} + +unsigned int +ifa_nametoindex(const char *ifa_name) +{ + struct node_host *p; + + for (p = iftab; p; p = p->next) { + if (p->af == AF_LINK && strcmp(p->ifname, ifa_name) == 0) + return (p->ifindex); + } + errno = ENXIO; + return (0); +} + +char * +ifa_indextoname(unsigned int ifindex, char *ifa_name) +{ + struct node_host *p; + + for (p = iftab; p; p = p->next) { + if (p->af == AF_LINK && ifindex == p->ifindex) { + strlcpy(ifa_name, p->ifname, IFNAMSIZ); + return (ifa_name); + } + } + errno = ENXIO; + return (NULL); +} + +struct node_host * +ifa_exists(const char *ifa_name) +{ + struct node_host *n; + struct ifgroupreq ifgr; + int s; + + if (iftab == NULL) + ifa_load(); + + /* check whether this is a group */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + err(1, "socket"); + bzero(&ifgr, sizeof(ifgr)); + strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name)); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == 0) { + /* fake a node_host */ + if ((n = calloc(1, sizeof(*n))) == NULL) + err(1, "calloc"); + if ((n->ifname = strdup(ifa_name)) == NULL) + err(1, "strdup"); + close(s); + return (n); + } + close(s); + + for (n = iftab; n; n = n->next) { + if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) + return (n); + } + + return (NULL); +} + +struct node_host * +ifa_grouplookup(const char *ifa_name, int flags) +{ + struct ifg_req *ifg; + struct ifgroupreq ifgr; + int s, len; + struct node_host *n, *h = NULL; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + err(1, "socket"); + bzero(&ifgr, sizeof(ifgr)); + strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name)); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) { + close(s); + return (NULL); + } + + len = ifgr.ifgr_len; + if ((ifgr.ifgr_groups = calloc(1, len)) == NULL) + err(1, "calloc"); + if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGMEMB"); + + for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req); + ifg++) { + len -= sizeof(struct ifg_req); + if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL) + continue; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n->tail; + } + } + free(ifgr.ifgr_groups); + close(s); + + return (h); +} + +struct node_host * +ifa_lookup(const char *ifa_name, int flags) +{ + struct node_host *p = NULL, *h = NULL, *n = NULL; + int got4 = 0, got6 = 0; + const char *last_if = NULL; + + if ((h = ifa_grouplookup(ifa_name, flags)) != NULL) + return (h); + + if (!strncmp(ifa_name, "self", IFNAMSIZ)) + ifa_name = NULL; + + if (iftab == NULL) + ifa_load(); + + for (p = iftab; p; p = p->next) { + if (ifa_skip_if(ifa_name, p)) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && + !(p->ifa_flags & IFF_BROADCAST)) + continue; + if ((flags & PFI_AFLAG_BROADCAST) && p->bcast.v4.s_addr == 0) + continue; + if ((flags & PFI_AFLAG_PEER) && + !(p->ifa_flags & IFF_POINTOPOINT)) + continue; + if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0) + continue; + if (last_if == NULL || strcmp(last_if, p->ifname)) + got4 = got6 = 0; + last_if = p->ifname; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4) + continue; + if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6) + continue; + if (p->af == AF_INET) + got4 = 1; + else + got6 = 1; + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "%s: calloc", __func__); + n->af = p->af; + if (flags & PFI_AFLAG_BROADCAST) + memcpy(&n->addr.v.a.addr, &p->bcast, + sizeof(struct pf_addr)); + else if (flags & PFI_AFLAG_PEER) + memcpy(&n->addr.v.a.addr, &p->peer, + sizeof(struct pf_addr)); + else + memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, + sizeof(struct pf_addr)); + if (flags & PFI_AFLAG_NETWORK) + set_ipmask(n, unmask(&p->addr.v.a.mask)); + else + set_ipmask(n, -1); + n->ifindex = p->ifindex; + + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + return (h); +} + +int +ifa_skip_if(const char *filter, struct node_host *p) +{ + int n; + + if (p->af != AF_INET && p->af != AF_INET6) + return (1); + if (filter == NULL || !*filter) + return (0); + if (!strcmp(p->ifname, filter)) + return (0); /* exact match */ + n = strlen(filter); + if (n < 1 || n >= IFNAMSIZ) + return (1); /* sanity check */ + if (filter[n-1] >= '0' && filter[n-1] <= '9') + return (1); /* only do exact match in that case */ + if (strncmp(p->ifname, filter, n)) + return (1); /* prefix doesn't match */ + return (p->ifname[n] < '0' || p->ifname[n] > '9'); +} + +struct node_host * +host(const char *s, int opts) +{ + struct node_host *h = NULL, *n; + int mask = -1; + char *p, *ps; + const char *errstr; + + if ((ps = strdup(s)) == NULL) + err(1, "%s: strdup", __func__); + + if ((p = strchr(ps, '/')) != NULL) { + mask = strtonum(p+1, 0, 128, &errstr); + if (errstr) { + fprintf(stderr, "netmask is %s: %s\n", errstr, p); + goto error; + } + p[0] = '\0'; + } + + if ((h = host_if(ps, mask)) == NULL && + (h = host_ip(ps, mask)) == NULL && + (h = host_dns(ps, mask, (opts & PF_OPT_NODNS))) == NULL) { + fprintf(stderr, "no IP address found for %s\n", s); + goto error; + } + + for (n = h; n != NULL; n = n->next) { + n->addr.type = PF_ADDR_ADDRMASK; + n->weight = 0; + } + +error: + free(ps); + return (h); +} + +struct node_host * +host_if(const char *s, int mask) +{ + struct node_host *n, *h = NULL; + char *p, *ps; + int flags = 0; + + if ((ps = strdup(s)) == NULL) + err(1, "host_if: strdup"); + while ((p = strrchr(ps, ':')) != NULL) { + if (!strcmp(p+1, "network")) + flags |= PFI_AFLAG_NETWORK; + else if (!strcmp(p+1, "broadcast")) + flags |= PFI_AFLAG_BROADCAST; + else if (!strcmp(p+1, "peer")) + flags |= PFI_AFLAG_PEER; + else if (!strcmp(p+1, "0")) + flags |= PFI_AFLAG_NOALIAS; + else + goto error; + *p = '\0'; + } + if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */ + fprintf(stderr, "illegal combination of interface modifiers\n"); + goto error; + } + if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) { + fprintf(stderr, "network or broadcast lookup, but " + "extra netmask given\n"); + goto error; + } + if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { + /* interface with this name exists */ + h = ifa_lookup(ps, flags); + if (mask > -1) + for (n = h; n != NULL; n = n->next) + set_ipmask(n, mask); + } + +error: + free(ps); + return (h); +} + +struct node_host * +host_ip(const char *s, int mask) +{ + struct addrinfo hints, *res; + struct node_host *h = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, NULL, &hints, &res) == 0) { + h = calloc(1, sizeof(*h)); + if (h == NULL) + err(1, "%s: calloc", __func__); + h->af = res->ai_family; + copy_satopfaddr(&h->addr.v.a.addr, res->ai_addr); + if (h->af == AF_INET6) + h->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + freeaddrinfo(res); + } else { /* ie. for 10/8 parsing */ + if (mask == -1) + return (NULL); + h = calloc(1, sizeof(*h)); + if (h == NULL) + err(1, "%s: calloc", __func__); + h->af = AF_INET; + if (inet_net_pton(AF_INET, s, &h->addr.v.a.addr.v4, + sizeof(h->addr.v.a.addr.v4)) == -1) { + free(h); + return (NULL); + } + } + set_ipmask(h, mask); + h->ifname = NULL; + h->next = NULL; + h->tail = h; + + return (h); +} + +struct node_host * +host_dns(const char *s, int mask, int numeric) +{ + struct addrinfo hints, *res0, *res; + struct node_host *n, *h = NULL; + int noalias = 0, got4 = 0, got6 = 0; + char *p, *ps; + + if ((ps = strdup(s)) == NULL) + err(1, "host_dns: strdup"); + if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) { + noalias = 1; + *p = '\0'; + } + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; /* DUMMY */ + if (numeric) + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(ps, NULL, &hints, &res0) != 0) + goto error; + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if (noalias) { + if (res->ai_family == AF_INET) { + if (got4) + continue; + got4 = 1; + } else { + if (got6) + continue; + got6 = 1; + } + } + n = calloc(1, sizeof(struct node_host)); + if (n == NULL) + err(1, "host_dns: calloc"); + n->ifname = NULL; + n->af = res->ai_family; + copy_satopfaddr(&n->addr.v.a.addr, res->ai_addr); + if (res->ai_family == AF_INET6) + n->ifindex = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + set_ipmask(n, mask); + n->next = NULL; + n->tail = n; + if (h == NULL) + h = n; + else { + h->tail->next = n; + h->tail = n; + } + } + freeaddrinfo(res0); +error: + free(ps); + + return (h); +} + +/* + * convert a hostname to a list of addresses and put them in the given buffer. + * test: + * if set to 1, only simple addresses are accepted (no netblock, no "!"). + */ +int +append_addr(struct pfr_buffer *b, char *s, int test, int opts) +{ + static int previous = 0; + static int expect = 0; + struct pfr_addr *a; + struct node_host *h, *n; + char *r; + const char *errstr; + int rv, not = 0, i = 0; + u_int16_t weight; + + /* skip weight if given */ + if (strcmp(s, "weight") == 0) { + expect = 1; + return (1); /* expecting further call */ + } + + /* check if previous host is set */ + if (expect) { + /* parse and append load balancing weight */ + weight = strtonum(s, 1, USHRT_MAX, &errstr); + if (errstr) { + fprintf(stderr, "failed to convert weight %s\n", s); + return (-1); + } + if (previous != -1) { + PFRB_FOREACH(a, b) { + if (++i >= previous) { + a->pfra_weight = weight; + a->pfra_type = PFRKE_COST; + } + } + } + expect = 0; + return (0); + } + + for (r = s; *r == '!'; r++) + not = !not; + if ((n = host(r, opts)) == NULL) { + errno = 0; + return (-1); + } + rv = append_addr_host(b, n, test, not); + previous = b->pfrb_size; + do { + h = n; + n = n->next; + free(h); + } while (n != NULL); + return (rv); +} + +/* + * same as previous function, but with a pre-parsed input and the ability + * to "negate" the result. Does not free the node_host list. + * not: + * setting it to 1 is equivalent to adding "!" in front of parameter s. + */ +int +append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) +{ + int bits; + struct pfr_addr addr; + + do { + bzero(&addr, sizeof(addr)); + addr.pfra_not = n->not ^ not; + addr.pfra_af = n->af; + addr.pfra_net = unmask(&n->addr.v.a.mask); + if (n->ifname) { + if (strlcpy(addr.pfra_ifname, n->ifname, + sizeof(addr.pfra_ifname)) >= sizeof(addr.pfra_ifname)) + errx(1, "append_addr_host: strlcpy"); + addr.pfra_type = PFRKE_ROUTE; + } + if (n->weight > 0) { + addr.pfra_weight = n->weight; + addr.pfra_type = PFRKE_COST; + } + switch (n->af) { + case AF_INET: + addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; + bits = 32; + break; + case AF_INET6: + memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, + sizeof(struct in6_addr)); + bits = 128; + break; + default: + errno = EINVAL; + return (-1); + } + if ((test && (not || addr.pfra_net != bits)) || + addr.pfra_net > bits) { + errno = EINVAL; + return (-1); + } + if (pfr_buf_add(b, &addr)) + return (-1); + } while ((n = n->next) != NULL); + + return (0); +} + +int +pfctl_add_trans(struct pfr_buffer *buf, int type, const char *anchor) +{ + struct pfioc_trans_e trans; + + bzero(&trans, sizeof(trans)); + trans.type = type; + if (strlcpy(trans.anchor, anchor, + sizeof(trans.anchor)) >= sizeof(trans.anchor)) + errx(1, "pfctl_add_trans: strlcpy"); + + return pfr_buf_add(buf, &trans); +} + +u_int32_t +pfctl_get_ticket(struct pfr_buffer *buf, int type, const char *anchor) +{ + struct pfioc_trans_e *p; + + PFRB_FOREACH(p, buf) + if (type == p->type && !strcmp(anchor, p->anchor)) + return (p->ticket); + errx(1, "pfctl_get_ticket: assertion failed"); +} + +int +pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from) +{ + struct pfioc_trans trans; + + bzero(&trans, sizeof(trans)); + trans.size = buf->pfrb_size - from; + trans.esize = sizeof(struct pfioc_trans_e); + trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from; + return ioctl(dev, cmd, &trans); +} diff --git a/tools/pfctl/pfctl_parser.h b/tools/pfctl/pfctl_parser.h new file mode 100644 index 0000000..31cfce2 --- /dev/null +++ b/tools/pfctl/pfctl_parser.h @@ -0,0 +1,346 @@ +/* $OpenBSD: pfctl_parser.h,v 1.124 2026/04/09 06:10:38 dlg Exp $ */ + +/* + * Copyright (c) 2001 Daniel Hartmeier + * Copyright (c) 2002 - 2013 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. + * + */ + +#ifndef _PFCTL_PARSER_H_ +#define _PFCTL_PARSER_H_ + +#define PF_OSFP_FILE "/etc/pf.os" + +#define PF_OPT_DISABLE 0x00001 +#define PF_OPT_ENABLE 0x00002 +#define PF_OPT_VERBOSE 0x00004 +#define PF_OPT_NOACTION 0x00008 +#define PF_OPT_QUIET 0x00010 +#define PF_OPT_CLRRULECTRS 0x00020 +#define PF_OPT_USEDNS 0x00040 +#define PF_OPT_VERBOSE2 0x00080 +#define PF_OPT_DUMMYACTION 0x00100 +#define PF_OPT_DEBUG 0x00200 +#define PF_OPT_SHOWALL 0x00400 +#define PF_OPT_OPTIMIZE 0x00800 +#define PF_OPT_NODNS 0x01000 +#define PF_OPT_RECURSE 0x04000 +#define PF_OPT_PORTNAMES 0x08000 +#define PF_OPT_IGNFAIL 0x10000 +#define PF_OPT_CALLSHOW 0x20000 + +#define PF_TH_ALL 0xFF + +#define PF_NAT_PROXY_PORT_LOW 50001 +#define PF_NAT_PROXY_PORT_HIGH 65535 + +#define PF_OPTIMIZE_BASIC 0x0001 +#define PF_OPTIMIZE_PROFILE 0x0002 + +#define FCNT_NAMES { \ + "searches", \ + "inserts", \ + "removals", \ + NULL \ +} + +struct pfr_buffer; /* forward definition */ + +struct pfctl_statelim { + struct pfioc_statelim ioc; + RBT_ENTRY(pfctl_statelim) id_entry; + RBT_ENTRY(pfctl_statelim) nm_entry; +}; + +RBT_HEAD(pfctl_statelim_ids, pfctl_statelim); +RBT_HEAD(pfctl_statelim_nms, pfctl_statelim); + +struct pfctl_sourcelim { + struct pfioc_sourcelim ioc; + RBT_ENTRY(pfctl_sourcelim) id_entry; + RBT_ENTRY(pfctl_sourcelim) nm_entry; +}; + +RBT_HEAD(pfctl_sourcelim_ids, pfctl_sourcelim); +RBT_HEAD(pfctl_sourcelim_nms, pfctl_sourcelim); + +struct pfctl { + int dev; + int opts; + int optimize; + int asd; /* anchor stack depth */ + int bn; /* brace number */ + int brace; + int tdirty; /* kernel dirty */ +#define PFCTL_ANCHOR_STACK_DEPTH 64 + struct pf_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH]; + struct pfioc_queue *pqueue; + struct pfr_buffer *trans; + struct pf_anchor *anchor, *alast; + struct pfr_ktablehead pfr_ktlast; + const char *ruleset; + + struct pfctl_statelim_ids statelim_ids; + struct pfctl_statelim_nms statelim_nms; + struct pfctl_sourcelim_ids sourcelim_ids; + struct pfctl_sourcelim_nms sourcelim_nms; + + /* 'set foo' options */ + u_int32_t timeout[PFTM_MAX]; + u_int32_t limit[PF_LIMIT_MAX]; + u_int32_t debug; + u_int32_t hostid; + u_int32_t reassemble; + u_int8_t syncookies; + u_int8_t syncookieswat[2]; /* lowat, hiwat */ + char *ifname; + + u_int8_t timeout_set[PFTM_MAX]; + u_int8_t limit_set[PF_LIMIT_MAX]; + u_int8_t debug_set; + u_int8_t hostid_set; + u_int8_t ifname_set; + u_int8_t reass_set; + u_int8_t syncookies_set; + u_int8_t syncookieswat_set; +}; + +struct node_if { + char ifname[IFNAMSIZ]; + u_int8_t not; + u_int8_t dynamic; /* antispoof */ + u_int8_t use_rdomain; + u_int ifa_flags; + int rdomain; + struct node_if *next; + struct node_if *tail; +}; + +struct node_host { + struct pf_addr_wrap addr; + struct pf_addr bcast; + struct pf_addr peer; + sa_family_t af; + u_int8_t not; + u_int32_t ifindex; /* link-local IPv6 addrs */ + u_int16_t weight; /* load balancing weight */ + char *ifname; + u_int ifa_flags; + struct node_host *next; + struct node_host *tail; +}; +void freehostlist(struct node_host *); + +struct node_os { + char *os; + pf_osfp_t fingerprint; + struct node_os *next; + struct node_os *tail; +}; + +struct node_queue_bw { + u_int64_t bw_absolute; + u_int16_t bw_percent; +}; + +struct node_hfsc_sc { + struct node_queue_bw m1; /* slope of 1st segment; bps */ + u_int d; /* x-projection of m1; msec */ + struct node_queue_bw m2; /* slope of 2nd segment; bps */ + u_int8_t used; +}; + +struct node_hfsc_opts { + struct node_hfsc_sc realtime; + struct node_hfsc_sc linkshare; + struct node_hfsc_sc upperlimit; + int flags; +}; + +struct node_queue_opt { + int qtype; + union { + struct priq_opts priq_opts; + struct node_hfsc_opts hfsc_opts; + } data; +}; + +SIMPLEQ_HEAD(node_tinithead, node_tinit); +struct node_tinit { /* table initializer */ + SIMPLEQ_ENTRY(node_tinit) entries; + struct node_host *host; + char *file; +}; + + +/* optimizer created tables */ +struct pf_opt_tbl { + char pt_name[PF_TABLE_NAME_SIZE]; + int pt_rulecount; + int pt_generated; + u_int32_t pt_flags; + u_int32_t pt_refcnt; + struct node_tinithead pt_nodes; + struct pfr_buffer *pt_buf; +}; + +/* optimizer pf_rule container */ +struct pf_opt_rule { + struct pf_rule por_rule; + struct pf_opt_tbl *por_src_tbl; + struct pf_opt_tbl *por_dst_tbl; + u_int64_t por_profile_count; + TAILQ_ENTRY(pf_opt_rule) por_entry; + TAILQ_ENTRY(pf_opt_rule) por_skip_entry[PF_SKIP_COUNT]; +}; + +TAILQ_HEAD(pf_opt_queue, pf_opt_rule); + +extern TAILQ_HEAD(pf_qihead, pfctl_qsitem) qspecs, rootqs; +struct pfctl_qsitem { + TAILQ_ENTRY(pfctl_qsitem) entries; + struct pf_queuespec qs; + struct pf_qihead children; + int matches; +}; + +struct pfctl_watermarks { + u_int32_t hi; + u_int32_t lo; +}; + +struct pfr_uktable; + +void copy_satopfaddr(struct pf_addr *, struct sockaddr *); + +int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *); +int pfctl_optimize_ruleset(struct pfctl *, struct pf_ruleset *); +int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *); +int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t, + struct pf_rule_addr *, char *); + +void pfctl_add_rule(struct pfctl *, struct pf_rule *); + +int pfctl_add_statelim(struct pfctl *, struct pfctl_statelim *); +struct pfctl_statelim * + pfctl_get_statelim_id(struct pfctl *, uint32_t); +struct pfctl_statelim * + pfctl_get_statelim_nm(struct pfctl *, const char *); +int pfctl_add_sourcelim(struct pfctl *, struct pfctl_sourcelim *); +struct pfctl_sourcelim * + pfctl_get_sourcelim_id(struct pfctl *, uint32_t); +struct pfctl_sourcelim * + pfctl_get_sourcelim_nm(struct pfctl *, const char *); + +int pfctl_set_timeout(struct pfctl *, const char *, int, int); +int pfctl_set_reassembly(struct pfctl *, int, int); +int pfctl_set_syncookies(struct pfctl *, u_int8_t, + struct pfctl_watermarks *); +int pfctl_set_optimization(struct pfctl *, const char *); +int pfctl_set_limit(struct pfctl *, const char *, unsigned int); +int pfctl_set_logif(struct pfctl *, char *); +void pfctl_set_hostid(struct pfctl *, u_int32_t); +int pfctl_set_debug(struct pfctl *, char *); +int pfctl_set_interface_flags(struct pfctl *, char *, int, int); + +int parse_config(char *, struct pfctl *); +int parse_flags(char *); +int pfctl_load_anchors(int, struct pfctl *); + +int pfctl_load_queues(struct pfctl *); +int pfctl_add_queue(struct pfctl *, struct pf_queuespec *); +struct pfctl_qsitem * pfctl_find_queue(char *, struct pf_qihead *); + +void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int, int); +void print_src_node(struct pf_src_node *, int); +void print_statelim(const struct pfioc_statelim *); +void print_sourcelim(const struct pfioc_sourcelim *); +void print_rule(struct pfctl *pf, struct pf_rule *, const char *, int); +void print_tabledef(const char *, int, int, struct node_tinithead *); +void print_status(struct pf_status *, struct pfctl_watermarks *, int); +void print_queuespec(struct pf_queuespec *); + +int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *, + u_int32_t, struct pfr_uktable *); +void pfctl_expand_label_nr(struct pf_rule *, unsigned int); + +void pfctl_clear_fingerprints(int, int); +int pfctl_file_fingerprints(int, int, const char *); +pf_osfp_t pfctl_get_fingerprint(const char *); +int pfctl_load_fingerprints(int, int); +char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); +void pfctl_show_fingerprints(int); + +struct icmptypeent { + const char *name; + u_int8_t type; +}; + +struct icmpcodeent { + const char *name; + u_int8_t type; + u_int8_t code; +}; + +/* PFLINUX: last arg is the address family; the definitions below use + * sa_family_t, which on Linux is `unsigned short` (OpenBSD: u_int8_t). + * Header and definition must agree. */ +const struct icmptypeent *geticmptypebynumber(u_int8_t, sa_family_t); +const struct icmptypeent *geticmptypebyname(char *, sa_family_t); +const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, sa_family_t); +const struct icmpcodeent *geticmpcodebyname(u_long, char *, sa_family_t); + +int string_to_loglevel(const char *); +const char *loglevel_to_string(int); + +struct pf_timeout { + const char *name; + int timeout; +}; + +extern const struct pf_timeout pf_timeouts[]; + +void set_ipmask(struct node_host *, int); +int check_netmask(struct node_host *, sa_family_t); +int unmask(struct pf_addr *); +struct node_host *gen_dynnode(struct node_host *, sa_family_t); +void ifa_load(void); +unsigned int ifa_nametoindex(const char *); +char *ifa_indextoname(unsigned int, char *); +struct node_host *ifa_exists(const char *); +struct node_host *ifa_lookup(const char *, int); +struct node_host *host(const char *, int); + +int append_addr(struct pfr_buffer *, char *, int, int); +int append_addr_host(struct pfr_buffer *, + struct node_host *, int, int); +int pfr_ktable_compare(struct pfr_ktable *, + struct pfr_ktable *); +RB_PROTOTYPE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); + +#endif /* _PFCTL_PARSER_H_ */ diff --git a/tools/pfctl/pfctl_queue.c b/tools/pfctl/pfctl_queue.c new file mode 100644 index 0000000..1f0d456 --- /dev/null +++ b/tools/pfctl/pfctl_queue.c @@ -0,0 +1,287 @@ +/* $OpenBSD: pfctl_queue.c,v 1.8 2024/05/19 10:39:40 jsg Exp $ */ + +/* + * Copyright (c) 2003 - 2013 Henning Brauer + * + * 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 +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pfctl.h" +#include "pfctl_parser.h" + +#define AVGN_MAX 8 +#define STAT_INTERVAL 5 + +struct queue_stats { + union { + struct hfsc_class_stats hfsc; + struct fqcodel_stats fqc; + } data; + int avgn; + double avg_bytes; + double avg_packets; + u_int64_t prev_bytes; + u_int64_t prev_packets; +}; + +struct pfctl_queue_node { + TAILQ_ENTRY(pfctl_queue_node) entries; + struct pf_queuespec qs; + struct queue_stats qstats; +}; +TAILQ_HEAD(qnodes, pfctl_queue_node) qnodes = TAILQ_HEAD_INITIALIZER(qnodes); + +int pfctl_update_qstats(int); +void pfctl_insert_queue_node(const struct pf_queuespec, + const struct queue_stats); +struct pfctl_queue_node *pfctl_find_queue_node(const char *, const char *); +void pfctl_print_queue_node(int, struct pfctl_queue_node *, + int); +void pfctl_print_queue_nodestat(int, + const struct pfctl_queue_node *); +void update_avg(struct queue_stats *); +char *rate2str(double); + +int +pfctl_show_queues(int dev, const char *iface, int opts, int verbose2) +{ + struct pfctl_queue_node *node; + int nodes, dotitle = (opts & PF_OPT_SHOWALL); + + + if ((nodes = pfctl_update_qstats(dev)) <= 0) + return (nodes); + + TAILQ_FOREACH(node, &qnodes, entries) { + if (iface != NULL && strcmp(node->qs.ifname, iface)) + continue; + if (dotitle) { + pfctl_print_title("QUEUES:"); + dotitle = 0; + } + pfctl_print_queue_node(dev, node, opts); + } + + while (verbose2 && nodes > 0) { + printf("\n"); + fflush(stdout); + sleep(STAT_INTERVAL); + if ((nodes = pfctl_update_qstats(dev)) == -1) + return (-1); + TAILQ_FOREACH(node, &qnodes, entries) { + if (iface != NULL && strcmp(node->qs.ifname, iface)) + continue; + pfctl_print_queue_node(dev, node, opts); + } + } + while ((node = TAILQ_FIRST(&qnodes)) != NULL) + TAILQ_REMOVE(&qnodes, node, entries); + return (0); +} + +int +pfctl_update_qstats(int dev) +{ + struct pfctl_queue_node *node; + struct pfioc_queue pq; + struct pfioc_qstats pqs; + u_int32_t mnr, nr; + struct queue_stats qstats; + static u_int32_t last_ticket; + + memset(&pq, 0, sizeof(pq)); + memset(&pqs, 0, sizeof(pqs)); + memset(&qstats, 0, sizeof(qstats)); + if (ioctl(dev, DIOCGETQUEUES, &pq) == -1) { + warn("DIOCGETQUEUES"); + return (-1); + } + + /* if a new set is found, start over */ + if (pq.ticket != last_ticket) + while ((node = TAILQ_FIRST(&qnodes)) != NULL) + TAILQ_REMOVE(&qnodes, node, entries); + last_ticket = pq.ticket; + + mnr = pq.nr; + for (nr = 0; nr < mnr; ++nr) { + pqs.nr = nr; + pqs.ticket = pq.ticket; + pqs.buf = &qstats.data; + pqs.nbytes = sizeof(qstats.data); + if (ioctl(dev, DIOCGETQSTATS, &pqs) == -1) { + warn("DIOCGETQSTATS"); + return (-1); + } + if ((node = pfctl_find_queue_node(pqs.queue.qname, + pqs.queue.ifname)) != NULL) { + memcpy(&node->qstats.data, &qstats.data, + sizeof(qstats.data)); + update_avg(&node->qstats); + } else { + pfctl_insert_queue_node(pqs.queue, qstats); + } + } + return (mnr); +} + +void +pfctl_insert_queue_node(const struct pf_queuespec qs, + const struct queue_stats qstats) +{ + struct pfctl_queue_node *node; + + node = calloc(1, sizeof(struct pfctl_queue_node)); + if (node == NULL) + err(1, "pfctl_insert_queue_node: calloc"); + memcpy(&node->qs, &qs, sizeof(qs)); + memcpy(&node->qstats, &qstats, sizeof(qstats)); + TAILQ_INSERT_TAIL(&qnodes, node, entries); + update_avg(&node->qstats); +} + +struct pfctl_queue_node * +pfctl_find_queue_node(const char *qname, const char *ifname) +{ + struct pfctl_queue_node *node; + + TAILQ_FOREACH(node, &qnodes, entries) + if (!strcmp(node->qs.qname, qname) + && !(strcmp(node->qs.ifname, ifname))) + return (node); + return (NULL); +} + +void +pfctl_print_queue_node(int dev, struct pfctl_queue_node *node, int opts) +{ + if (node == NULL) + return; + + print_queuespec(&node->qs); + if (opts & PF_OPT_VERBOSE) + pfctl_print_queue_nodestat(dev, node); + + if (opts & PF_OPT_DEBUG) + printf(" [ qid=%u parent_qid=%u ifname=%s]\n", + node->qs.qid, node->qs.parent_qid, node->qs.ifname); +} + +void +pfctl_print_queue_nodestat(int dev, const struct pfctl_queue_node *node) +{ + struct hfsc_class_stats *stats = + (struct hfsc_class_stats *)&node->qstats.data.hfsc; + struct fqcodel_stats *fqstats = + (struct fqcodel_stats *)&node->qstats.data.fqc; + + printf(" [ pkts: %10llu bytes: %10llu " + "dropped pkts: %6llu bytes: %6llu ]\n", + (unsigned long long)stats->xmit_cnt.packets, + (unsigned long long)stats->xmit_cnt.bytes, + (unsigned long long)stats->drop_cnt.packets, + (unsigned long long)stats->drop_cnt.bytes); + if (node->qs.parent_qid == 0 && (node->qs.flags & PFQS_FLOWQUEUE) && + !(node->qs.flags & PFQS_ROOTCLASS)) { + double avg = 0, dev = 0; + + if (fqstats->flows > 0) { + avg = (double)fqstats->delaysum / + (double)fqstats->flows; + dev = sqrt(fmax(0, (double)fqstats->delaysumsq / + (double)fqstats->flows - avg * avg)); + } + + printf(" [ qlength: %3d/%3d avg delay: %.3fms std-dev: %.3fms" + " flows: %3d ]\n", stats->qlength, stats->qlimit, + avg / 1000, dev / 1000, fqstats->flows); + } else + printf(" [ qlength: %3d/%3d ]\n", stats->qlength, + stats->qlimit); + + if (node->qstats.avgn < 2) + return; + + printf(" [ measured: %7.1f packets/s, %s/s ]\n", + node->qstats.avg_packets / STAT_INTERVAL, + rate2str((8 * node->qstats.avg_bytes) / STAT_INTERVAL)); +} + +void +update_avg(struct queue_stats *s) +{ + struct hfsc_class_stats *stats = + (struct hfsc_class_stats *)&s->data; + + if (s->avgn > 0) { + if (stats->xmit_cnt.bytes >= s->prev_bytes) + s->avg_bytes = ((s->avg_bytes * (s->avgn - 1)) + + (stats->xmit_cnt.bytes - s->prev_bytes)) / + s->avgn; + if (stats->xmit_cnt.packets >= s->prev_packets) + s->avg_packets = ((s->avg_packets * (s->avgn - 1)) + + (stats->xmit_cnt.packets - s->prev_packets)) / + s->avgn; + } + + s->prev_bytes = stats->xmit_cnt.bytes; + s->prev_packets = stats->xmit_cnt.packets; + if (s->avgn < AVGN_MAX) + s->avgn++; +} + +#define R2S_BUFS 8 +#define RATESTR_MAX 16 + +char * +rate2str(double rate) +{ + char *buf; + static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */ + static int idx = 0; + int i; + static const char unit[] = " KMG"; + + buf = r2sbuf[idx++]; + if (idx == R2S_BUFS) + idx = 0; + + for (i = 0; rate >= 1000 && i <= 3; i++) + rate /= 1000; + + if ((int)(rate * 100) % 100) + snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]); + else + snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]); + + return (buf); +} diff --git a/tools/pfctl/pfctl_radix.c b/tools/pfctl/pfctl_radix.c new file mode 100644 index 0000000..ec1ffdc --- /dev/null +++ b/tools/pfctl/pfctl_radix.c @@ -0,0 +1,598 @@ +/* $OpenBSD: pfctl_radix.c,v 1.40 2024/11/20 13:57:29 kirill Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl.h" +#include "pfctl_parser.h" + +#define BUF_SIZE 256 + +extern int dev; + +static int pfr_next_token(char buf[BUF_SIZE], FILE *); + +struct pfr_ktablehead pfr_ktables = { 0 }; +RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); + +int +pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q) +{ + int d; + + if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE))) + return (d); + return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor)); +} + +int +pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags) +{ + struct pfioc_table io; + + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + if (ioctl(dev, DIOCRCLRTABLES, &io) == -1) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDTABLES, &io) == -1) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELTABLES, &io) == -1) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTABLES, &io) == -1) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size, + int flags) +{ + struct pfioc_table io; + + if (size == NULL || *size < 0 || (*size && tbl == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + if (filter != NULL) + io.pfrio_table = *filter; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETTSTATS, &io) == -1) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + if (ioctl(dev, DIOCRCLRADDRS, &io) == -1) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRADDADDRS, &io) == -1) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + return (0); +} + +int +pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *ndel, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRDELADDRS, &io) == -1) + return (-1); + if (ndel != NULL) + *ndel = io.pfrio_ndel; + return (0); +} + +int +pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *size2, int *nadd, int *ndel, int *nchange, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; + if (ioctl(dev, DIOCRSETADDRS, &io) == -1) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (ndel != NULL) + *ndel = io.pfrio_ndel; + if (nchange != NULL) + *nchange = io.pfrio_nchange; + if (size2 != NULL) + *size2 = io.pfrio_size2; + return (0); +} + +int +pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || + (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETADDRS, &io) == -1) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, + int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size == NULL || *size < 0 || + (*size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = *size; + if (ioctl(dev, DIOCRGETASTATS, &io) == -1) + return (-1); + *size = io.pfrio_size; + return (0); +} + +int +pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl) || addr == NULL) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRASTATS, &io) == -1) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags) +{ + struct pfioc_table io; + + if (size < 0 || (size && !tbl)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_buffer = tbl; + io.pfrio_esize = sizeof(*tbl); + io.pfrio_size = size; + if (ioctl(dev, DIOCRCLRTSTATS, &io) == -1) + return (-1); + if (nzero) + *nzero = io.pfrio_nzero; + return (0); +} + +int +pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nmatch, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + if (ioctl(dev, DIOCRTSTADDRS, &io) == -1) + return (-1); + if (nmatch) + *nmatch = io.pfrio_nmatch; + return (0); +} + +int +pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, + int *nadd, int *naddr, int ticket, int flags) +{ + struct pfioc_table io; + + if (tbl == NULL || size < 0 || (size && addr == NULL)) { + DBGPRINT("%s %p %d %p\n", __func__, tbl, size, addr); + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + io.pfrio_flags = flags; + io.pfrio_table = *tbl; + io.pfrio_buffer = addr; + io.pfrio_esize = sizeof(*addr); + io.pfrio_size = size; + io.pfrio_ticket = ticket; + if (ioctl(dev, DIOCRINADEFINE, &io) == -1) + return (-1); + if (nadd != NULL) + *nadd = io.pfrio_nadd; + if (naddr != NULL) + *naddr = io.pfrio_naddr; + return (0); +} + +/* interface management code */ + +int +pfi_get_ifaces(const char *filter, struct pfi_kif *buf, int *size) +{ + struct pfioc_iface io; + + if (size == NULL || *size < 0 || (*size && buf == NULL)) { + errno = EINVAL; + return (-1); + } + bzero(&io, sizeof io); + if (filter != NULL) + if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >= + sizeof(io.pfiio_name)) { + errno = EINVAL; + return (-1); + } + io.pfiio_buffer = buf; + io.pfiio_esize = sizeof(*buf); + io.pfiio_size = *size; + if (ioctl(dev, DIOCIGETIFACES, &io) == -1) + return (-1); + *size = io.pfiio_size; + return (0); +} + +/* buffer management code */ + +size_t buf_esize[PFRB_MAX] = { 0, + sizeof(struct pfr_table), sizeof(struct pfr_tstats), + sizeof(struct pfr_addr), sizeof(struct pfr_astats), + sizeof(struct pfi_kif), sizeof(struct pfioc_trans_e) +}; + +/* + * add one element to the buffer + */ +int +pfr_buf_add(struct pfr_buffer *b, const void *e) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || + e == NULL) { + errno = EINVAL; + return (-1); + } + bs = buf_esize[b->pfrb_type]; + if (b->pfrb_size == b->pfrb_msize) + if (pfr_buf_grow(b, 0)) + return (-1); + memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); + b->pfrb_size++; + return (0); +} + +/* + * return next element of the buffer (or first one if prev is NULL) + * see PFRB_FOREACH macro + */ +void * +pfr_buf_next(struct pfr_buffer *b, const void *prev) +{ + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) + return (NULL); + if (b->pfrb_size == 0) + return (NULL); + if (prev == NULL) + return (b->pfrb_caddr); + bs = buf_esize[b->pfrb_type]; + if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1) + return (NULL); + return (((caddr_t)prev) + bs); +} + +/* + * minsize: + * 0: make the buffer somewhat bigger + * n: make room for "n" entries in the buffer + */ +int +pfr_buf_grow(struct pfr_buffer *b, int minsize) +{ + caddr_t p; + size_t bs; + + if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { + errno = EINVAL; + return (-1); + } + if (minsize != 0 && minsize <= b->pfrb_msize) + return (0); + bs = buf_esize[b->pfrb_type]; + if (!b->pfrb_msize) { + if (minsize < 64) + minsize = 64; + } + if (minsize == 0) + minsize = b->pfrb_msize * 2; + p = reallocarray(b->pfrb_caddr, minsize, bs); + if (p == NULL) + return (-1); + bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); + b->pfrb_caddr = p; + b->pfrb_msize = minsize; + return (0); +} + +/* + * reset buffer and free memory. + */ +void +pfr_buf_clear(struct pfr_buffer *b) +{ + if (b == NULL) + return; + free(b->pfrb_caddr); + b->pfrb_caddr = NULL; + b->pfrb_size = b->pfrb_msize = 0; +} + +int +pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, int opts) +{ + FILE *fp; + char buf[BUF_SIZE]; + int rv; + int ev = 0; + + if (file == NULL) + return (0); + if (!strcmp(file, "-")) + fp = stdin; + else { + fp = pfctl_fopen(file, "r"); + if (fp == NULL) + return (-1); + } + while ((rv = pfr_next_token(buf, fp)) == 1) + if ((ev = append_addr(b, buf, nonetwork, opts)) == -1) { + rv = -1; + break; + } + if (ev == 1) /* expected further append_addr call */ + rv = -1; + if (fp != stdin) + fclose(fp); + return (rv); +} + +int +pfr_next_token(char buf[BUF_SIZE], FILE *fp) +{ + static char next_ch = ' '; + int i = 0; + + for (;;) { + /* skip spaces */ + while (isspace((unsigned char)next_ch) && !feof(fp)) + next_ch = fgetc(fp); + /* remove from '#' until end of line */ + if (next_ch == '#') + while (!feof(fp)) { + next_ch = fgetc(fp); + if (next_ch == '\n') + break; + } + else + break; + } + if (feof(fp)) { + next_ch = ' '; + return (0); + } + do { + if (i < BUF_SIZE) + buf[i++] = next_ch; + next_ch = fgetc(fp); + } while (!feof(fp) && !isspace((unsigned char)next_ch)); + if (i >= BUF_SIZE) { + errno = EINVAL; + return (-1); + } + buf[i] = '\0'; + return (1); +} diff --git a/tools/pfctl/pfctl_table.c b/tools/pfctl/pfctl_table.c new file mode 100644 index 0000000..7dd0a22 --- /dev/null +++ b/tools/pfctl/pfctl_table.c @@ -0,0 +1,692 @@ +/* $OpenBSD: pfctl_table.c,v 1.91 2024/11/20 13:57:29 kirill Exp $ */ + +/* + * Copyright (c) 2002 Cedric Berger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pfctl_parser.h" +#include "pfctl.h" + +extern void usage(void); +static void print_table(struct pfr_table *, int, int); +static void print_tstats(struct pfr_tstats *, int); +static int load_addr(struct pfr_buffer *, int, char *[], char *, int, int); +static void print_addrx(struct pfr_addr *, struct pfr_addr *, int); +static void print_astats(struct pfr_astats *, int); +static void xprintf(int, const char *, ...); +static void print_iface(struct pfi_kif *, int); + +static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { + { "In/Block:", "In/Match:", "In/Pass:", "In/XPass:" }, + { "Out/Block:", "Out/Match:", "Out/Pass:", "Out/XPass:" } +}; + +static const char *istats_text[2][2][2] = { + { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } }, + { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } } +}; + +#define RVTEST(fct) do { \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (fct)) { \ + if ((opts & PF_OPT_RECURSE) == 0) \ + warnx("%s", pf_strerror(errno)); \ + goto _error; \ + } \ + } while (0) + +#define CREATE_TABLE do { \ + warn_duplicate_tables(table.pfrt_name, \ + table.pfrt_anchor); \ + table.pfrt_flags |= PFR_TFLAG_PERSIST; \ + if ((!(opts & PF_OPT_NOACTION) || \ + (opts & PF_OPT_DUMMYACTION)) && \ + (pfr_add_tables(&table, 1, &nadd, flags)) && \ + (errno != EPERM)) { \ + warnx("%s", pf_strerror(errno)); \ + goto _error; \ + } \ + if (nadd) { \ + xprintf(opts, "%d table created", nadd); \ + if (opts & PF_OPT_NOACTION) \ + return (0); \ + } \ + table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ + } while(0) + +int +pfctl_clear_tables(const char *anchor, int opts) +{ + int rv; + + if ((rv = pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts)) == -1) { + if ((opts & PF_OPT_IGNFAIL) == 0) + exit(1); + } + + return (rv); +} + +void +pfctl_show_tables(const char *anchor, int opts) +{ + if (pfctl_table(0, NULL, NULL, "-s", NULL, anchor, opts) == -1) + exit(1); +} + +int +pfctl_table(int argc, char *argv[], char *tname, const char *command, + char *file, const char *anchor, int opts) +{ + struct pfr_table table; + struct pfr_buffer b, b2; + struct pfr_addr *a, *a2; + int nadd = 0, ndel = 0, nchange = 0, nzero = 0; + int rv = 0, flags = 0, nmatch = 0; + void *p; + + if (command == NULL) + usage(); + if (opts & PF_OPT_NOACTION) + flags |= PFR_FLAG_DUMMY; + + bzero(&b, sizeof(b)); + bzero(&b2, sizeof(b2)); + bzero(&table, sizeof(table)); + if (tname != NULL) { + if (strlen(tname) >= PF_TABLE_NAME_SIZE) + usage(); + if (strlcpy(table.pfrt_name, tname, + sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) + errx(1, "pfctl_table: strlcpy"); + } + if (strlcpy(table.pfrt_anchor, anchor, + sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor)) + errx(1, "pfctl_table: strlcpy"); + + if (!strcmp(command, "-F")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_tables(&table, &ndel, flags)); + xprintf(opts, "%d tables deleted", ndel); + } else if (!strcmp(command, "-s")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? + PFRB_TSTATS : PFRB_TABLES; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE2) + RVTEST(pfr_get_tstats(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + else + RVTEST(pfr_get_tables(&table, + b.pfrb_caddr, &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + + if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0) + pfctl_print_title("TABLES:"); + + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE2) + print_tstats(p, opts & PF_OPT_DEBUG); + else + print_table(p, opts & PF_OPT_VERBOSE, + opts & PF_OPT_DEBUG); + } else if (!strcmp(command, "kill")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_del_tables(&table, 1, &ndel, flags)); + xprintf(opts, "%d table deleted", ndel); + } else if (!strcmp(command, "flush")) { + if (argc || file != NULL) + usage(); + RVTEST(pfr_clr_addrs(&table, &ndel, flags)); + xprintf(opts, "%d addresses deleted", ndel); + } else if (!strcmp(command, "add")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, flags)); + xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "delete")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "replace")) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + CREATE_TABLE; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + for (;;) { + int sz2 = b.pfrb_msize; + + RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &sz2, &nadd, &ndel, &nchange, flags)); + if (sz2 <= b.pfrb_msize) { + b.pfrb_size = sz2; + break; + } else + pfr_buf_grow(&b, sz2); + } + if (nadd) + xprintf(opts, "%d addresses added", nadd); + if (ndel) + xprintf(opts, "%d addresses deleted", ndel); + if (nchange) + xprintf(opts, "%d addresses changed", nchange); + if (!nadd && !ndel && !nchange) + xprintf(opts, "no changes"); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "expire")) { + const char *errstr; + u_int lifetime; + + b.pfrb_type = PFRB_ASTATS; + b2.pfrb_type = PFRB_ADDRS; + if (argc != 1 || file != NULL) + usage(); + lifetime = strtonum(*argv, 0, UINT_MAX, &errstr); + if (errstr) + errx(1, "expiry time: %s", errstr); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) { + ((struct pfr_astats *)p)->pfras_a.pfra_fback = PFR_FB_NONE; + if (time(NULL) - ((struct pfr_astats *)p)->pfras_tzero > + lifetime) + if (pfr_buf_add(&b2, + &((struct pfr_astats *)p)->pfras_a)) + err(1, "duplicate buffer"); + } + + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_del_addrs(&table, b2.pfrb_caddr, b2.pfrb_size, + &ndel, flags)); + xprintf(opts, "%d/%d addresses expired", ndel, b2.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b2) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "show")) { + b.pfrb_type = (opts & PF_OPT_VERBOSE) ? + PFRB_ASTATS : PFRB_ADDRS; + if (argc || file != NULL) + usage(); + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (opts & PF_OPT_VERBOSE) + RVTEST(pfr_get_astats(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + else + RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, + &b.pfrb_size, flags)); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(p, &b) + if (opts & PF_OPT_VERBOSE) + print_astats(p, opts & PF_OPT_USEDNS); + else + print_addrx(p, NULL, opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "test")) { + b.pfrb_type = PFRB_ADDRS; + b2.pfrb_type = PFRB_ADDRS; + + if (load_addr(&b, argc, argv, file, 1, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE2) { + flags |= PFR_FLAG_REPLACE; + PFRB_FOREACH(a, &b) + if (pfr_buf_add(&b2, a)) + err(1, "duplicate buffer"); + } + RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nmatch, flags)); + xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); + if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2)) + PFRB_FOREACH(a, &b) + if (a->pfra_fback == PFR_FB_MATCH) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + if (opts & PF_OPT_VERBOSE2) { + a2 = NULL; + PFRB_FOREACH(a, &b) { + a2 = pfr_buf_next(&b2, a2); + print_addrx(a2, a, opts & PF_OPT_USEDNS); + } + } + if (nmatch < b.pfrb_size) + rv = 2; + } else if (!strcmp(command, "zero") && (argc || file != NULL)) { + b.pfrb_type = PFRB_ADDRS; + if (load_addr(&b, argc, argv, file, 0, opts)) + goto _error; + if (opts & PF_OPT_VERBOSE) + flags |= PFR_FLAG_FEEDBACK; + RVTEST(pfr_clr_astats(&table, b.pfrb_caddr, b.pfrb_size, + &nzero, flags)); + xprintf(opts, "%d/%d addresses cleared", nzero, b.pfrb_size); + if (opts & PF_OPT_VERBOSE) + PFRB_FOREACH(a, &b) + if (opts & PF_OPT_VERBOSE2 || + a->pfra_fback != PFR_FB_NONE) + print_addrx(a, NULL, + opts & PF_OPT_USEDNS); + } else if (!strcmp(command, "zero")) { + flags |= PFR_FLAG_ADDRSTOO; + RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags)); + xprintf(opts, "%d table/stats cleared", nzero); + } else + warnx("pfctl_table: unknown command '%s'", command); + goto _cleanup; + +_error: + rv = -1; +_cleanup: + pfr_buf_clear(&b); + pfr_buf_clear(&b2); + return (rv); +} + +void +print_table(struct pfr_table *ta, int verbose, int debug) +{ + if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) + return; + if (verbose) + printf("%c%c%c%c%c%c%c\t", + (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', + (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', + (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', + (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', + (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', + (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-'); + + printf("%s", ta->pfrt_name); + if (ta->pfrt_anchor[0] != '\0') + printf("@%s", ta->pfrt_anchor); + + printf("\n"); +} + +void +print_tstats(struct pfr_tstats *ts, int debug) +{ + time_t time = ts->pfrts_tzero; + int dir, op; + char *ct; + + if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) + return; + ct = ctime(&time); + print_table(&ts->pfrts_t, 1, debug); + printf("\tAddresses: %d\n", ts->pfrts_cnt); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", time); + printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", + ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], + ts->pfrts_refcnt[PFR_REFCNT_RULE]); + printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", + (unsigned long long)ts->pfrts_nomatch, + (unsigned long long)ts->pfrts_match); + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_TABLE_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)ts->pfrts_packets[dir][op], + (unsigned long long)ts->pfrts_bytes[dir][op]); +} + +int +load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, + int nonetwork, int opts) +{ + int ev = 0; + while (argc--) + if ((ev = append_addr(b, *argv++, nonetwork, opts)) == -1) { + if (errno) + warn("cannot decode %s", argv[-1]); + return (-1); + } + if (ev == 1) { /* expected further append_addr call */ + warnx("failed to decode %s", argv[-1]); + return (-1); + } + if (pfr_buf_load(b, file, nonetwork, opts)) { + warn("cannot load %s", file); + return (-1); + } + return (0); +} + +void +print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) +{ + char ch, buf[256] = "{error}"; + char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' }; + unsigned int fback, hostnet; + + fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; + ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; + hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; + inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); + printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); + if (ad->pfra_net < hostnet) + printf("/%d", ad->pfra_net); + if (rad != NULL && fback != PFR_FB_NONE) { + if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) + errx(1, "print_addrx: strlcpy"); + inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); + printf("\t%c%s", (rad->pfra_not?'!':' '), buf); + if (rad->pfra_net < hostnet) + printf("/%d", rad->pfra_net); + } + if (rad != NULL && fback == PFR_FB_NONE) + printf("\t nomatch"); + if (dns && ad->pfra_net == hostnet) { + char host[NI_MAXHOST]; + struct sockaddr_storage ss; + socklen_t slen; /* PFLINUX: sa_len replacement */ + + strlcpy(host, "?", sizeof(host)); + bzero(&ss, sizeof(ss)); + ss.ss_family = ad->pfra_af; + if (ss.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&ss; + + /* PFLINUX: no sa_len on Linux; getnameinfo(3) + * takes an explicit length instead. */ + sin->sin_addr = ad->pfra_ip4addr; + slen = sizeof(*sin); + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; + + sin6->sin6_addr = ad->pfra_ip6addr; + slen = sizeof(*sin6); + } + if (getnameinfo((struct sockaddr *)&ss, slen, host, + sizeof(host), NULL, 0, NI_NAMEREQD) == 0) + printf("\t(%s)", host); + } + if (ad->pfra_ifname[0] != '\0') + printf("@%s", ad->pfra_ifname); + printf("\n"); +} + +void +print_astats(struct pfr_astats *as, int dns) +{ + time_t time = as->pfras_tzero; + int dir, op; + char *ct; + + ct = ctime(&time); + print_addrx(&as->pfras_a, NULL, dns); + if (ct) + printf("\tCleared: %s", ctime(&time)); + else + printf("\tCleared: %lld\n", time); + if (as->pfras_a.pfra_states) + printf("\tActive States: %d\n", as->pfras_a.pfra_states); + if (as->pfras_a.pfra_type == PFRKE_COST) + printf("\tWeight: %d\n", as->pfras_a.pfra_weight); + if (as->pfras_a.pfra_ifname[0]) + printf("\tInterface: %s\n", as->pfras_a.pfra_ifname); + if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT) + return; + for (dir = 0; dir < PFR_DIR_MAX; dir++) + for (op = 0; op < PFR_OP_ADDR_MAX; op++) + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + stats_text[dir][op], + (unsigned long long)as->pfras_packets[dir][op], + (unsigned long long)as->pfras_bytes[dir][op]); +} + +int +pfctl_define_table(char *name, int flags, int addrs, const char *anchor, + struct pfr_buffer *ab, u_int32_t ticket, struct pfr_uktable *ukt) +{ + struct pfr_table tbl_buf; + struct pfr_table *tbl; + + if (ukt == NULL) { + bzero(&tbl_buf, sizeof(tbl_buf)); + tbl = &tbl_buf; + } else { + if (ab->pfrb_size != 0) { + /* + * copy IP addresses which come with table from + * temporal buffer to buffer attached to table. + */ + ukt->pfrukt_addrs = *ab; + ab->pfrb_size = 0; + ab->pfrb_msize = 0; + ab->pfrb_caddr = NULL; + } else + memset(&ukt->pfrukt_addrs, 0, + sizeof(struct pfr_buffer)); + + tbl = &ukt->pfrukt_t; + } + + if (strlcpy(tbl->pfrt_name, name, sizeof(tbl->pfrt_name)) >= + sizeof(tbl->pfrt_name) || strlcpy(tbl->pfrt_anchor, anchor, + sizeof(tbl->pfrt_anchor)) >= sizeof(tbl->pfrt_anchor)) + errx(1, "%s: strlcpy", __func__); + tbl->pfrt_flags = flags; + DBGPRINT("%s %s@%s [%x]\n", __func__, tbl->pfrt_name, + tbl->pfrt_anchor, tbl->pfrt_flags); + + /* + * non-root anchors processed by parse.y are loaded to kernel later. + * Here we load tables, which are either created for root anchor + * or by 'pfctl -t ... -T ...' command. + */ + if (ukt != NULL) + return (0); + + return pfr_ina_define(tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, + NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0); +} + +void +warn_duplicate_tables(const char *tablename, const char *anchorname) +{ + struct pfr_buffer b; + struct pfr_table *t; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_TABLES; + for (;;) { + pfr_buf_grow(&b, b.pfrb_size); + b.pfrb_size = b.pfrb_msize; + if (pfr_get_tables(NULL, b.pfrb_caddr, + &b.pfrb_size, PFR_FLAG_ALLRSETS)) + err(1, "pfr_get_tables"); + if (b.pfrb_size <= b.pfrb_msize) + break; + } + PFRB_FOREACH(t, &b) { + if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) + continue; + if (!strcmp(anchorname, t->pfrt_anchor)) + continue; + if (!strcmp(tablename, t->pfrt_name)) + warnx("warning: table <%s> already defined" + " in anchor \"%s\"", tablename, + t->pfrt_anchor[0] ? t->pfrt_anchor : "/"); + } + pfr_buf_clear(&b); +} + +void +xprintf(int opts, const char *fmt, ...) +{ + va_list args; + + if (opts & PF_OPT_QUIET) + return; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (opts & PF_OPT_DUMMYACTION) + fprintf(stderr, " (dummy).\n"); + else if (opts & PF_OPT_NOACTION) + fprintf(stderr, " (syntax only).\n"); + else + fprintf(stderr, ".\n"); +} + + +/* interface stuff */ + +void +pfctl_show_ifaces(const char *filter, int opts) +{ + struct pfr_buffer b; + struct pfi_kif *p; + + bzero(&b, sizeof(b)); + b.pfrb_type = PFRB_IFACES; + for (;;) { + pfr_buf_grow(&b, 0); + b.pfrb_size = b.pfrb_msize; + if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size)) + errx(1, "%s", pf_strerror(errno)); + if (b.pfrb_size < b.pfrb_msize) + break; + } + if (opts & PF_OPT_SHOWALL) + pfctl_print_title("INTERFACES:"); + PFRB_FOREACH(p, &b) + print_iface(p, opts); +} + +void +print_iface(struct pfi_kif *p, int opts) +{ + time_t tzero = p->pfik_tzero; + int i, af, dir, act; + char *ct; + + printf("%s", p->pfik_name); + if (opts & PF_OPT_VERBOSE) { + if (p->pfik_flags & PFI_IFLAG_SKIP) + printf(" (skip)"); + } + printf("\n"); + + if (!(opts & PF_OPT_VERBOSE2)) + return; + + ct = ctime(&tzero); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", tzero); + printf("\tReferences: [ States: %-18d Rules: %-18d ]\n", + p->pfik_states, p->pfik_rules); + for (i = 0; i < 8; i++) { + af = (i>>2) & 1; + dir = (i>>1) &1; + act = i & 1; + printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", + istats_text[af][dir][act], + (unsigned long long)p->pfik_packets[af][dir][act], + (unsigned long long)p->pfik_bytes[af][dir][act]); + } +} diff --git a/tools/tests/01_basic.conf b/tools/tests/01_basic.conf new file mode 100644 index 0000000..ea06221 --- /dev/null +++ b/tools/tests/01_basic.conf @@ -0,0 +1,32 @@ +# PFLINUX offline parse test 1 — macros, tables, anchors, filter rules. +# Only lo is referenced so the config parses on any Linux host. + +xif = "lo" +int_net = "127.0.0.0/8" + +table persist +table const { 10.0.0.0/8, 127.0.0.1 } +table const { $int_net } + +set skip on lo +set block-policy return +set loginterface $xif +set ruleset-optimization basic +set state-policy if-bound +set timeout { tcp.established 3600, tcp.closed 90 } +set limit states 100000 +set limit src-nodes 3000 + +anchor "in/*" +anchor "testsub" { + block drop in quick on $xif from to any label "blocked-in" + pass in quick on $xif proto tcp from any to any port 22 keep state +} + +load anchor "in/loaded" from "tests/anchor_loaded.conf" + +pass in quick on $xif inet proto tcp from any to any port { 80 443 8080 } \ + tag WEB keep state (max 1000) +pass in quick on $xif inet6 proto tcp from fe80::/10 to any port 443 +pass out quick on $xif proto udp from any to any port 53 +block drop in log quick on $xif from to any diff --git a/tools/tests/02_translations.conf b/tools/tests/02_translations.conf new file mode 100644 index 0000000..cf6d62c --- /dev/null +++ b/tools/tests/02_translations.conf @@ -0,0 +1,23 @@ +# PFLINUX offline parse test 2 — translations, redirections, diversion. + +xif = "lo" +table const { 127.0.0.1 } + +set reassemble yes no-df +set syncookies adaptive (start 25%, end 12%) + +pass in quick on $xif inet proto tcp from any to any port 8080 \ + rdr-to 127.0.0.1 port 80 sticky-address +pass out quick on $xif inet proto tcp from 127.0.0.0/8 to any \ + nat-to static-port +pass in quick on $xif inet6 proto tcp from any to any port 8080 \ + rdr-to ::1 +pass in quick on $xif inet proto tcp from any to any port 1723 \ + route-to 127.0.0.1 +pass in quick on $xif proto tcp from any to any port 21 \ + divert-to 127.0.0.1 port 7000 +pass in quick on $xif proto tcp from any to any port 110 divert-packet port 2000 +pass quick on $xif inet proto tcp from 127.0.0.5 to any binat-to 127.0.0.9 +pass in quick on $xif inet proto tcp all af-to inet6 from ::1 +match out quick on $xif inet proto tcp all set (delay 100) +block drop in quick on $xif inet from urpf-failed to any diff --git a/tools/tests/03_queues_scrub.conf b/tools/tests/03_queues_scrub.conf new file mode 100644 index 0000000..e8a6eb0 --- /dev/null +++ b/tools/tests/03_queues_scrub.conf @@ -0,0 +1,18 @@ +# PFLINUX offline parse test 3 — HFSC/fq_codel queues, scrub, limits, delay. + +xif = "lo" + +queue rootq on $xif bandwidth 1G, max 900M, min 10M +queue rtq on $xif bandwidth 100M burst 200M for 50ms parent rootq qlimit 500 +queue bulkq on $xif bandwidth 50M, min 5M parent rootq default +queue fqcq on $xif bandwidth 20M flows 1024 quantum 1514 parent rootq + +set timeout { frag 20, interval 10 } +set limit frags 10000 + +match in on $xif proto tcp all scrub (random-id) +match in on $xif inet proto tcp all scrub (max-mss 1440 min-ttl 10) +pass in quick on $xif proto tcp from any to any port 80 queue rtq +pass out quick on $xif proto tcp from any to any queue bulkq +pass in quick on $xif proto udp from any to any queue fqcq +block drop in quick on $xif all diff --git a/tools/tests/anchor_loaded.conf b/tools/tests/anchor_loaded.conf new file mode 100644 index 0000000..f05dfad --- /dev/null +++ b/tools/tests/anchor_loaded.conf @@ -0,0 +1,4 @@ +# PFLINUX offline parse test 1 — anchor body loaded from a file. + +block drop in quick on lo proto tcp from any to any port 139 +pass in quick on lo proto tcp from 127.0.0.0/8 to any port 389 tag LDAP diff --git a/tools/tests/broken.conf b/tools/tests/broken.conf new file mode 100644 index 0000000..f484294 --- /dev/null +++ b/tools/tests/broken.conf @@ -0,0 +1,5 @@ +# PFLINUX offline parse test — intentionally BROKEN config. +# `port` with no port spec is a syntax error; pfctl -n must exit non-zero. + +xif = "lo" +pass in quick on $xif all port