diff --git a/lib/Makefile b/lib/Makefile
index 80b77f1fd704..26b867ec00e1 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,231 +1,232 @@
 #	@(#)Makefile	8.1 (Berkeley) 6/4/93
 # $FreeBSD$
 
 .include <src.opts.mk>
 
 # The SUBDIR_BOOTSTRAP list is a small set of libraries which are used by many
 # of the other libraries.  These are built first with a .WAIT between them
 # and the main list to avoid needing a SUBDIR_DEPEND line on every library
 # naming just these few items.
 
 SUBDIR_BOOTSTRAP= \
 	csu \
 	.WAIT \
 	libc \
 	libc_nonshared \
 	libcompiler_rt \
 	${_libclang_rt} \
 	${_libcplusplus} \
 	${_libcxxrt} \
 	libelf \
 	libssp \
 	libssp_nonshared \
 	msun
 
 # The main list; please keep these sorted alphabetically.
 # The only exception is sqlite3: we place it at the start of the list since it
 # takes a long time to build and starting it first improves parallelism.
 
 SUBDIR=	${SUBDIR_BOOTSTRAP} \
 	.WAIT \
 	libsqlite3 \
 	geom \
 	lib9p \
 	libalias \
 	libarchive \
 	libauditd \
 	libbegemot \
 	libblocksruntime \
 	libbsdstat \
 	libbsm \
 	libbz2 \
 	libcalendar \
 	libcam \
 	libcapsicum \
 	libcasper \
 	libcompat \
 	libcrypt \
 	libdevctl \
 	libdevinfo \
 	libdevstat \
 	libdl \
 	libdwarf \
 	libedit \
 	libelftc \
 	libevent1 \
 	libexecinfo \
 	libexpat \
 	libfetch \
 	libfigpar \
 	libgcc_eh \
 	libgcc_s \
 	libgeom \
 	libifconfig \
 	libipsec \
 	libjail \
 	libkiconv \
 	libkvm \
 	liblua \
 	liblzma \
 	libmemstat \
 	libmd \
 	libmt \
 	lib80211 \
 	libnetbsd \
 	libnetmap \
 	libnv \
 	libopenbsd \
 	libopie \
 	libpam \
 	libpathconv \
 	libpcap \
 	libpjdlog \
 	libproc \
 	libprocstat \
 	libregex \
 	librpcsvc \
 	librss \
 	librt \
 	librtld_db \
 	libsbuf \
 	libsmb \
 	libstdbuf \
 	libstdthreads \
 	libsysdecode \
 	libtacplus \
 	libthr \
 	libthread_db \
 	libucl \
 	libufs \
 	libugidfw \
 	libulog \
 	libutil \
 	${_libvgl} \
 	libwrap \
 	libxo \
 	liby \
 	libz \
 	libzstd \
 	ncurses
 
 # Inter-library dependencies.  When the makefile for a library contains LDADD
 # libraries, those libraries should be listed as build order dependencies here.
 
 SUBDIR_DEPEND_geom=	libufs
 SUBDIR_DEPEND_googletest= libregex
 SUBDIR_DEPEND_libarchive= libz libbz2 libexpat liblzma libmd libzstd
 SUBDIR_DEPEND_libauditdm= libbsm
 SUBDIR_DEPEND_libbsnmp= ${_libnetgraph}
 SUBDIR_DEPEND_libc++:= libcxxrt
 # libssp_nonshared doesn't need to be linked into libc on every arch, but it is
 # small enough to build that this bit of serialization is likely insignificant.
 SUBDIR_DEPEND_libc= libcompiler_rt libssp_nonshared
 SUBDIR_DEPEND_libcam= libsbuf
 SUBDIR_DEPEND_libcasper= libnv
 SUBDIR_DEPEND_libdevstat= libkvm
 SUBDIR_DEPEND_libdpv= libfigpar ncurses libutil
 SUBDIR_DEPEND_libedit= ncurses
 SUBDIR_DEPEND_libgeom= libexpat libsbuf
 SUBDIR_DEPEND_librpcsec_gss= libgssapi
 SUBDIR_DEPEND_libmagic= libz
 SUBDIR_DEPEND_libmemstat= libkvm
 SUBDIR_DEPEND_libopie= libmd
 SUBDIR_DEPEND_libpam= libcrypt libopie ${_libradius} librpcsvc libtacplus libutil ${_libypclnt} ${_libcom_err} 
 SUBDIR_DEPEND_libpjdlog= libutil
 SUBDIR_DEPEND_libprocstat= libkvm libutil
 SUBDIR_DEPEND_libradius= libmd
 SUBDIR_DEPEND_libsmb= libkiconv
 SUBDIR_DEPEND_libtacplus= libmd
 SUBDIR_DEPEND_libulog= libmd
 SUBDIR_DEPEND_libunbound= ${_libldns}
 SUBDIR_DEPEND_liblzma= libthr
 .if ${MK_OFED} != "no"
 SUBDIR_DEPEND_libpcap= ofed
 .endif
 
 .if !defined(COMPAT_32BIT)
 SUBDIR+=	flua
 SUBDIR_DEPEND_flua=	libjail
 .endif
 
 # NB: keep these sorted by MK_* knobs
 
 SUBDIR.${MK_ATM}+=	libngatm
 SUBDIR.${MK_BEARSSL}+=	libbearssl libsecureboot
 SUBDIR.${MK_BLACKLIST}+=libblacklist
 SUBDIR.${MK_BLUETOOTH}+=libbluetooth libsdp
 SUBDIR.${MK_BSNMP}+=	libbsnmp
 
 .if !defined(COMPAT_32BIT) && !defined(COMPAT_SOFTFP)
 SUBDIR.${MK_CLANG}+=	clang
 .endif
 
 SUBDIR.${MK_CUSE}+= 	libcuse
 SUBDIR.${MK_CXX}+=	libdevdctl
 SUBDIR.${MK_TOOLCHAIN}+=libpe
 SUBDIR.${MK_DIALOG}+=	libdpv
 SUBDIR.${MK_FILE}+=	libmagic
 SUBDIR.${MK_GPIO}+=	libgpio
 SUBDIR.${MK_GSSAPI}+=	libgssapi librpcsec_gss
 SUBDIR.${MK_ICONV}+=	libiconv_modules
 SUBDIR.${MK_KERBEROS_SUPPORT}+=	libcom_err
 SUBDIR.${MK_LDNS}+=	libldns
 SUBDIR.${MK_STATS}+=	libstats
 
 # The libraries under libclang_rt can only be built by clang, and only make
 # sense to build when clang is enabled at all.  Furthermore, they can only be
 # built for certain architectures.
 .if ${COMPILER_TYPE} == "clang" && \
     (${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \
     ${MACHINE_CPUARCH} == "arm" || ${MACHINE_CPUARCH} == "i386" || \
     ${MACHINE_CPUARCH} == "powerpc")
 _libclang_rt=	libclang_rt
 .endif
 
 .if ${MK_CXX} != "no"
 _libcxxrt=	libcxxrt
 _libcplusplus=	libc++
 _libcplusplus+=	libc++experimental
 .endif
 
 SUBDIR.${MK_EFI}+=	libefivar
 SUBDIR.${MK_GOOGLETEST}+=	googletest
 SUBDIR.${MK_NETGRAPH}+=	libnetgraph
 SUBDIR.${MK_NIS}+=	libypclnt
 
 .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64"
 _libvgl=	libvgl
 .endif
 
 .if ${MACHINE_CPUARCH} == "aarch64"
 SUBDIR.${MK_PMC}+=	libopencsd
 .endif
 
 .if ${MACHINE_CPUARCH} == "amd64"
 SUBDIR.${MK_PMC}+=	libipt
 SUBDIR.${MK_BHYVE}+=	libvmmapi
 .endif
 
 .if ${MACHINE_ARCH} != "powerpc"
 SUBDIR.${MK_OPENMP}+=	libomp
 .endif
 SUBDIR.${MK_OPENSSL}+=	libmp
+SUBDIR.${MK_PF}+=	libpfctl
 SUBDIR.${MK_PMC}+=	libpmc libpmcstat
 SUBDIR.${MK_RADIUS_SUPPORT}+=	libradius
 SUBDIR.${MK_SENDMAIL}+=	libmilter libsm libsmdb libsmutil
 SUBDIR.${MK_TELNET}+=	libtelnet
 SUBDIR.${MK_TESTS_SUPPORT}+=	atf
 SUBDIR.${MK_TESTS_SUPPORT}.${MK_CXX}+=	kyua
 SUBDIR.${MK_TESTS_SUPPORT}.${MK_CXX}+=	liblutok
 SUBDIR.${MK_TESTS}+=	tests
 SUBDIR.${MK_UNBOUND}+=	libunbound
 SUBDIR.${MK_USB}+=	libusbhid libusb
 SUBDIR.${MK_OFED}+=	ofed
 SUBDIR.${MK_VERIEXEC}+=	libveriexec
 SUBDIR.${MK_ZFS}+=	libbe
 
 .if !make(install)
 SUBDIR_PARALLEL=
 .endif
 
 .include <bsd.subdir.mk>
diff --git a/lib/libpfctl/Makefile b/lib/libpfctl/Makefile
new file mode 100644
index 000000000000..d7a00a94b349
--- /dev/null
+++ b/lib/libpfctl/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PACKAGE=	lib${LIB}
+LIB=		pfctl
+INTERNALLIB=	true
+
+SRCS=	libpfctl.c
+INCS=	libpfctl.h
+
+CFLAGS+=	-fPIC
+
+.include <bsd.lib.mk>
diff --git a/sbin/pfctl/pfctl_ioctl.c b/lib/libpfctl/libpfctl.c
similarity index 62%
rename from sbin/pfctl/pfctl_ioctl.c
rename to lib/libpfctl/libpfctl.c
index 878a57de0fe4..e0d429112f5b 100644
--- a/sbin/pfctl/pfctl_ioctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1,339 +1,536 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  *
  *    - Redistributions of source code must retain the above copyright
  *      notice, this list of conditions and the following disclaimer.
  *    - Redistributions in binary form must reproduce the above
  *      copyright notice, this list of conditions and the following
  *      disclaimer in the documentation and/or other materials provided
  *      with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #include <sys/cdefs.h>
 
 #include <sys/ioctl.h>
 #include <sys/nv.h>
 #include <sys/queue.h>
 #include <sys/types.h>
 
 #include <net/if.h>
 #include <net/pfvar.h>
 #include <netinet/in.h>
 
 #include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "pfctl_ioctl.h"
+#include "libpfctl.h"
 
 static void
 pf_nvuint_8_array(const nvlist_t *nvl, const char *name, size_t maxelems,
     u_int8_t *numbers, size_t *nelems)
 {
 	const uint64_t *tmp;
 	size_t elems;
 
 	tmp = nvlist_get_number_array(nvl, name, &elems);
 	assert(elems <= maxelems);
 
 	for (size_t i = 0; i < elems; i++)
 		numbers[i] = tmp[i];
 
 	if (nelems)
 		*nelems = elems;
 }
 
 static void
 pf_nvuint_16_array(const nvlist_t *nvl, const char *name, size_t maxelems,
     u_int16_t *numbers, size_t *nelems)
 {
 	const uint64_t *tmp;
 	size_t elems;
 
 	tmp = nvlist_get_number_array(nvl, name, &elems);
 	assert(elems <= maxelems);
 
 	for (size_t i = 0; i < elems; i++)
 		numbers[i] = tmp[i];
 
 	if (nelems)
 		*nelems = elems;
 }
 
 static void
 pf_nvuint_32_array(const nvlist_t *nvl, const char *name, size_t maxelems,
     u_int32_t *numbers, size_t *nelems)
 {
 	const uint64_t *tmp;
 	size_t elems;
 
 	tmp = nvlist_get_number_array(nvl, name, &elems);
 	assert(elems <= maxelems);
 
 	for (size_t i = 0; i < elems; i++)
 		numbers[i] = tmp[i];
 
 	if (nelems)
 		*nelems = elems;
 }
 
 static void
 pf_nvuint_64_array(const nvlist_t *nvl, const char *name, size_t maxelems,
     u_int64_t *numbers, size_t *nelems)
 {
 	const uint64_t *tmp;
 	size_t elems;
 
 	tmp = nvlist_get_number_array(nvl, name, &elems);
 	assert(elems <= maxelems);
 
 	for (size_t i = 0; i < elems; i++)
 		numbers[i] = tmp[i];
 
 	if (nelems)
 		*nelems = elems;
 }
 
+static void
+pfctl_nv_add_addr(nvlist_t *nvparent, const char *name,
+    const struct pf_addr *addr)
+{
+	nvlist_t *nvl = nvlist_create(0);
+
+	nvlist_add_binary(nvl, "addr", addr, sizeof(*addr));
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvaddr_to_addr(const nvlist_t *nvl, struct pf_addr *addr)
 {
 	size_t len;
 	const void *data;
 
 	data = nvlist_get_binary(nvl, "addr", &len);
 	assert(len == sizeof(struct pf_addr));
 	memcpy(addr, data, len);
 }
 
+static void
+pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name,
+    const struct pf_addr_wrap *addr)
+{
+	nvlist_t *nvl = nvlist_create(0);
+
+	nvlist_add_number(nvl, "type", addr->type);
+	nvlist_add_number(nvl, "iflags", addr->iflags);
+	nvlist_add_string(nvl, "ifname", addr->v.ifname);
+	nvlist_add_string(nvl, "tblname", addr->v.tblname);
+	pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr);
+	pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask);
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvaddr_wrap_to_addr_wrap(const nvlist_t *nvl, struct pf_addr_wrap *addr)
 {
 	addr->type = nvlist_get_number(nvl, "type");
 	addr->iflags = nvlist_get_number(nvl, "iflags");
 	strlcpy(addr->v.ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ);
 	strlcpy(addr->v.tblname, nvlist_get_string(nvl, "tblname"),
 	    PF_TABLE_NAME_SIZE);
 
 	pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &addr->v.a.addr);
 	pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "mask"), &addr->v.a.mask);
 }
 
+static void
+pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name,
+    const struct pf_rule_addr *addr)
+{
+	u_int64_t ports[2];
+	nvlist_t *nvl = nvlist_create(0);
+
+	pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr);
+	ports[0] = addr->port[0];
+	ports[1] = addr->port[1];
+	nvlist_add_number_array(nvl, "port", ports, 2);
+	nvlist_add_number(nvl, "neg", addr->neg);
+	nvlist_add_number(nvl, "port_op", addr->port_op);
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvrule_addr_to_rule_addr(const nvlist_t *nvl, struct pf_rule_addr *addr)
 {
 	pf_nvaddr_wrap_to_addr_wrap(nvlist_get_nvlist(nvl, "addr"), &addr->addr);
 
 	pf_nvuint_16_array(nvl, "port", 2, addr->port, NULL);
 	addr->neg = nvlist_get_number(nvl, "neg");
 	addr->port_op = nvlist_get_number(nvl, "port_op");
 }
 
+static void
+pfctl_nv_add_pool(nvlist_t *nvparent, const char *name,
+    const struct pf_pool *pool)
+{
+	u_int64_t ports[2];
+	nvlist_t *nvl = nvlist_create(0);
+
+	nvlist_add_binary(nvl, "key", &pool->key, sizeof(pool->key));
+	pfctl_nv_add_addr(nvl, "counter", &pool->counter);
+	nvlist_add_number(nvl, "tblidx", pool->tblidx);
+
+	ports[0] = pool->proxy_port[0];
+	ports[1] = pool->proxy_port[1];
+	nvlist_add_number_array(nvl, "proxy_port", ports, 2);
+	nvlist_add_number(nvl, "opts", pool->opts);
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvpool_to_pool(const nvlist_t *nvl, struct pf_pool *pool)
 {
 	size_t len;
 	const void *data;
 
 	data = nvlist_get_binary(nvl, "key", &len);
 	assert(len == sizeof(pool->key));
 	memcpy(&pool->key, data, len);
 
 	pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "counter"), &pool->counter);
 
 	pool->tblidx = nvlist_get_number(nvl, "tblidx");
 	pf_nvuint_16_array(nvl, "proxy_port", 2, pool->proxy_port, NULL);
 	pool->opts = nvlist_get_number(nvl, "opts");
 }
 
+static void
+pfctl_nv_add_uid(nvlist_t *nvparent, const char *name,
+    const struct pf_rule_uid *uid)
+{
+	u_int64_t uids[2];
+	nvlist_t *nvl = nvlist_create(0);
+
+	uids[0] = uid->uid[0];
+	uids[1] = uid->uid[1];
+	nvlist_add_number_array(nvl, "uid", uids, 2);
+	nvlist_add_number(nvl, "op", uid->op);
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvrule_uid_to_rule_uid(const nvlist_t *nvl, struct pf_rule_uid *uid)
 {
 	pf_nvuint_32_array(nvl, "uid", 2, uid->uid, NULL);
 	uid->op = nvlist_get_number(nvl, "op");
 }
 
+static void
+pfctl_nv_add_divert(nvlist_t *nvparent, const char *name,
+    const struct pf_rule *r)
+{
+	nvlist_t *nvl = nvlist_create(0);
+
+	pfctl_nv_add_addr(nvl, "addr", &r->divert.addr);
+	nvlist_add_number(nvl, "port", r->divert.port);
+
+	nvlist_add_nvlist(nvparent, name, nvl);
+}
+
 static void
 pf_nvdivert_to_divert(const nvlist_t *nvl, struct pf_rule *rule)
 {
 	pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &rule->divert.addr);
 	rule->divert.port = nvlist_get_number(nvl, "port");
 }
 
 static void
 pf_nvrule_to_rule(const nvlist_t *nvl, struct pf_rule *rule)
 {
 	const uint64_t *skip;
 	size_t skipcount;
 
 	rule->nr = nvlist_get_number(nvl, "nr");
 
 	pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "src"), &rule->src);
 	pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst);
 
 	skip = nvlist_get_number_array(nvl, "skip", &skipcount);
 	assert(skip);
 	assert(skipcount == PF_SKIP_COUNT);
 	for (int i = 0; i < PF_SKIP_COUNT; i++)
 		rule->skip[i].nr = skip[i];
 
 	strlcpy(rule->label, nvlist_get_string(nvl, "label"), PF_RULE_LABEL_SIZE);
 	strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ);
 	strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE);
 	strlcpy(rule->pqname, nvlist_get_string(nvl, "pqname"), PF_QNAME_SIZE);
 	strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"),
 	    PF_TAG_NAME_SIZE);
 	strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"),
 	    PF_TAG_NAME_SIZE);
 
 	strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"),
 	    PF_TABLE_NAME_SIZE);
 
 	pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool);
 
 	rule->evaluations = nvlist_get_number(nvl, "evaluations");
 	pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL);
 	pf_nvuint_64_array(nvl, "bytes", 2, rule->bytes, NULL);
 
 	rule->os_fingerprint = nvlist_get_number(nvl, "os_fingerprint");
 
 	rule->rtableid = nvlist_get_number(nvl, "rtableid");
 	pf_nvuint_32_array(nvl, "timeout", PFTM_MAX, rule->timeout, NULL);
 	rule->max_states = nvlist_get_number(nvl, "max_states");
 	rule->max_src_nodes = nvlist_get_number(nvl, "max_src_nodes");
 	rule->max_src_states = nvlist_get_number(nvl, "max_src_states");
 	rule->max_src_conn = nvlist_get_number(nvl, "max_src_conn");
 	rule->max_src_conn_rate.limit =
 	    nvlist_get_number(nvl, "max_src_conn_rate.limit");
 	rule->max_src_conn_rate.seconds =
 	    nvlist_get_number(nvl, "max_src_conn_rate.seconds");
 	rule->qid = nvlist_get_number(nvl, "qid");
 	rule->pqid = nvlist_get_number(nvl, "pqid");
 	rule->prob = nvlist_get_number(nvl, "prob");
 	rule->cuid = nvlist_get_number(nvl, "cuid");
 	rule->cpid = nvlist_get_number(nvl, "cpid");
 
 	rule->return_icmp = nvlist_get_number(nvl, "return_icmp");
 	rule->return_icmp6 = nvlist_get_number(nvl, "return_icmp6");
 	rule->max_mss = nvlist_get_number(nvl, "max_mss");
 	rule->scrub_flags = nvlist_get_number(nvl, "scrub_flags");
 
 	pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "uid"), &rule->uid);
 	pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "gid"),
 	    (struct pf_rule_uid *)&rule->gid);
 
 	rule->rule_flag = nvlist_get_number(nvl, "rule_flag");
 	rule->action = nvlist_get_number(nvl, "action");
 	rule->direction = nvlist_get_number(nvl, "direction");
 	rule->log = nvlist_get_number(nvl, "log");
 	rule->logif = nvlist_get_number(nvl, "logif");
 	rule->quick = nvlist_get_number(nvl, "quick");
 	rule->ifnot = nvlist_get_number(nvl, "ifnot");
 	rule->match_tag_not = nvlist_get_number(nvl, "match_tag_not");
 	rule->natpass = nvlist_get_number(nvl, "natpass");
 
 	rule->keep_state = nvlist_get_number(nvl, "keep_state");
 	rule->af = nvlist_get_number(nvl, "af");
 	rule->proto = nvlist_get_number(nvl, "proto");
 	rule->type = nvlist_get_number(nvl, "type");
 	rule->code = nvlist_get_number(nvl, "code");
 	rule->flags = nvlist_get_number(nvl, "flags");
 	rule->flagset = nvlist_get_number(nvl, "flagset");
 	rule->min_ttl = nvlist_get_number(nvl, "min_ttl");
 	rule->allow_opts = nvlist_get_number(nvl, "allow_opts");
 	rule->rt = nvlist_get_number(nvl, "rt");
 	rule->return_ttl  = nvlist_get_number(nvl, "return_ttl");
 	rule->tos = nvlist_get_number(nvl, "tos");
 	rule->set_tos = nvlist_get_number(nvl, "set_tos");
 	rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative");
 	rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard");
 
 	rule->flush = nvlist_get_number(nvl, "flush");
 	rule->prio = nvlist_get_number(nvl, "prio");
 	pf_nvuint_8_array(nvl, "set_prio", 2, rule->set_prio, NULL);
 
 	pf_nvdivert_to_divert(nvlist_get_nvlist(nvl, "divert"), rule);
 
 	rule->u_states_cur = nvlist_get_number(nvl, "states_cur");
 	rule->u_states_tot = nvlist_get_number(nvl, "states_tot");
 	rule->u_src_nodes = nvlist_get_number(nvl, "src_nodes");
 }
 
+int
+pfctl_add_rule(int dev, const struct pf_rule *r, const char *anchor,
+    const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket)
+{
+	struct pfioc_nv nv;
+	u_int64_t timeouts[PFTM_MAX];
+	u_int64_t set_prio[2];
+	nvlist_t *nvl, *nvlr;
+	int ret;
+
+	nvl = nvlist_create(0);
+	nvlr = nvlist_create(0);
+
+	nvlist_add_number(nvl, "ticket", ticket);
+	nvlist_add_number(nvl, "pool_ticket", pool_ticket);
+	nvlist_add_string(nvl, "anchor", anchor);
+	nvlist_add_string(nvl, "anchor_call", anchor_call);
+
+	nvlist_add_number(nvlr, "nr", r->nr);
+	pfctl_nv_add_rule_addr(nvlr, "src", &r->src);
+	pfctl_nv_add_rule_addr(nvlr, "dst", &r->dst);
+
+	nvlist_add_string(nvlr, "label", r->label);
+	nvlist_add_string(nvlr, "ifname", r->ifname);
+	nvlist_add_string(nvlr, "qname", r->qname);
+	nvlist_add_string(nvlr, "pqname", r->pqname);
+	nvlist_add_string(nvlr, "tagname", r->tagname);
+	nvlist_add_string(nvlr, "match_tagname", r->match_tagname);
+	nvlist_add_string(nvlr, "overload_tblname", r->overload_tblname);
+
+	pfctl_nv_add_pool(nvlr, "rpool", &r->rpool);
+
+	nvlist_add_number(nvlr, "os_fingerprint", r->os_fingerprint);
+
+	nvlist_add_number(nvlr, "rtableid", r->rtableid);
+	for (int i = 0; i < PFTM_MAX; i++)
+		timeouts[i] = r->timeout[i];
+	nvlist_add_number_array(nvlr, "timeout", timeouts, PFTM_MAX);
+	nvlist_add_number(nvlr, "max_states", r->max_states);
+	nvlist_add_number(nvlr, "max_src_nodes", r->max_src_nodes);
+	nvlist_add_number(nvlr, "max_src_states", r->max_src_states);
+	nvlist_add_number(nvlr, "max_src_conn", r->max_src_conn);
+	nvlist_add_number(nvlr, "max_src_conn_rate.limit",
+	    r->max_src_conn_rate.limit);
+	nvlist_add_number(nvlr, "max_src_conn_rate.seconds",
+	    r->max_src_conn_rate.seconds);
+	nvlist_add_number(nvlr, "prob", r->prob);
+	nvlist_add_number(nvlr, "cuid", r->cuid);
+	nvlist_add_number(nvlr, "cpid", r->cpid);
+
+	nvlist_add_number(nvlr, "return_icmp", r->return_icmp);
+	nvlist_add_number(nvlr, "return_icmp6", r->return_icmp6);
+
+	nvlist_add_number(nvlr, "max_mss", r->max_mss);
+	nvlist_add_number(nvlr, "scrub_flags", r->scrub_flags);
+
+	pfctl_nv_add_uid(nvlr, "uid", &r->uid);
+	pfctl_nv_add_uid(nvlr, "gid", (const struct pf_rule_uid *)&r->gid);
+
+	nvlist_add_number(nvlr, "rule_flag", r->rule_flag);
+	nvlist_add_number(nvlr, "action", r->action);
+	nvlist_add_number(nvlr, "direction", r->direction);
+	nvlist_add_number(nvlr, "log", r->log);
+	nvlist_add_number(nvlr, "logif", r->logif);
+	nvlist_add_number(nvlr, "quick", r->quick);
+	nvlist_add_number(nvlr, "ifnot", r->ifnot);
+	nvlist_add_number(nvlr, "match_tag_not", r->match_tag_not);
+	nvlist_add_number(nvlr, "natpass", r->natpass);
+
+	nvlist_add_number(nvlr, "keep_state", r->keep_state);
+	nvlist_add_number(nvlr, "af", r->af);
+	nvlist_add_number(nvlr, "proto", r->proto);
+	nvlist_add_number(nvlr, "type", r->type);
+	nvlist_add_number(nvlr, "code", r->code);
+	nvlist_add_number(nvlr, "flags", r->flags);
+	nvlist_add_number(nvlr, "flagset", r->flagset);
+	nvlist_add_number(nvlr, "min_ttl", r->min_ttl);
+	nvlist_add_number(nvlr, "allow_opts", r->allow_opts);
+	nvlist_add_number(nvlr, "rt", r->rt);
+	nvlist_add_number(nvlr, "return_ttl", r->return_ttl);
+	nvlist_add_number(nvlr, "tos", r->tos);
+	nvlist_add_number(nvlr, "set_tos", r->set_tos);
+	nvlist_add_number(nvlr, "anchor_relative", r->anchor_relative);
+	nvlist_add_number(nvlr, "anchor_wildcard", r->anchor_wildcard);
+
+	nvlist_add_number(nvlr, "flush", r->flush);
+
+	nvlist_add_number(nvlr, "prio", r->prio);
+	set_prio[0] = r->set_prio[0];
+	set_prio[1] = r->set_prio[1];
+	nvlist_add_number_array(nvlr, "set_prio", set_prio, 2);
+
+	pfctl_nv_add_divert(nvlr, "divert", r);
+
+	nvlist_add_nvlist(nvl, "rule", nvlr);
+
+	/* Now do the call. */
+	nv.data = nvlist_pack(nvl, &nv.len);
+	nv.size = nv.len;
+
+	ret = ioctl(dev, DIOCADDRULENV, &nv);
+
+	free(nv.data);
+	nvlist_destroy(nvl);
+
+	return (ret);
+}
 
 int
 pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, const char *anchor,
     u_int32_t ruleset, struct pf_rule *rule, char *anchor_call)
 {
 	struct pfioc_nv nv;
 	nvlist_t *nvl;
 	void *nvlpacked;
 	int ret;
 
 	nvl = nvlist_create(0);
 	if (nvl == 0)
 		return (ENOMEM);
 
 	nvlist_add_number(nvl, "nr", nr);
 	nvlist_add_number(nvl, "ticket", ticket);
 	nvlist_add_string(nvl, "anchor", anchor);
 	nvlist_add_number(nvl, "ruleset", ruleset);
 
 	nvlpacked = nvlist_pack(nvl, &nv.len);
 	if (nvlpacked == NULL) {
 		nvlist_destroy(nvl);
 		return (ENOMEM);
 	}
 	nv.data = malloc(8182);
 	nv.size = 8192;
 	assert(nv.len <= nv.size);
 	memcpy(nv.data, nvlpacked, nv.len);
 	nvlist_destroy(nvl);
 	nvl = NULL;
 	free(nvlpacked);
 
 	ret = ioctl(dev, DIOCGETRULENV, &nv);
 	if (ret != 0) {
 		free(nv.data);
 		return (ret);
 	}
 
 	nvl = nvlist_unpack(nv.data, nv.len, 0);
 	if (nvl == NULL) {
 		free(nv.data);
 		return (EIO);
 	}
 
 	pf_nvrule_to_rule(nvlist_get_nvlist(nvl, "rule"), rule);
 
 	if (anchor_call)
 		strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"),
 		    MAXPATHLEN);
 
 	free(nv.data);
 	nvlist_destroy(nvl);
 
 	return (0);
 }
diff --git a/sbin/pfctl/pfctl_ioctl.h b/lib/libpfctl/libpfctl.h
similarity index 92%
copy from sbin/pfctl/pfctl_ioctl.h
copy to lib/libpfctl/libpfctl.h
index 41dd0776854a..65ff2179f23d 100644
--- a/sbin/pfctl/pfctl_ioctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -1,43 +1,45 @@
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  *
  *    - Redistributions of source code must retain the above copyright
  *      notice, this list of conditions and the following disclaimer.
  *    - Redistributions in binary form must reproduce the above
  *      copyright notice, this list of conditions and the following
  *      disclaimer in the documentation and/or other materials provided
  *      with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * $FreeBSD$
  */
 
 #ifndef _PFCTL_IOCTL_H_
 #define _PFCTL_IOCTL_H_
 
 #include <netpfil/pf/pf.h>
 
 int	pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket,
 	    const char *anchor, u_int32_t ruleset, struct pf_rule *rule,
 	    char *anchor_call);
+int	pfctl_add_rule(int dev, const struct pf_rule *r, const char *anchor,
+	    const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket);
 
 #endif
diff --git a/sbin/pfctl/Makefile b/sbin/pfctl/Makefile
index c84d558c989d..49bdfb9e3733 100644
--- a/sbin/pfctl/Makefile
+++ b/sbin/pfctl/Makefile
@@ -1,35 +1,36 @@
 # $FreeBSD$
 
 .include <src.opts.mk>
 
 PACKAGE=pf
 CONFS=	pf.os
 PROG=	pfctl
 MAN=	pfctl.8
 
 SRCS = pfctl.c parse.y pfctl_parser.c pf_print_state.c pfctl_altq.c
 SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c pfctl_qstats.c
-SRCS+= pfctl_optimize.c pfctl_ioctl.c
+SRCS+= pfctl_optimize.c
 SRCS+= pf_ruleset.c
 
 WARNS?=	2
 CFLAGS+= -Wall -Wmissing-prototypes -Wno-uninitialized
 CFLAGS+= -Wstrict-prototypes
 CFLAGS+= -DENABLE_ALTQ -I${.CURDIR}
+CFLAGS+= -I${SRCTOP}/lib/libpfctl -I${OBJTOP}/lib/libpfctl
 
 # Need to use "WITH_" prefix to not conflict with the l/y INET/INET6 keywords
 .if ${MK_INET6_SUPPORT} != "no"
 CFLAGS+= -DWITH_INET6
 .endif
 .if ${MK_INET_SUPPORT} != "no"
 CFLAGS+= -DWITH_INET
 .endif
 
 YFLAGS=
 
-LIBADD=	m md nv
+LIBADD=	m md pfctl
 
 HAS_TESTS=
 SUBDIR.${MK_TESTS}+= tests
 
 .include <bsd.prog.mk>
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 9db85538feaf..89e421e6b5ad 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -1,6378 +1,6378 @@
 /*	$OpenBSD: parse.y,v 1.554 2008/10/17 12:59:53 henning Exp $	*/
 
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * 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,2003 Henning Brauer. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 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 <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #define PFIOC_USE_LATEST
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #ifdef __FreeBSD__
 #include <sys/sysctl.h>
 #endif
 #include <net/if.h>
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
 #include <net/pfvar.h>
 #include <arpa/inet.h>
 #include <net/altq/altq.h>
 #include <net/altq/altq_cbq.h>
 #include <net/altq/altq_codel.h>
 #include <net/altq/altq_priq.h>
 #include <net/altq/altq_hfsc.h>
 #include <net/altq/altq_fairq.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <netdb.h>
 #include <stdarg.h>
 #include <errno.h>
 #include <string.h>
 #include <ctype.h>
 #include <math.h>
 #include <err.h>
 #include <limits.h>
 #include <pwd.h>
 #include <grp.h>
 #include <md5.h>
 
 #include "pfctl_parser.h"
 #include "pfctl.h"
 
 static struct pfctl	*pf = NULL;
 static int		 debug = 0;
 static int		 rulestate = 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		 failpolicy = PFRULE_DROP;
 static int		 require_order = 1;
 static int		 default_statelock;
 
 static TAILQ_HEAD(files, file)	 files = TAILQ_HEAD_INITIALIZER(files);
 static struct file {
 	TAILQ_ENTRY(file)	 entry;
 	FILE			*stream;
 	char			*name;
 	int			 lineno;
 	int			 errors;
 } *file;
 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 *, ...);
 int		 kw_cmp(const void *, const void *);
 int		 lookup(char *);
 int		 lgetc(int);
 int		 lungetc(int);
 int		 findeol(void);
 
 static 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 *);
 
 enum {
 	PFCTL_STATE_NONE,
 	PFCTL_STATE_OPTION,
 	PFCTL_STATE_SCRUB,
 	PFCTL_STATE_QUEUE,
 	PFCTL_STATE_NAT,
 	PFCTL_STATE_FILTER
 };
 
 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_int8_t		 code;
 	u_int8_t		 type;
 	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, };
 
 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;
 };
 
 static 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;
 }	*queues = NULL;
 
 struct node_qassign {
 	char		*qname;
 	char		*pqname;
 };
 
 static struct filter_opts {
 	int			 marker;
 #define FOM_FLAGS	0x01
 #define FOM_ICMP	0x02
 #define FOM_TOS		0x04
 #define FOM_KEEP	0x08
 #define FOM_SRCTRACK	0x10
 #define FOM_SETPRIO	0x0400
 #define FOM_PRIO	0x2000
 	struct node_uid		*uid;
 	struct node_gid		*gid;
 	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 {
 		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];
 	struct {
 		struct node_host	*addr;
 		u_int16_t		port;
 	}			 divert;
 } filter_opts;
 
 static struct antispoof_opts {
 	char			*label;
 	u_int			 rtableid;
 } antispoof_opts;
 
 static struct scrub_opts {
 	int			 marker;
 #define SOM_MINTTL	0x01
 #define SOM_MAXMSS	0x02
 #define SOM_FRAGCACHE	0x04
 #define SOM_SETTOS	0x08
 	int			 nodf;
 	int			 minttl;
 	int			 maxmss;
 	int			 settos;
 	int			 fragcache;
 	int			 randomid;
 	int			 reassemble_tcp;
 	char			*match_tag;
 	u_int8_t		 match_tag_not;
 	u_int			 rtableid;
 } scrub_opts;
 
 static struct queue_opts {
 	int			marker;
 #define QOM_BWSPEC	0x01
 #define QOM_SCHEDULER	0x02
 #define QOM_PRIORITY	0x04
 #define QOM_TBRSIZE	0x08
 #define QOM_QLIMIT	0x10
 	struct node_queue_bw	queue_bwspec;
 	struct node_queue_opt	scheduler;
 	int			priority;
 	unsigned int		tbrsize;
 	int			qlimit;
 } queue_opts;
 
 static struct table_opts {
 	int			flags;
 	int			init_addr;
 	struct node_tinithead	init_nodes;
 } table_opts;
 
 static 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;
 
 static struct codel_opts	 codel_opts;
 static struct node_hfsc_opts	 hfsc_opts;
 static struct node_fairq_opts	 fairq_opts;
 static struct node_state_opt	*keep_state_defaults = NULL;
 
 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);
 int		 filter_consistent(struct pf_rule *, int);
 int		 nat_consistent(struct pf_rule *);
 int		 rdr_consistent(struct pf_rule *);
 int		 process_tabledef(char *, struct table_opts *);
 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, u_int8_t,
 		    struct node_host *);
 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_nr(const char *, char *, size_t);
 void		 expand_label(char *, size_t, const char *, u_int8_t,
 		    struct node_host *, struct node_port *, struct node_host *,
 		    struct node_port *, u_int8_t);
 void		 expand_rule(struct pf_rule *, struct node_if *,
 		    struct node_host *, 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_icmp *, const char *);
 int		 expand_altq(struct pf_altq *, struct node_if *,
 		    struct node_queue *, struct node_queue_bw bwspec,
 		    struct node_queue_opt *);
 int		 expand_queue(struct pf_altq *, struct node_if *,
 		    struct node_queue *, struct node_queue_bw,
 		    struct node_queue_opt *);
 int		 expand_skip_interface(struct node_if *);
 
 int	 check_rulestate(int);
 int	 getservice(char *);
 int	 rule_label(struct pf_rule *, char *);
 int	 rt_tableid_max(void);
 
 void	 mv_rules(struct pf_ruleset *, struct pf_ruleset *);
 void	 decide_address_family(struct node_host *, sa_family_t *);
 void	 remove_invalid_hosts(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 *);
 
 static 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;
 		struct {
 			u_int8_t	 b1;
 			u_int8_t	 b2;
 			u_int16_t	 w;
 			u_int16_t	 w2;
 		}			 b;
 		struct range {
 			int		 a;
 			int		 b;
 			int		 t;
 		}			 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 {
 			struct node_host	*host;
 			u_int8_t		 rt;
 			u_int8_t		 pool_opts;
 			sa_family_t		 af;
 			struct pf_poolhashkey	*key;
 		}			 route;
 		struct redirection {
 			struct node_host	*host;
 			struct range		 rport;
 		}			*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 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 node_fairq_opts	 fairq_opts;
 		struct codel_opts	 codel_opts;
 	} 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((addr).v.ifname[strlen((addr).v.ifname)-1])))
 
 %}
 
 %token	PASS BLOCK 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 RDR NAT BINAT ARROW NODF
 %token	MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL
 %token	NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE
 %token	REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
 %token	SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY
 %token	RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
 %token	ANTISPOOF FOR INCLUDE
 %token	BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY
 %token	ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
 %token	UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL
 %token	LOAD RULESET_OPTIMIZATION PRIO
 %token	STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
 %token	MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY
 %token	TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
 %token	DIVERTTO DIVERTREPLY
 %token	<v.string>		STRING
 %token	<v.number>		NUMBER
 %token	<v.i>			PORTBINARY
 %type	<v.interface>		interface if_list if_item_not if_item
 %type	<v.number>		number icmptype icmp6type uid gid
 %type	<v.number>		tos not yesno
 %type	<v.probability>		probability
 %type	<v.i>			no dir af fragcache optimizer
 %type	<v.i>			sourcetrack flush unaryop statelock
 %type	<v.b>			action nataction natpasslog scrubaction
 %type	<v.b>			flags flag blockspec prio
 %type	<v.range>		portplain portstar portrange
 %type	<v.hashkey>		hashkey
 %type	<v.proto>		proto proto_list proto_item
 %type	<v.number>		protoval
 %type	<v.icmp>		icmpspec
 %type	<v.icmp>		icmp_list icmp_item
 %type	<v.icmp>		icmp6_list icmp6_item
 %type	<v.number>		reticmpspec reticmp6spec
 %type	<v.fromto>		fromto
 %type	<v.peer>		ipportspec from to
 %type	<v.host>		ipspec toipspec xhost host dynaddr host_list
 %type	<v.host>		redir_host_list redirspec
 %type	<v.host>		route_host route_host_list routespec
 %type	<v.os>			os xos os_list
 %type	<v.port>		portspec port_list port_item
 %type	<v.uid>			uids uid_list uid_item
 %type	<v.gid>			gids gid_list gid_item
 %type	<v.route>		route
 %type	<v.redirection>		redirection redirpool
 %type	<v.string>		label stringall tag anchorname
 %type	<v.string>		string varstring numberstring
 %type	<v.keep_state>		keep
 %type	<v.state_opt>		state_opt_spec state_opt_list state_opt_item
 %type	<v.logquick>		logquick quick log logopts logopt
 %type	<v.interface>		antispoof_ifspc antispoof_iflst antispoof_if
 %type	<v.qassign>		qname
 %type	<v.queue>		qassign qassign_list qassign_item
 %type	<v.queue_options>	scheduler
 %type	<v.number>		cbqflags_list cbqflags_item
 %type	<v.number>		priqflags_list priqflags_item
 %type	<v.hfsc_opts>		hfscopts_list hfscopts_item hfsc_opts
 %type	<v.fairq_opts>		fairqopts_list fairqopts_item fairq_opts
 %type	<v.codel_opts>		codelopts_list codelopts_item codel_opts
 %type	<v.queue_bwspec>	bandwidth
 %type	<v.filter_opts>		filter_opts filter_opt filter_opts_l
 %type	<v.filter_opts>		filter_sets filter_set filter_sets_l
 %type	<v.antispoof_opts>	antispoof_opts antispoof_opt antispoof_opts_l
 %type	<v.queue_opts>		queue_opts queue_opt queue_opts_l
 %type	<v.scrub_opts>		scrub_opts scrub_opt scrub_opts_l
 %type	<v.table_opts>		table_opts table_opt table_opts_l
 %type	<v.pool_opts>		pool_opts pool_opt pool_opts_l
 %type	<v.tagged>		tagged
 %type	<v.rtableid>		rtable
 %%
 
 ruleset		: /* empty */
 		| ruleset include '\n'
 		| ruleset '\n'
 		| ruleset option '\n'
 		| ruleset scrubrule '\n'
 		| ruleset natrule '\n'
 		| ruleset binatrule '\n'
 		| ruleset pfrule '\n'
 		| ruleset anchorrule '\n'
 		| ruleset loadrule '\n'
 		| ruleset altqif '\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 previouslys specified rule: must be careful to note
  * what that is: pf or nat or binat or rdr
  */
 fakeanchor	: fakeanchor '\n'
 		| fakeanchor anchorrule '\n'
 		| fakeanchor binatrule '\n'
 		| fakeanchor natrule '\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;
 			}
 		}
 		;
 
 option		: SET OPTIMIZATION STRING		{
 			if (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($3);
 				YYERROR;
 			}
 			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 (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($3);
 				YYERROR;
 			}
 			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;
 			}
 			if (pfctl_set_hostid(pf, $3) != 0) {
 				yyerror("error setting hostid %08x", $3);
 				YYERROR;
 			}
 		}
 		| SET BLOCKPOLICY DROP	{
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set block-policy drop\n");
 			if (check_rulestate(PFCTL_STATE_OPTION))
 				YYERROR;
 			blockpolicy = PFRULE_DROP;
 		}
 		| SET BLOCKPOLICY RETURN {
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set block-policy return\n");
 			if (check_rulestate(PFCTL_STATE_OPTION))
 				YYERROR;
 			blockpolicy = PFRULE_RETURN;
 		}
 		| SET FAILPOLICY DROP	{
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set fail-policy drop\n");
 			if (check_rulestate(PFCTL_STATE_OPTION))
 				YYERROR;
 			failpolicy = PFRULE_DROP;
 		}
 		| SET FAILPOLICY RETURN {
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set fail-policy return\n");
 			if (check_rulestate(PFCTL_STATE_OPTION))
 				YYERROR;
 			failpolicy = PFRULE_RETURN;
 		}
 		| SET REQUIREORDER yesno {
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set require-order %s\n",
 				    $3 == 1 ? "yes" : "no");
 			require_order = $3;
 		}
 		| SET FINGERPRINTS STRING {
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf("set fingerprints \"%s\"\n", $3);
 			if (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($3);
 				YYERROR;
 			}
 			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 (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($3);
 				YYERROR;
 			}
 			if (pfctl_set_debug(pf, $3) != 0) {
 				yyerror("error setting debuglevel %s", $3);
 				free($3);
 				YYERROR;
 			}
 			free($3);
 		}
 		| 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;
 		}
 		;
 
 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", (long long)$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");
 					YYERROR;
 				}
 			}
 			if (symset($1, $3, 0) == -1)
 				err(1, "cannot store variable %s", $1);
 			free($1);
 			free($3);
 		}
 		;
 
 anchorname	: STRING			{ $$ = $1; }
 		| /* empty */			{ $$ = NULL; }
 		;
 
 pfa_anchorlist	: /* empty */
 		| pfa_anchorlist '\n'
 		| pfa_anchorlist pfrule '\n'
 		| pfa_anchorlist anchorrule '\n'
 		;
 
 pfa_anchor	: '{'
 		{
 			char ta[PF_ANCHOR_NAME_SIZE];
 			struct pf_ruleset *rs;
 
 			/* steping into a brace anchor */
 			pf->asd++;
 			pf->bn++;
 			pf->brace = 1;
 
 			/* create a holding ruleset in the root */
 			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");
 			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;
 
 			if (check_rulestate(PFCTL_STATE_FILTER)) {
 				if ($2)
 					free($2);
 				YYERROR;
 			}
 
 			if ($2 && ($2[0] == '_' || strstr($2, "/_") != NULL)) {
 				free($2);
 				yyerror("anchor names beginning with '_' "
 				    "are reserved for internal use");
 				YYERROR;
 			}
 
 			memset(&r, 0, sizeof(r));
 			if (pf->astack[pf->asd + 1]) {
 				/* 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);
 				}
 				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;
 				}
 			}
 			r.direction = $3;
 			r.quick = $4.quick;
 			r.af = $6;
 			r.prob = $9.prob;
 			r.rtableid = $9.rtableid;
 
 			if ($9.tag)
 				if (strlcpy(r.tagname, $9.tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			if ($9.match_tag)
 				if (strlcpy(r.match_tagname, $9.match_tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			r.match_tag_not = $9.match_tag_not;
 			if (rule_label(&r, $9.label))
 				YYERROR;
 			free($9.label);
 			r.flags = $9.flags.b1;
 			r.flagset = $9.flags.b2;
 			if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) {
 				yyerror("flags always false");
 				YYERROR;
 			}
 			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;
 				}
 			}
 
 			r.tos = $9.tos;
 
 			if ($9.keep.action) {
 				yyerror("cannot specify state handling "
 				    "on anchors");
 				YYERROR;
 			}
 
 			if ($9.match_tag)
 				if (strlcpy(r.match_tagname, $9.match_tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			r.match_tag_not = $9.match_tag_not;
 			if ($9.marker & FOM_PRIO) {
 				if ($9.prio == 0)
 					r.prio = PF_PRIO_ZERO;
 				else
 					r.prio = $9.prio;
 			}
 			if ($9.marker & FOM_SETPRIO) {
 				r.set_prio[0] = $9.set_prio[0];
 				r.set_prio[1] = $9.set_prio[1];
 				r.scrub_flags |= PFSTATE_SETPRIO;
 			}
 
 			decide_address_family($8.src.host, &r.af);
 			decide_address_family($8.dst.host, &r.af);
 
 			expand_rule(&r, $5, NULL, $7, $8.src_os,
 			    $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
 			    $9.uid, $9.gid, $9.icmpspec,
 			    pf->astack[pf->asd + 1] ? pf->alast->name : $2);
 			free($2);
 			pf->astack[pf->asd + 1] = NULL;
 		}
 		| NATANCHOR string interface af proto fromto rtable {
 			struct pf_rule	r;
 
 			if (check_rulestate(PFCTL_STATE_NAT)) {
 				free($2);
 				YYERROR;
 			}
 
 			memset(&r, 0, sizeof(r));
 			r.action = PF_NAT;
 			r.af = $4;
 			r.rtableid = $7;
 
 			decide_address_family($6.src.host, &r.af);
 			decide_address_family($6.dst.host, &r.af);
 
 			expand_rule(&r, $3, NULL, $5, $6.src_os,
 			    $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
 			    0, 0, 0, $2);
 			free($2);
 		}
 		| RDRANCHOR string interface af proto fromto rtable {
 			struct pf_rule	r;
 
 			if (check_rulestate(PFCTL_STATE_NAT)) {
 				free($2);
 				YYERROR;
 			}
 
 			memset(&r, 0, sizeof(r));
 			r.action = PF_RDR;
 			r.af = $4;
 			r.rtableid = $7;
 
 			decide_address_family($6.src.host, &r.af);
 			decide_address_family($6.dst.host, &r.af);
 
 			if ($6.src.port != NULL) {
 				yyerror("source port parameter not supported"
 				    " in rdr-anchor");
 				YYERROR;
 			}
 			if ($6.dst.port != NULL) {
 				if ($6.dst.port->next != NULL) {
 					yyerror("destination port list "
 					    "expansion not supported in "
 					    "rdr-anchor");
 					YYERROR;
 				} else if ($6.dst.port->op != PF_OP_EQ) {
 					yyerror("destination port operators"
 					    " not supported in rdr-anchor");
 					YYERROR;
 				}
 				r.dst.port[0] = $6.dst.port->port[0];
 				r.dst.port[1] = $6.dst.port->port[1];
 				r.dst.port_op = $6.dst.port->op;
 			}
 
 			expand_rule(&r, $3, NULL, $5, $6.src_os,
 			    $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
 			    0, 0, 0, $2);
 			free($2);
 		}
 		| BINATANCHOR string interface af proto fromto rtable {
 			struct pf_rule	r;
 
 			if (check_rulestate(PFCTL_STATE_NAT)) {
 				free($2);
 				YYERROR;
 			}
 
 			memset(&r, 0, sizeof(r));
 			r.action = PF_BINAT;
 			r.af = $4;
 			r.rtableid = $7;
 			if ($5 != NULL) {
 				if ($5->next != NULL) {
 					yyerror("proto list expansion"
 					    " not supported in binat-anchor");
 					YYERROR;
 				}
 				r.proto = $5->proto;
 				free($5);
 			}
 
 			if ($6.src.host != NULL || $6.src.port != NULL ||
 			    $6.dst.host != NULL || $6.dst.port != NULL) {
 				yyerror("fromto parameter not supported"
 				    " in binat-anchor");
 				YYERROR;
 			}
 
 			decide_address_family($6.src.host, &r.af);
 			decide_address_family($6.dst.host, &r.af);
 
-			pfctl_add_rule(pf, &r, $2);
+			pfctl_append_rule(pf, &r, $2);
 			free($2);
 		}
 		;
 
 loadrule	: LOAD ANCHOR string FROM string	{
 			struct loadanchors	*loadanchor;
 
 			if (strlen(pf->anchor->name) + 1 +
 			    strlen($3) >= MAXPATHLEN) {
 				yyerror("anchorname %s too long, max %u\n",
 				    $3, MAXPATHLEN - 1);
 				free($3);
 				YYERROR;
 			}
 			loadanchor = calloc(1, sizeof(struct loadanchors));
 			if (loadanchor == NULL)
 				err(1, "loadrule: calloc");
 			if ((loadanchor->anchorname = malloc(MAXPATHLEN)) ==
 			    NULL)
 				err(1, "loadrule: malloc");
 			if (pf->anchor->name[0])
 				snprintf(loadanchor->anchorname, MAXPATHLEN,
 				    "%s/%s", pf->anchor->name, $3);
 			else
 				strlcpy(loadanchor->anchorname, $3, MAXPATHLEN);
 			if ((loadanchor->filename = strdup($5)) == NULL)
 				err(1, "loadrule: strdup");
 
 			TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor,
 			    entries);
 
 			free($3);
 			free($5);
 		};
 
 scrubaction	: no SCRUB {
 			$$.b2 = $$.w = 0;
 			if ($1)
 				$$.b1 = PF_NOSCRUB;
 			else
 				$$.b1 = PF_SCRUB;
 		}
 		;
 
 scrubrule	: scrubaction dir logquick interface af proto fromto scrub_opts
 		{
 			struct pf_rule	r;
 
 			if (check_rulestate(PFCTL_STATE_SCRUB))
 				YYERROR;
 
 			memset(&r, 0, sizeof(r));
 
 			r.action = $1.b1;
 			r.direction = $2;
 
 			r.log = $3.log;
 			r.logif = $3.logif;
 			if ($3.quick) {
 				yyerror("scrub rules do not support 'quick'");
 				YYERROR;
 			}
 
 			r.af = $5;
 			if ($8.nodf)
 				r.rule_flag |= PFRULE_NODF;
 			if ($8.randomid)
 				r.rule_flag |= PFRULE_RANDOMID;
 			if ($8.reassemble_tcp) {
 				if (r.direction != PF_INOUT) {
 					yyerror("reassemble tcp rules can not "
 					    "specify direction");
 					YYERROR;
 				}
 				r.rule_flag |= PFRULE_REASSEMBLE_TCP;
 			}
 			if ($8.minttl)
 				r.min_ttl = $8.minttl;
 			if ($8.maxmss)
 				r.max_mss = $8.maxmss;
 			if ($8.marker & SOM_SETTOS) {
 				r.rule_flag |= PFRULE_SET_TOS;
 				r.set_tos = $8.settos;
 			}
 			if ($8.fragcache)
 				r.rule_flag |= $8.fragcache;
 			if ($8.match_tag)
 				if (strlcpy(r.match_tagname, $8.match_tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			r.match_tag_not = $8.match_tag_not;
 			r.rtableid = $8.rtableid;
 
 			expand_rule(&r, $4, NULL, $6, $7.src_os,
 			    $7.src.host, $7.src.port, $7.dst.host, $7.dst.port,
 			    NULL, NULL, NULL, "");
 		}
 		;
 
 scrub_opts	:	{
 				bzero(&scrub_opts, sizeof scrub_opts);
 				scrub_opts.rtableid = -1;
 			}
 		    scrub_opts_l
 			{ $$ = scrub_opts; }
 		| /* empty */ {
 			bzero(&scrub_opts, sizeof scrub_opts);
 			scrub_opts.rtableid = -1;
 			$$ = scrub_opts;
 		}
 		;
 
 scrub_opts_l	: scrub_opts_l 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 & SOM_MINTTL) {
 				yyerror("min-ttl cannot be respecified");
 				YYERROR;
 			}
 			if ($2 < 0 || $2 > 255) {
 				yyerror("illegal min-ttl value %d", $2);
 				YYERROR;
 			}
 			scrub_opts.marker |= SOM_MINTTL;
 			scrub_opts.minttl = $2;
 		}
 		| MAXMSS NUMBER {
 			if (scrub_opts.marker & SOM_MAXMSS) {
 				yyerror("max-mss cannot be respecified");
 				YYERROR;
 			}
 			if ($2 < 0 || $2 > 65535) {
 				yyerror("illegal max-mss value %d", $2);
 				YYERROR;
 			}
 			scrub_opts.marker |= SOM_MAXMSS;
 			scrub_opts.maxmss = $2;
 		}
 		| SETTOS tos {
 			if (scrub_opts.marker & SOM_SETTOS) {
 				yyerror("set-tos cannot be respecified");
 				YYERROR;
 			}
 			scrub_opts.marker |= SOM_SETTOS;
 			scrub_opts.settos = $2;
 		}
 		| fragcache {
 			if (scrub_opts.marker & SOM_FRAGCACHE) {
 				yyerror("fragcache cannot be respecified");
 				YYERROR;
 			}
 			scrub_opts.marker |= SOM_FRAGCACHE;
 			scrub_opts.fragcache = $1;
 		}
 		| 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;
 		}
 		| RTABLE NUMBER				{
 			if ($2 < 0 || $2 > rt_tableid_max()) {
 				yyerror("invalid rtable id");
 				YYERROR;
 			}
 			scrub_opts.rtableid = $2;
 		}
 		| not TAGGED string			{
 			scrub_opts.match_tag = $3;
 			scrub_opts.match_tag_not = $1;
 		}
 		;
 
 fragcache	: FRAGMENT REASSEMBLE	{ $$ = 0; /* default */ }
 		| FRAGMENT FRAGCROP	{ $$ = 0; }
 		| FRAGMENT FRAGDROP	{ $$ = 0; }
 		;
 
 antispoof	: ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
 			struct pf_rule		 r;
 			struct node_host	*h = NULL, *hh;
 			struct node_if		*i, *j;
 
 			if (check_rulestate(PFCTL_STATE_FILTER))
 				YYERROR;
 
 			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, j, NULL, NULL, NULL, h,
 					    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, NULL, NULL,
 						    NULL, NULL, h, 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	{
 			if (antispoof_opts.label) {
 				yyerror("label cannot be redefined");
 				YYERROR;
 			}
 			antispoof_opts.label = $1;
 		}
 		| RTABLE NUMBER				{
 			if ($2 < 0 || $2 > rt_tableid_max()) {
 				yyerror("invalid rtable id");
 				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 (pf->loadopt & PFCTL_FLAG_TABLE)
 				if (process_tabledef($3, &$5)) {
 					free($3);
 					YYERROR;
 				}
 			free($3);
 			for (ti = SIMPLEQ_FIRST(&$5.init_nodes);
 			    ti != SIMPLEQ_END(&$5.init_nodes); 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 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;
 		}
 		;
 
 altqif		: ALTQ interface queue_opts QUEUE qassign {
 			struct pf_altq	a;
 
 			if (check_rulestate(PFCTL_STATE_QUEUE))
 				YYERROR;
 
 			memset(&a, 0, sizeof(a));
 			if ($3.scheduler.qtype == ALTQT_NONE) {
 				yyerror("no scheduler specified!");
 				YYERROR;
 			}
 			a.scheduler = $3.scheduler.qtype;
 			a.qlimit = $3.qlimit;
 			a.tbrsize = $3.tbrsize;
 			if ($5 == NULL && $3.scheduler.qtype != ALTQT_CODEL) {
 				yyerror("no child queues specified");
 				YYERROR;
 			}
 			if (expand_altq(&a, $2, $5, $3.queue_bwspec,
 			    &$3.scheduler))
 				YYERROR;
 		}
 		;
 
 queuespec	: QUEUE STRING interface queue_opts qassign {
 			struct pf_altq	a;
 
 			if (check_rulestate(PFCTL_STATE_QUEUE)) {
 				free($2);
 				YYERROR;
 			}
 
 			memset(&a, 0, sizeof(a));
 
 			if (strlcpy(a.qname, $2, sizeof(a.qname)) >=
 			    sizeof(a.qname)) {
 				yyerror("queue name too long (max "
 				    "%d chars)", PF_QNAME_SIZE-1);
 				free($2);
 				YYERROR;
 			}
 			free($2);
 			if ($4.tbrsize) {
 				yyerror("cannot specify tbrsize for queue");
 				YYERROR;
 			}
 			if ($4.priority > 255) {
 				yyerror("priority out of range: max 255");
 				YYERROR;
 			}
 			a.priority = $4.priority;
 			a.qlimit = $4.qlimit;
 			a.scheduler = $4.scheduler.qtype;
 			if (expand_queue(&a, $3, $5, $4.queue_bwspec,
 			    &$4.scheduler)) {
 				yyerror("errors in queue definition");
 				YYERROR;
 			}
 		}
 		;
 
 queue_opts	:	{
 			bzero(&queue_opts, sizeof queue_opts);
 			queue_opts.priority = DEFAULT_PRIORITY;
 			queue_opts.qlimit = DEFAULT_QLIMIT;
 			queue_opts.scheduler.qtype = ALTQT_NONE;
 			queue_opts.queue_bwspec.bw_percent = 100;
 		}
 		    queue_opts_l
 			{ $$ = queue_opts; }
 		| /* empty */ {
 			bzero(&queue_opts, sizeof queue_opts);
 			queue_opts.priority = DEFAULT_PRIORITY;
 			queue_opts.qlimit = DEFAULT_QLIMIT;
 			queue_opts.scheduler.qtype = ALTQT_NONE;
 			queue_opts.queue_bwspec.bw_percent = 100;
 			$$ = queue_opts;
 		}
 		;
 
 queue_opts_l	: queue_opts_l queue_opt
 		| queue_opt
 		;
 
 queue_opt	: BANDWIDTH bandwidth	{
 			if (queue_opts.marker & QOM_BWSPEC) {
 				yyerror("bandwidth cannot be respecified");
 				YYERROR;
 			}
 			queue_opts.marker |= QOM_BWSPEC;
 			queue_opts.queue_bwspec = $2;
 		}
 		| PRIORITY NUMBER	{
 			if (queue_opts.marker & QOM_PRIORITY) {
 				yyerror("priority cannot be respecified");
 				YYERROR;
 			}
 			if ($2 < 0 || $2 > 255) {
 				yyerror("priority out of range: max 255");
 				YYERROR;
 			}
 			queue_opts.marker |= QOM_PRIORITY;
 			queue_opts.priority = $2;
 		}
 		| 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;
 		}
 		| scheduler	{
 			if (queue_opts.marker & QOM_SCHEDULER) {
 				yyerror("scheduler cannot be respecified");
 				YYERROR;
 			}
 			queue_opts.marker |= QOM_SCHEDULER;
 			queue_opts.scheduler = $1;
 		}
 		| TBRSIZE NUMBER	{
 			if (queue_opts.marker & QOM_TBRSIZE) {
 				yyerror("tbrsize cannot be respecified");
 				YYERROR;
 			}
 			if ($2 < 0 || $2 > UINT_MAX) {
 				yyerror("tbrsize too big: max %u", UINT_MAX);
 				YYERROR;
 			}
 			queue_opts.marker |= QOM_TBRSIZE;
 			queue_opts.tbrsize = $2;
 		}
 		;
 
 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);
 			$$.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;
 		}
 		;
 
 scheduler	: CBQ				{
 			$$.qtype = ALTQT_CBQ;
 			$$.data.cbq_opts.flags = 0;
 		}
 		| CBQ '(' cbqflags_list ')'	{
 			$$.qtype = ALTQT_CBQ;
 			$$.data.cbq_opts.flags = $3;
 		}
 		| PRIQ				{
 			$$.qtype = ALTQT_PRIQ;
 			$$.data.priq_opts.flags = 0;
 		}
 		| PRIQ '(' priqflags_list ')'	{
 			$$.qtype = ALTQT_PRIQ;
 			$$.data.priq_opts.flags = $3;
 		}
 		| HFSC				{
 			$$.qtype = ALTQT_HFSC;
 			bzero(&$$.data.hfsc_opts,
 			    sizeof(struct node_hfsc_opts));
 		}
 		| HFSC '(' hfsc_opts ')'	{
 			$$.qtype = ALTQT_HFSC;
 			$$.data.hfsc_opts = $3;
 		}
 		| FAIRQ				{
 			$$.qtype = ALTQT_FAIRQ;
 			bzero(&$$.data.fairq_opts,
 				sizeof(struct node_fairq_opts));
 		}
 		| FAIRQ '(' fairq_opts ')'      {
 			$$.qtype = ALTQT_FAIRQ;
 			$$.data.fairq_opts = $3;
 		}
 		| CODEL				{
 			$$.qtype = ALTQT_CODEL;
 			bzero(&$$.data.codel_opts,
 				sizeof(struct codel_opts));
 		}
 		| CODEL '(' codel_opts ')'	{
 			$$.qtype = ALTQT_CODEL;
 			$$.data.codel_opts = $3;
 		}
 		;
 
 cbqflags_list	: cbqflags_item				{ $$ |= $1; }
 		| cbqflags_list comma cbqflags_item	{ $$ |= $3; }
 		;
 
 cbqflags_item	: STRING	{
 			if (!strcmp($1, "default"))
 				$$ = CBQCLF_DEFCLASS;
 			else if (!strcmp($1, "borrow"))
 				$$ = CBQCLF_BORROW;
 			else if (!strcmp($1, "red"))
 				$$ = CBQCLF_RED;
 			else if (!strcmp($1, "ecn"))
 				$$ = CBQCLF_RED|CBQCLF_ECN;
 			else if (!strcmp($1, "rio"))
 				$$ = CBQCLF_RIO;
 			else if (!strcmp($1, "codel"))
 				$$ = CBQCLF_CODEL;
 			else {
 				yyerror("unknown cbq flag \"%s\"", $1);
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 priqflags_list	: priqflags_item			{ $$ |= $1; }
 		| priqflags_list comma priqflags_item	{ $$ |= $3; }
 		;
 
 priqflags_item	: STRING	{
 			if (!strcmp($1, "default"))
 				$$ = PRCF_DEFAULTCLASS;
 			else if (!strcmp($1, "red"))
 				$$ = PRCF_RED;
 			else if (!strcmp($1, "ecn"))
 				$$ = PRCF_RED|PRCF_ECN;
 			else if (!strcmp($1, "rio"))
 				$$ = PRCF_RIO;
 			else if (!strcmp($1, "codel"))
 				$$ = PRCF_CODEL;
 			else {
 				yyerror("unknown priq flag \"%s\"", $1);
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 hfsc_opts	:	{
 				bzero(&hfsc_opts,
 				    sizeof(struct node_hfsc_opts));
 			}
 		    hfscopts_list				{
 			$$ = hfsc_opts;
 		}
 		;
 
 hfscopts_list	: hfscopts_item
 		| hfscopts_list comma hfscopts_item
 		;
 
 hfscopts_item	: LINKSHARE bandwidth				{
 			if (hfsc_opts.linkshare.used) {
 				yyerror("linkshare already specified");
 				YYERROR;
 			}
 			hfsc_opts.linkshare.m2 = $2;
 			hfsc_opts.linkshare.used = 1;
 		}
 		| LINKSHARE '(' bandwidth comma NUMBER comma bandwidth ')'
 		    {
 			if ($5 < 0 || $5 > INT_MAX) {
 				yyerror("timing in curve out of range");
 				YYERROR;
 			}
 			if (hfsc_opts.linkshare.used) {
 				yyerror("linkshare already specified");
 				YYERROR;
 			}
 			hfsc_opts.linkshare.m1 = $3;
 			hfsc_opts.linkshare.d = $5;
 			hfsc_opts.linkshare.m2 = $7;
 			hfsc_opts.linkshare.used = 1;
 		}
 		| REALTIME bandwidth				{
 			if (hfsc_opts.realtime.used) {
 				yyerror("realtime already specified");
 				YYERROR;
 			}
 			hfsc_opts.realtime.m2 = $2;
 			hfsc_opts.realtime.used = 1;
 		}
 		| REALTIME '(' bandwidth comma NUMBER comma bandwidth ')'
 		    {
 			if ($5 < 0 || $5 > INT_MAX) {
 				yyerror("timing in curve out of range");
 				YYERROR;
 			}
 			if (hfsc_opts.realtime.used) {
 				yyerror("realtime already specified");
 				YYERROR;
 			}
 			hfsc_opts.realtime.m1 = $3;
 			hfsc_opts.realtime.d = $5;
 			hfsc_opts.realtime.m2 = $7;
 			hfsc_opts.realtime.used = 1;
 		}
 		| UPPERLIMIT bandwidth				{
 			if (hfsc_opts.upperlimit.used) {
 				yyerror("upperlimit already specified");
 				YYERROR;
 			}
 			hfsc_opts.upperlimit.m2 = $2;
 			hfsc_opts.upperlimit.used = 1;
 		}
 		| UPPERLIMIT '(' bandwidth comma NUMBER comma bandwidth ')'
 		    {
 			if ($5 < 0 || $5 > INT_MAX) {
 				yyerror("timing in curve out of range");
 				YYERROR;
 			}
 			if (hfsc_opts.upperlimit.used) {
 				yyerror("upperlimit already specified");
 				YYERROR;
 			}
 			hfsc_opts.upperlimit.m1 = $3;
 			hfsc_opts.upperlimit.d = $5;
 			hfsc_opts.upperlimit.m2 = $7;
 			hfsc_opts.upperlimit.used = 1;
 		}
 		| STRING	{
 			if (!strcmp($1, "default"))
 				hfsc_opts.flags |= HFCF_DEFAULTCLASS;
 			else if (!strcmp($1, "red"))
 				hfsc_opts.flags |= HFCF_RED;
 			else if (!strcmp($1, "ecn"))
 				hfsc_opts.flags |= HFCF_RED|HFCF_ECN;
 			else if (!strcmp($1, "rio"))
 				hfsc_opts.flags |= HFCF_RIO;
 			else if (!strcmp($1, "codel"))
 				hfsc_opts.flags |= HFCF_CODEL;
 			else {
 				yyerror("unknown hfsc flag \"%s\"", $1);
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 fairq_opts	:	{
 				bzero(&fairq_opts,
 				    sizeof(struct node_fairq_opts));
 			}
 		    fairqopts_list				{
 			$$ = fairq_opts;
 		}
 		;
 
 fairqopts_list	: fairqopts_item
 		| fairqopts_list comma fairqopts_item
 		;
 
 fairqopts_item	: LINKSHARE bandwidth				{
 			if (fairq_opts.linkshare.used) {
 				yyerror("linkshare already specified");
 				YYERROR;
 			}
 			fairq_opts.linkshare.m2 = $2;
 			fairq_opts.linkshare.used = 1;
 		}
 		| LINKSHARE '(' bandwidth number bandwidth ')'	{
 			if (fairq_opts.linkshare.used) {
 				yyerror("linkshare already specified");
 				YYERROR;
 			}
 			fairq_opts.linkshare.m1 = $3;
 			fairq_opts.linkshare.d = $4;
 			fairq_opts.linkshare.m2 = $5;
 			fairq_opts.linkshare.used = 1;
 		}
 		| HOGS bandwidth {
 			fairq_opts.hogs_bw = $2;
 		}
 		| BUCKETS number {
 			fairq_opts.nbuckets = $2;
 		}
 		| STRING	{
 			if (!strcmp($1, "default"))
 				fairq_opts.flags |= FARF_DEFAULTCLASS;
 			else if (!strcmp($1, "red"))
 				fairq_opts.flags |= FARF_RED;
 			else if (!strcmp($1, "ecn"))
 				fairq_opts.flags |= FARF_RED|FARF_ECN;
 			else if (!strcmp($1, "rio"))
 				fairq_opts.flags |= FARF_RIO;
 			else if (!strcmp($1, "codel"))
 				fairq_opts.flags |= FARF_CODEL;
 			else {
 				yyerror("unknown fairq flag \"%s\"", $1);
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 codel_opts	:	{
 				bzero(&codel_opts,
 				    sizeof(struct codel_opts));
 			}
 		    codelopts_list				{
 			$$ = codel_opts;
 		}
 		;
 
 codelopts_list	: codelopts_item
 		| codelopts_list comma codelopts_item
 		;
 
 codelopts_item	: INTERVAL number				{
 			if (codel_opts.interval) {
 				yyerror("interval already specified");
 				YYERROR;
 			}
 			codel_opts.interval = $2;
 		}
 		| TARGET number					{
 			if (codel_opts.target) {
 				yyerror("target already specified");
 				YYERROR;
 			}
 			codel_opts.target = $2;
 		}
 		| STRING					{
 			if (!strcmp($1, "ecn"))
 				codel_opts.ecn = 1;
 			else {
 				yyerror("unknown codel option \"%s\"", $1);
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 qassign		: /* empty */		{ $$ = NULL; }
 		| qassign_item		{ $$ = $1; }
 		| '{' optnl qassign_list '}'	{ $$ = $3; }
 		;
 
 qassign_list	: qassign_item optnl		{ $$ = $1; }
 		| qassign_list comma qassign_item optnl	{
 			$1->tail->next = $3;
 			$1->tail = $3;
 			$$ = $1;
 		}
 		;
 
 qassign_item	: STRING			{
 			$$ = calloc(1, sizeof(struct node_queue));
 			if ($$ == NULL)
 				err(1, "qassign_item: calloc");
 			if (strlcpy($$->queue, $1, sizeof($$->queue)) >=
 			    sizeof($$->queue)) {
 				yyerror("queue name '%s' too long (max "
 				    "%d chars)", $1, sizeof($$->queue)-1);
 				free($1);
 				free($$);
 				YYERROR;
 			}
 			free($1);
 			$$->next = NULL;
 			$$->tail = $$;
 		}
 		;
 
 pfrule		: action dir logquick interface route 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;
 
 			if (check_rulestate(PFCTL_STATE_FILTER))
 				YYERROR;
 
 			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.prob = $9.prob;
 			r.rtableid = $9.rtableid;
 
 			if ($9.marker & FOM_PRIO) {
 				if ($9.prio == 0)
 					r.prio = PF_PRIO_ZERO;
 				else
 					r.prio = $9.prio;
 			}
 			if ($9.marker & FOM_SETPRIO) {
 				r.set_prio[0] = $9.set_prio[0];
 				r.set_prio[1] = $9.set_prio[1];
 				r.scrub_flags |= PFSTATE_SETPRIO;
 			}
 
 			r.af = $6;
 			if ($9.tag)
 				if (strlcpy(r.tagname, $9.tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			if ($9.match_tag)
 				if (strlcpy(r.match_tagname, $9.match_tag,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			r.match_tag_not = $9.match_tag_not;
 			if (rule_label(&r, $9.label))
 				YYERROR;
 			free($9.label);
 			r.flags = $9.flags.b1;
 			r.flagset = $9.flags.b2;
 			if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) {
 				yyerror("flags always false");
 				YYERROR;
 			}
 			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 "
 						    "apply to tcp");
 					YYERROR;
 				}
 #if 0
 				if (($9.flags.b1 & parse_flags("S")) == 0 &&
 				    $8.src_os) {
 					yyerror("OS fingerprinting requires "
 					    "the SYN TCP flag (flags S/SA)");
 					YYERROR;
 				}
 #endif
 			}
 
 			r.tos = $9.tos;
 			r.keep_state = $9.keep.action;
 			o = $9.keep.options;
 
 			/* 'keep state' by default on pass rules. */
 			if (!r.keep_state && !r.action &&
 			    !($9.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_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;
 				}
 				o = o->next;
 				if (!defaults)
 					free(p);
 			}
 
 			/* 'flags S/SA' by default on stateful rules */
 			if (!r.action && !r.flags && !r.flagset &&
 			    !$9.fragment && !($9.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;
 
 			if ($9.fragment)
 				r.rule_flag |= PFRULE_FRAGMENT;
 			r.allow_opts = $9.allowopts;
 
 			decide_address_family($8.src.host, &r.af);
 			decide_address_family($8.dst.host, &r.af);
 
 			if ($5.rt) {
 				if (!r.direction) {
 					yyerror("direction must be explicit "
 					    "with rules that specify routing");
 					YYERROR;
 				}
 				r.rt = $5.rt;
 				r.rpool.opts = $5.pool_opts;
 				if ($5.key != NULL)
 					memcpy(&r.rpool.key, $5.key,
 					    sizeof(struct pf_poolhashkey));
 			}
 			if (r.rt) {
 				decide_address_family($5.host, &r.af);
 				remove_invalid_hosts(&$5.host, &r.af);
 				if ($5.host == NULL) {
 					yyerror("no routing address with "
 					    "matching address family found.");
 					YYERROR;
 				}
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) ==
 				    PF_POOL_NONE && ($5.host->next != NULL ||
 				    $5.host->addr.type == PF_ADDR_TABLE ||
 				    DYNIF_MULTIADDR($5.host->addr)))
 					r.rpool.opts |= PF_POOL_ROUNDROBIN;
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 				    PF_POOL_ROUNDROBIN &&
 				    disallow_table($5.host, "tables are only "
 				    "supported in round-robin routing pools"))
 					YYERROR;
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 				    PF_POOL_ROUNDROBIN &&
 				    disallow_alias($5.host, "interface (%s) "
 				    "is only supported in round-robin "
 				    "routing pools"))
 					YYERROR;
 				if ($5.host->next != NULL) {
 					if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 					    PF_POOL_ROUNDROBIN) {
 						yyerror("r.rpool.opts must "
 						    "be PF_POOL_ROUNDROBIN");
 						YYERROR;
 					}
 				}
 			}
 			if ($9.queues.qname != NULL) {
 				if (strlcpy(r.qname, $9.queues.qname,
 				    sizeof(r.qname)) >= sizeof(r.qname)) {
 					yyerror("rule qname too long (max "
 					    "%d chars)", sizeof(r.qname)-1);
 					YYERROR;
 				}
 				free($9.queues.qname);
 			}
 			if ($9.queues.pqname != NULL) {
 				if (strlcpy(r.pqname, $9.queues.pqname,
 				    sizeof(r.pqname)) >= sizeof(r.pqname)) {
 					yyerror("rule pqname too long (max "
 					    "%d chars)", sizeof(r.pqname)-1);
 					YYERROR;
 				}
 				free($9.queues.pqname);
 			}
 #ifdef __FreeBSD__
 			r.divert.port = $9.divert.port;
 #else
 			if ((r.divert.port = $9.divert.port)) {
 				if (r.direction == PF_OUT) {
 					if ($9.divert.addr) {
 						yyerror("address specified "
 						    "for outgoing divert");
 						YYERROR;
 					}
 					bzero(&r.divert.addr,
 					    sizeof(r.divert.addr));
 				} else {
 					if (!$9.divert.addr) {
 						yyerror("no address specified "
 						    "for incoming divert");
 						YYERROR;
 					}
 					if ($9.divert.addr->af != r.af) {
 						yyerror("address family "
 						    "mismatch for divert");
 						YYERROR;
 					}
 					r.divert.addr =
 					    $9.divert.addr->addr.v.a.addr;
 				}
 			}
 #endif
 
 			expand_rule(&r, $4, $5.host, $7, $8.src_os,
 			    $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
 			    $9.uid, $9.gid, $9.icmpspec, "");
 		}
 		;
 
 filter_opts	:	{
 				bzero(&filter_opts, sizeof filter_opts);
 				filter_opts.rtableid = -1;
 			}
 		    filter_opts_l
 			{ $$ = filter_opts; }
 		| /* empty */	{
 			bzero(&filter_opts, sizeof filter_opts);
 			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 > PF_PRIO_MAX) {
 				yyerror("prio must be 0 - %u", PF_PRIO_MAX);
 				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	{
 			if (filter_opts.label) {
 				yyerror("label cannot be redefined");
 				YYERROR;
 			}
 			filter_opts.label = $1;
 		}
 		| qname	{
 			if (filter_opts.queues.qname) {
 				yyerror("queue cannot be redefined");
 				YYERROR;
 			}
 			filter_opts.queues = $1;
 		}
 		| 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: %lf", p);
 				YYERROR;
 			}
 			filter_opts.prob = (u_int32_t)p;
 			if (filter_opts.prob == 0)
 				filter_opts.prob = 1;
 		}
 		| RTABLE NUMBER				{
 			if ($2 < 0 || $2 > rt_tableid_max()) {
 				yyerror("invalid rtable id");
 				YYERROR;
 			}
 			filter_opts.rtableid = $2;
 		}
 		| DIVERTTO portplain {
 #ifdef __FreeBSD__
 			filter_opts.divert.port = $2.a;
 			if (!filter_opts.divert.port) {
 				yyerror("invalid divert port: %u", ntohs($2.a));
 				YYERROR;
 			}
 #endif
 		}
 		| DIVERTTO STRING PORT portplain {
 #ifndef __FreeBSD__
 			if ((filter_opts.divert.addr = host($2)) == NULL) {
 				yyerror("could not parse divert address: %s",
 				    $2);
 				free($2);
 				YYERROR;
 			}
 #else
 			if ($2)
 #endif
 			free($2);
 			filter_opts.divert.port = $4.a;
 			if (!filter_opts.divert.port) {
 				yyerror("invalid divert port: %u", ntohs($4.a));
 				YYERROR;
 			}
 		}
 		| DIVERTREPLY {
 #ifdef __FreeBSD__
 			yyerror("divert-reply has no meaning in FreeBSD pf(4)");
 			YYERROR;
 #else
 			filter_opts.divert.port = 1;	/* some random value */
 #endif
 		}
 		| 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;
 		}
 prio		: PRIO NUMBER {
 			if ($2 < 0 || $2 > PF_PRIO_MAX) {
 				yyerror("prio must be 0 - %u", PF_PRIO_MAX);
 				YYERROR;
 			}
 			$$.b1 = $$.b2 = $2;
 		}
 		| PRIO '(' NUMBER comma NUMBER ')' {
 			if ($3 < 0 || $3 > PF_PRIO_MAX ||
 			    $5 < 0 || $5 > PF_PRIO_MAX) {
 				yyerror("prio must be 0 - %u", PF_PRIO_MAX);
 				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 = failpolicy;
 			$$.w = returnicmpdefault;
 			$$.w2 = returnicmp6default;
 		}
 		| 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 %d", $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 %lu", $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 %lu", $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; }
 		| USER		{ $$.log = PF_LOG_SOCKET_LOOKUP; $$.logif = 0; }
 		| GROUP		{ $$.log = PF_LOG_SOCKET_LOOKUP; $$.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 = $$;
 		}
 		;
 
 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
 		|
 		;
 
 ipspec		: ANY				{ $$ = NULL; }
 		| xhost				{ $$ = $1; }
 		| '{' optnl host_list '}'	{ $$ = $3; }
 		;
 
 toipspec	: TO ipspec			{ $$ = $2; }
 		| /* empty */			{ $$ = NULL; }
 		;
 
 host_list	: ipspec optnl			{ $$ = $1; }
 		| host_list comma ipspec optnl	{
 			if ($3 == NULL)
 				$$ = $1;
 			else if ($1 == NULL)
 				$$ = $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 = $$;
 		}
 		;
 
 host		: STRING			{
 			if (($$ = host($1)) == 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)) == NULL || (e = host($3)) == 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) !=
 			    (b->af == AF_INET ? 32 : 128) ||
 			    unmask(&e->addr.v.a.mask, e->af) !=
 			    (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, (long long)$3) == -1)
 				err(1, "host: asprintf");
 			free($1);
 			if (($$ = host(buf)) == 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 */
 #ifdef __FreeBSD__
 			if (asprintf(&buf, "%lld/%lld", (long long)$1, (long long)$3) == -1)
 #else
 			if (asprintf(&buf, "%lld/%lld", $1, $3) == -1)
 #endif
 				err(1, "host: asprintf");
 			if (($$ = host(buf)) == 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 = $$;
 		}
 		;
 
 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(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;
 			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;
 			$$->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 == UID_MAX && $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 == UID_MAX || $3 == UID_MAX) {
 				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"))
 				$$ = UID_MAX;
 			else {
 				struct passwd	*pw;
 
 				if ((pw = getpwnam($1)) == NULL) {
 					yyerror("unknown user %s", $1);
 					free($1);
 					YYERROR;
 				}
 				$$ = pw->pw_uid;
 			}
 			free($1);
 		}
 		| NUMBER			{
 			if ($1 < 0 || $1 >= UID_MAX) {
 				yyerror("illegal uid value %lu", $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 == GID_MAX && $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 == GID_MAX || $3 == GID_MAX) {
 				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"))
 				$$ = GID_MAX;
 			else {
 				struct group	*grp;
 
 				if ((grp = getgrnam($1)) == NULL) {
 					yyerror("unknown group %s", $1);
 					free($1);
 					YYERROR;
 				}
 				$$ = grp->gr_gid;
 			}
 			free($1);
 		}
 		| NUMBER			{
 			if ($1 < 0 || $1 >= GID_MAX) {
 				yyerror("illegal gid value %lu", $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 %lu", $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 %lu", $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 %lu", $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 %lu", $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 %s", $1);
 				YYERROR;
 			}
 		}
 		;
 
 sourcetrack	: SOURCETRACK		{ $$ = PF_SRCTRACK; }
 		| SOURCETRACK GLOBAL	{ $$ = PF_SRCTRACK_GLOBAL; }
 		| SOURCETRACK 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 {
 			$$ = calloc(1, sizeof(struct node_state_opt));
 			if ($$ == NULL)
 				err(1, "state_opt_item: calloc");
 			$$->type = PF_STATE_OPT_SRCTRACK;
 			$$->data.src_track = $1;
 			$$->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 = $$;
 		}
 		| 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		: LABEL STRING			{
 			$$ = $2;
 		}
 		;
 
 qname		: QUEUE STRING				{
 			$$.qname = $2;
 			$$.pqname = NULL;
 		}
 		| QUEUE '(' STRING ')'			{
 			$$.qname = $3;
 			$$.pqname = NULL;
 		}
 		| QUEUE '(' STRING comma STRING ')'	{
 			$$.qname = $3;
 			$$.pqname = $5;
 		}
 		;
 
 no		: /* empty */			{ $$ = 0; }
 		| NO				{ $$ = 1; }
 		;
 
 portstar	: numberstring			{
 			if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) {
 				free($1);
 				YYERROR;
 			}
 			free($1);
 		}
 		;
 
 redirspec	: host				{ $$ = $1; }
 		| '{' optnl redir_host_list '}'	{ $$ = $3; }
 		;
 
 redir_host_list	: host optnl			{ $$ = $1; }
 		| redir_host_list comma host optnl {
 			$1->tail->next = $3;
 			$1->tail = $3->tail;
 			$$ = $1;
 		}
 		;
 
 redirpool	: /* empty */			{ $$ = NULL; }
 		| ARROW redirspec		{
 			$$ = calloc(1, sizeof(struct redirection));
 			if ($$ == NULL)
 				err(1, "redirection: calloc");
 			$$->host = $2;
 			$$->rport.a = $$->rport.b = $$->rport.t = 0;
 		}
 		| ARROW redirspec PORT portstar	{
 			$$ = calloc(1, sizeof(struct redirection));
 			if ($$ == NULL)
 				err(1, "redirection: calloc");
 			$$->host = $2;
 			$$->rport = $4;
 		}
 		;
 
 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;
 		}
 		| STATICPORT	{
 			if (pool_opts.staticport) {
 				yyerror("static-port cannot be redefined");
 				YYERROR;
 			}
 			pool_opts.staticport = 1;
 		}
 		| STICKYADDRESS	{
 			if (filter_opts.marker & POM_STICKYADDRESS) {
 				yyerror("sticky-address cannot be redefined");
 				YYERROR;
 			}
 			pool_opts.marker |= POM_STICKYADDRESS;
 			pool_opts.opts |= PF_POOL_STICKYADDR;
 		}
 		;
 
 redirection	: /* empty */			{ $$ = NULL; }
 		| ARROW host			{
 			$$ = calloc(1, sizeof(struct redirection));
 			if ($$ == NULL)
 				err(1, "redirection: calloc");
 			$$->host = $2;
 			$$->rport.a = $$->rport.b = $$->rport.t = 0;
 		}
 		| ARROW host PORT portstar	{
 			$$ = calloc(1, sizeof(struct redirection));
 			if ($$ == NULL)
 				err(1, "redirection: calloc");
 			$$->host = $2;
 			$$->rport = $4;
 		}
 		;
 
 natpasslog	: /* empty */	{ $$.b1 = $$.b2 = 0; $$.w2 = 0; }
 		| PASS		{ $$.b1 = 1; $$.b2 = 0; $$.w2 = 0; }
 		| PASS log	{ $$.b1 = 1; $$.b2 = $2.log; $$.w2 = $2.logif; }
 		| log		{ $$.b1 = 0; $$.b2 = $1.log; $$.w2 = $1.logif; }
 		;
 
 nataction	: no NAT natpasslog {
 			if ($1 && $3.b1) {
 				yyerror("\"pass\" not valid with \"no\"");
 				YYERROR;
 			}
 			if ($1)
 				$$.b1 = PF_NONAT;
 			else
 				$$.b1 = PF_NAT;
 			$$.b2 = $3.b1;
 			$$.w = $3.b2;
 			$$.w2 = $3.w2;
 		}
 		| no RDR natpasslog {
 			if ($1 && $3.b1) {
 				yyerror("\"pass\" not valid with \"no\"");
 				YYERROR;
 			}
 			if ($1)
 				$$.b1 = PF_NORDR;
 			else
 				$$.b1 = PF_RDR;
 			$$.b2 = $3.b1;
 			$$.w = $3.b2;
 			$$.w2 = $3.w2;
 		}
 		;
 
 natrule		: nataction interface af proto fromto tag tagged rtable
 		    redirpool pool_opts
 		{
 			struct pf_rule	r;
 
 			if (check_rulestate(PFCTL_STATE_NAT))
 				YYERROR;
 
 			memset(&r, 0, sizeof(r));
 
 			r.action = $1.b1;
 			r.natpass = $1.b2;
 			r.log = $1.w;
 			r.logif = $1.w2;
 			r.af = $3;
 
 			if (!r.af) {
 				if ($5.src.host && $5.src.host->af &&
 				    !$5.src.host->ifindex)
 					r.af = $5.src.host->af;
 				else if ($5.dst.host && $5.dst.host->af &&
 				    !$5.dst.host->ifindex)
 					r.af = $5.dst.host->af;
 			}
 
 			if ($6 != NULL)
 				if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >=
 				    PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 
 			if ($7.name)
 				if (strlcpy(r.match_tagname, $7.name,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			r.match_tag_not = $7.neg;
 			r.rtableid = $8;
 
 			if (r.action == PF_NONAT || r.action == PF_NORDR) {
 				if ($9 != NULL) {
 					yyerror("translation rule with 'no' "
 					    "does not need '->'");
 					YYERROR;
 				}
 			} else {
 				if ($9 == NULL || $9->host == NULL) {
 					yyerror("translation rule requires '-> "
 					    "address'");
 					YYERROR;
 				}
 				if (!r.af && ! $9->host->ifindex)
 					r.af = $9->host->af;
 
 				remove_invalid_hosts(&$9->host, &r.af);
 				if (invalid_redirect($9->host, r.af))
 					YYERROR;
 				if (check_netmask($9->host, r.af))
 					YYERROR;
 
 				r.rpool.proxy_port[0] = ntohs($9->rport.a);
 
 				switch (r.action) {
 				case PF_RDR:
 					if (!$9->rport.b && $9->rport.t &&
 					    $5.dst.port != NULL) {
 						r.rpool.proxy_port[1] =
 						    ntohs($9->rport.a) +
 						    (ntohs(
 						    $5.dst.port->port[1]) -
 						    ntohs(
 						    $5.dst.port->port[0]));
 					} else
 						r.rpool.proxy_port[1] =
 						    ntohs($9->rport.b);
 					break;
 				case PF_NAT:
 					r.rpool.proxy_port[1] =
 					    ntohs($9->rport.b);
 					if (!r.rpool.proxy_port[0] &&
 					    !r.rpool.proxy_port[1]) {
 						r.rpool.proxy_port[0] =
 						    PF_NAT_PROXY_PORT_LOW;
 						r.rpool.proxy_port[1] =
 						    PF_NAT_PROXY_PORT_HIGH;
 					} else if (!r.rpool.proxy_port[1])
 						r.rpool.proxy_port[1] =
 						    r.rpool.proxy_port[0];
 					break;
 				default:
 					break;
 				}
 
 				r.rpool.opts = $10.type;
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) ==
 				    PF_POOL_NONE && ($9->host->next != NULL ||
 				    $9->host->addr.type == PF_ADDR_TABLE ||
 				    DYNIF_MULTIADDR($9->host->addr)))
 					r.rpool.opts = PF_POOL_ROUNDROBIN;
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 				    PF_POOL_ROUNDROBIN &&
 				    disallow_table($9->host, "tables are only "
 				    "supported in round-robin redirection "
 				    "pools"))
 					YYERROR;
 				if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 				    PF_POOL_ROUNDROBIN &&
 				    disallow_alias($9->host, "interface (%s) "
 				    "is only supported in round-robin "
 				    "redirection pools"))
 					YYERROR;
 				if ($9->host->next != NULL) {
 					if ((r.rpool.opts & PF_POOL_TYPEMASK) !=
 					    PF_POOL_ROUNDROBIN) {
 						yyerror("only round-robin "
 						    "valid for multiple "
 						    "redirection addresses");
 						YYERROR;
 					}
 				}
 			}
 
 			if ($10.key != NULL)
 				memcpy(&r.rpool.key, $10.key,
 				    sizeof(struct pf_poolhashkey));
 
 			 if ($10.opts)
 				r.rpool.opts |= $10.opts;
 
 			if ($10.staticport) {
 				if (r.action != PF_NAT) {
 					yyerror("the 'static-port' option is "
 					    "only valid with nat rules");
 					YYERROR;
 				}
 				if (r.rpool.proxy_port[0] !=
 				    PF_NAT_PROXY_PORT_LOW &&
 				    r.rpool.proxy_port[1] !=
 				    PF_NAT_PROXY_PORT_HIGH) {
 					yyerror("the 'static-port' option can't"
 					    " be used when specifying a port"
 					    " range");
 					YYERROR;
 				}
 				r.rpool.proxy_port[0] = 0;
 				r.rpool.proxy_port[1] = 0;
 			}
 
 			expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4,
 			    $5.src_os, $5.src.host, $5.src.port, $5.dst.host,
 			    $5.dst.port, 0, 0, 0, "");
 			free($9);
 		}
 		;
 
 binatrule	: no BINAT natpasslog interface af proto FROM ipspec toipspec tag
 		    tagged rtable redirection
 		{
 			struct pf_rule		binat;
 			struct pf_pooladdr	*pa;
 
 			if (check_rulestate(PFCTL_STATE_NAT))
 				YYERROR;
 			if (disallow_urpf_failed($9, "\"urpf-failed\" is not "
 			    "permitted as a binat destination"))
 				YYERROR;
 
 			memset(&binat, 0, sizeof(binat));
 
 			if ($1 && $3.b1) {
 				yyerror("\"pass\" not valid with \"no\"");
 				YYERROR;
 			}
 			if ($1)
 				binat.action = PF_NOBINAT;
 			else
 				binat.action = PF_BINAT;
 			binat.natpass = $3.b1;
 			binat.log = $3.b2;
 			binat.logif = $3.w2;
 			binat.af = $5;
 			if (!binat.af && $8 != NULL && $8->af)
 				binat.af = $8->af;
 			if (!binat.af && $9 != NULL && $9->af)
 				binat.af = $9->af;
 
 			if (!binat.af && $13 != NULL && $13->host)
 				binat.af = $13->host->af;
 			if (!binat.af) {
 				yyerror("address family (inet/inet6) "
 				    "undefined");
 				YYERROR;
 			}
 
 			if ($4 != NULL) {
 				memcpy(binat.ifname, $4->ifname,
 				    sizeof(binat.ifname));
 				binat.ifnot = $4->not;
 				free($4);
 			}
 
 			if ($10 != NULL)
 				if (strlcpy(binat.tagname, $10,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			if ($11.name)
 				if (strlcpy(binat.match_tagname, $11.name,
 				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
 					yyerror("tag too long, max %u chars",
 					    PF_TAG_NAME_SIZE - 1);
 					YYERROR;
 				}
 			binat.match_tag_not = $11.neg;
 			binat.rtableid = $12;
 
 			if ($6 != NULL) {
 				binat.proto = $6->proto;
 				free($6);
 			}
 
 			if ($8 != NULL && disallow_table($8, "invalid use of "
 			    "table <%s> as the source address of a binat rule"))
 				YYERROR;
 			if ($8 != NULL && disallow_alias($8, "invalid use of "
 			    "interface (%s) as the source address of a binat "
 			    "rule"))
 				YYERROR;
 			if ($13 != NULL && $13->host != NULL && disallow_table(
 			    $13->host, "invalid use of table <%s> as the "
 			    "redirect address of a binat rule"))
 				YYERROR;
 			if ($13 != NULL && $13->host != NULL && disallow_alias(
 			    $13->host, "invalid use of interface (%s) as the "
 			    "redirect address of a binat rule"))
 				YYERROR;
 
 			if ($8 != NULL) {
 				if ($8->next) {
 					yyerror("multiple binat ip addresses");
 					YYERROR;
 				}
 				if ($8->addr.type == PF_ADDR_DYNIFTL)
 					$8->af = binat.af;
 				if ($8->af != binat.af) {
 					yyerror("binat ip versions must match");
 					YYERROR;
 				}
 				if (check_netmask($8, binat.af))
 					YYERROR;
 				memcpy(&binat.src.addr, &$8->addr,
 				    sizeof(binat.src.addr));
 				free($8);
 			}
 			if ($9 != NULL) {
 				if ($9->next) {
 					yyerror("multiple binat ip addresses");
 					YYERROR;
 				}
 				if ($9->af != binat.af && $9->af) {
 					yyerror("binat ip versions must match");
 					YYERROR;
 				}
 				if (check_netmask($9, binat.af))
 					YYERROR;
 				memcpy(&binat.dst.addr, &$9->addr,
 				    sizeof(binat.dst.addr));
 				binat.dst.neg = $9->not;
 				free($9);
 			}
 
 			if (binat.action == PF_NOBINAT) {
 				if ($13 != NULL) {
 					yyerror("'no binat' rule does not need"
 					    " '->'");
 					YYERROR;
 				}
 			} else {
 				if ($13 == NULL || $13->host == NULL) {
 					yyerror("'binat' rule requires"
 					    " '-> address'");
 					YYERROR;
 				}
 
 				remove_invalid_hosts(&$13->host, &binat.af);
 				if (invalid_redirect($13->host, binat.af))
 					YYERROR;
 				if ($13->host->next != NULL) {
 					yyerror("binat rule must redirect to "
 					    "a single address");
 					YYERROR;
 				}
 				if (check_netmask($13->host, binat.af))
 					YYERROR;
 
 				if (!PF_AZERO(&binat.src.addr.v.a.mask,
 				    binat.af) &&
 				    !PF_AEQ(&binat.src.addr.v.a.mask,
 				    &$13->host->addr.v.a.mask, binat.af)) {
 					yyerror("'binat' source mask and "
 					    "redirect mask must be the same");
 					YYERROR;
 				}
 
 				TAILQ_INIT(&binat.rpool.list);
 				pa = calloc(1, sizeof(struct pf_pooladdr));
 				if (pa == NULL)
 					err(1, "binat: calloc");
 				pa->addr = $13->host->addr;
 				pa->ifname[0] = 0;
 				TAILQ_INSERT_TAIL(&binat.rpool.list,
 				    pa, entries);
 
 				free($13);
 			}
 
-			pfctl_add_rule(pf, &binat, "");
+			pfctl_append_rule(pf, &binat, "");
 		}
 		;
 
 tag		: /* empty */		{ $$ = NULL; }
 		| TAG STRING		{ $$ = $2; }
 		;
 
 tagged		: /* empty */		{ $$.neg = 0; $$.name = NULL; }
 		| not TAGGED string	{ $$.neg = $1; $$.name = $3; }
 		;
 
 rtable		: /* empty */		{ $$ = -1; }
 		| RTABLE NUMBER		{
 			if ($2 < 0 || $2 > rt_tableid_max()) {
 				yyerror("invalid rtable id");
 				YYERROR;
 			}
 			$$ = $2;
 		}
 		;
 
 route_host	: STRING			{
 			$$ = calloc(1, sizeof(struct node_host));
 			if ($$ == NULL)
 				err(1, "route_host: calloc");
 			$$->ifname = strdup($1);
 			set_ipmask($$, 128);
 			$$->next = NULL;
 			$$->tail = $$;
 		}
 		| '(' STRING host ')'		{
 			struct node_host *n;
 
 			$$ = $3;
 			for (n = $3; n != NULL; n = n->next)
 				n->ifname = strdup($2);
 		}
 		;
 
 route_host_list	: route_host optnl			{ $$ = $1; }
 		| route_host_list comma route_host optnl {
 			if ($1->af == 0)
 				$1->af = $3->af;
 			if ($1->af != $3->af) {
 				yyerror("all pool addresses must be in the "
 				    "same address family");
 				YYERROR;
 			}
 			$1->tail->next = $3;
 			$1->tail = $3->tail;
 			$$ = $1;
 		}
 		;
 
 routespec	: route_host			{ $$ = $1; }
 		| '{' optnl route_host_list '}'	{ $$ = $3; }
 		;
 
 route		: /* empty */			{
 			$$.host = NULL;
 			$$.rt = 0;
 			$$.pool_opts = 0;
 		}
 		| FASTROUTE {
 			/* backwards-compat */
 			$$.host = NULL;
 			$$.rt = 0;
 			$$.pool_opts = 0;
 		}
 		| ROUTETO routespec pool_opts {
 			$$.host = $2;
 			$$.rt = PF_ROUTETO;
 			$$.pool_opts = $3.type | $3.opts;
 			if ($3.key != NULL)
 				$$.key = $3.key;
 		}
 		| REPLYTO routespec pool_opts {
 			$$.host = $2;
 			$$.rt = PF_REPLYTO;
 			$$.pool_opts = $3.type | $3.opts;
 			if ($3.key != NULL)
 				$$.key = $3.key;
 		}
 		| DUPTO routespec pool_opts {
 			$$.host = $2;
 			$$.rt = PF_DUPTO;
 			$$.pool_opts = $3.type | $3.opts;
 			if ($3.key != NULL)
 				$$.key = $3.key;
 		}
 		;
 
 timeout_spec	: STRING NUMBER
 		{
 			if (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($1);
 				YYERROR;
 			}
 			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);
 		}
 		| INTERVAL NUMBER		{
 			if (check_rulestate(PFCTL_STATE_OPTION))
 				YYERROR;
 			if ($2 < 0 || $2 > UINT_MAX) {
 				yyerror("only positive values permitted");
 				YYERROR;
 			}
 			if (pfctl_set_timeout(pf, "interval", $2, 0) != 0)
 				YYERROR;
 		}
 		;
 
 timeout_list	: timeout_list comma timeout_spec optnl
 		| timeout_spec optnl
 		;
 
 limit_spec	: STRING NUMBER
 		{
 			if (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($1);
 				YYERROR;
 			}
 			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 %u", $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; }
 		| '!' '='	{ $$ = PF_OP_NE; }
 		| '<' '='	{ $$ = PF_OP_LE; }
 		| '<'		{ $$ = PF_OP_LT; }
 		| '>' '='	{ $$ = 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
 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(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 anchor_call)
 {
 	int	problems = 0;
 
 	switch (r->action) {
 	case PF_PASS:
 	case PF_DROP:
 	case PF_SCRUB:
 	case PF_NOSCRUB:
 		problems = filter_consistent(r, anchor_call);
 		break;
 	case PF_NAT:
 	case PF_NONAT:
 		problems = nat_consistent(r);
 		break;
 	case PF_RDR:
 	case PF_NORDR:
 		problems = rdr_consistent(r);
 		break;
 	case PF_BINAT:
 	case PF_NOBINAT:
 	default:
 		break;
 	}
 	return (problems);
 }
 
 int
 filter_consistent(struct pf_rule *r, int anchor_call)
 {
 	int	problems = 0;
 
 	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_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->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_DROP && r->keep_state) {
 		yyerror("keep state on block rules doesn't make sense");
 		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++;
 	}
 	return (-problems);
 }
 
 int
 nat_consistent(struct pf_rule *r)
 {
 	return (0);	/* yeah! */
 }
 
 int
 rdr_consistent(struct pf_rule *r)
 {
 	int			 problems = 0;
 
 	if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) {
 		if (r->src.port_op) {
 			yyerror("src port only applies to tcp/udp");
 			problems++;
 		}
 		if (r->dst.port_op) {
 			yyerror("dst port only applies to tcp/udp");
 			problems++;
 		}
 		if (r->rpool.proxy_port[0]) {
 			yyerror("rpool port only applies to tcp/udp");
 			problems++;
 		}
 	}
 	if (r->dst.port_op &&
 	    r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) {
 		yyerror("invalid port operator for rdr destination port");
 		problems++;
 	}
 	return (-problems);
 }
 
 int
 process_tabledef(char *name, struct table_opts *opts)
 {
 	struct pfr_buffer	 ab;
 	struct node_tinit	*ti;
 	unsigned long		 maxcount;
 	size_t			 s = sizeof(maxcount);
 
 	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, append_addr)) {
 				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) &&
 	    pfctl_define_table(name, opts->flags, opts->init_addr,
 	    pf->anchor->name, &ab, pf->anchor->ruleset.tticket)) {
 
 		if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s,
 		    NULL, 0) == -1)
 			maxcount = 65535;
 
 		if (ab.pfrb_size > maxcount)
 			yyerror("cannot define table %s: too many elements.\n"
 			    "Consider increasing net.pf.request_maxcount.",
 			    name);
 		else
 			yyerror("cannot define table %s: %s", name,
 			    pfr_strerror(errno));
 
 		goto _error;
 	}
 	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, "expand_label_str: calloc");
 	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, af);
 					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
 expand_label_nr(const char *name, char *label, size_t len)
 {
 	char n[11];
 
 	if (strstr(label, name) != NULL) {
 		snprintf(n, sizeof(n), "%u", pf->anchor->match);
 		expand_label_str(label, len, name, 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);
 	expand_label_nr("$nr", label, len);
 }
 
 int
 expand_altq(struct pf_altq *a, struct node_if *interfaces,
     struct node_queue *nqueues, struct node_queue_bw bwspec,
     struct node_queue_opt *opts)
 {
 	struct pf_altq		 pa, pb;
 	char			 qname[PF_QNAME_SIZE];
 	struct node_queue	*n;
 	struct node_queue_bw	 bw;
 	int			 errs = 0;
 
 	if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
 		FREE_LIST(struct node_if, interfaces);
 		if (nqueues)
 			FREE_LIST(struct node_queue, nqueues);
 		return (0);
 	}
 
 	LOOP_THROUGH(struct node_if, interface, interfaces,
 		memcpy(&pa, a, sizeof(struct pf_altq));
 		if (strlcpy(pa.ifname, interface->ifname,
 		    sizeof(pa.ifname)) >= sizeof(pa.ifname))
 			errx(1, "expand_altq: strlcpy");
 
 		if (interface->not) {
 			yyerror("altq on ! <interface> is not supported");
 			errs++;
 		} else {
 			if (eval_pfaltq(pf, &pa, &bwspec, opts))
 				errs++;
 			else
 				if (pfctl_add_altq(pf, &pa))
 					errs++;
 
 			if (pf->opts & PF_OPT_VERBOSE) {
 				print_altq(&pf->paltq->altq, 0,
 				    &bwspec, opts);
 				if (nqueues && nqueues->tail) {
 					printf("queue { ");
 					LOOP_THROUGH(struct node_queue, queue,
 					    nqueues,
 						printf("%s ",
 						    queue->queue);
 					);
 					printf("}");
 				}
 				printf("\n");
 			}
 
 			if (pa.scheduler == ALTQT_CBQ ||
 			    pa.scheduler == ALTQT_HFSC) {
 				/* now create a root queue */
 				memset(&pb, 0, sizeof(struct pf_altq));
 				if (strlcpy(qname, "root_", sizeof(qname)) >=
 				    sizeof(qname))
 					errx(1, "expand_altq: strlcpy");
 				if (strlcat(qname, interface->ifname,
 				    sizeof(qname)) >= sizeof(qname))
 					errx(1, "expand_altq: strlcat");
 				if (strlcpy(pb.qname, qname,
 				    sizeof(pb.qname)) >= sizeof(pb.qname))
 					errx(1, "expand_altq: strlcpy");
 				if (strlcpy(pb.ifname, interface->ifname,
 				    sizeof(pb.ifname)) >= sizeof(pb.ifname))
 					errx(1, "expand_altq: strlcpy");
 				pb.qlimit = pa.qlimit;
 				pb.scheduler = pa.scheduler;
 				bw.bw_absolute = pa.ifbandwidth;
 				bw.bw_percent = 0;
 				if (eval_pfqueue(pf, &pb, &bw, opts))
 					errs++;
 				else
 					if (pfctl_add_altq(pf, &pb))
 						errs++;
 			}
 
 			LOOP_THROUGH(struct node_queue, queue, nqueues,
 				n = calloc(1, sizeof(struct node_queue));
 				if (n == NULL)
 					err(1, "expand_altq: calloc");
 				if (pa.scheduler == ALTQT_CBQ ||
 				    pa.scheduler == ALTQT_HFSC)
 					if (strlcpy(n->parent, qname,
 					    sizeof(n->parent)) >=
 					    sizeof(n->parent))
 						errx(1, "expand_altq: strlcpy");
 				if (strlcpy(n->queue, queue->queue,
 				    sizeof(n->queue)) >= sizeof(n->queue))
 					errx(1, "expand_altq: strlcpy");
 				if (strlcpy(n->ifname, interface->ifname,
 				    sizeof(n->ifname)) >= sizeof(n->ifname))
 					errx(1, "expand_altq: strlcpy");
 				n->scheduler = pa.scheduler;
 				n->next = NULL;
 				n->tail = n;
 				if (queues == NULL)
 					queues = n;
 				else {
 					queues->tail->next = n;
 					queues->tail = n;
 				}
 			);
 		}
 	);
 	FREE_LIST(struct node_if, interfaces);
 	if (nqueues)
 		FREE_LIST(struct node_queue, nqueues);
 
 	return (errs);
 }
 
 int
 expand_queue(struct pf_altq *a, struct node_if *interfaces,
     struct node_queue *nqueues, struct node_queue_bw bwspec,
     struct node_queue_opt *opts)
 {
 	struct node_queue	*n, *nq;
 	struct pf_altq		 pa;
 	u_int8_t		 found = 0;
 	u_int8_t		 errs = 0;
 
 	if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
 		FREE_LIST(struct node_queue, nqueues);
 		return (0);
 	}
 
 	if (queues == NULL) {
 		yyerror("queue %s has no parent", a->qname);
 		FREE_LIST(struct node_queue, nqueues);
 		return (1);
 	}
 
 	LOOP_THROUGH(struct node_if, interface, interfaces,
 		LOOP_THROUGH(struct node_queue, tqueue, queues,
 			if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) &&
 			    (interface->ifname[0] == 0 ||
 			    (!interface->not && !strncmp(interface->ifname,
 			    tqueue->ifname, IFNAMSIZ)) ||
 			    (interface->not && strncmp(interface->ifname,
 			    tqueue->ifname, IFNAMSIZ)))) {
 				/* found ourself in queues */
 				found++;
 
 				memcpy(&pa, a, sizeof(struct pf_altq));
 
 				if (pa.scheduler != ALTQT_NONE &&
 				    pa.scheduler != tqueue->scheduler) {
 					yyerror("exactly one scheduler type "
 					    "per interface allowed");
 					return (1);
 				}
 				pa.scheduler = tqueue->scheduler;
 
 				/* scheduler dependent error checking */
 				switch (pa.scheduler) {
 				case ALTQT_PRIQ:
 					if (nqueues != NULL) {
 						yyerror("priq queues cannot "
 						    "have child queues");
 						return (1);
 					}
 					if (bwspec.bw_absolute > 0 ||
 					    bwspec.bw_percent < 100) {
 						yyerror("priq doesn't take "
 						    "bandwidth");
 						return (1);
 					}
 					break;
 				default:
 					break;
 				}
 
 				if (strlcpy(pa.ifname, tqueue->ifname,
 				    sizeof(pa.ifname)) >= sizeof(pa.ifname))
 					errx(1, "expand_queue: strlcpy");
 				if (strlcpy(pa.parent, tqueue->parent,
 				    sizeof(pa.parent)) >= sizeof(pa.parent))
 					errx(1, "expand_queue: strlcpy");
 
 				if (eval_pfqueue(pf, &pa, &bwspec, opts))
 					errs++;
 				else
 					if (pfctl_add_altq(pf, &pa))
 						errs++;
 
 				for (nq = nqueues; nq != NULL; nq = nq->next) {
 					if (!strcmp(a->qname, nq->queue)) {
 						yyerror("queue cannot have "
 						    "itself as child");
 						errs++;
 						continue;
 					}
 					n = calloc(1,
 					    sizeof(struct node_queue));
 					if (n == NULL)
 						err(1, "expand_queue: calloc");
 					if (strlcpy(n->parent, a->qname,
 					    sizeof(n->parent)) >=
 					    sizeof(n->parent))
 						errx(1, "expand_queue strlcpy");
 					if (strlcpy(n->queue, nq->queue,
 					    sizeof(n->queue)) >=
 					    sizeof(n->queue))
 						errx(1, "expand_queue strlcpy");
 					if (strlcpy(n->ifname, tqueue->ifname,
 					    sizeof(n->ifname)) >=
 					    sizeof(n->ifname))
 						errx(1, "expand_queue strlcpy");
 					n->scheduler = tqueue->scheduler;
 					n->next = NULL;
 					n->tail = n;
 					if (queues == NULL)
 						queues = n;
 					else {
 						queues->tail->next = n;
 						queues->tail = n;
 					}
 				}
 				if ((pf->opts & PF_OPT_VERBOSE) && (
 				    (found == 1 && interface->ifname[0] == 0) ||
 				    (found > 0 && interface->ifname[0] != 0))) {
 					print_queue(&pf->paltq->altq, 0,
 					    &bwspec, interface->ifname[0] != 0,
 					    opts);
 					if (nqueues && nqueues->tail) {
 						printf("{ ");
 						LOOP_THROUGH(struct node_queue,
 						    queue, nqueues,
 							printf("%s ",
 							    queue->queue);
 						);
 						printf("}");
 					}
 					printf("\n");
 				}
 			}
 		);
 	);
 
 	FREE_LIST(struct node_queue, nqueues);
 	FREE_LIST(struct node_if, interfaces);
 
 	if (!found) {
 		yyerror("queue %s has no parent", a->qname);
 		errs++;
 	}
 
 	if (errs)
 		return (1);
 	else
 		return (0);
 }
 
 void
 expand_rule(struct pf_rule *r,
     struct node_if *interfaces, struct node_host *rpool_hosts,
     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_icmp *icmp_types,
     const char *anchor_call)
 {
 	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];
 	struct pf_pooladdr	*pa;
 	struct node_host	*h;
 	u_int8_t		 flags, flagset, keep_state;
 
 	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;
 
 	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_port, src_port, src_ports,
 	LOOP_THROUGH(struct node_os, src_os, src_oses,
 	LOOP_THROUGH(struct node_host, dst_host, dst_hosts,
 	LOOP_THROUGH(struct node_port, dst_port, dst_ports,
 	LOOP_THROUGH(struct node_uid, uid, uids,
 	LOOP_THROUGH(struct node_gid, gid, gids,
 
 		r->af = af;
 		/* 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 != if_nametoindex(interface->ifname)) ||
 		    (dst_host->ifindex && *interface->ifname &&
 		    dst_host->ifindex != if_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 (if_indextoname(src_host->ifindex, ifname))
 			strlcpy(r->ifname, ifname, sizeof(r->ifname));
 		else if (if_indextoname(dst_host->ifindex, ifname))
 			strlcpy(r->ifname, ifname, sizeof(r->ifname));
 		else
 			memset(r->ifname, '\0', sizeof(r->ifname));
 
 		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);
 
 		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];
 		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;
 		}
 
 		TAILQ_INIT(&r->rpool.list);
 		for (h = rpool_hosts; h != NULL; h = h->next) {
 			pa = calloc(1, sizeof(struct pf_pooladdr));
 			if (pa == NULL)
 				err(1, "expand_rule: calloc");
 			pa->addr = h->addr;
 			if (h->ifname != NULL) {
 				if (strlcpy(pa->ifname, h->ifname,
 				    sizeof(pa->ifname)) >=
 				    sizeof(pa->ifname))
 					errx(1, "expand_rule: strlcpy");
 			} else
 				pa->ifname[0] = 0;
 			TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries);
 		}
 
 		if (rule_consistent(r, anchor_call[0]) < 0 || error)
 			yyerror("skipping rule due to errors");
 		else {
 			r->nr = pf->astack[pf->asd]->match++;
-			pfctl_add_rule(pf, r, anchor_call);
+			pfctl_append_rule(pf, r, anchor_call);
 			added++;
 		}
 
 	))))))))));
 
 	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);
 	FREE_LIST(struct node_host, rpool_hosts);
 
 	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 ! <interface> 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);
 }
 
 #undef FREE_LIST
 #undef LOOP_THROUGH
 
 int
 check_rulestate(int desired_state)
 {
 	if (require_order && (rulestate > desired_state)) {
 		yyerror("Rules must be in order: options, normalization, "
 		    "queueing, translation, filtering");
 		return (1);
 	}
 	rulestate = desired_state;
 	return (0);
 }
 
 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[] = {
 		{ "all",		ALL},
 		{ "allow-opts",		ALLOWOPTS},
 		{ "altq",		ALTQ},
 		{ "anchor",		ANCHOR},
 		{ "antispoof",		ANTISPOOF},
 		{ "any",		ANY},
 		{ "bandwidth",		BANDWIDTH},
 		{ "binat",		BINAT},
 		{ "binat-anchor",	BINATANCHOR},
 		{ "bitmask",		BITMASK},
 		{ "block",		BLOCK},
 		{ "block-policy",	BLOCKPOLICY},
 		{ "buckets",		BUCKETS},
 		{ "cbq",		CBQ},
 		{ "code",		CODE},
 		{ "codelq",		CODEL},
 		{ "crop",		FRAGCROP},
 		{ "debug",		DEBUG},
 		{ "divert-reply",	DIVERTREPLY},
 		{ "divert-to",		DIVERTTO},
 		{ "drop",		DROP},
 		{ "drop-ovl",		FRAGDROP},
 		{ "dup-to",		DUPTO},
 		{ "fail-policy",	FAILPOLICY},
 		{ "fairq",		FAIRQ},
 		{ "fastroute",		FASTROUTE},
 		{ "file",		FILENAME},
 		{ "fingerprints",	FINGERPRINTS},
 		{ "flags",		FLAGS},
 		{ "floating",		FLOATING},
 		{ "flush",		FLUSH},
 		{ "for",		FOR},
 		{ "fragment",		FRAGMENT},
 		{ "from",		FROM},
 		{ "global",		GLOBAL},
 		{ "group",		GROUP},
 		{ "hfsc",		HFSC},
 		{ "hogs",		HOGS},
 		{ "hostid",		HOSTID},
 		{ "icmp-type",		ICMPTYPE},
 		{ "icmp6-type",		ICMP6TYPE},
 		{ "if-bound",		IFBOUND},
 		{ "in",			IN},
 		{ "include",		INCLUDE},
 		{ "inet",		INET},
 		{ "inet6",		INET6},
 		{ "interval",		INTERVAL},
 		{ "keep",		KEEP},
 		{ "label",		LABEL},
 		{ "limit",		LIMIT},
 		{ "linkshare",		LINKSHARE},
 		{ "load",		LOAD},
 		{ "log",		LOG},
 		{ "loginterface",	LOGINTERFACE},
 		{ "max",		MAXIMUM},
 		{ "max-mss",		MAXMSS},
 		{ "max-src-conn",	MAXSRCCONN},
 		{ "max-src-conn-rate",	MAXSRCCONNRATE},
 		{ "max-src-nodes",	MAXSRCNODES},
 		{ "max-src-states",	MAXSRCSTATES},
 		{ "min-ttl",		MINTTL},
 		{ "modulate",		MODULATE},
 		{ "nat",		NAT},
 		{ "nat-anchor",		NATANCHOR},
 		{ "no",			NO},
 		{ "no-df",		NODF},
 		{ "no-route",		NOROUTE},
 		{ "no-sync",		NOSYNC},
 		{ "on",			ON},
 		{ "optimization",	OPTIMIZATION},
 		{ "os",			OS},
 		{ "out",		OUT},
 		{ "overload",		OVERLOAD},
 		{ "pass",		PASS},
 		{ "port",		PORT},
 		{ "prio",		PRIO},
 		{ "priority",		PRIORITY},
 		{ "priq",		PRIQ},
 		{ "probability",	PROBABILITY},
 		{ "proto",		PROTO},
 		{ "qlimit",		QLIMIT},
 		{ "queue",		QUEUE},
 		{ "quick",		QUICK},
 		{ "random",		RANDOM},
 		{ "random-id",		RANDOMID},
 		{ "rdr",		RDR},
 		{ "rdr-anchor",		RDRANCHOR},
 		{ "realtime",		REALTIME},
 		{ "reassemble",		REASSEMBLE},
 		{ "reply-to",		REPLYTO},
 		{ "require-order",	REQUIREORDER},
 		{ "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},
 		{ "set-tos",		SETTOS},
 		{ "skip",		SKIP},
 		{ "sloppy",		SLOPPY},
 		{ "source-hash",	SOURCEHASH},
 		{ "source-track",	SOURCETRACK},
 		{ "state",		STATE},
 		{ "state-defaults",	STATEDEFAULTS},
 		{ "state-policy",	STATEPOLICY},
 		{ "static-port",	STATICPORT},
 		{ "sticky-address",	STICKYADDRESS},
 		{ "synproxy",		SYNPROXY},
 		{ "table",		TABLE},
 		{ "tag",		TAG},
 		{ "tagged",		TAGGED},
 		{ "target",		TARGET},
 		{ "tbrsize",		TBRSIZE},
 		{ "timeout",		TIMEOUT},
 		{ "to",			TO},
 		{ "tos",		TOS},
 		{ "ttl",		TTL},
 		{ "upperlimit",		UPPERLIMIT},
 		{ "urpf-failed",	URPFFAILED},
 		{ "user",		USER},
 	};
 	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 MAXPUSHBACK	128
 
 static char	*parsebuf;
 static int	 parseindex;
 static char	 pushback_buffer[MAXPUSHBACK];
 static int	 pushback_index = 0;
 
 int
 lgetc(int quotec)
 {
 	int		c, next;
 
 	if (parsebuf) {
 		/* Read character from the parsebuffer instead of input. */
 		if (parseindex >= 0) {
 			c = parsebuf[parseindex++];
 			if (c != '\0')
 				return (c);
 			parsebuf = NULL;
 		} else
 			parseindex++;
 	}
 
 	if (pushback_index)
 		return (pushback_buffer[--pushback_index]);
 
 	if (quotec) {
 		if ((c = getc(file->stream)) == EOF) {
 			yyerror("reached end of file while parsing quoted string");
 			if (popfile() == EOF)
 				return (EOF);
 			return (quotec);
 		}
 		return (c);
 	}
 
 	while ((c = getc(file->stream)) == '\\') {
 		next = getc(file->stream);
 		if (next != '\n') {
 			c = next;
 			break;
 		}
 		yylval.lineno = file->lineno;
 		file->lineno++;
 	}
 
 	while (c == EOF) {
 		if (popfile() == EOF)
 			return (EOF);
 		c = getc(file->stream);
 	}
 	return (c);
 }
 
 int
 lungetc(int c)
 {
 	if (c == EOF)
 		return (EOF);
 	if (parsebuf) {
 		parseindex--;
 		if (parseindex >= 0)
 			return (c);
 	}
 	if (pushback_index < MAXPUSHBACK-1)
 		return (pushback_buffer[pushback_index++] = c);
 	else
 		return (EOF);
 }
 
 int
 findeol(void)
 {
 	int	c;
 
 	parsebuf = NULL;
 
 	/* skip to either EOF or the first real EOL */
 	while (1) {
 		if (pushback_index)
 			c = pushback_buffer[--pushback_index];
 		else
 			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 == '$' && parsebuf == NULL) {
 		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++ = (char)c;
 				continue;
 			}
 			*p = '\0';
 			lungetc(c);
 			break;
 		}
 		val = symget(buf);
 		if (val == NULL) {
 			yyerror("macro '%s' not defined", buf);
 			return (findeol());
 		}
 		parsebuf = val;
 		parseindex = 0;
 		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 || c == ' ' || c == '\t')
 					c = next;
 				else if (next == '\n') {
 					file->lineno++;
 					continue;
 				}
 				else
 					lungetc(next);
 			} else if (c == quotec) {
 				*p = '\0';
 				break;
 			}
 			if (p + 1 >= buf + sizeof(buf) - 1) {
 				yyerror("string too long");
 				return (findeol());
 			}
 			*p++ = (char)c;
 		}
 		yylval.v.string = strdup(buf);
 		if (yylval.v.string == NULL)
 			err(1, "yylex: strdup");
 		return (STRING);
 	case '<':
 		next = lgetc(0);
 		if (next == '>') {
 			yylval.v.i = PF_OP_XRG;
 			return (PORTBINARY);
 		}
 		lungetc(next);
 		break;
 	case '>':
 		next = lgetc(0);
 		if (next == '<') {
 			yylval.v.i = PF_OP_IRG;
 			return (PORTBINARY);
 		}
 		lungetc(next);
 		break;
 	case '-':
 		next = lgetc(0);
 		if (next == '>')
 			return (ARROW);
 		lungetc(next);
 		break;
 	}
 
 #define allowed_to_end_number(x) \
 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
 
 	if (c == '-' || isdigit(c)) {
 		do {
 			*p++ = c;
 			if ((unsigned)(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(*--p);
 			c = *--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 ((unsigned)(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, "yylex: strdup");
 		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_IRWXG | S_IRWXO)) {
 		warnx("%s: group/world readable/writeable", 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("malloc");
 		return (NULL);
 	}
 	if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) {
 		nfile->stream = stdin;
 		free(nfile->name);
 		if ((nfile->name = strdup("stdin")) == NULL) {
 			warn("strdup");
 			free(nfile);
 			return (NULL);
 		}
 	} else if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
 		warn("%s", 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 = 1;
 	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);
 		file = prev;
 		return (0);
 	}
 	return (EOF);
 }
 
 int
 parse_config(char *filename, struct pfctl *xpf)
 {
 	int		 errors = 0;
 	struct sym	*sym;
 
 	pf = xpf;
 	errors = 0;
 	rulestate = PFCTL_STATE_NONE;
 	returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
 	returnicmp6default =
 	    (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
 	blockpolicy = PFRULE_DROP;
 	failpolicy = PFRULE_DROP;
 	require_order = 1;
 
 	if ((file = pushfile(filename, 0)) == NULL) {
 		warn("cannot open the main config file!");
 		return (-1);
 	}
 
 	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;
 
 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
 	    sym = TAILQ_NEXT(sym, entry))
 		;	/* nothing */
 
 	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);
 
 	if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL)
 		err(1, "pfctl_cmdline_symset: malloc");
 
 	strlcpy(sym, s, strlen(s) - strlen(val) + 1);
 
 	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)
 {
 	int i;
 	struct pf_rule *r;
 
 	for (i = 0; i < PF_RULESET_MAX; ++i) {
 		while ((r = TAILQ_FIRST(src->rules[i].active.ptr))
 		    != NULL) {
 			TAILQ_REMOVE(src->rules[i].active.ptr, r, entries);
 			TAILQ_INSERT_TAIL(dst->rules[i].active.ptr, r, entries);
 			dst->anchor->match++;
 		}
 		src->anchor->match = 0;
 		while ((r = TAILQ_FIRST(src->rules[i].inactive.ptr))
 		    != NULL) {
 			TAILQ_REMOVE(src->rules[i].inactive.ptr, r, entries);
 			TAILQ_INSERT_TAIL(dst->rules[i].inactive.ptr,
 				r, entries);
 		}
 	}
 }
 
 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;
 		}
 	}
 }
 
 void
 remove_invalid_hosts(struct node_host **nh, sa_family_t *af)
 {
 	struct node_host	*n = *nh, *prev = NULL;
 
 	while (n != NULL) {
 		if (*af && n->af && n->af != *af) {
 			/* unlink and free n */
 			struct node_host *next = n->next;
 
 			/* adjust tail pointer */
 			if (n == (*nh)->tail)
 				(*nh)->tail = prev;
 			/* adjust previous node's next pointer */
 			if (prev == NULL)
 				*nh = next;
 			else
 				prev->next = next;
 			/* free node */
 			if (n->ifname != NULL)
 				free(n->ifname);
 			free(n);
 			n = next;
 		} else {
 			if (n->af && !*af)
 				*af = n->af;
 			prev = n;
 			n = n->next;
 		}
 	}
 }
 
 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 %d 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);
 	}
 	return (-1);
 }
 
 int
 pfctl_load_anchors(int dev, struct pfctl *pf, struct pfr_buffer *trans)
 {
 	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, 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
 rt_tableid_max(void)
 {
 #ifdef __FreeBSD__
 	int fibs;
 	size_t l = sizeof(fibs);
 
         if (sysctlbyname("net.fibs", &fibs, &l, NULL, 0) == -1)
 		fibs = 16;	/* XXX RT_MAXFIBS, at least limit it some. */
 	/*
 	 * As the OpenBSD code only compares > and not >= we need to adjust
 	 * here given we only accept values of 0..n and want to avoid #ifdefs
 	 * in the grammar.
 	 */
 	return (fibs - 1);
 #else
 	return (RT_TABLEID_MAX);
 #endif
 }
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 1aa17065597b..fde9d61260ef 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1,2710 +1,2512 @@
 /*	$OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */
 
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * Copyright (c) 2001 Daniel Hartmeier
  * Copyright (c) 2002,2003 Henning Brauer
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  *
  *    - Redistributions of source code must retain the above copyright
  *      notice, this list of conditions and the following disclaimer.
  *    - Redistributions in binary form must reproduce the above
  *      copyright notice, this list of conditions and the following
  *      disclaimer in the documentation and/or other materials provided
  *      with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  *
  */
 
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #define PFIOC_USE_LATEST
 
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/nv.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/endian.h>
 
 #include <net/if.h>
 #include <netinet/in.h>
 #include <net/pfvar.h>
 #include <arpa/inet.h>
 #include <net/altq/altq.h>
 #include <sys/sysctl.h>
 
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <libpfctl.h>
 #include <limits.h>
 #include <netdb.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-#include "pfctl_ioctl.h"
 #include "pfctl_parser.h"
 #include "pfctl.h"
 
 void	 usage(void);
 int	 pfctl_enable(int, int);
 int	 pfctl_disable(int, int);
 int	 pfctl_clear_stats(int, int);
 int	 pfctl_get_skip_ifaces(void);
 int	 pfctl_check_skip_ifaces(char *);
 int	 pfctl_adjust_skip_ifaces(struct pfctl *);
 int	 pfctl_clear_interface_flags(int, int);
 int	 pfctl_clear_rules(int, int, char *);
 int	 pfctl_clear_nat(int, int, char *);
 int	 pfctl_clear_altq(int, int);
 int	 pfctl_clear_src_nodes(int, int);
 int	 pfctl_clear_states(int, const char *, int);
 void	 pfctl_addrprefix(char *, struct pf_addr *);
 int	 pfctl_kill_src_nodes(int, const char *, int);
 int	 pfctl_net_kill_states(int, const char *, int);
 int	 pfctl_label_kill_states(int, const char *, int);
 int	 pfctl_id_kill_states(int, const char *, int);
 void	 pfctl_init_options(struct pfctl *);
 int	 pfctl_load_options(struct pfctl *);
 int	 pfctl_load_limit(struct pfctl *, unsigned int, unsigned int);
 int	 pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int);
 int	 pfctl_load_debug(struct pfctl *, unsigned int);
 int	 pfctl_load_logif(struct pfctl *, char *);
 int	 pfctl_load_hostid(struct pfctl *, u_int32_t);
 int	 pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int,
 	    char *);
 void	 pfctl_print_rule_counters(struct pf_rule *, int);
 int	 pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int);
 int	 pfctl_show_nat(int, int, char *);
 int	 pfctl_show_src_nodes(int, int);
 int	 pfctl_show_states(int, const char *, int);
 int	 pfctl_show_status(int, int);
 int	 pfctl_show_running(int);
 int	 pfctl_show_timeouts(int, int);
 int	 pfctl_show_limits(int, int);
 void	 pfctl_debug(int, u_int32_t, int);
 int	 pfctl_test_altqsupport(int, int);
 int	 pfctl_show_anchors(int, int, char *);
 int	 pfctl_ruleset_trans(struct pfctl *, char *, struct pf_anchor *);
 int	 pfctl_load_ruleset(struct pfctl *, char *,
 		struct pf_ruleset *, int, int);
 int	 pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int);
 const char	*pfctl_lookup_option(char *, const char * const *);
 
 static struct pf_anchor_global	 pf_anchors;
 static struct pf_anchor	 pf_main_anchor;
 static struct pfr_buffer skip_b;
 
 static const char	*clearopt;
 static char		*rulesopt;
 static const char	*showopt;
 static const char	*debugopt;
 static char		*anchoropt;
 static const char	*optiopt = NULL;
 static const char	*pf_device = "/dev/pf";
 static char		*ifaceopt;
 static char		*tableopt;
 static const char	*tblcmdopt;
 static int		 src_node_killers;
 static char		*src_node_kill[2];
 static int		 state_killers;
 static char		*state_kill[2];
 int			 loadopt;
 int			 altqsupport;
 
 int			 dev = -1;
 static int		 first_title = 1;
 static int		 labels = 0;
 
 #define INDENT(d, o)	do {						\
 				if (o) {				\
 					int i;				\
 					for (i=0; i < d; i++)		\
 						printf("  ");		\
 				}					\
 			} while (0);					\
 
 
 static const struct {
 	const char	*name;
 	int		index;
 } pf_limits[] = {
 	{ "states",		PF_LIMIT_STATES },
 	{ "src-nodes",		PF_LIMIT_SRC_NODES },
 	{ "frags",		PF_LIMIT_FRAGS },
 	{ "table-entries",	PF_LIMIT_TABLE_ENTRIES },
 	{ NULL,			0 }
 };
 
 struct pf_hint {
 	const char	*name;
 	int		timeout;
 };
 static const struct pf_hint pf_hint_normal[] = {
 	{ "tcp.first",		2 * 60 },
 	{ "tcp.opening",	30 },
 	{ "tcp.established",	24 * 60 * 60 },
 	{ "tcp.closing",	15 * 60 },
 	{ "tcp.finwait",	45 },
 	{ "tcp.closed",		90 },
 	{ "tcp.tsdiff",		30 },
 	{ NULL,			0 }
 };
 static const struct pf_hint pf_hint_satellite[] = {
 	{ "tcp.first",		3 * 60 },
 	{ "tcp.opening",	30 + 5 },
 	{ "tcp.established",	24 * 60 * 60 },
 	{ "tcp.closing",	15 * 60 + 5 },
 	{ "tcp.finwait",	45 + 5 },
 	{ "tcp.closed",		90 + 5 },
 	{ "tcp.tsdiff",		60 },
 	{ NULL,			0 }
 };
 static const struct pf_hint pf_hint_conservative[] = {
 	{ "tcp.first",		60 * 60 },
 	{ "tcp.opening",	15 * 60 },
 	{ "tcp.established",	5 * 24 * 60 * 60 },
 	{ "tcp.closing",	60 * 60 },
 	{ "tcp.finwait",	10 * 60 },
 	{ "tcp.closed",		3 * 60 },
 	{ "tcp.tsdiff",		60 },
 	{ NULL,			0 }
 };
 static const struct pf_hint pf_hint_aggressive[] = {
 	{ "tcp.first",		30 },
 	{ "tcp.opening",	5 },
 	{ "tcp.established",	5 * 60 * 60 },
 	{ "tcp.closing",	60 },
 	{ "tcp.finwait",	30 },
 	{ "tcp.closed",		30 },
 	{ "tcp.tsdiff",		10 },
 	{ NULL,			0 }
 };
 
 static const struct {
 	const char *name;
 	const struct pf_hint *hint;
 } pf_hints[] = {
 	{ "normal",		pf_hint_normal },
 	{ "satellite",		pf_hint_satellite },
 	{ "high-latency",	pf_hint_satellite },
 	{ "conservative",	pf_hint_conservative },
 	{ "aggressive",		pf_hint_aggressive },
 	{ NULL,			NULL }
 };
 
 static const char * const clearopt_list[] = {
 	"nat", "queue", "rules", "Sources",
 	"states", "info", "Tables", "osfp", "all", NULL
 };
 
 static const char * const showopt_list[] = {
 	"nat", "queue", "rules", "Anchors", "Sources", "states", "info",
 	"Interfaces", "labels", "timeouts", "memory", "Tables", "osfp",
 	"Running", "all", NULL
 };
 
 static const char * const tblcmdopt_list[] = {
 	"kill", "flush", "add", "delete", "load", "replace", "show",
 	"test", "zero", "expire", NULL
 };
 
 static const char * const debugopt_list[] = {
 	"none", "urgent", "misc", "loud", NULL
 };
 
 static const char * const optiopt_list[] = {
 	"none", "basic", "profile", NULL
 };
 
 void
 usage(void)
 {
 	extern char *__progname;
 
 	fprintf(stderr,
 "usage: %s [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n"
 	"\t[-f file] [-i interface] [-K host | network]\n"
 	"\t[-k host | network | label | id] [-o level] [-p device]\n"
 	"\t[-s modifier] [-t table -T command [address ...]] [-x level]\n",
 	    __progname);
 
 	exit(1);
 }
 
 int
 pfctl_enable(int dev, int opts)
 {
 	if (ioctl(dev, DIOCSTART)) {
 		if (errno == EEXIST)
 			errx(1, "pf already enabled");
 		else if (errno == ESRCH)
 			errx(1, "pfil registeration failed");
 		else
 			err(1, "DIOCSTART");
 	}
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "pf enabled\n");
 
 	if (altqsupport && ioctl(dev, DIOCSTARTALTQ))
 		if (errno != EEXIST)
 			err(1, "DIOCSTARTALTQ");
 
 	return (0);
 }
 
 int
 pfctl_disable(int dev, int opts)
 {
 	if (ioctl(dev, DIOCSTOP)) {
 		if (errno == ENOENT)
 			errx(1, "pf not enabled");
 		else
 			err(1, "DIOCSTOP");
 	}
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "pf disabled\n");
 
 	if (altqsupport && ioctl(dev, DIOCSTOPALTQ))
 			if (errno != ENOENT)
 				err(1, "DIOCSTOPALTQ");
 
 	return (0);
 }
 
 int
 pfctl_clear_stats(int dev, int opts)
 {
 	if (ioctl(dev, DIOCCLRSTATUS))
 		err(1, "DIOCCLRSTATUS");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "pf: statistics cleared\n");
 	return (0);
 }
 
 int
 pfctl_get_skip_ifaces(void)
 {
 	bzero(&skip_b, sizeof(skip_b));
 	skip_b.pfrb_type = PFRB_IFACES;
 	for (;;) {
 		pfr_buf_grow(&skip_b, skip_b.pfrb_size);
 		skip_b.pfrb_size = skip_b.pfrb_msize;
 		if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size))
 			err(1, "pfi_get_ifaces");
 		if (skip_b.pfrb_size <= skip_b.pfrb_msize)
 			break;
 	}
 	return (0);
 }
 
 int
 pfctl_check_skip_ifaces(char *ifname)
 {
 	struct pfi_kif		*p;
 	struct node_host	*h = NULL, *n = NULL;
 
 	PFRB_FOREACH(p, &skip_b) {
 		if (!strcmp(ifname, p->pfik_name) &&
 		    (p->pfik_flags & PFI_IFLAG_SKIP))
 			p->pfik_flags &= ~PFI_IFLAG_SKIP;
 		if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) {
 			if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL)
 				continue;
 
 			for (n = h; n != NULL; n = n->next) {
 				if (p->pfik_ifp == NULL)
 					continue;
 				if (strncmp(p->pfik_name, ifname, IFNAMSIZ))
 					continue;
 
 				p->pfik_flags &= ~PFI_IFLAG_SKIP;
 			}
 		}
 	}
 	return (0);
 }
 
 int
 pfctl_adjust_skip_ifaces(struct pfctl *pf)
 {
 	struct pfi_kif		*p, *pp;
 	struct node_host	*h = NULL, *n = NULL;
 
 	PFRB_FOREACH(p, &skip_b) {
 		if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP))
 			continue;
 
 		pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0);
 		if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL)
 			continue;
 
 		for (n = h; n != NULL; n = n->next)
 			PFRB_FOREACH(pp, &skip_b) {
 				if (pp->pfik_ifp == NULL)
 					continue;
 
 				if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ))
 					continue;
 
 				if (!(pp->pfik_flags & PFI_IFLAG_SKIP))
 					pfctl_set_interface_flags(pf,
 					    pp->pfik_name, PFI_IFLAG_SKIP, 1);
 				if (pp->pfik_flags & PFI_IFLAG_SKIP)
 					pp->pfik_flags &= ~PFI_IFLAG_SKIP;
 			}
 	}
 
 	PFRB_FOREACH(p, &skip_b) {
 		if (p->pfik_ifp == NULL || ! (p->pfik_flags & PFI_IFLAG_SKIP))
 			continue;
 
 		pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0);
 	}
 
 	return (0);
 }
 
 int
 pfctl_clear_interface_flags(int dev, int opts)
 {
 	struct pfioc_iface	pi;
 
 	if ((opts & PF_OPT_NOACTION) == 0) {
 		bzero(&pi, sizeof(pi));
 		pi.pfiio_flags = PFI_IFLAG_SKIP;
 
 		if (ioctl(dev, DIOCCLRIFFLAG, &pi))
 			err(1, "DIOCCLRIFFLAG");
 		if ((opts & PF_OPT_QUIET) == 0)
 			fprintf(stderr, "pf: interface flags reset\n");
 	}
 	return (0);
 }
 
 int
 pfctl_clear_rules(int dev, int opts, char *anchorname)
 {
 	struct pfr_buffer t;
 
 	memset(&t, 0, sizeof(t));
 	t.pfrb_type = PFRB_TRANS;
 	if (pfctl_add_trans(&t, PF_RULESET_SCRUB, anchorname) ||
 	    pfctl_add_trans(&t, PF_RULESET_FILTER, anchorname) ||
 	    pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
 	    pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
 		err(1, "pfctl_clear_rules");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "rules cleared\n");
 	return (0);
 }
 
 int
 pfctl_clear_nat(int dev, int opts, char *anchorname)
 {
 	struct pfr_buffer t;
 
 	memset(&t, 0, sizeof(t));
 	t.pfrb_type = PFRB_TRANS;
 	if (pfctl_add_trans(&t, PF_RULESET_NAT, anchorname) ||
 	    pfctl_add_trans(&t, PF_RULESET_BINAT, anchorname) ||
 	    pfctl_add_trans(&t, PF_RULESET_RDR, anchorname) ||
 	    pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
 	    pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
 		err(1, "pfctl_clear_nat");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "nat cleared\n");
 	return (0);
 }
 
 int
 pfctl_clear_altq(int dev, int opts)
 {
 	struct pfr_buffer t;
 
 	if (!altqsupport)
 		return (-1);
 	memset(&t, 0, sizeof(t));
 	t.pfrb_type = PFRB_TRANS;
 	if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") ||
 	    pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
 	    pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
 		err(1, "pfctl_clear_altq");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "altq cleared\n");
 	return (0);
 }
 
 int
 pfctl_clear_src_nodes(int dev, int opts)
 {
 	if (ioctl(dev, DIOCCLRSRCNODES))
 		err(1, "DIOCCLRSRCNODES");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "source tracking entries cleared\n");
 	return (0);
 }
 
 int
 pfctl_clear_states(int dev, const char *iface, int opts)
 {
 	struct pfioc_state_kill psk;
 
 	memset(&psk, 0, sizeof(psk));
 	if (iface != NULL && strlcpy(psk.psk_ifname, iface,
 	    sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
 		errx(1, "invalid interface: %s", iface);
 
 	if (ioctl(dev, DIOCCLRSTATES, &psk))
 		err(1, "DIOCCLRSTATES");
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "%d states cleared\n", psk.psk_killed);
 	return (0);
 }
 
 void
 pfctl_addrprefix(char *addr, struct pf_addr *mask)
 {
 	char *p;
 	const char *errstr;
 	int prefix, ret_ga, q, r;
 	struct addrinfo hints, *res;
 
 	if ((p = strchr(addr, '/')) == NULL)
 		return;
 
 	*p++ = '\0';
 	prefix = strtonum(p, 0, 128, &errstr);
 	if (errstr)
 		errx(1, "prefix is %s: %s", errstr, p);
 
 	bzero(&hints, sizeof(hints));
 	/* prefix only with numeric addresses */
 	hints.ai_flags |= AI_NUMERICHOST;
 
 	if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) {
 		errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
 		/* NOTREACHED */
 	}
 
 	if (res->ai_family == AF_INET && prefix > 32)
 		errx(1, "prefix too long for AF_INET");
 	else if (res->ai_family == AF_INET6 && prefix > 128)
 		errx(1, "prefix too long for AF_INET6");
 
 	q = prefix >> 3;
 	r = prefix & 7;
 	switch (res->ai_family) {
 	case AF_INET:
 		bzero(&mask->v4, sizeof(mask->v4));
 		mask->v4.s_addr = htonl((u_int32_t)
 		    (0xffffffffffULL << (32 - prefix)));
 		break;
 	case AF_INET6:
 		bzero(&mask->v6, sizeof(mask->v6));
 		if (q > 0)
 			memset((void *)&mask->v6, 0xff, q);
 		if (r > 0)
 			*((u_char *)&mask->v6 + q) =
 			    (0xff00 >> r) & 0xff;
 		break;
 	}
 	freeaddrinfo(res);
 }
 
 int
 pfctl_kill_src_nodes(int dev, const char *iface, int opts)
 {
 	struct pfioc_src_node_kill psnk;
 	struct addrinfo *res[2], *resp[2];
 	struct sockaddr last_src, last_dst;
 	int killed, sources, dests;
 	int ret_ga;
 
 	killed = sources = dests = 0;
 
 	memset(&psnk, 0, sizeof(psnk));
 	memset(&psnk.psnk_src.addr.v.a.mask, 0xff,
 	    sizeof(psnk.psnk_src.addr.v.a.mask));
 	memset(&last_src, 0xff, sizeof(last_src));
 	memset(&last_dst, 0xff, sizeof(last_dst));
 
 	pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask);
 
 	if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) {
 		errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
 		/* NOTREACHED */
 	}
 	for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
 		if (resp[0]->ai_addr == NULL)
 			continue;
 		/* We get lots of duplicates.  Catch the easy ones */
 		if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
 			continue;
 		last_src = *(struct sockaddr *)resp[0]->ai_addr;
 
 		psnk.psnk_af = resp[0]->ai_family;
 		sources++;
 
 		if (psnk.psnk_af == AF_INET)
 			psnk.psnk_src.addr.v.a.addr.v4 =
 			    ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr;
 		else if (psnk.psnk_af == AF_INET6)
 			psnk.psnk_src.addr.v.a.addr.v6 =
 			    ((struct sockaddr_in6 *)resp[0]->ai_addr)->
 			    sin6_addr;
 		else
 			errx(1, "Unknown address family %d", psnk.psnk_af);
 
 		if (src_node_killers > 1) {
 			dests = 0;
 			memset(&psnk.psnk_dst.addr.v.a.mask, 0xff,
 			    sizeof(psnk.psnk_dst.addr.v.a.mask));
 			memset(&last_dst, 0xff, sizeof(last_dst));
 			pfctl_addrprefix(src_node_kill[1],
 			    &psnk.psnk_dst.addr.v.a.mask);
 			if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL,
 			    &res[1]))) {
 				errx(1, "getaddrinfo: %s",
 				    gai_strerror(ret_ga));
 				/* NOTREACHED */
 			}
 			for (resp[1] = res[1]; resp[1];
 			    resp[1] = resp[1]->ai_next) {
 				if (resp[1]->ai_addr == NULL)
 					continue;
 				if (psnk.psnk_af != resp[1]->ai_family)
 					continue;
 
 				if (memcmp(&last_dst, resp[1]->ai_addr,
 				    sizeof(last_dst)) == 0)
 					continue;
 				last_dst = *(struct sockaddr *)resp[1]->ai_addr;
 
 				dests++;
 
 				if (psnk.psnk_af == AF_INET)
 					psnk.psnk_dst.addr.v.a.addr.v4 =
 					    ((struct sockaddr_in *)resp[1]->
 					    ai_addr)->sin_addr;
 				else if (psnk.psnk_af == AF_INET6)
 					psnk.psnk_dst.addr.v.a.addr.v6 =
 					    ((struct sockaddr_in6 *)resp[1]->
 					    ai_addr)->sin6_addr;
 				else
 					errx(1, "Unknown address family %d",
 					    psnk.psnk_af);
 
 				if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
 					err(1, "DIOCKILLSRCNODES");
 				killed += psnk.psnk_killed;
 			}
 			freeaddrinfo(res[1]);
 		} else {
 			if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
 				err(1, "DIOCKILLSRCNODES");
 			killed += psnk.psnk_killed;
 		}
 	}
 
 	freeaddrinfo(res[0]);
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d src nodes from %d sources and %d "
 		    "destinations\n", killed, sources, dests);
 	return (0);
 }
 
 int
 pfctl_net_kill_states(int dev, const char *iface, int opts)
 {
 	struct pfioc_state_kill psk;
 	struct addrinfo *res[2], *resp[2];
 	struct sockaddr last_src, last_dst;
 	int killed, sources, dests;
 	int ret_ga;
 
 	killed = sources = dests = 0;
 
 	memset(&psk, 0, sizeof(psk));
 	memset(&psk.psk_src.addr.v.a.mask, 0xff,
 	    sizeof(psk.psk_src.addr.v.a.mask));
 	memset(&last_src, 0xff, sizeof(last_src));
 	memset(&last_dst, 0xff, sizeof(last_dst));
 	if (iface != NULL && strlcpy(psk.psk_ifname, iface,
 	    sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
 		errx(1, "invalid interface: %s", iface);
 
 	pfctl_addrprefix(state_kill[0], &psk.psk_src.addr.v.a.mask);
 
 	if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) {
 		errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
 		/* NOTREACHED */
 	}
 	for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
 		if (resp[0]->ai_addr == NULL)
 			continue;
 		/* We get lots of duplicates.  Catch the easy ones */
 		if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
 			continue;
 		last_src = *(struct sockaddr *)resp[0]->ai_addr;
 
 		psk.psk_af = resp[0]->ai_family;
 		sources++;
 
 		if (psk.psk_af == AF_INET)
 			psk.psk_src.addr.v.a.addr.v4 =
 			    ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr;
 		else if (psk.psk_af == AF_INET6)
 			psk.psk_src.addr.v.a.addr.v6 =
 			    ((struct sockaddr_in6 *)resp[0]->ai_addr)->
 			    sin6_addr;
 		else
 			errx(1, "Unknown address family %d", psk.psk_af);
 
 		if (state_killers > 1) {
 			dests = 0;
 			memset(&psk.psk_dst.addr.v.a.mask, 0xff,
 			    sizeof(psk.psk_dst.addr.v.a.mask));
 			memset(&last_dst, 0xff, sizeof(last_dst));
 			pfctl_addrprefix(state_kill[1],
 			    &psk.psk_dst.addr.v.a.mask);
 			if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL,
 			    &res[1]))) {
 				errx(1, "getaddrinfo: %s",
 				    gai_strerror(ret_ga));
 				/* NOTREACHED */
 			}
 			for (resp[1] = res[1]; resp[1];
 			    resp[1] = resp[1]->ai_next) {
 				if (resp[1]->ai_addr == NULL)
 					continue;
 				if (psk.psk_af != resp[1]->ai_family)
 					continue;
 
 				if (memcmp(&last_dst, resp[1]->ai_addr,
 				    sizeof(last_dst)) == 0)
 					continue;
 				last_dst = *(struct sockaddr *)resp[1]->ai_addr;
 
 				dests++;
 
 				if (psk.psk_af == AF_INET)
 					psk.psk_dst.addr.v.a.addr.v4 =
 					    ((struct sockaddr_in *)resp[1]->
 					    ai_addr)->sin_addr;
 				else if (psk.psk_af == AF_INET6)
 					psk.psk_dst.addr.v.a.addr.v6 =
 					    ((struct sockaddr_in6 *)resp[1]->
 					    ai_addr)->sin6_addr;
 				else
 					errx(1, "Unknown address family %d",
 					    psk.psk_af);
 
 				if (ioctl(dev, DIOCKILLSTATES, &psk))
 					err(1, "DIOCKILLSTATES");
 				killed += psk.psk_killed;
 			}
 			freeaddrinfo(res[1]);
 		} else {
 			if (ioctl(dev, DIOCKILLSTATES, &psk))
 				err(1, "DIOCKILLSTATES");
 			killed += psk.psk_killed;
 		}
 	}
 
 	freeaddrinfo(res[0]);
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states from %d sources and %d "
 		    "destinations\n", killed, sources, dests);
 	return (0);
 }
 
 int
 pfctl_label_kill_states(int dev, const char *iface, int opts)
 {
 	struct pfioc_state_kill psk;
 
 	if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
 		warnx("no label specified");
 		usage();
 	}
 	memset(&psk, 0, sizeof(psk));
 	if (iface != NULL && strlcpy(psk.psk_ifname, iface,
 	    sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname))
 		errx(1, "invalid interface: %s", iface);
 
 	if (strlcpy(psk.psk_label, state_kill[1], sizeof(psk.psk_label)) >=
 	    sizeof(psk.psk_label))
 		errx(1, "label too long: %s", state_kill[1]);
 
 	if (ioctl(dev, DIOCKILLSTATES, &psk))
 		err(1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states\n", psk.psk_killed);
 
 	return (0);
 }
 
 int
 pfctl_id_kill_states(int dev, const char *iface, int opts)
 {
 	struct pfioc_state_kill psk;
 	
 	if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
 		warnx("no id specified");
 		usage();
 	}
 
 	memset(&psk, 0, sizeof(psk));
 	if ((sscanf(state_kill[1], "%jx/%x",
 	    &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2)
 		HTONL(psk.psk_pfcmp.creatorid);
 	else if ((sscanf(state_kill[1], "%jx", &psk.psk_pfcmp.id)) == 1) {
 		psk.psk_pfcmp.creatorid = 0;
 	} else {
 		warnx("wrong id format specified");
 		usage();
 	}
 	if (psk.psk_pfcmp.id == 0) {
 		warnx("cannot kill id 0");
 		usage();
 	}
 
 	psk.psk_pfcmp.id = htobe64(psk.psk_pfcmp.id);
 	if (ioctl(dev, DIOCKILLSTATES, &psk))
 		err(1, "DIOCKILLSTATES");
 
 	if ((opts & PF_OPT_QUIET) == 0)
 		fprintf(stderr, "killed %d states\n", psk.psk_killed);
 
 	return (0);
 }
 
 int
 pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr,
     u_int32_t ticket, int r_action, char *anchorname)
 {
 	struct pfioc_pooladdr pp;
 	struct pf_pooladdr *pa;
 	u_int32_t pnr, mpnr;
 
 	memset(&pp, 0, sizeof(pp));
 	memcpy(pp.anchor, anchorname, sizeof(pp.anchor));
 	pp.r_action = r_action;
 	pp.r_num = nr;
 	pp.ticket = ticket;
 	if (ioctl(dev, DIOCGETADDRS, &pp)) {
 		warn("DIOCGETADDRS");
 		return (-1);
 	}
 	mpnr = pp.nr;
 	TAILQ_INIT(&pool->list);
 	for (pnr = 0; pnr < mpnr; ++pnr) {
 		pp.nr = pnr;
 		if (ioctl(dev, DIOCGETADDR, &pp)) {
 			warn("DIOCGETADDR");
 			return (-1);
 		}
 		pa = calloc(1, sizeof(struct pf_pooladdr));
 		if (pa == NULL)
 			err(1, "calloc");
 		bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr));
 		TAILQ_INSERT_TAIL(&pool->list, pa, entries);
 	}
 
 	return (0);
 }
 
 void
 pfctl_move_pool(struct pf_pool *src, struct pf_pool *dst)
 {
 	struct pf_pooladdr *pa;
 
 	while ((pa = TAILQ_FIRST(&src->list)) != NULL) {
 		TAILQ_REMOVE(&src->list, pa, entries);
 		TAILQ_INSERT_TAIL(&dst->list, pa, entries);
 	}
 }
 
 void
 pfctl_clear_pool(struct pf_pool *pool)
 {
 	struct pf_pooladdr *pa;
 
 	while ((pa = TAILQ_FIRST(&pool->list)) != NULL) {
 		TAILQ_REMOVE(&pool->list, pa, entries);
 		free(pa);
 	}
 }
 
 void
 pfctl_print_rule_counters(struct pf_rule *rule, int opts)
 {
 	if (opts & PF_OPT_DEBUG) {
 		const char *t[PF_SKIP_COUNT] = { "i", "d", "f",
 		    "p", "sa", "sp", "da", "dp" };
 		int i;
 
 		printf("  [ Skip steps: ");
 		for (i = 0; i < PF_SKIP_COUNT; ++i) {
 			if (rule->skip[i].nr == rule->nr + 1)
 				continue;
 			printf("%s=", t[i]);
 			if (rule->skip[i].nr == -1)
 				printf("end ");
 			else
 				printf("%u ", rule->skip[i].nr);
 		}
 		printf("]\n");
 
 		printf("  [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n",
 		    rule->qname, rule->qid, rule->pqname, rule->pqid);
 	}
 	if (opts & PF_OPT_VERBOSE) {
 		printf("  [ Evaluations: %-8llu  Packets: %-8llu  "
 			    "Bytes: %-10llu  States: %-6ju]\n",
 			    (unsigned long long)rule->evaluations,
 			    (unsigned long long)(rule->packets[0] +
 			    rule->packets[1]),
 			    (unsigned long long)(rule->bytes[0] +
 			    rule->bytes[1]), (uintmax_t)rule->u_states_cur);
 		if (!(opts & PF_OPT_DEBUG))
 			printf("  [ Inserted: uid %u pid %u "
 			    "State Creations: %-6ju]\n",
 			    (unsigned)rule->cuid, (unsigned)rule->cpid,
 			    (uintmax_t)rule->u_states_tot);
 	}
 }
 
 void
 pfctl_print_title(char *title)
 {
 	if (!first_title)
 		printf("\n");
 	first_title = 0;
 	printf("%s\n", title);
 }
 
 int
 pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
     char *anchorname, int depth)
 {
 	struct pfioc_rule pr;
 	u_int32_t nr, mnr, header = 0;
 	int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG);
 	int numeric = opts & PF_OPT_NUMERIC;
 	int len = strlen(path);
 	int brace;
 	char *p;
 
 	if (path[0])
 		snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname);
 	else
 		snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname);
 
 	memset(&pr, 0, sizeof(pr));
 	memcpy(pr.anchor, path, sizeof(pr.anchor));
 	if (opts & PF_OPT_SHOWALL) {
 		pr.rule.action = PF_PASS;
 		if (ioctl(dev, DIOCGETRULES, &pr)) {
 			warn("DIOCGETRULES");
 			goto error;
 		}
 		header++;
 	}
 	pr.rule.action = PF_SCRUB;
 	if (ioctl(dev, DIOCGETRULES, &pr)) {
 		warn("DIOCGETRULES");
 		goto error;
 	}
 	if (opts & PF_OPT_SHOWALL) {
 		if (format == PFCTL_SHOW_RULES && (pr.nr > 0 || header))
 			pfctl_print_title("FILTER RULES:");
 		else if (format == PFCTL_SHOW_LABELS && labels)
 			pfctl_print_title("LABEL COUNTERS:");
 	}
 	mnr = pr.nr;
 	if (opts & PF_OPT_CLRRULECTRS)
 		pr.action = PF_GET_CLR_CNTR;
 
 	for (nr = 0; nr < mnr; ++nr) {
 		pr.nr = nr;
 		if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_SCRUB,
 		    &pr.rule, pr.anchor_call)) {
 			warn("DIOCGETRULENV");
 			goto error;
 		}
 
 		if (pfctl_get_pool(dev, &pr.rule.rpool,
 		    nr, pr.ticket, PF_SCRUB, path) != 0)
 			goto error;
 
 		switch (format) {
 		case PFCTL_SHOW_LABELS:
 			break;
 		case PFCTL_SHOW_RULES:
 			if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL))
 				labels = 1;
 			print_rule(&pr.rule, pr.anchor_call, rule_numbers, numeric);
 			printf("\n");
 			pfctl_print_rule_counters(&pr.rule, opts);
 			break;
 		case PFCTL_SHOW_NOTHING:
 			break;
 		}
 		pfctl_clear_pool(&pr.rule.rpool);
 	}
 	pr.rule.action = PF_PASS;
 	if (ioctl(dev, DIOCGETRULES, &pr)) {
 		warn("DIOCGETRULES");
 		goto error;
 	}
 	mnr = pr.nr;
 	for (nr = 0; nr < mnr; ++nr) {
 		pr.nr = nr;
 		if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_PASS,
 		    &pr.rule, pr.anchor_call)) {
 			warn("DIOCGETRULE");
 			goto error;
 		}
 
 		if (pfctl_get_pool(dev, &pr.rule.rpool,
 		    nr, pr.ticket, PF_PASS, path) != 0)
 			goto error;
 
 		switch (format) {
 		case PFCTL_SHOW_LABELS:
 			if (pr.rule.label[0]) {
 				printf("%s %llu %llu %llu %llu"
 				    " %llu %llu %llu %ju\n",
 				    pr.rule.label,
 				    (unsigned long long)pr.rule.evaluations,
 				    (unsigned long long)(pr.rule.packets[0] +
 				    pr.rule.packets[1]),
 				    (unsigned long long)(pr.rule.bytes[0] +
 				    pr.rule.bytes[1]),
 				    (unsigned long long)pr.rule.packets[0],
 				    (unsigned long long)pr.rule.bytes[0],
 				    (unsigned long long)pr.rule.packets[1],
 				    (unsigned long long)pr.rule.bytes[1],
 				    (uintmax_t)pr.rule.u_states_tot);
 			}
 			break;
 		case PFCTL_SHOW_RULES:
 			brace = 0;
 			if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL))
 				labels = 1;
 			INDENT(depth, !(opts & PF_OPT_VERBOSE));
 			if (pr.anchor_call[0] &&
 			   ((((p = strrchr(pr.anchor_call, '_')) != NULL) &&
 			   ((void *)p == (void *)pr.anchor_call ||
 			   *(--p) == '/')) || (opts & PF_OPT_RECURSE))) {
 				brace++;
 				if ((p = strrchr(pr.anchor_call, '/')) !=
 				    NULL)
 					p++;
 				else
 					p = &pr.anchor_call[0];
 			} else
 				p = &pr.anchor_call[0];
 		
 			print_rule(&pr.rule, p, rule_numbers, numeric);
 			if (brace)
 				printf(" {\n");
 			else
 				printf("\n");
 			pfctl_print_rule_counters(&pr.rule, opts);
 			if (brace) { 
 				pfctl_show_rules(dev, path, opts, format,
 				    p, depth + 1);
 				INDENT(depth, !(opts & PF_OPT_VERBOSE));
 				printf("}\n");
 			}
 			break;
 		case PFCTL_SHOW_NOTHING:
 			break;
 		}
 		pfctl_clear_pool(&pr.rule.rpool);
 	}
 	path[len] = '\0';
 	return (0);
 
  error:
 	path[len] = '\0';
 	return (-1);
 }
 
 int
 pfctl_show_nat(int dev, int opts, char *anchorname)
 {
 	struct pfioc_rule pr;
 	u_int32_t mnr, nr;
 	static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT };
 	int i, dotitle = opts & PF_OPT_SHOWALL;
 
 	memset(&pr, 0, sizeof(pr));
 	memcpy(pr.anchor, anchorname, sizeof(pr.anchor));
 	for (i = 0; i < 3; i++) {
 		pr.rule.action = nattype[i];
 		if (ioctl(dev, DIOCGETRULES, &pr)) {
 			warn("DIOCGETRULES");
 			return (-1);
 		}
 		mnr = pr.nr;
 		for (nr = 0; nr < mnr; ++nr) {
 			pr.nr = nr;
 			if (pfctl_get_rule(dev, nr, pr.ticket, anchorname,
 			    nattype[i], &pr.rule, pr.anchor_call)) {
 				warn("DIOCGETRULE");
 				return (-1);
 			}
 			if (pfctl_get_pool(dev, &pr.rule.rpool, nr,
 			    pr.ticket, nattype[i], anchorname) != 0)
 				return (-1);
 			if (dotitle) {
 				pfctl_print_title("TRANSLATION RULES:");
 				dotitle = 0;
 			}
 			print_rule(&pr.rule, pr.anchor_call,
 			    opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC);
 			printf("\n");
 			pfctl_print_rule_counters(&pr.rule, opts);
 			pfctl_clear_pool(&pr.rule.rpool);
 		}
 	}
 	return (0);
 }
 
 int
 pfctl_show_src_nodes(int dev, int opts)
 {
 	struct pfioc_src_nodes psn;
 	struct pf_src_node *p;
 	char *inbuf = NULL, *newinbuf = NULL;
 	unsigned int len = 0;
 	int i;
 
 	memset(&psn, 0, sizeof(psn));
 	for (;;) {
 		psn.psn_len = len;
 		if (len) {
 			newinbuf = realloc(inbuf, len);
 			if (newinbuf == NULL)
 				err(1, "realloc");
 			psn.psn_buf = inbuf = newinbuf;
 		}
 		if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) {
 			warn("DIOCGETSRCNODES");
 			free(inbuf);
 			return (-1);
 		}
 		if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len)
 			break;
 		if (len == 0 && psn.psn_len == 0)
 			goto done;
 		if (len == 0 && psn.psn_len != 0)
 			len = psn.psn_len;
 		if (psn.psn_len == 0)
 			goto done;	/* no src_nodes */
 		len *= 2;
 	}
 	p = psn.psn_src_nodes;
 	if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL))
 		pfctl_print_title("SOURCE TRACKING NODES:");
 	for (i = 0; i < psn.psn_len; i += sizeof(*p)) {
 		print_src_node(p, opts);
 		p++;
 	}
 done:
 	free(inbuf);
 	return (0);
 }
 
 int
 pfctl_show_states(int dev, const char *iface, int opts)
 {
 	struct pfioc_states ps;
 	struct pfsync_state *p;
 	char *inbuf = NULL, *newinbuf = NULL;
 	unsigned int len = 0;
 	int i, dotitle = (opts & PF_OPT_SHOWALL);
 
 	memset(&ps, 0, sizeof(ps));
 	for (;;) {
 		ps.ps_len = len;
 		if (len) {
 			newinbuf = realloc(inbuf, len);
 			if (newinbuf == NULL)
 				err(1, "realloc");
 			ps.ps_buf = inbuf = newinbuf;
 		}
 		if (ioctl(dev, DIOCGETSTATES, &ps) < 0) {
 			warn("DIOCGETSTATES");
 			free(inbuf);
 			return (-1);
 		}
 		if (ps.ps_len + sizeof(struct pfioc_states) < len)
 			break;
 		if (len == 0 && ps.ps_len == 0)
 			goto done;
 		if (len == 0 && ps.ps_len != 0)
 			len = ps.ps_len;
 		if (ps.ps_len == 0)
 			goto done;	/* no states */
 		len *= 2;
 	}
 	p = ps.ps_states;
 	for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) {
 		if (iface != NULL && strcmp(p->ifname, iface))
 			continue;
 		if (dotitle) {
 			pfctl_print_title("STATES:");
 			dotitle = 0;
 		}
 		print_state(p, opts);
 	}
 done:
 	free(inbuf);
 	return (0);
 }
 
 int
 pfctl_show_status(int dev, int opts)
 {
 	struct pf_status status;
 
 	if (ioctl(dev, DIOCGETSTATUS, &status)) {
 		warn("DIOCGETSTATUS");
 		return (-1);
 	}
 	if (opts & PF_OPT_SHOWALL)
 		pfctl_print_title("INFO:");
 	print_status(&status, opts);
 	return (0);
 }
 
 int
 pfctl_show_running(int dev)
 {
 	struct pf_status status;
 
 	if (ioctl(dev, DIOCGETSTATUS, &status)) {
 		warn("DIOCGETSTATUS");
 		return (-1);
 	}
 
 	print_running(&status);
 	return (!status.running);
 }
 
 int
 pfctl_show_timeouts(int dev, int opts)
 {
 	struct pfioc_tm pt;
 	int i;
 
 	if (opts & PF_OPT_SHOWALL)
 		pfctl_print_title("TIMEOUTS:");
 	memset(&pt, 0, sizeof(pt));
 	for (i = 0; pf_timeouts[i].name; i++) {
 		pt.timeout = pf_timeouts[i].timeout;
 		if (ioctl(dev, DIOCGETTIMEOUT, &pt))
 			err(1, "DIOCGETTIMEOUT");
 		printf("%-20s %10d", pf_timeouts[i].name, pt.seconds);
 		if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START &&
 		    pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END)
 			printf(" states");
 		else
 			printf("s");
 		printf("\n");
 	}
 	return (0);
 
 }
 
 int
 pfctl_show_limits(int dev, int opts)
 {
 	struct pfioc_limit pl;
 	int i;
 
 	if (opts & PF_OPT_SHOWALL)
 		pfctl_print_title("LIMITS:");
 	memset(&pl, 0, sizeof(pl));
 	for (i = 0; pf_limits[i].name; i++) {
 		pl.index = pf_limits[i].index;
 		if (ioctl(dev, DIOCGETLIMIT, &pl))
 			err(1, "DIOCGETLIMIT");
 		printf("%-13s ", pf_limits[i].name);
 		if (pl.limit == UINT_MAX)
 			printf("unlimited\n");
 		else
 			printf("hard limit %8u\n", pl.limit);
 	}
 	return (0);
 }
 
 /* callbacks for rule/nat/rdr/addr */
 int
 pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af)
 {
 	struct pf_pooladdr *pa;
 
 	if ((pf->opts & PF_OPT_NOACTION) == 0) {
 		if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr))
 			err(1, "DIOCBEGINADDRS");
 	}
 
 	pf->paddr.af = af;
 	TAILQ_FOREACH(pa, &p->list, entries) {
 		memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr));
 		if ((pf->opts & PF_OPT_NOACTION) == 0) {
 			if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr))
 				err(1, "DIOCADDADDR");
 		}
 	}
 	return (0);
 }
 
 int
-pfctl_add_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call)
+pfctl_append_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call)
 {
 	u_int8_t		rs_num;
 	struct pf_rule		*rule;
 	struct pf_ruleset	*rs;
 	char 			*p;
 
 	rs_num = pf_get_ruleset_number(r->action);
 	if (rs_num == PF_RULESET_MAX)
 		errx(1, "Invalid rule type %d", r->action);
 
 	rs = &pf->anchor->ruleset;
 
 	if (anchor_call[0] && r->anchor == NULL) {
 		/* 
 		 * Don't make non-brace anchors part of the main anchor pool.
 		 */
 		if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL)
-			err(1, "pfctl_add_rule: calloc");
+			err(1, "pfctl_append_rule: calloc");
 		
 		pf_init_ruleset(&r->anchor->ruleset);
 		r->anchor->ruleset.anchor = r->anchor;
 		if (strlcpy(r->anchor->path, anchor_call,
 		    sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path))
-			errx(1, "pfctl_add_rule: strlcpy");
+			errx(1, "pfctl_append_rule: strlcpy");
 		if ((p = strrchr(anchor_call, '/')) != NULL) {
 			if (!strlen(p))
-				err(1, "pfctl_add_rule: bad anchor name %s",
+				err(1, "pfctl_append_rule: bad anchor name %s",
 				    anchor_call);
 		} else
 			p = (char *)anchor_call;
 		if (strlcpy(r->anchor->name, p,
 		    sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name))
-			errx(1, "pfctl_add_rule: strlcpy");
+			errx(1, "pfctl_append_rule: strlcpy");
 	}
 
 	if ((rule = calloc(1, sizeof(*rule))) == NULL)
 		err(1, "calloc");
 	bcopy(r, rule, sizeof(*rule));
 	TAILQ_INIT(&rule->rpool.list);
 	pfctl_move_pool(&r->rpool, &rule->rpool);
 
 	TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries);
 	return (0);
 }
 
 int
 pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pf_anchor *a)
 {
 	int osize = pf->trans->pfrb_size;
 
 	if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) {
 		if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) ||
 		    pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) ||
 		    pfctl_add_trans(pf->trans, PF_RULESET_RDR, path))
 			return (1);
 	}
 	if (a == pf->astack[0] && ((altqsupport &&
 	    (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) {
 		if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path))
 			return (2);
 	}
 	if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) {
 		if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) ||
 		    pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path))
 			return (3);
 	}
 	if (pf->loadopt & PFCTL_FLAG_TABLE)
 		if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path))
 			return (4);
 	if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize))
 		return (5);
 
 	return (0);
 }
 
 int
 pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs,
     int rs_num, int depth)
 {
 	struct pf_rule *r;
 	int		error, len = strlen(path);
 	int		brace = 0;
 
 	pf->anchor = rs->anchor;
 
 	if (path[0])
 		snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name);
 	else
 		snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name);
 
 	if (depth) {
 		if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) {
 			brace++;
 			if (pf->opts & PF_OPT_VERBOSE)
 				printf(" {\n");
 			if ((pf->opts & PF_OPT_NOACTION) == 0 &&
 			    (error = pfctl_ruleset_trans(pf,
 			    path, rs->anchor))) {
 				printf("pfctl_load_rulesets: "
 				    "pfctl_ruleset_trans %d\n", error);
 				goto error;
 			}
 		} else if (pf->opts & PF_OPT_VERBOSE)
 			printf("\n");
 
 	}
 
 	if (pf->optimize && rs_num == PF_RULESET_FILTER)
 		pfctl_optimize_ruleset(pf, rs);
 
 	while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) {
 		TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries);
 		if ((error = pfctl_load_rule(pf, path, r, depth)))
 			goto error;
 		if (r->anchor) {
 			if ((error = pfctl_load_ruleset(pf, path,
 			    &r->anchor->ruleset, rs_num, depth + 1)))
 				goto error;
 		} else if (pf->opts & PF_OPT_VERBOSE)
 			printf("\n");
 		free(r);
 	}
 	if (brace && pf->opts & PF_OPT_VERBOSE) {
 		INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE));
 		printf("}\n");
 	}
 	path[len] = '\0';
 	return (0);
 
  error:
 	path[len] = '\0';
 	return (error);
 
 }
 
-static void
-pfctl_nv_add_addr(nvlist_t *nvparent, const char *name,
-    const struct pf_addr *addr)
-{
-	nvlist_t *nvl = nvlist_create(0);
-
-	nvlist_add_binary(nvl, "addr", addr, sizeof(*addr));
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static void
-pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name,
-    const struct pf_addr_wrap *addr)
-{
-	nvlist_t *nvl = nvlist_create(0);
-
-	nvlist_add_number(nvl, "type", addr->type);
-	nvlist_add_number(nvl, "iflags", addr->iflags);
-	nvlist_add_string(nvl, "ifname", addr->v.ifname);
-	nvlist_add_string(nvl, "tblname", addr->v.tblname);
-	pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr);
-	pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask);
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static void
-pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name,
-    const struct pf_rule_addr *addr)
-{
-	u_int64_t ports[2];
-	nvlist_t *nvl = nvlist_create(0);
-
-	pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr);
-	ports[0] = addr->port[0];
-	ports[1] = addr->port[1];
-	nvlist_add_number_array(nvl, "port", ports, 2);
-	nvlist_add_number(nvl, "neg", addr->neg);
-	nvlist_add_number(nvl, "port_op", addr->port_op);
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static void
-pfctl_nv_add_pool(nvlist_t *nvparent, const char *name,
-    const struct pf_pool *pool)
-{
-	u_int64_t ports[2];
-	nvlist_t *nvl = nvlist_create(0);
-
-	nvlist_add_binary(nvl, "key", &pool->key, sizeof(pool->key));
-	pfctl_nv_add_addr(nvl, "counter", &pool->counter);
-	nvlist_add_number(nvl, "tblidx", pool->tblidx);
-
-	ports[0] = pool->proxy_port[0];
-	ports[1] = pool->proxy_port[1];
-	nvlist_add_number_array(nvl, "proxy_port", ports, 2);
-	nvlist_add_number(nvl, "opts", pool->opts);
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static void
-pfctl_nv_add_uid(nvlist_t *nvparent, const char *name,
-    const struct pf_rule_uid *uid)
-{
-	u_int64_t uids[2];
-	nvlist_t *nvl = nvlist_create(0);
-
-	uids[0] = uid->uid[0];
-	uids[1] = uid->uid[1];
-	nvlist_add_number_array(nvl, "uid", uids, 2);
-	nvlist_add_number(nvl, "op", uid->op);
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static void
-pfctl_nv_add_divert(nvlist_t *nvparent, const char *name,
-    const struct pf_rule *r)
-{
-	nvlist_t *nvl = nvlist_create(0);
-
-	pfctl_nv_add_addr(nvl, "addr", &r->divert.addr);
-	nvlist_add_number(nvl, "port", r->divert.port);
-
-	nvlist_add_nvlist(nvparent, name, nvl);
-}
-
-static int
-pfctl_addrule(struct pfctl *pf, const struct pf_rule *r, const char *anchor,
-    const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket)
-{
-	struct pfioc_nv nv;
-	u_int64_t timeouts[PFTM_MAX];
-	u_int64_t set_prio[2];
-	nvlist_t *nvl, *nvlr;
-	int ret;
-
-	nvl = nvlist_create(0);
-	nvlr = nvlist_create(0);
-
-	nvlist_add_number(nvl, "ticket", ticket);
-	nvlist_add_number(nvl, "pool_ticket", pool_ticket);
-	nvlist_add_string(nvl, "anchor", anchor);
-	nvlist_add_string(nvl, "anchor_call", anchor_call);
-
-	nvlist_add_number(nvlr, "nr", r->nr);
-	pfctl_nv_add_rule_addr(nvlr, "src", &r->src);
-	pfctl_nv_add_rule_addr(nvlr, "dst", &r->dst);
-
-	nvlist_add_string(nvlr, "label", r->label);
-	nvlist_add_string(nvlr, "ifname", r->ifname);
-	nvlist_add_string(nvlr, "qname", r->qname);
-	nvlist_add_string(nvlr, "pqname", r->pqname);
-	nvlist_add_string(nvlr, "tagname", r->tagname);
-	nvlist_add_string(nvlr, "match_tagname", r->match_tagname);
-	nvlist_add_string(nvlr, "overload_tblname", r->overload_tblname);
-
-	pfctl_nv_add_pool(nvlr, "rpool", &r->rpool);
-
-	nvlist_add_number(nvlr, "os_fingerprint", r->os_fingerprint);
-
-	nvlist_add_number(nvlr, "rtableid", r->rtableid);
-	for (int i = 0; i < PFTM_MAX; i++)
-		timeouts[i] = r->timeout[i];
-	nvlist_add_number_array(nvlr, "timeout", timeouts, PFTM_MAX);
-	nvlist_add_number(nvlr, "max_states", r->max_states);
-	nvlist_add_number(nvlr, "max_src_nodes", r->max_src_nodes);
-	nvlist_add_number(nvlr, "max_src_states", r->max_src_states);
-	nvlist_add_number(nvlr, "max_src_conn", r->max_src_conn);
-	nvlist_add_number(nvlr, "max_src_conn_rate.limit",
-	    r->max_src_conn_rate.limit);
-	nvlist_add_number(nvlr, "max_src_conn_rate.seconds",
-	    r->max_src_conn_rate.seconds);
-	nvlist_add_number(nvlr, "prob", r->prob);
-	nvlist_add_number(nvlr, "cuid", r->cuid);
-	nvlist_add_number(nvlr, "cpid", r->cpid);
-
-	nvlist_add_number(nvlr, "return_icmp", r->return_icmp);
-	nvlist_add_number(nvlr, "return_icmp6", r->return_icmp6);
-
-	nvlist_add_number(nvlr, "max_mss", r->max_mss);
-	nvlist_add_number(nvlr, "scrub_flags", r->scrub_flags);
-
-	pfctl_nv_add_uid(nvlr, "uid", &r->uid);
-	pfctl_nv_add_uid(nvlr, "gid", (struct pf_rule_uid *)&r->gid);
-
-	nvlist_add_number(nvlr, "rule_flag", r->rule_flag);
-	nvlist_add_number(nvlr, "action", r->action);
-	nvlist_add_number(nvlr, "direction", r->direction);
-	nvlist_add_number(nvlr, "log", r->log);
-	nvlist_add_number(nvlr, "logif", r->logif);
-	nvlist_add_number(nvlr, "quick", r->quick);
-	nvlist_add_number(nvlr, "ifnot", r->ifnot);
-	nvlist_add_number(nvlr, "match_tag_not", r->match_tag_not);
-	nvlist_add_number(nvlr, "natpass", r->natpass);
-
-	nvlist_add_number(nvlr, "keep_state", r->keep_state);
-	nvlist_add_number(nvlr, "af", r->af);
-	nvlist_add_number(nvlr, "proto", r->proto);
-	nvlist_add_number(nvlr, "type", r->type);
-	nvlist_add_number(nvlr, "code", r->code);
-	nvlist_add_number(nvlr, "flags", r->flags);
-	nvlist_add_number(nvlr, "flagset", r->flagset);
-	nvlist_add_number(nvlr, "min_ttl", r->min_ttl);
-	nvlist_add_number(nvlr, "allow_opts", r->allow_opts);
-	nvlist_add_number(nvlr, "rt", r->rt);
-	nvlist_add_number(nvlr, "return_ttl", r->return_ttl);
-	nvlist_add_number(nvlr, "tos", r->tos);
-	nvlist_add_number(nvlr, "set_tos", r->set_tos);
-	nvlist_add_number(nvlr, "anchor_relative", r->anchor_relative);
-	nvlist_add_number(nvlr, "anchor_wildcard", r->anchor_wildcard);
-
-	nvlist_add_number(nvlr, "flush", r->flush);
-
-	nvlist_add_number(nvlr, "prio", r->prio);
-	set_prio[0] = r->set_prio[0];
-	set_prio[1] = r->set_prio[1];
-	nvlist_add_number_array(nvlr, "set_prio", set_prio, 2);
-
-	pfctl_nv_add_divert(nvlr, "divert", r);
-
-	nvlist_add_nvlist(nvl, "rule", nvlr);
-
-	/* Now do the call. */
-	nv.data = nvlist_pack(nvl, &nv.len);
-	nv.size = nv.len;
-
-	ret = ioctl(pf->dev, DIOCADDRULENV, &nv);
-
-	free(nv.data);
-	nvlist_destroy(nvl);
-
-	return (ret);
-}
-
 int
 pfctl_load_rule(struct pfctl *pf, char *path, struct pf_rule *r, int depth)
 {
 	u_int8_t		rs_num = pf_get_ruleset_number(r->action);
 	char			*name;
 	u_int32_t		ticket;
 	char			anchor[PF_ANCHOR_NAME_SIZE];
 	int			len = strlen(path);
 
 	/* set up anchor before adding to path for anchor_call */
 	if ((pf->opts & PF_OPT_NOACTION) == 0)
 		ticket = pfctl_get_ticket(pf->trans, rs_num, path);
 	if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor))
 		errx(1, "pfctl_load_rule: strlcpy");
 
 	if (r->anchor) {
 		if (r->anchor->match) {
 			if (path[0])
 				snprintf(&path[len], MAXPATHLEN - len,
 				    "/%s", r->anchor->name);
 			else
 				snprintf(&path[len], MAXPATHLEN - len,
 				    "%s", r->anchor->name);
 			name = r->anchor->name;
 		} else
 			name = r->anchor->path;
 	} else
 		name = "";
 
 	if ((pf->opts & PF_OPT_NOACTION) == 0) {
 		if (pfctl_add_pool(pf, &r->rpool, r->af))
 			return (1);
-		if (pfctl_addrule(pf, r, anchor, name, ticket,
+		if (pfctl_add_rule(pf->dev, r, anchor, name, ticket,
 		    pf->paddr.ticket))
 			err(1, "DIOCADDRULENV");
 	}
 
 	if (pf->opts & PF_OPT_VERBOSE) {
 		INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2));
 		print_rule(r, r->anchor ? r->anchor->name : "",
 		    pf->opts & PF_OPT_VERBOSE2,
 		    pf->opts & PF_OPT_NUMERIC);
 	}
 	path[len] = '\0';
 	pfctl_clear_pool(&r->rpool);
 	return (0);
 }
 
 int
 pfctl_add_altq(struct pfctl *pf, struct pf_altq *a)
 {
 	if (altqsupport &&
 	    (loadopt & PFCTL_FLAG_ALTQ) != 0) {
 		memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq));
 		if ((pf->opts & PF_OPT_NOACTION) == 0) {
 			if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) {
 				if (errno == ENXIO)
 					errx(1, "qtype not configured");
 				else if (errno == ENODEV)
 					errx(1, "%s: driver does not support "
 					    "altq", a->ifname);
 				else
 					err(1, "DIOCADDALTQ");
 			}
 		}
 		pfaltq_store(&pf->paltq->altq);
 	}
 	return (0);
 }
 
 int
 pfctl_rules(int dev, char *filename, int opts, int optimize,
     char *anchorname, struct pfr_buffer *trans)
 {
 #define ERR(x) do { warn(x); goto _error; } while(0)
 #define ERRX(x) do { warnx(x); goto _error; } while(0)
 
 	struct pfr_buffer	*t, buf;
 	struct pfioc_altq	 pa;
 	struct pfctl		 pf;
 	struct pf_ruleset	*rs;
 	struct pfr_table	 trs;
 	char			*path;
 	int			 osize;
 
 	RB_INIT(&pf_anchors);
 	memset(&pf_main_anchor, 0, sizeof(pf_main_anchor));
 	pf_init_ruleset(&pf_main_anchor.ruleset);
 	pf_main_anchor.ruleset.anchor = &pf_main_anchor;
 	if (trans == NULL) {
 		bzero(&buf, sizeof(buf));
 		buf.pfrb_type = PFRB_TRANS;
 		t = &buf;
 		osize = 0;
 	} else {
 		t = trans;
 		osize = t->pfrb_size;
 	}
 
 	memset(&pa, 0, sizeof(pa));
 	pa.version = PFIOC_ALTQ_VERSION;
 	memset(&pf, 0, sizeof(pf));
 	memset(&trs, 0, sizeof(trs));
 	if ((path = calloc(1, MAXPATHLEN)) == NULL)
 		ERRX("pfctl_rules: calloc");
 	if (strlcpy(trs.pfrt_anchor, anchorname,
 	    sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor))
 		ERRX("pfctl_rules: strlcpy");
 	pf.dev = dev;
 	pf.opts = opts;
 	pf.optimize = optimize;
 	pf.loadopt = loadopt;
 
 	/* non-brace anchor, create without resolving the path */
 	if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL)
 		ERRX("pfctl_rules: calloc");
 	rs = &pf.anchor->ruleset;
 	pf_init_ruleset(rs);
 	rs->anchor = pf.anchor;
 	if (strlcpy(pf.anchor->path, anchorname,
 	    sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path))
 		errx(1, "pfctl_add_rule: strlcpy");
 	if (strlcpy(pf.anchor->name, anchorname,
 	    sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name))
 		errx(1, "pfctl_add_rule: strlcpy");
 
 
 	pf.astack[0] = pf.anchor;
 	pf.asd = 0;
 	if (anchorname[0])
 		pf.loadopt &= ~PFCTL_FLAG_ALTQ;
 	pf.paltq = &pa;
 	pf.trans = t;
 	pfctl_init_options(&pf);
 
 	if ((opts & PF_OPT_NOACTION) == 0) {
 		/*
 		 * XXX For the time being we need to open transactions for
 		 * the main ruleset before parsing, because tables are still
 		 * loaded at parse time.
 		 */
 		if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor))
 			ERRX("pfctl_rules");
 		if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ))
 			pa.ticket =
 			    pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname);
 		if (pf.loadopt & PFCTL_FLAG_TABLE)
 			pf.astack[0]->ruleset.tticket =
 			    pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname);
 	}
 
 	if (parse_config(filename, &pf) < 0) {
 		if ((opts & PF_OPT_NOACTION) == 0)
 			ERRX("Syntax error in config file: "
 			    "pf rules not loaded");
 		else
 			goto _error;
 	}
 	if (loadopt & PFCTL_FLAG_OPTION)
 		pfctl_adjust_skip_ifaces(&pf);
 
 	if ((pf.loadopt & PFCTL_FLAG_FILTER &&
 	    (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) ||
 	    (pf.loadopt & PFCTL_FLAG_NAT &&
 	    (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) ||
 	    pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) ||
 	    pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) ||
 	    (pf.loadopt & PFCTL_FLAG_FILTER &&
 	    pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) {
 		if ((opts & PF_OPT_NOACTION) == 0)
 			ERRX("Unable to load rules into kernel");
 		else
 			goto _error;
 	}
 
 	if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0))
 		if (check_commit_altq(dev, opts) != 0)
 			ERRX("errors in altq config");
 
 	/* process "load anchor" directives */
 	if (!anchorname[0])
 		if (pfctl_load_anchors(dev, &pf, t) == -1)
 			ERRX("load anchors");
 
 	if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) {
 		if (!anchorname[0])
 			if (pfctl_load_options(&pf))
 				goto _error;
 		if (pfctl_trans(dev, t, DIOCXCOMMIT, osize))
 			ERR("DIOCXCOMMIT");
 	}
 	free(path);
 	return (0);
 
 _error:
 	if (trans == NULL) {	/* main ruleset */
 		if ((opts & PF_OPT_NOACTION) == 0)
 			if (pfctl_trans(dev, t, DIOCXROLLBACK, osize))
 				err(1, "DIOCXROLLBACK");
 		exit(1);
 	} else {		/* sub ruleset */
 		free(path);
 		return (-1);
 	}
 
 #undef ERR
 #undef ERRX
 }
 
 FILE *
 pfctl_fopen(const char *name, const char *mode)
 {
 	struct stat	 st;
 	FILE		*fp;
 
 	fp = fopen(name, mode);
 	if (fp == NULL)
 		return (NULL);
 	if (fstat(fileno(fp), &st)) {
 		fclose(fp);
 		return (NULL);
 	}
 	if (S_ISDIR(st.st_mode)) {
 		fclose(fp);
 		errno = EISDIR;
 		return (NULL);
 	}
 	return (fp);
 }
 
 void
 pfctl_init_options(struct pfctl *pf)
 {
 
 	pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
 	pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL;
 	pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
 	pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL;
 	pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL;
 	pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL;
 	pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL;
 	pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL;
 	pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL;
 	pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL;
 	pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL;
 	pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL;
 	pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL;
 	pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL;
 	pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL;
 	pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL;
 	pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL;
 	pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL;
 	pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START;
 	pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END;
 
 	pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT;
 	pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT;
 	pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT;
 	pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT;
 
 	pf->debug = PF_DEBUG_URGENT;
 }
 
 int
 pfctl_load_options(struct pfctl *pf)
 {
 	int i, error = 0;
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	/* load limits */
 	for (i = 0; i < PF_LIMIT_MAX; i++) {
 		if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i])
 			continue;
 		if (pfctl_load_limit(pf, i, pf->limit[i]))
 			error = 1;
 	}
 
 	/*
 	 * If we've set the limit, but haven't explicitly set adaptive
 	 * timeouts, do it now with a start of 60% and end of 120%.
 	 */
 	if (pf->limit_set[PF_LIMIT_STATES] &&
 	    !pf->timeout_set[PFTM_ADAPTIVE_START] &&
 	    !pf->timeout_set[PFTM_ADAPTIVE_END]) {
 		pf->timeout[PFTM_ADAPTIVE_START] =
 			(pf->limit[PF_LIMIT_STATES] / 10) * 6;
 		pf->timeout_set[PFTM_ADAPTIVE_START] = 1;
 		pf->timeout[PFTM_ADAPTIVE_END] =
 			(pf->limit[PF_LIMIT_STATES] / 10) * 12;
 		pf->timeout_set[PFTM_ADAPTIVE_END] = 1;
 	}
 
 	/* load timeouts */
 	for (i = 0; i < PFTM_MAX; i++) {
 		if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i])
 			continue;
 		if (pfctl_load_timeout(pf, i, pf->timeout[i]))
 			error = 1;
 	}
 
 	/* load debug */
 	if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set)
 		if (pfctl_load_debug(pf, pf->debug))
 			error = 1;
 
 	/* load logif */
 	if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set)
 		if (pfctl_load_logif(pf, pf->ifname))
 			error = 1;
 
 	/* load hostid */
 	if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set)
 		if (pfctl_load_hostid(pf, pf->hostid))
 			error = 1;
 
 	return (error);
 }
 
 int
 pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit)
 {
 	int i;
 
 
 	for (i = 0; pf_limits[i].name; i++) {
 		if (strcasecmp(opt, pf_limits[i].name) == 0) {
 			pf->limit[pf_limits[i].index] = limit;
 			pf->limit_set[pf_limits[i].index] = 1;
 			break;
 		}
 	}
 	if (pf_limits[i].name == NULL) {
 		warnx("Bad pool name.");
 		return (1);
 	}
 
 	if (pf->opts & PF_OPT_VERBOSE)
 		printf("set limit %s %d\n", opt, limit);
 
 	return (0);
 }
 
 int
 pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit)
 {
 	struct pfioc_limit pl;
 
 	memset(&pl, 0, sizeof(pl));
 	pl.index = index;
 	pl.limit = limit;
 	if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) {
 		if (errno == EBUSY)
 			warnx("Current pool size exceeds requested hard limit");
 		else
 			warnx("DIOCSETLIMIT");
 		return (1);
 	}
 	return (0);
 }
 
 int
 pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet)
 {
 	int i;
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	for (i = 0; pf_timeouts[i].name; i++) {
 		if (strcasecmp(opt, pf_timeouts[i].name) == 0) {
 			pf->timeout[pf_timeouts[i].timeout] = seconds;
 			pf->timeout_set[pf_timeouts[i].timeout] = 1;
 			break;
 		}
 	}
 
 	if (pf_timeouts[i].name == NULL) {
 		warnx("Bad timeout name.");
 		return (1);
 	}
 
 
 	if (pf->opts & PF_OPT_VERBOSE && ! quiet)
 		printf("set timeout %s %d\n", opt, seconds);
 
 	return (0);
 }
 
 int
 pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds)
 {
 	struct pfioc_tm pt;
 
 	memset(&pt, 0, sizeof(pt));
 	pt.timeout = timeout;
 	pt.seconds = seconds;
 	if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) {
 		warnx("DIOCSETTIMEOUT");
 		return (1);
 	}
 	return (0);
 }
 
 int
 pfctl_set_optimization(struct pfctl *pf, const char *opt)
 {
 	const struct pf_hint *hint;
 	int i, r;
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	for (i = 0; pf_hints[i].name; i++)
 		if (strcasecmp(opt, pf_hints[i].name) == 0)
 			break;
 
 	hint = pf_hints[i].hint;
 	if (hint == NULL) {
 		warnx("invalid state timeouts optimization");
 		return (1);
 	}
 
 	for (i = 0; hint[i].name; i++)
 		if ((r = pfctl_set_timeout(pf, hint[i].name,
 		    hint[i].timeout, 1)))
 			return (r);
 
 	if (pf->opts & PF_OPT_VERBOSE)
 		printf("set optimization %s\n", opt);
 
 	return (0);
 }
 
 int
 pfctl_set_logif(struct pfctl *pf, char *ifname)
 {
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	if (!strcmp(ifname, "none")) {
 		free(pf->ifname);
 		pf->ifname = NULL;
 	} else {
 		pf->ifname = strdup(ifname);
 		if (!pf->ifname)
 			errx(1, "pfctl_set_logif: strdup");
 	}
 	pf->ifname_set = 1;
 
 	if (pf->opts & PF_OPT_VERBOSE)
 		printf("set loginterface %s\n", ifname);
 
 	return (0);
 }
 
 int
 pfctl_load_logif(struct pfctl *pf, char *ifname)
 {
 	struct pfioc_if pi;
 
 	memset(&pi, 0, sizeof(pi));
 	if (ifname && strlcpy(pi.ifname, ifname,
 	    sizeof(pi.ifname)) >= sizeof(pi.ifname)) {
 		warnx("pfctl_load_logif: strlcpy");
 		return (1);
 	}
 	if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) {
 		warnx("DIOCSETSTATUSIF");
 		return (1);
 	}
 	return (0);
 }
 
 int
 pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid)
 {
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	HTONL(hostid);
 
 	pf->hostid = hostid;
 	pf->hostid_set = 1;
 
 	if (pf->opts & PF_OPT_VERBOSE)
 		printf("set hostid 0x%08x\n", ntohl(hostid));
 
 	return (0);
 }
 
 int
 pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid)
 {
 	if (ioctl(dev, DIOCSETHOSTID, &hostid)) {
 		warnx("DIOCSETHOSTID");
 		return (1);
 	}
 	return (0);
 }
 
 int
 pfctl_set_debug(struct pfctl *pf, char *d)
 {
 	u_int32_t	level;
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	if (!strcmp(d, "none"))
 		pf->debug = PF_DEBUG_NONE;
 	else if (!strcmp(d, "urgent"))
 		pf->debug = PF_DEBUG_URGENT;
 	else if (!strcmp(d, "misc"))
 		pf->debug = PF_DEBUG_MISC;
 	else if (!strcmp(d, "loud"))
 		pf->debug = PF_DEBUG_NOISY;
 	else {
 		warnx("unknown debug level \"%s\"", d);
 		return (-1);
 	}
 
 	pf->debug_set = 1;
 	level = pf->debug;
 
 	if ((pf->opts & PF_OPT_NOACTION) == 0)
 		if (ioctl(dev, DIOCSETDEBUG, &level))
 			err(1, "DIOCSETDEBUG");
 
 	if (pf->opts & PF_OPT_VERBOSE)
 		printf("set debug %s\n", d);
 
 	return (0);
 }
 
 int
 pfctl_load_debug(struct pfctl *pf, unsigned int level)
 {
 	if (ioctl(pf->dev, DIOCSETDEBUG, &level)) {
 		warnx("DIOCSETDEBUG");
 		return (1);
 	}
 	return (0);
 }
 
 int
 pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how)
 {
 	struct pfioc_iface	pi;
 	struct node_host	*h = NULL, *n = NULL;
 
 	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
 		return (0);
 
 	bzero(&pi, sizeof(pi));
 
 	pi.pfiio_flags = flags;
 
 	/* Make sure our cache matches the kernel. If we set or clear the flag
 	 * for a group this applies to all members. */
 	h = ifa_grouplookup(ifname, 0);
 	for (n = h; n != NULL; n = n->next)
 		pfctl_set_interface_flags(pf, n->ifname, flags, how);
 
 	if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >=
 	    sizeof(pi.pfiio_name))
 		errx(1, "pfctl_set_interface_flags: strlcpy");
 
 	if ((pf->opts & PF_OPT_NOACTION) == 0) {
 		if (how == 0) {
 			if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi))
 				err(1, "DIOCCLRIFFLAG");
 		} else {
 			if (ioctl(pf->dev, DIOCSETIFFLAG, &pi))
 				err(1, "DIOCSETIFFLAG");
 			pfctl_check_skip_ifaces(ifname);
 		}
 	}
 	return (0);
 }
 
 void
 pfctl_debug(int dev, u_int32_t level, int opts)
 {
 	if (ioctl(dev, DIOCSETDEBUG, &level))
 		err(1, "DIOCSETDEBUG");
 	if ((opts & PF_OPT_QUIET) == 0) {
 		fprintf(stderr, "debug level set to '");
 		switch (level) {
 		case PF_DEBUG_NONE:
 			fprintf(stderr, "none");
 			break;
 		case PF_DEBUG_URGENT:
 			fprintf(stderr, "urgent");
 			break;
 		case PF_DEBUG_MISC:
 			fprintf(stderr, "misc");
 			break;
 		case PF_DEBUG_NOISY:
 			fprintf(stderr, "loud");
 			break;
 		default:
 			fprintf(stderr, "<invalid>");
 			break;
 		}
 		fprintf(stderr, "'\n");
 	}
 }
 
 int
 pfctl_test_altqsupport(int dev, int opts)
 {
 	struct pfioc_altq pa;
 
 	pa.version = PFIOC_ALTQ_VERSION;
 	if (ioctl(dev, DIOCGETALTQS, &pa)) {
 		if (errno == ENODEV) {
 			if (opts & PF_OPT_VERBOSE)
 				fprintf(stderr, "No ALTQ support in kernel\n"
 				    "ALTQ related functions disabled\n");
 			return (0);
 		} else
 			err(1, "DIOCGETALTQS");
 	}
 	return (1);
 }
 
 int
 pfctl_show_anchors(int dev, int opts, char *anchorname)
 {
 	struct pfioc_ruleset	 pr;
 	u_int32_t		 mnr, nr;
 
 	memset(&pr, 0, sizeof(pr));
 	memcpy(pr.path, anchorname, sizeof(pr.path));
 	if (ioctl(dev, DIOCGETRULESETS, &pr)) {
 		if (errno == EINVAL)
 			fprintf(stderr, "Anchor '%s' not found.\n",
 			    anchorname);
 		else
 			err(1, "DIOCGETRULESETS");
 		return (-1);
 	}
 	mnr = pr.nr;
 	for (nr = 0; nr < mnr; ++nr) {
 		char sub[MAXPATHLEN];
 
 		pr.nr = nr;
 		if (ioctl(dev, DIOCGETRULESET, &pr))
 			err(1, "DIOCGETRULESET");
 		if (!strcmp(pr.name, PF_RESERVED_ANCHOR))
 			continue;
 		sub[0] = 0;
 		if (pr.path[0]) {
 			strlcat(sub, pr.path, sizeof(sub));
 			strlcat(sub, "/", sizeof(sub));
 		}
 		strlcat(sub, pr.name, sizeof(sub));
 		if (sub[0] != '_' || (opts & PF_OPT_VERBOSE))
 			printf("  %s\n", sub);
 		if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub))
 			return (-1);
 	}
 	return (0);
 }
 
 const char *
 pfctl_lookup_option(char *cmd, const char * const *list)
 {
 	if (cmd != NULL && *cmd)
 		for (; *list; list++)
 			if (!strncmp(cmd, *list, strlen(cmd)))
 				return (*list);
 	return (NULL);
 }
 
 int
 main(int argc, char *argv[])
 {
 	int	 error = 0;
 	int	 ch;
 	int	 mode = O_RDONLY;
 	int	 opts = 0;
 	int	 optimize = PF_OPTIMIZE_BASIC;
 	char	 anchorname[MAXPATHLEN];
 	char	*path;
 
 	if (argc < 2)
 		usage();
 
 	while ((ch = getopt(argc, argv,
 	    "a:AdD:eqf:F:ghi:k:K:mnNOo:Pp:rRs:t:T:vx:z")) != -1) {
 		switch (ch) {
 		case 'a':
 			anchoropt = optarg;
 			break;
 		case 'd':
 			opts |= PF_OPT_DISABLE;
 			mode = O_RDWR;
 			break;
 		case 'D':
 			if (pfctl_cmdline_symset(optarg) < 0)
 				warnx("could not parse macro definition %s",
 				    optarg);
 			break;
 		case 'e':
 			opts |= PF_OPT_ENABLE;
 			mode = O_RDWR;
 			break;
 		case 'q':
 			opts |= PF_OPT_QUIET;
 			break;
 		case 'F':
 			clearopt = pfctl_lookup_option(optarg, clearopt_list);
 			if (clearopt == NULL) {
 				warnx("Unknown flush modifier '%s'", optarg);
 				usage();
 			}
 			mode = O_RDWR;
 			break;
 		case 'i':
 			ifaceopt = optarg;
 			break;
 		case 'k':
 			if (state_killers >= 2) {
 				warnx("can only specify -k twice");
 				usage();
 				/* NOTREACHED */
 			}
 			state_kill[state_killers++] = optarg;
 			mode = O_RDWR;
 			break;
 		case 'K':
 			if (src_node_killers >= 2) {
 				warnx("can only specify -K twice");
 				usage();
 				/* NOTREACHED */
 			}
 			src_node_kill[src_node_killers++] = optarg;
 			mode = O_RDWR;
 			break;
 		case 'm':
 			opts |= PF_OPT_MERGE;
 			break;
 		case 'n':
 			opts |= PF_OPT_NOACTION;
 			break;
 		case 'N':
 			loadopt |= PFCTL_FLAG_NAT;
 			break;
 		case 'r':
 			opts |= PF_OPT_USEDNS;
 			break;
 		case 'f':
 			rulesopt = optarg;
 			mode = O_RDWR;
 			break;
 		case 'g':
 			opts |= PF_OPT_DEBUG;
 			break;
 		case 'A':
 			loadopt |= PFCTL_FLAG_ALTQ;
 			break;
 		case 'R':
 			loadopt |= PFCTL_FLAG_FILTER;
 			break;
 		case 'o':
 			optiopt = pfctl_lookup_option(optarg, optiopt_list);
 			if (optiopt == NULL) {
 				warnx("Unknown optimization '%s'", optarg);
 				usage();
 			}
 			opts |= PF_OPT_OPTIMIZE;
 			break;
 		case 'O':
 			loadopt |= PFCTL_FLAG_OPTION;
 			break;
 		case 'p':
 			pf_device = optarg;
 			break;
 		case 'P':
 			opts |= PF_OPT_NUMERIC;
 			break;
 		case 's':
 			showopt = pfctl_lookup_option(optarg, showopt_list);
 			if (showopt == NULL) {
 				warnx("Unknown show modifier '%s'", optarg);
 				usage();
 			}
 			break;
 		case 't':
 			tableopt = optarg;
 			break;
 		case 'T':
 			tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list);
 			if (tblcmdopt == NULL) {
 				warnx("Unknown table command '%s'", optarg);
 				usage();
 			}
 			break;
 		case 'v':
 			if (opts & PF_OPT_VERBOSE)
 				opts |= PF_OPT_VERBOSE2;
 			opts |= PF_OPT_VERBOSE;
 			break;
 		case 'x':
 			debugopt = pfctl_lookup_option(optarg, debugopt_list);
 			if (debugopt == NULL) {
 				warnx("Unknown debug level '%s'", optarg);
 				usage();
 			}
 			mode = O_RDWR;
 			break;
 		case 'z':
 			opts |= PF_OPT_CLRRULECTRS;
 			mode = O_RDWR;
 			break;
 		case 'h':
 			/* FALLTHROUGH */
 		default:
 			usage();
 			/* NOTREACHED */
 		}
 	}
 
 	if (tblcmdopt != NULL) {
 		argc -= optind;
 		argv += optind;
 		ch = *tblcmdopt;
 		if (ch == 'l') {
 			loadopt |= PFCTL_FLAG_TABLE;
 			tblcmdopt = NULL;
 		} else
 			mode = strchr("acdefkrz", ch) ? O_RDWR : O_RDONLY;
 	} else if (argc != optind) {
 		warnx("unknown command line argument: %s ...", argv[optind]);
 		usage();
 		/* NOTREACHED */
 	}
 	if (loadopt == 0)
 		loadopt = ~0;
 
 	if ((path = calloc(1, MAXPATHLEN)) == NULL)
 		errx(1, "pfctl: calloc");
 	memset(anchorname, 0, sizeof(anchorname));
 	if (anchoropt != NULL) {
 		int len = strlen(anchoropt);
 
 		if (anchoropt[len - 1] == '*') {
 			if (len >= 2 && anchoropt[len - 2] == '/')
 				anchoropt[len - 2] = '\0';
 			else
 				anchoropt[len - 1] = '\0';
 			opts |= PF_OPT_RECURSE;
 		}
 		if (strlcpy(anchorname, anchoropt,
 		    sizeof(anchorname)) >= sizeof(anchorname))
 			errx(1, "anchor name '%s' too long",
 			    anchoropt);
 		loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE;
 	}
 
 	if ((opts & PF_OPT_NOACTION) == 0) {
 		dev = open(pf_device, mode);
 		if (dev == -1)
 			err(1, "%s", pf_device);
 		altqsupport = pfctl_test_altqsupport(dev, opts);
 	} else {
 		dev = open(pf_device, O_RDONLY);
 		if (dev >= 0)
 			opts |= PF_OPT_DUMMYACTION;
 		/* turn off options */
 		opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE);
 		clearopt = showopt = debugopt = NULL;
 #if !defined(ENABLE_ALTQ)
 		altqsupport = 0;
 #else
 		altqsupport = 1;
 #endif
 	}
 
 	if (opts & PF_OPT_DISABLE)
 		if (pfctl_disable(dev, opts))
 			error = 1;
 
 	if (showopt != NULL) {
 		switch (*showopt) {
 		case 'A':
 			pfctl_show_anchors(dev, opts, anchorname);
 			break;
 		case 'r':
 			pfctl_load_fingerprints(dev, opts);
 			pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES,
 			    anchorname, 0);
 			break;
 		case 'l':
 			pfctl_load_fingerprints(dev, opts);
 			pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS,
 			    anchorname, 0);
 			break;
 		case 'n':
 			pfctl_load_fingerprints(dev, opts);
 			pfctl_show_nat(dev, opts, anchorname);
 			break;
 		case 'q':
 			pfctl_show_altq(dev, ifaceopt, opts,
 			    opts & PF_OPT_VERBOSE2);
 			break;
 		case 's':
 			pfctl_show_states(dev, ifaceopt, opts);
 			break;
 		case 'S':
 			pfctl_show_src_nodes(dev, opts);
 			break;
 		case 'i':
 			pfctl_show_status(dev, opts);
 			break;
 		case 'R':
 			error = pfctl_show_running(dev);
 			break;
 		case 't':
 			pfctl_show_timeouts(dev, opts);
 			break;
 		case 'm':
 			pfctl_show_limits(dev, opts);
 			break;
 		case 'a':
 			opts |= PF_OPT_SHOWALL;
 			pfctl_load_fingerprints(dev, opts);
 
 			pfctl_show_nat(dev, opts, anchorname);
 			pfctl_show_rules(dev, path, opts, 0, anchorname, 0);
 			pfctl_show_altq(dev, ifaceopt, opts, 0);
 			pfctl_show_states(dev, ifaceopt, opts);
 			pfctl_show_src_nodes(dev, opts);
 			pfctl_show_status(dev, opts);
 			pfctl_show_rules(dev, path, opts, 1, anchorname, 0);
 			pfctl_show_timeouts(dev, opts);
 			pfctl_show_limits(dev, opts);
 			pfctl_show_tables(anchorname, opts);
 			pfctl_show_fingerprints(opts);
 			break;
 		case 'T':
 			pfctl_show_tables(anchorname, opts);
 			break;
 		case 'o':
 			pfctl_load_fingerprints(dev, opts);
 			pfctl_show_fingerprints(opts);
 			break;
 		case 'I':
 			pfctl_show_ifaces(ifaceopt, opts);
 			break;
 		}
 	}
 
 	if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL)
 		pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING,
 		    anchorname, 0);
 
 	if (clearopt != NULL) {
 		if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL)
 			errx(1, "anchor names beginning with '_' cannot "
 			    "be modified from the command line");
 
 		switch (*clearopt) {
 		case 'r':
 			pfctl_clear_rules(dev, opts, anchorname);
 			break;
 		case 'n':
 			pfctl_clear_nat(dev, opts, anchorname);
 			break;
 		case 'q':
 			pfctl_clear_altq(dev, opts);
 			break;
 		case 's':
 			pfctl_clear_states(dev, ifaceopt, opts);
 			break;
 		case 'S':
 			pfctl_clear_src_nodes(dev, opts);
 			break;
 		case 'i':
 			pfctl_clear_stats(dev, opts);
 			break;
 		case 'a':
 			pfctl_clear_rules(dev, opts, anchorname);
 			pfctl_clear_nat(dev, opts, anchorname);
 			pfctl_clear_tables(anchorname, opts);
 			if (!*anchorname) {
 				pfctl_clear_altq(dev, opts);
 				pfctl_clear_states(dev, ifaceopt, opts);
 				pfctl_clear_src_nodes(dev, opts);
 				pfctl_clear_stats(dev, opts);
 				pfctl_clear_fingerprints(dev, opts);
 				pfctl_clear_interface_flags(dev, opts);
 			}
 			break;
 		case 'o':
 			pfctl_clear_fingerprints(dev, opts);
 			break;
 		case 'T':
 			pfctl_clear_tables(anchorname, opts);
 			break;
 		}
 	}
 	if (state_killers) {
 		if (!strcmp(state_kill[0], "label"))
 			pfctl_label_kill_states(dev, ifaceopt, opts);
 		else if (!strcmp(state_kill[0], "id"))
 			pfctl_id_kill_states(dev, ifaceopt, opts);
 		else
 			pfctl_net_kill_states(dev, ifaceopt, opts);
 	}
 
 	if (src_node_killers)
 		pfctl_kill_src_nodes(dev, ifaceopt, opts);
 
 	if (tblcmdopt != NULL) {
 		error = pfctl_command_tables(argc, argv, tableopt,
 		    tblcmdopt, rulesopt, anchorname, opts);
 		rulesopt = NULL;
 	}
 	if (optiopt != NULL) {
 		switch (*optiopt) {
 		case 'n':
 			optimize = 0;
 			break;
 		case 'b':
 			optimize |= PF_OPTIMIZE_BASIC;
 			break;
 		case 'o':
 		case 'p':
 			optimize |= PF_OPTIMIZE_PROFILE;
 			break;
 		}
 	}
 
 	if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) &&
 	    !anchorname[0] && !(opts & PF_OPT_NOACTION))
 		if (pfctl_get_skip_ifaces())
 			error = 1;
 
 	if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) &&
 	    !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION))
 		if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE))
 			error = 1;
 
 	if (rulesopt != NULL) {
 		if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL)
 			errx(1, "anchor names beginning with '_' cannot "
 			    "be modified from the command line");
 		if (pfctl_rules(dev, rulesopt, opts, optimize,
 		    anchorname, NULL))
 			error = 1;
 		else if (!(opts & PF_OPT_NOACTION) &&
 		    (loadopt & PFCTL_FLAG_TABLE))
 			warn_namespace_collision(NULL);
 	}
 
 	if (opts & PF_OPT_ENABLE)
 		if (pfctl_enable(dev, opts))
 			error = 1;
 
 	if (debugopt != NULL) {
 		switch (*debugopt) {
 		case 'n':
 			pfctl_debug(dev, PF_DEBUG_NONE, opts);
 			break;
 		case 'u':
 			pfctl_debug(dev, PF_DEBUG_URGENT, opts);
 			break;
 		case 'm':
 			pfctl_debug(dev, PF_DEBUG_MISC, opts);
 			break;
 		case 'l':
 			pfctl_debug(dev, PF_DEBUG_NOISY, opts);
 			break;
 		}
 	}
 
 	exit(error);
 }
diff --git a/sbin/pfctl/pfctl_ioctl.h b/sbin/pfctl/pfctl_ioctl.h
index 41dd0776854a..e69de29bb2d1 100644
--- a/sbin/pfctl/pfctl_ioctl.h
+++ b/sbin/pfctl/pfctl_ioctl.h
@@ -1,43 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- *    - Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *    - Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials provided
- *      with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * $FreeBSD$
- */
-
-#ifndef _PFCTL_IOCTL_H_
-#define _PFCTL_IOCTL_H_
-
-#include <netpfil/pf/pf.h>
-
-int	pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket,
-	    const char *anchor, u_int32_t ruleset, struct pf_rule *rule,
-	    char *anchor_call);
-
-#endif
diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c
index d3f0aa1bf3a4..821a528932f3 100644
--- a/sbin/pfctl/pfctl_optimize.c
+++ b/sbin/pfctl/pfctl_optimize.c
@@ -1,1669 +1,1669 @@
 /*	$OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */
 
 /*
  * Copyright (c) 2004 Mike Frantzen <frantzen@openbsd.org>
  *
  * 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 <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 
 #include <net/if.h>
 #include <net/pfvar.h>
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
 #include <assert.h>
 #include <ctype.h>
 #include <err.h>
 #include <errno.h>
+#include <libpfctl.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "pfctl_ioctl.h"
 #include "pfctl_parser.h"
 #include "pfctl.h"
 
 /* The size at which a table becomes faster than individual rules */
 #define TABLE_THRESHOLD		6
 
 
 /* #define OPT_DEBUG	1 */
 #ifdef OPT_DEBUG
 # define DEBUG(str, v...) \
 	printf("%s: " str "\n", __FUNCTION__ , ## v)
 #else
 # define DEBUG(str, v...) ((void)0)
 #endif
 
 
 /*
  * A container that lets us sort a superblock to optimize the skip step jumps
  */
 struct pf_skip_step {
 	int				ps_count;	/* number of items */
 	TAILQ_HEAD( , pf_opt_rule)	ps_rules;
 	TAILQ_ENTRY(pf_skip_step)	ps_entry;
 };
 
 
 /*
  * A superblock is a block of adjacent rules of similar action.  If there
  * are five PASS rules in a row, they all become members of a superblock.
  * Once we have a superblock, we are free to re-order any rules within it
  * in order to improve performance; if a packet is passed, it doesn't matter
  * who passed it.
  */
 struct superblock {
 	TAILQ_HEAD( , pf_opt_rule)		 sb_rules;
 	TAILQ_ENTRY(superblock)			 sb_entry;
 	struct superblock			*sb_profiled_block;
 	TAILQ_HEAD(skiplist, pf_skip_step)	 sb_skipsteps[PF_SKIP_COUNT];
 };
 TAILQ_HEAD(superblocks, superblock);
 
 
 /*
  * Description of the PF rule structure.
  */
 enum {
     BARRIER,	/* the presence of the field puts the rule in its own block */
     BREAK,	/* the field may not differ between rules in a superblock */
     NOMERGE,	/* the field may not differ between rules when combined */
     COMBINED,	/* the field may itself be combined with other rules */
     DC,		/* we just don't care about the field */
     NEVER};	/* we should never see this field set?!? */
 static struct pf_rule_field {
 	const char	*prf_name;
 	int		 prf_type;
 	size_t		 prf_offset;
 	size_t		 prf_size;
 } pf_rule_desc[] = {
 #define PF_RULE_FIELD(field, ty)	\
     {#field,				\
     ty,					\
     offsetof(struct pf_rule, field),	\
     sizeof(((struct pf_rule *)0)->field)}
 
 
     /*
      * The presence of these fields in a rule put the rule in its own
      * superblock.  Thus it will not be optimized.  It also prevents the
      * rule from being re-ordered at all.
      */
     PF_RULE_FIELD(label,		BARRIER),
     PF_RULE_FIELD(prob,			BARRIER),
     PF_RULE_FIELD(max_states,		BARRIER),
     PF_RULE_FIELD(max_src_nodes,	BARRIER),
     PF_RULE_FIELD(max_src_states,	BARRIER),
     PF_RULE_FIELD(max_src_conn,		BARRIER),
     PF_RULE_FIELD(max_src_conn_rate,	BARRIER),
     PF_RULE_FIELD(anchor,		BARRIER),	/* for now */
 
     /*
      * These fields must be the same between all rules in the same superblock.
      * These rules are allowed to be re-ordered but only among like rules.
      * For instance we can re-order all 'tag "foo"' rules because they have the
      * same tag.  But we can not re-order between a 'tag "foo"' and a
      * 'tag "bar"' since that would change the meaning of the ruleset.
      */
     PF_RULE_FIELD(tagname,		BREAK),
     PF_RULE_FIELD(keep_state,		BREAK),
     PF_RULE_FIELD(qname,		BREAK),
     PF_RULE_FIELD(pqname,		BREAK),
     PF_RULE_FIELD(rt,			BREAK),
     PF_RULE_FIELD(allow_opts,		BREAK),
     PF_RULE_FIELD(rule_flag,		BREAK),
     PF_RULE_FIELD(action,		BREAK),
     PF_RULE_FIELD(log,			BREAK),
     PF_RULE_FIELD(quick,		BREAK),
     PF_RULE_FIELD(return_ttl,		BREAK),
     PF_RULE_FIELD(overload_tblname,	BREAK),
     PF_RULE_FIELD(flush,		BREAK),
     PF_RULE_FIELD(rpool,		BREAK),
     PF_RULE_FIELD(logif,		BREAK),
 
     /*
      * Any fields not listed in this structure act as BREAK fields
      */
 
 
     /*
      * These fields must not differ when we merge two rules together but
      * their difference isn't enough to put the rules in different superblocks.
      * There are no problems re-ordering any rules with these fields.
      */
     PF_RULE_FIELD(af,			NOMERGE),
     PF_RULE_FIELD(ifnot,		NOMERGE),
     PF_RULE_FIELD(ifname,		NOMERGE),	/* hack for IF groups */
     PF_RULE_FIELD(match_tag_not,	NOMERGE),
     PF_RULE_FIELD(match_tagname,	NOMERGE),
     PF_RULE_FIELD(os_fingerprint,	NOMERGE),
     PF_RULE_FIELD(timeout,		NOMERGE),
     PF_RULE_FIELD(return_icmp,		NOMERGE),
     PF_RULE_FIELD(return_icmp6,		NOMERGE),
     PF_RULE_FIELD(uid,			NOMERGE),
     PF_RULE_FIELD(gid,			NOMERGE),
     PF_RULE_FIELD(direction,		NOMERGE),
     PF_RULE_FIELD(proto,		NOMERGE),
     PF_RULE_FIELD(type,			NOMERGE),
     PF_RULE_FIELD(code,			NOMERGE),
     PF_RULE_FIELD(flags,		NOMERGE),
     PF_RULE_FIELD(flagset,		NOMERGE),
     PF_RULE_FIELD(tos,			NOMERGE),
     PF_RULE_FIELD(src.port,		NOMERGE),
     PF_RULE_FIELD(dst.port,		NOMERGE),
     PF_RULE_FIELD(src.port_op,		NOMERGE),
     PF_RULE_FIELD(dst.port_op,		NOMERGE),
     PF_RULE_FIELD(src.neg,		NOMERGE),
     PF_RULE_FIELD(dst.neg,		NOMERGE),
 
     /* These fields can be merged */
     PF_RULE_FIELD(src.addr,		COMBINED),
     PF_RULE_FIELD(dst.addr,		COMBINED),
 
     /* We just don't care about these fields.  They're set by the kernel */
     PF_RULE_FIELD(skip,			DC),
     PF_RULE_FIELD(evaluations,		DC),
     PF_RULE_FIELD(packets,		DC),
     PF_RULE_FIELD(bytes,		DC),
     PF_RULE_FIELD(kif,			DC),
     PF_RULE_FIELD(states_cur,		DC),
     PF_RULE_FIELD(states_tot,		DC),
     PF_RULE_FIELD(src_nodes,		DC),
     PF_RULE_FIELD(nr,			DC),
     PF_RULE_FIELD(entries,		DC),
     PF_RULE_FIELD(qid,			DC),
     PF_RULE_FIELD(pqid,			DC),
     PF_RULE_FIELD(anchor_relative,	DC),
     PF_RULE_FIELD(anchor_wildcard,	DC),
     PF_RULE_FIELD(tag,			DC),
     PF_RULE_FIELD(match_tag,		DC),
     PF_RULE_FIELD(overload_tbl,		DC),
 
     /* These fields should never be set in a PASS/BLOCK rule */
     PF_RULE_FIELD(natpass,		NEVER),
     PF_RULE_FIELD(max_mss,		NEVER),
     PF_RULE_FIELD(min_ttl,		NEVER),
     PF_RULE_FIELD(set_tos,		NEVER),
 };
 
 
 
 int	add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t,
 	    struct pf_rule_addr *);
 int	addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *);
 int	addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *);
 int	block_feedback(struct pfctl *, struct superblock *);
 int	combine_rules(struct pfctl *, struct superblock *);
 void	comparable_rule(struct pf_rule *, const struct pf_rule *, int);
 int	construct_superblocks(struct pfctl *, struct pf_opt_queue *,
 	    struct superblocks *);
 void	exclude_supersets(struct pf_rule *, struct pf_rule *);
 int	interface_group(const char *);
 int	load_feedback_profile(struct pfctl *, struct superblocks *);
 int	optimize_superblock(struct pfctl *, struct superblock *);
 int	pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *);
 void	remove_from_skipsteps(struct skiplist *, struct superblock *,
 	    struct pf_opt_rule *, struct pf_skip_step *);
 int	remove_identical_rules(struct pfctl *, struct superblock *);
 int	reorder_rules(struct pfctl *, struct superblock *, int);
 int	rules_combineable(struct pf_rule *, struct pf_rule *);
 void	skip_append(struct superblock *, int, struct pf_skip_step *,
 	    struct pf_opt_rule *);
 int	skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *);
 void	skip_init(void);
 int	skip_cmp_af(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_dir(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_dst_addr(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_dst_port(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_ifp(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_proto(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_src_addr(struct pf_rule *, struct pf_rule *);
 int	skip_cmp_src_port(struct pf_rule *, struct pf_rule *);
 int	superblock_inclusive(struct superblock *, struct pf_opt_rule *);
 void	superblock_free(struct pfctl *, struct superblock *);
 
 
 static int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *,
     struct pf_rule *);
 static const char *skip_comparitors_names[PF_SKIP_COUNT];
 #define PF_SKIP_COMPARITORS {				\
     { "ifp", PF_SKIP_IFP, skip_cmp_ifp },		\
     { "dir", PF_SKIP_DIR, skip_cmp_dir },		\
     { "af", PF_SKIP_AF, skip_cmp_af },			\
     { "proto", PF_SKIP_PROTO, skip_cmp_proto },		\
     { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr },	\
     { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port },	\
     { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr },	\
     { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port }	\
 }
 
 static struct pfr_buffer table_buffer;
 static int table_identifier;
 
 
 int
 pfctl_optimize_ruleset(struct pfctl *pf, struct pf_ruleset *rs)
 {
 	struct superblocks superblocks;
 	struct pf_opt_queue opt_queue;
 	struct superblock *block;
 	struct pf_opt_rule *por;
 	struct pf_rule *r;
 	struct pf_rulequeue *old_rules;
 
 	DEBUG("optimizing ruleset");
 	memset(&table_buffer, 0, sizeof(table_buffer));
 	skip_init();
 	TAILQ_INIT(&opt_queue);
 
 	old_rules = rs->rules[PF_RULESET_FILTER].active.ptr;
 	rs->rules[PF_RULESET_FILTER].active.ptr =
 	    rs->rules[PF_RULESET_FILTER].inactive.ptr;
 	rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules;
 
 	/*
 	 * XXX expanding the pf_opt_rule format throughout pfctl might allow
 	 * us to avoid all this copying.
 	 */
 	while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr))
 	    != NULL) {
 		TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r,
 		    entries);
 		if ((por = calloc(1, sizeof(*por))) == NULL)
 			err(1, "calloc");
 		memcpy(&por->por_rule, r, sizeof(*r));
 		if (TAILQ_FIRST(&r->rpool.list) != NULL) {
 			TAILQ_INIT(&por->por_rule.rpool.list);
 			pfctl_move_pool(&r->rpool, &por->por_rule.rpool);
 		} else
 			bzero(&por->por_rule.rpool,
 			    sizeof(por->por_rule.rpool));
 
 
 		TAILQ_INSERT_TAIL(&opt_queue, por, por_entry);
 	}
 
 	TAILQ_INIT(&superblocks);
 	if (construct_superblocks(pf, &opt_queue, &superblocks))
 		goto error;
 
 	if (pf->optimize & PF_OPTIMIZE_PROFILE) {
 		if (load_feedback_profile(pf, &superblocks))
 			goto error;
 	}
 
 	TAILQ_FOREACH(block, &superblocks, sb_entry) {
 		if (optimize_superblock(pf, block))
 			goto error;
 	}
 
 	rs->anchor->refcnt = 0;
 	while ((block = TAILQ_FIRST(&superblocks))) {
 		TAILQ_REMOVE(&superblocks, block, sb_entry);
 
 		while ((por = TAILQ_FIRST(&block->sb_rules))) {
 			TAILQ_REMOVE(&block->sb_rules, por, por_entry);
 			por->por_rule.nr = rs->anchor->refcnt++;
 			if ((r = calloc(1, sizeof(*r))) == NULL)
 				err(1, "calloc");
 			memcpy(r, &por->por_rule, sizeof(*r));
 			TAILQ_INIT(&r->rpool.list);
 			pfctl_move_pool(&por->por_rule.rpool, &r->rpool);
 			TAILQ_INSERT_TAIL(
 			    rs->rules[PF_RULESET_FILTER].active.ptr,
 			    r, entries);
 			free(por);
 		}
 		free(block);
 	}
 
 	return (0);
 
 error:
 	while ((por = TAILQ_FIRST(&opt_queue))) {
 		TAILQ_REMOVE(&opt_queue, por, por_entry);
 		if (por->por_src_tbl) {
 			pfr_buf_clear(por->por_src_tbl->pt_buf);
 			free(por->por_src_tbl->pt_buf);
 			free(por->por_src_tbl);
 		}
 		if (por->por_dst_tbl) {
 			pfr_buf_clear(por->por_dst_tbl->pt_buf);
 			free(por->por_dst_tbl->pt_buf);
 			free(por->por_dst_tbl);
 		}
 		free(por);
 	}
 	while ((block = TAILQ_FIRST(&superblocks))) {
 		TAILQ_REMOVE(&superblocks, block, sb_entry);
 		superblock_free(pf, block);
 	}
 	return (1);
 }
 
 
 /*
  * Go ahead and optimize a superblock
  */
 int
 optimize_superblock(struct pfctl *pf, struct superblock *block)
 {
 #ifdef OPT_DEBUG
 	struct pf_opt_rule *por;
 #endif /* OPT_DEBUG */
 
 	/* We have a few optimization passes:
 	 *   1) remove duplicate rules or rules that are a subset of other
 	 *      rules
 	 *   2) combine otherwise identical rules with different IP addresses
 	 *      into a single rule and put the addresses in a table.
 	 *   3) re-order the rules to improve kernel skip steps
 	 *   4) re-order the 'quick' rules based on feedback from the
 	 *      active ruleset statistics
 	 *
 	 * XXX combine_rules() doesn't combine v4 and v6 rules.  would just
 	 *     have to keep af in the table container, make af 'COMBINE' and
 	 *     twiddle the af on the merged rule
 	 * XXX maybe add a weighting to the metric on skipsteps when doing
 	 *     reordering.  sometimes two sequential tables will be better
 	 *     that four consecutive interfaces.
 	 * XXX need to adjust the skipstep count of everything after PROTO,
 	 *     since they aren't actually checked on a proto mismatch in
 	 *     pf_test_{tcp, udp, icmp}()
 	 * XXX should i treat proto=0, af=0 or dir=0 special in skepstep
 	 *     calculation since they are a DC?
 	 * XXX keep last skiplist of last superblock to influence this
 	 *     superblock.  '5 inet6 log' should make '3 inet6' come before '4
 	 *     inet' in the next superblock.
 	 * XXX would be useful to add tables for ports
 	 * XXX we can also re-order some mutually exclusive superblocks to
 	 *     try merging superblocks before any of these optimization passes.
 	 *     for instance a single 'log in' rule in the middle of non-logging
 	 *     out rules.
 	 */
 
 	/* shortcut.  there will be a lot of 1-rule superblocks */
 	if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry))
 		return (0);
 
 #ifdef OPT_DEBUG
 	printf("--- Superblock ---\n");
 	TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
 		printf("  ");
 		print_rule(&por->por_rule, por->por_rule.anchor ?
 		    por->por_rule.anchor->name : "", 1, 0);
 	}
 #endif /* OPT_DEBUG */
 
 
 	if (remove_identical_rules(pf, block))
 		return (1);
 	if (combine_rules(pf, block))
 		return (1);
 	if ((pf->optimize & PF_OPTIMIZE_PROFILE) &&
 	    TAILQ_FIRST(&block->sb_rules)->por_rule.quick &&
 	    block->sb_profiled_block) {
 		if (block_feedback(pf, block))
 			return (1);
 	} else if (reorder_rules(pf, block, 0)) {
 		return (1);
 	}
 
 	/*
 	 * Don't add any optimization passes below reorder_rules().  It will
 	 * have divided superblocks into smaller blocks for further refinement
 	 * and doesn't put them back together again.  What once was a true
 	 * superblock might have been split into multiple superblocks.
 	 */
 
 #ifdef OPT_DEBUG
 	printf("--- END Superblock ---\n");
 #endif /* OPT_DEBUG */
 	return (0);
 }
 
 
 /*
  * Optimization pass #1: remove identical rules
  */
 int
 remove_identical_rules(struct pfctl *pf, struct superblock *block)
 {
 	struct pf_opt_rule *por1, *por2, *por_next, *por2_next;
 	struct pf_rule a, a2, b, b2;
 
 	for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) {
 		por_next = TAILQ_NEXT(por1, por_entry);
 		for (por2 = por_next; por2; por2 = por2_next) {
 			por2_next = TAILQ_NEXT(por2, por_entry);
 			comparable_rule(&a, &por1->por_rule, DC);
 			comparable_rule(&b, &por2->por_rule, DC);
 			memcpy(&a2, &a, sizeof(a2));
 			memcpy(&b2, &b, sizeof(b2));
 
 			exclude_supersets(&a, &b);
 			exclude_supersets(&b2, &a2);
 			if (memcmp(&a, &b, sizeof(a)) == 0) {
 				DEBUG("removing identical rule  nr%d = *nr%d*",
 				    por1->por_rule.nr, por2->por_rule.nr);
 				TAILQ_REMOVE(&block->sb_rules, por2, por_entry);
 				if (por_next == por2)
 					por_next = TAILQ_NEXT(por1, por_entry);
 				free(por2);
 			} else if (memcmp(&a2, &b2, sizeof(a2)) == 0) {
 				DEBUG("removing identical rule  *nr%d* = nr%d",
 				    por1->por_rule.nr, por2->por_rule.nr);
 				TAILQ_REMOVE(&block->sb_rules, por1, por_entry);
 				free(por1);
 				break;
 			}
 		}
 	}
 
 	return (0);
 }
 
 
 /*
  * Optimization pass #2: combine similar rules with different addresses
  * into a single rule and a table
  */
 int
 combine_rules(struct pfctl *pf, struct superblock *block)
 {
 	struct pf_opt_rule *p1, *p2, *por_next;
 	int src_eq, dst_eq;
 
 	if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) {
 		warnx("Must enable table loading for optimizations");
 		return (1);
 	}
 
 	/* First we make a pass to combine the rules.  O(n log n) */
 	TAILQ_FOREACH(p1, &block->sb_rules, por_entry) {
 		for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) {
 			por_next = TAILQ_NEXT(p2, por_entry);
 
 			src_eq = addrs_equal(&p1->por_rule.src,
 			    &p2->por_rule.src);
 			dst_eq = addrs_equal(&p1->por_rule.dst,
 			    &p2->por_rule.dst);
 
 			if (src_eq && !dst_eq && p1->por_src_tbl == NULL &&
 			    p2->por_dst_tbl == NULL &&
 			    p2->por_src_tbl == NULL &&
 			    rules_combineable(&p1->por_rule, &p2->por_rule) &&
 			    addrs_combineable(&p1->por_rule.dst,
 			    &p2->por_rule.dst)) {
 				DEBUG("can combine rules  nr%d = nr%d",
 				    p1->por_rule.nr, p2->por_rule.nr);
 				if (p1->por_dst_tbl == NULL &&
 				    add_opt_table(pf, &p1->por_dst_tbl,
 				    p1->por_rule.af, &p1->por_rule.dst))
 					return (1);
 				if (add_opt_table(pf, &p1->por_dst_tbl,
 				    p1->por_rule.af, &p2->por_rule.dst))
 					return (1);
 				p2->por_dst_tbl = p1->por_dst_tbl;
 				if (p1->por_dst_tbl->pt_rulecount >=
 				    TABLE_THRESHOLD) {
 					TAILQ_REMOVE(&block->sb_rules, p2,
 					    por_entry);
 					free(p2);
 				}
 			} else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL
 			    && p2->por_src_tbl == NULL &&
 			    p2->por_dst_tbl == NULL &&
 			    rules_combineable(&p1->por_rule, &p2->por_rule) &&
 			    addrs_combineable(&p1->por_rule.src,
 			    &p2->por_rule.src)) {
 				DEBUG("can combine rules  nr%d = nr%d",
 				    p1->por_rule.nr, p2->por_rule.nr);
 				if (p1->por_src_tbl == NULL &&
 				    add_opt_table(pf, &p1->por_src_tbl,
 				    p1->por_rule.af, &p1->por_rule.src))
 					return (1);
 				if (add_opt_table(pf, &p1->por_src_tbl,
 				    p1->por_rule.af, &p2->por_rule.src))
 					return (1);
 				p2->por_src_tbl = p1->por_src_tbl;
 				if (p1->por_src_tbl->pt_rulecount >=
 				    TABLE_THRESHOLD) {
 					TAILQ_REMOVE(&block->sb_rules, p2,
 					    por_entry);
 					free(p2);
 				}
 			}
 		}
 	}
 
 
 	/*
 	 * Then we make a final pass to create a valid table name and
 	 * insert the name into the rules.
 	 */
 	for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) {
 		por_next = TAILQ_NEXT(p1, por_entry);
 		assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL);
 
 		if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >=
 		    TABLE_THRESHOLD) {
 			if (p1->por_src_tbl->pt_generated) {
 				/* This rule is included in a table */
 				TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
 				free(p1);
 				continue;
 			}
 			p1->por_src_tbl->pt_generated = 1;
 
 			if ((pf->opts & PF_OPT_NOACTION) == 0 &&
 			    pf_opt_create_table(pf, p1->por_src_tbl))
 				return (1);
 
 			pf->tdirty = 1;
 
 			if (pf->opts & PF_OPT_VERBOSE)
 				print_tabledef(p1->por_src_tbl->pt_name,
 				    PFR_TFLAG_CONST, 1,
 				    &p1->por_src_tbl->pt_nodes);
 
 			memset(&p1->por_rule.src.addr, 0,
 			    sizeof(p1->por_rule.src.addr));
 			p1->por_rule.src.addr.type = PF_ADDR_TABLE;
 			strlcpy(p1->por_rule.src.addr.v.tblname,
 			    p1->por_src_tbl->pt_name,
 			    sizeof(p1->por_rule.src.addr.v.tblname));
 
 			pfr_buf_clear(p1->por_src_tbl->pt_buf);
 			free(p1->por_src_tbl->pt_buf);
 			p1->por_src_tbl->pt_buf = NULL;
 		}
 		if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >=
 		    TABLE_THRESHOLD) {
 			if (p1->por_dst_tbl->pt_generated) {
 				/* This rule is included in a table */
 				TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
 				free(p1);
 				continue;
 			}
 			p1->por_dst_tbl->pt_generated = 1;
 
 			if ((pf->opts & PF_OPT_NOACTION) == 0 &&
 			    pf_opt_create_table(pf, p1->por_dst_tbl))
 				return (1);
 			pf->tdirty = 1;
 
 			if (pf->opts & PF_OPT_VERBOSE)
 				print_tabledef(p1->por_dst_tbl->pt_name,
 				    PFR_TFLAG_CONST, 1,
 				    &p1->por_dst_tbl->pt_nodes);
 
 			memset(&p1->por_rule.dst.addr, 0,
 			    sizeof(p1->por_rule.dst.addr));
 			p1->por_rule.dst.addr.type = PF_ADDR_TABLE;
 			strlcpy(p1->por_rule.dst.addr.v.tblname,
 			    p1->por_dst_tbl->pt_name,
 			    sizeof(p1->por_rule.dst.addr.v.tblname));
 
 			pfr_buf_clear(p1->por_dst_tbl->pt_buf);
 			free(p1->por_dst_tbl->pt_buf);
 			p1->por_dst_tbl->pt_buf = NULL;
 		}
 	}
 
 	return (0);
 }
 
 
 /*
  * Optimization pass #3: re-order rules to improve skip steps
  */
 int
 reorder_rules(struct pfctl *pf, struct superblock *block, int depth)
 {
 	struct superblock *newblock;
 	struct pf_skip_step *skiplist;
 	struct pf_opt_rule *por;
 	int i, largest, largest_list, rule_count = 0;
 	TAILQ_HEAD( , pf_opt_rule) head;
 
 	/*
 	 * Calculate the best-case skip steps.  We put each rule in a list
 	 * of other rules with common fields
 	 */
 	for (i = 0; i < PF_SKIP_COUNT; i++) {
 		TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
 			TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i],
 			    ps_entry) {
 				if (skip_compare(i, skiplist, por) == 0)
 					break;
 			}
 			if (skiplist == NULL) {
 				if ((skiplist = calloc(1, sizeof(*skiplist))) ==
 				    NULL)
 					err(1, "calloc");
 				TAILQ_INIT(&skiplist->ps_rules);
 				TAILQ_INSERT_TAIL(&block->sb_skipsteps[i],
 				    skiplist, ps_entry);
 			}
 			skip_append(block, i, skiplist, por);
 		}
 	}
 
 	TAILQ_FOREACH(por, &block->sb_rules, por_entry)
 		rule_count++;
 
 	/*
 	 * Now we're going to ignore any fields that are identical between
 	 * all of the rules in the superblock and those fields which differ
 	 * between every rule in the superblock.
 	 */
 	largest = 0;
 	for (i = 0; i < PF_SKIP_COUNT; i++) {
 		skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
 		if (skiplist->ps_count == rule_count) {
 			DEBUG("(%d) original skipstep '%s' is all rules",
 			    depth, skip_comparitors_names[i]);
 			skiplist->ps_count = 0;
 		} else if (skiplist->ps_count == 1) {
 			skiplist->ps_count = 0;
 		} else {
 			DEBUG("(%d) original skipstep '%s' largest jump is %d",
 			    depth, skip_comparitors_names[i],
 			    skiplist->ps_count);
 			if (skiplist->ps_count > largest)
 				largest = skiplist->ps_count;
 		}
 	}
 	if (largest == 0) {
 		/* Ugh.  There is NO commonality in the superblock on which
 		 * optimize the skipsteps optimization.
 		 */
 		goto done;
 	}
 
 	/*
 	 * Now we're going to empty the superblock rule list and re-create
 	 * it based on a more optimal skipstep order.
 	 */
 	TAILQ_INIT(&head);
 	while ((por = TAILQ_FIRST(&block->sb_rules))) {
 		TAILQ_REMOVE(&block->sb_rules, por, por_entry);
 		TAILQ_INSERT_TAIL(&head, por, por_entry);
 	}
 
 
 	while (!TAILQ_EMPTY(&head)) {
 		largest = 1;
 
 		/*
 		 * Find the most useful skip steps remaining
 		 */
 		for (i = 0; i < PF_SKIP_COUNT; i++) {
 			skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
 			if (skiplist->ps_count > largest) {
 				largest = skiplist->ps_count;
 				largest_list = i;
 			}
 		}
 
 		if (largest <= 1) {
 			/*
 			 * Nothing useful left.  Leave remaining rules in order.
 			 */
 			DEBUG("(%d) no more commonality for skip steps", depth);
 			while ((por = TAILQ_FIRST(&head))) {
 				TAILQ_REMOVE(&head, por, por_entry);
 				TAILQ_INSERT_TAIL(&block->sb_rules, por,
 				    por_entry);
 			}
 		} else {
 			/*
 			 * There is commonality.  Extract those common rules
 			 * and place them in the ruleset adjacent to each
 			 * other.
 			 */
 			skiplist = TAILQ_FIRST(&block->sb_skipsteps[
 			    largest_list]);
 			DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d",
 			    depth, skip_comparitors_names[largest_list],
 			    largest, TAILQ_FIRST(&TAILQ_FIRST(&block->
 			    sb_skipsteps [largest_list])->ps_rules)->
 			    por_rule.nr);
 			TAILQ_REMOVE(&block->sb_skipsteps[largest_list],
 			    skiplist, ps_entry);
 
 
 			/*
 			 * There may be further commonality inside these
 			 * rules.  So we'll split them off into they're own
 			 * superblock and pass it back into the optimizer.
 			 */
 			if (skiplist->ps_count > 2) {
 				if ((newblock = calloc(1, sizeof(*newblock)))
 				    == NULL) {
 					warn("calloc");
 					return (1);
 				}
 				TAILQ_INIT(&newblock->sb_rules);
 				for (i = 0; i < PF_SKIP_COUNT; i++)
 					TAILQ_INIT(&newblock->sb_skipsteps[i]);
 				TAILQ_INSERT_BEFORE(block, newblock, sb_entry);
 				DEBUG("(%d) splitting off %d rules from superblock @ #%d",
 				    depth, skiplist->ps_count,
 				    TAILQ_FIRST(&skiplist->ps_rules)->
 				    por_rule.nr);
 			} else {
 				newblock = block;
 			}
 
 			while ((por = TAILQ_FIRST(&skiplist->ps_rules))) {
 				TAILQ_REMOVE(&head, por, por_entry);
 				TAILQ_REMOVE(&skiplist->ps_rules, por,
 				    por_skip_entry[largest_list]);
 				TAILQ_INSERT_TAIL(&newblock->sb_rules, por,
 				    por_entry);
 
 				/* Remove this rule from all other skiplists */
 				remove_from_skipsteps(&block->sb_skipsteps[
 				    largest_list], block, por, skiplist);
 			}
 			free(skiplist);
 			if (newblock != block)
 				if (reorder_rules(pf, newblock, depth + 1))
 					return (1);
 		}
 	}
 
 done:
 	for (i = 0; i < PF_SKIP_COUNT; i++) {
 		while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) {
 			TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist,
 			    ps_entry);
 			free(skiplist);
 		}
 	}
 
 	return (0);
 }
 
 
 /*
  * Optimization pass #4: re-order 'quick' rules based on feedback from the
  * currently running ruleset
  */
 int
 block_feedback(struct pfctl *pf, struct superblock *block)
 {
 	TAILQ_HEAD( , pf_opt_rule) queue;
 	struct pf_opt_rule *por1, *por2;
 	u_int64_t total_count = 0;
 	struct pf_rule a, b;
 
 
 	/*
 	 * Walk through all of the profiled superblock's rules and copy
 	 * the counters onto our rules.
 	 */
 	TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) {
 		comparable_rule(&a, &por1->por_rule, DC);
 		total_count += por1->por_rule.packets[0] +
 		    por1->por_rule.packets[1];
 		TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
 			if (por2->por_profile_count)
 				continue;
 			comparable_rule(&b, &por2->por_rule, DC);
 			if (memcmp(&a, &b, sizeof(a)) == 0) {
 				por2->por_profile_count =
 				    por1->por_rule.packets[0] +
 				    por1->por_rule.packets[1];
 				break;
 			}
 		}
 	}
 	superblock_free(pf, block->sb_profiled_block);
 	block->sb_profiled_block = NULL;
 
 	/*
 	 * Now we pull all of the rules off the superblock and re-insert them
 	 * in sorted order.
 	 */
 
 	TAILQ_INIT(&queue);
 	while ((por1 = TAILQ_FIRST(&block->sb_rules)) != NULL) {
 		TAILQ_REMOVE(&block->sb_rules, por1, por_entry);
 		TAILQ_INSERT_TAIL(&queue, por1, por_entry);
 	}
 
 	while ((por1 = TAILQ_FIRST(&queue)) != NULL) {
 		TAILQ_REMOVE(&queue, por1, por_entry);
 /* XXX I should sort all of the unused rules based on skip steps */
 		TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
 			if (por1->por_profile_count > por2->por_profile_count) {
 				TAILQ_INSERT_BEFORE(por2, por1, por_entry);
 				break;
 			}
 		}
 #ifdef __FreeBSD__
 		if (por2 == NULL)
 #else
 		if (por2 == TAILQ_END(&block->sb_rules))
 #endif
 			TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry);
 	}
 
 	return (0);
 }
 
 
 /*
  * Load the current ruleset from the kernel and try to associate them with
  * the ruleset we're optimizing.
  */
 int
 load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks)
 {
 	struct superblock *block, *blockcur;
 	struct superblocks prof_superblocks;
 	struct pf_opt_rule *por;
 	struct pf_opt_queue queue;
 	struct pfioc_rule pr;
 	struct pf_rule a, b;
 	int nr, mnr;
 
 	TAILQ_INIT(&queue);
 	TAILQ_INIT(&prof_superblocks);
 
 	memset(&pr, 0, sizeof(pr));
 	pr.rule.action = PF_PASS;
 	if (ioctl(pf->dev, DIOCGETRULES, &pr)) {
 		warn("DIOCGETRULES");
 		return (1);
 	}
 	mnr = pr.nr;
 
 	DEBUG("Loading %d active rules for a feedback profile", mnr);
 	for (nr = 0; nr < mnr; ++nr) {
 		struct pf_ruleset *rs;
 		if ((por = calloc(1, sizeof(*por))) == NULL) {
 			warn("calloc");
 			return (1);
 		}
 		pr.nr = nr;
 
 		if (pfctl_get_rule(pf->dev, nr, pr.ticket, "", PF_PASS,
 		    &pr.rule, pr.anchor_call)) {
 			warn("DIOCGETRULENV");
 			return (1);
 		}
 		memcpy(&por->por_rule, &pr.rule, sizeof(por->por_rule));
 		rs = pf_find_or_create_ruleset(pr.anchor_call);
 		por->por_rule.anchor = rs->anchor;
 		if (TAILQ_EMPTY(&por->por_rule.rpool.list))
 			memset(&por->por_rule.rpool, 0,
 			    sizeof(por->por_rule.rpool));
 		TAILQ_INSERT_TAIL(&queue, por, por_entry);
 
 		/* XXX pfctl_get_pool(pf->dev, &pr.rule.rpool, nr, pr.ticket,
 		 *         PF_PASS, pf->anchor) ???
 		 * ... pfctl_clear_pool(&pr.rule.rpool)
 		 */
 	}
 
 	if (construct_superblocks(pf, &queue, &prof_superblocks))
 		return (1);
 
 
 	/*
 	 * Now we try to associate the active ruleset's superblocks with
 	 * the superblocks we're compiling.
 	 */
 	block = TAILQ_FIRST(superblocks);
 	blockcur = TAILQ_FIRST(&prof_superblocks);
 	while (block && blockcur) {
 		comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule,
 		    BREAK);
 		comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule,
 		    BREAK);
 		if (memcmp(&a, &b, sizeof(a)) == 0) {
 			/* The two superblocks lined up */
 			block->sb_profiled_block = blockcur;
 		} else {
 			DEBUG("superblocks don't line up between #%d and #%d",
 			    TAILQ_FIRST(&block->sb_rules)->por_rule.nr,
 			    TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr);
 			break;
 		}
 		block = TAILQ_NEXT(block, sb_entry);
 		blockcur = TAILQ_NEXT(blockcur, sb_entry);
 	}
 
 
 
 	/* Free any superblocks we couldn't link */
 	while (blockcur) {
 		block = TAILQ_NEXT(blockcur, sb_entry);
 		superblock_free(pf, blockcur);
 		blockcur = block;
 	}
 	return (0);
 }
 
 
 /*
  * Compare a rule to a skiplist to see if the rule is a member
  */
 int
 skip_compare(int skipnum, struct pf_skip_step *skiplist,
     struct pf_opt_rule *por)
 {
 	struct pf_rule *a, *b;
 	if (skipnum >= PF_SKIP_COUNT || skipnum < 0)
 		errx(1, "skip_compare() out of bounds");
 	a = &por->por_rule;
 	b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule;
 
 	return ((skip_comparitors[skipnum])(a, b));
 }
 
 
 /*
  * Add a rule to a skiplist
  */
 void
 skip_append(struct superblock *superblock, int skipnum,
     struct pf_skip_step *skiplist, struct pf_opt_rule *por)
 {
 	struct pf_skip_step *prev;
 
 	skiplist->ps_count++;
 	TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]);
 
 	/* Keep the list of skiplists sorted by whichever is larger */
 	while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) &&
 	    prev->ps_count < skiplist->ps_count) {
 		TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum],
 		    skiplist, ps_entry);
 		TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry);
 	}
 }
 
 
 /*
  * Remove a rule from the other skiplist calculations.
  */
 void
 remove_from_skipsteps(struct skiplist *head, struct superblock *block,
     struct pf_opt_rule *por, struct pf_skip_step *active_list)
 {
 	struct pf_skip_step *sk, *next;
 	struct pf_opt_rule *p2;
 	int i, found;
 
 	for (i = 0; i < PF_SKIP_COUNT; i++) {
 		sk = TAILQ_FIRST(&block->sb_skipsteps[i]);
 		if (sk == NULL || sk == active_list || sk->ps_count <= 1)
 			continue;
 		found = 0;
 		do {
 			TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i])
 				if (p2 == por) {
 					TAILQ_REMOVE(&sk->ps_rules, p2,
 					    por_skip_entry[i]);
 					found = 1;
 					sk->ps_count--;
 					break;
 				}
 		} while (!found && (sk = TAILQ_NEXT(sk, ps_entry)));
 		if (found && sk) {
 			/* Does this change the sorting order? */
 			while ((next = TAILQ_NEXT(sk, ps_entry)) &&
 			    next->ps_count > sk->ps_count) {
 				TAILQ_REMOVE(head, sk, ps_entry);
 				TAILQ_INSERT_AFTER(head, next, sk, ps_entry);
 			}
 #ifdef OPT_DEBUG
 			next = TAILQ_NEXT(sk, ps_entry);
 			assert(next == NULL || next->ps_count <= sk->ps_count);
 #endif /* OPT_DEBUG */
 		}
 	}
 }
 
 
 /* Compare two rules AF field for skiplist construction */
 int
 skip_cmp_af(struct pf_rule *a, struct pf_rule *b)
 {
 	if (a->af != b->af || a->af == 0)
 		return (1);
 	return (0);
 }
 
 /* Compare two rules DIRECTION field for skiplist construction */
 int
 skip_cmp_dir(struct pf_rule *a, struct pf_rule *b)
 {
 	if (a->direction == 0 || a->direction != b->direction)
 		return (1);
 	return (0);
 }
 
 /* Compare two rules DST Address field for skiplist construction */
 int
 skip_cmp_dst_addr(struct pf_rule *a, struct pf_rule *b)
 {
 	if (a->dst.neg != b->dst.neg ||
 	    a->dst.addr.type != b->dst.addr.type)
 		return (1);
 	/* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
 	 *    && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
 	 *    a->proto == IPPROTO_ICMP
 	 *	return (1);
 	 */
 	switch (a->dst.addr.type) {
 	case PF_ADDR_ADDRMASK:
 		if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr,
 		    sizeof(a->dst.addr.v.a.addr)) ||
 		    memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
 		    sizeof(a->dst.addr.v.a.mask)) ||
 		    (a->dst.addr.v.a.addr.addr32[0] == 0 &&
 		    a->dst.addr.v.a.addr.addr32[1] == 0 &&
 		    a->dst.addr.v.a.addr.addr32[2] == 0 &&
 		    a->dst.addr.v.a.addr.addr32[3] == 0))
 			return (1);
 		return (0);
 	case PF_ADDR_DYNIFTL:
 		if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 ||
 		    a->dst.addr.iflags != b->dst.addr.iflags ||
 		    memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
 		    sizeof(a->dst.addr.v.a.mask)))
 			return (1);
 		return (0);
 	case PF_ADDR_NOROUTE:
 	case PF_ADDR_URPFFAILED:
 		return (0);
 	case PF_ADDR_TABLE:
 		return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname));
 	}
 	return (1);
 }
 
 /* Compare two rules DST port field for skiplist construction */
 int
 skip_cmp_dst_port(struct pf_rule *a, struct pf_rule *b)
 {
 	/* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
 	 *    && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
 	 *    a->proto == IPPROTO_ICMP
 	 *	return (1);
 	 */
 	if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op ||
 	    a->dst.port[0] != b->dst.port[0] ||
 	    a->dst.port[1] != b->dst.port[1])
 		return (1);
 	return (0);
 }
 
 /* Compare two rules IFP field for skiplist construction */
 int
 skip_cmp_ifp(struct pf_rule *a, struct pf_rule *b)
 {
 	if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0')
 		return (1);
 	return (a->ifnot != b->ifnot);
 }
 
 /* Compare two rules PROTO field for skiplist construction */
 int
 skip_cmp_proto(struct pf_rule *a, struct pf_rule *b)
 {
 	return (a->proto != b->proto || a->proto == 0);
 }
 
 /* Compare two rules SRC addr field for skiplist construction */
 int
 skip_cmp_src_addr(struct pf_rule *a, struct pf_rule *b)
 {
 	if (a->src.neg != b->src.neg ||
 	    a->src.addr.type != b->src.addr.type)
 		return (1);
 	/* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
 	 *    && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
 	 *    a->proto == IPPROTO_ICMP
 	 *	return (1);
 	 */
 	switch (a->src.addr.type) {
 	case PF_ADDR_ADDRMASK:
 		if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr,
 		    sizeof(a->src.addr.v.a.addr)) ||
 		    memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
 		    sizeof(a->src.addr.v.a.mask)) ||
 		    (a->src.addr.v.a.addr.addr32[0] == 0 &&
 		    a->src.addr.v.a.addr.addr32[1] == 0 &&
 		    a->src.addr.v.a.addr.addr32[2] == 0 &&
 		    a->src.addr.v.a.addr.addr32[3] == 0))
 			return (1);
 		return (0);
 	case PF_ADDR_DYNIFTL:
 		if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 ||
 		    a->src.addr.iflags != b->src.addr.iflags ||
 		    memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
 		    sizeof(a->src.addr.v.a.mask)))
 			return (1);
 		return (0);
 	case PF_ADDR_NOROUTE:
 	case PF_ADDR_URPFFAILED:
 		return (0);
 	case PF_ADDR_TABLE:
 		return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname));
 	}
 	return (1);
 }
 
 /* Compare two rules SRC port field for skiplist construction */
 int
 skip_cmp_src_port(struct pf_rule *a, struct pf_rule *b)
 {
 	if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op ||
 	    a->src.port[0] != b->src.port[0] ||
 	    a->src.port[1] != b->src.port[1])
 		return (1);
 	/* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
 	 *    && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
 	 *    a->proto == IPPROTO_ICMP
 	 *	return (1);
 	 */
 	return (0);
 }
 
 
 void
 skip_init(void)
 {
 	struct {
 		char *name;
 		int skipnum;
 		int (*func)(struct pf_rule *, struct pf_rule *);
 	} comps[] = PF_SKIP_COMPARITORS;
 	int skipnum, i;
 
 	for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) {
 		for (i = 0; i < sizeof(comps)/sizeof(*comps); i++)
 			if (comps[i].skipnum == skipnum) {
 				skip_comparitors[skipnum] = comps[i].func;
 				skip_comparitors_names[skipnum] = comps[i].name;
 			}
 	}
 	for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++)
 		if (skip_comparitors[skipnum] == NULL)
 			errx(1, "Need to add skip step comparitor to pfctl?!");
 }
 
 /*
  * Add a host/netmask to a table
  */
 int
 add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af,
     struct pf_rule_addr *addr)
 {
 #ifdef OPT_DEBUG
 	char buf[128];
 #endif /* OPT_DEBUG */
 	static int tablenum = 0;
 	struct node_host node_host;
 
 	if (*tbl == NULL) {
 		if ((*tbl = calloc(1, sizeof(**tbl))) == NULL ||
 		    ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) ==
 		    NULL)
 			err(1, "calloc");
 		(*tbl)->pt_buf->pfrb_type = PFRB_ADDRS;
 		SIMPLEQ_INIT(&(*tbl)->pt_nodes);
 
 		/* This is just a temporary table name */
 		snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d",
 		    PF_OPT_TABLE_PREFIX, tablenum++);
 		DEBUG("creating table <%s>", (*tbl)->pt_name);
 	}
 
 	memset(&node_host, 0, sizeof(node_host));
 	node_host.af = af;
 	node_host.addr = addr->addr;
 
 #ifdef OPT_DEBUG
 	DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af,
 	    &node_host.addr.v.a.addr, buf, sizeof(buf)),
 	    unmask(&node_host.addr.v.a.mask, af));
 #endif /* OPT_DEBUG */
 
 	if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) {
 		warn("failed to add host");
 		return (1);
 	}
 	if (pf->opts & PF_OPT_VERBOSE) {
 		struct node_tinit *ti;
 
 		if ((ti = calloc(1, sizeof(*ti))) == NULL)
 			err(1, "malloc");
 		if ((ti->host = malloc(sizeof(*ti->host))) == NULL)
 			err(1, "malloc");
 		memcpy(ti->host, &node_host, sizeof(*ti->host));
 		SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries);
 	}
 
 	(*tbl)->pt_rulecount++;
 	if ((*tbl)->pt_rulecount == TABLE_THRESHOLD)
 		DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name);
 
 	return (0);
 }
 
 
 /*
  * Do the dirty work of choosing an unused table name and creating it.
  * (be careful with the table name, it might already be used in another anchor)
  */
 int
 pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl)
 {
 	static int tablenum;
 	struct pfr_table *t;
 
 	if (table_buffer.pfrb_type == 0) {
 		/* Initialize the list of tables */
 		table_buffer.pfrb_type = PFRB_TABLES;
 		for (;;) {
 			pfr_buf_grow(&table_buffer, table_buffer.pfrb_size);
 			table_buffer.pfrb_size = table_buffer.pfrb_msize;
 			if (pfr_get_tables(NULL, table_buffer.pfrb_caddr,
 			    &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS))
 				err(1, "pfr_get_tables");
 			if (table_buffer.pfrb_size <= table_buffer.pfrb_msize)
 				break;
 		}
 		table_identifier = arc4random();
 	}
 
 	/* XXX would be *really* nice to avoid duplicating identical tables */
 
 	/* Now we have to pick a table name that isn't used */
 again:
 	DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name,
 	    PF_OPT_TABLE_PREFIX, table_identifier, tablenum);
 	snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d",
 	    PF_OPT_TABLE_PREFIX, table_identifier, tablenum);
 	PFRB_FOREACH(t, &table_buffer) {
 		if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) {
 			/* Collision.  Try again */
 			DEBUG("wow, table <%s> in use.  trying again",
 			    tbl->pt_name);
 			table_identifier = arc4random();
 			goto again;
 		}
 	}
 	tablenum++;
 
 
 	if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1,
 	    pf->astack[0]->name, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) {
 		warn("failed to create table %s in %s",
 		    tbl->pt_name, pf->astack[0]->name);
 		return (1);
 	}
 	return (0);
 }
 
 /*
  * Partition the flat ruleset into a list of distinct superblocks
  */
 int
 construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue,
     struct superblocks *superblocks)
 {
 	struct superblock *block = NULL;
 	struct pf_opt_rule *por;
 	int i;
 
 	while (!TAILQ_EMPTY(opt_queue)) {
 		por = TAILQ_FIRST(opt_queue);
 		TAILQ_REMOVE(opt_queue, por, por_entry);
 		if (block == NULL || !superblock_inclusive(block, por)) {
 			if ((block = calloc(1, sizeof(*block))) == NULL) {
 				warn("calloc");
 				return (1);
 			}
 			TAILQ_INIT(&block->sb_rules);
 			for (i = 0; i < PF_SKIP_COUNT; i++)
 				TAILQ_INIT(&block->sb_skipsteps[i]);
 			TAILQ_INSERT_TAIL(superblocks, block, sb_entry);
 		}
 		TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry);
 	}
 
 	return (0);
 }
 
 
 /*
  * Compare two rule addresses
  */
 int
 addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b)
 {
 	if (a->neg != b->neg)
 		return (0);
 	return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0);
 }
 
 
 /*
  * The addresses are not equal, but can we combine them into one table?
  */
 int
 addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b)
 {
 	if (a->addr.type != PF_ADDR_ADDRMASK ||
 	    b->addr.type != PF_ADDR_ADDRMASK)
 		return (0);
 	if (a->neg != b->neg || a->port_op != b->port_op ||
 	    a->port[0] != b->port[0] || a->port[1] != b->port[1])
 		return (0);
 	return (1);
 }
 
 
 /*
  * Are we allowed to combine these two rules
  */
 int
 rules_combineable(struct pf_rule *p1, struct pf_rule *p2)
 {
 	struct pf_rule a, b;
 
 	comparable_rule(&a, p1, COMBINED);
 	comparable_rule(&b, p2, COMBINED);
 	return (memcmp(&a, &b, sizeof(a)) == 0);
 }
 
 
 /*
  * Can a rule be included inside a superblock
  */
 int
 superblock_inclusive(struct superblock *block, struct pf_opt_rule *por)
 {
 	struct pf_rule a, b;
 	int i, j;
 
 	/* First check for hard breaks */
 	for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) {
 		if (pf_rule_desc[i].prf_type == BARRIER) {
 			for (j = 0; j < pf_rule_desc[i].prf_size; j++)
 				if (((char *)&por->por_rule)[j +
 				    pf_rule_desc[i].prf_offset] != 0)
 					return (0);
 		}
 	}
 
 	/* per-rule src-track is also a hard break */
 	if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK)
 		return (0);
 
 	/*
 	 * Have to handle interface groups separately.  Consider the following
 	 * rules:
 	 *	block on EXTIFS to any port 22
 	 *	pass  on em0 to any port 22
 	 * (where EXTIFS is an arbitrary interface group)
 	 * The optimizer may decide to re-order the pass rule in front of the
 	 * block rule.  But what if EXTIFS includes em0???  Such a reordering
 	 * would change the meaning of the ruleset.
 	 * We can't just lookup the EXTIFS group and check if em0 is a member
 	 * because the user is allowed to add interfaces to a group during
 	 * runtime.
 	 * Ergo interface groups become a defacto superblock break :-(
 	 */
 	if (interface_group(por->por_rule.ifname) ||
 	    interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) {
 		if (strcasecmp(por->por_rule.ifname,
 		    TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0)
 			return (0);
 	}
 
 	comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE);
 	comparable_rule(&b, &por->por_rule, NOMERGE);
 	if (memcmp(&a, &b, sizeof(a)) == 0)
 		return (1);
 
 #ifdef OPT_DEBUG
 	for (i = 0; i < sizeof(por->por_rule); i++) {
 		int closest = -1;
 		if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) {
 			for (j = 0; j < sizeof(pf_rule_desc) /
 			    sizeof(*pf_rule_desc); j++) {
 				if (i >= pf_rule_desc[j].prf_offset &&
 				    i < pf_rule_desc[j].prf_offset +
 				    pf_rule_desc[j].prf_size) {
 					DEBUG("superblock break @ %d due to %s",
 					    por->por_rule.nr,
 					    pf_rule_desc[j].prf_name);
 					return (0);
 				}
 				if (i > pf_rule_desc[j].prf_offset) {
 					if (closest == -1 ||
 					    i-pf_rule_desc[j].prf_offset <
 					    i-pf_rule_desc[closest].prf_offset)
 						closest = j;
 				}
 			}
 
 			if (closest >= 0)
 				DEBUG("superblock break @ %d on %s+%xh",
 				    por->por_rule.nr,
 				    pf_rule_desc[closest].prf_name,
 				    i - pf_rule_desc[closest].prf_offset -
 				    pf_rule_desc[closest].prf_size);
 			else
 				DEBUG("superblock break @ %d on field @ %d",
 				    por->por_rule.nr, i);
 			return (0);
 		}
 	}
 #endif /* OPT_DEBUG */
 
 	return (0);
 }
 
 
 /*
  * Figure out if an interface name is an actual interface or actually a
  * group of interfaces.
  */
 int
 interface_group(const char *ifname)
 {
 	int			s;
 	struct ifgroupreq	ifgr;
 
 	if (ifname == NULL || !ifname[0])
 		return (0);
 
 	s = get_query_socket();
 
 	memset(&ifgr, 0, sizeof(ifgr));
 	strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ);
 	if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
 		if (errno == ENOENT)
 			return (0);
 		else
 			err(1, "SIOCGIFGMEMB");
 	}
 
 	return (1);
 }
 
 
 /*
  * Make a rule that can directly compared by memcmp()
  */
 void
 comparable_rule(struct pf_rule *dst, const struct pf_rule *src, int type)
 {
 	int i;
 	/*
 	 * To simplify the comparison, we just zero out the fields that are
 	 * allowed to be different and then do a simple memcmp()
 	 */
 	memcpy(dst, src, sizeof(*dst));
 	for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++)
 		if (pf_rule_desc[i].prf_type >= type) {
 #ifdef OPT_DEBUG
 			assert(pf_rule_desc[i].prf_type != NEVER ||
 			    *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0);
 #endif /* OPT_DEBUG */
 			memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0,
 			    pf_rule_desc[i].prf_size);
 		}
 }
 
 
 /*
  * Remove superset information from two rules so we can directly compare them
  * with memcmp()
  */
 void
 exclude_supersets(struct pf_rule *super, struct pf_rule *sub)
 {
 	if (super->ifname[0] == '\0')
 		memset(sub->ifname, 0, sizeof(sub->ifname));
 	if (super->direction == PF_INOUT)
 		sub->direction = PF_INOUT;
 	if ((super->proto == 0 || super->proto == sub->proto) &&
 	    super->flags == 0 && super->flagset == 0 && (sub->flags ||
 	    sub->flagset)) {
 		sub->flags = super->flags;
 		sub->flagset = super->flagset;
 	}
 	if (super->proto == 0)
 		sub->proto = 0;
 
 	if (super->src.port_op == 0) {
 		sub->src.port_op = 0;
 		sub->src.port[0] = 0;
 		sub->src.port[1] = 0;
 	}
 	if (super->dst.port_op == 0) {
 		sub->dst.port_op = 0;
 		sub->dst.port[0] = 0;
 		sub->dst.port[1] = 0;
 	}
 
 	if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg &&
 	    !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 &&
 	    super->src.addr.v.a.mask.addr32[1] == 0 &&
 	    super->src.addr.v.a.mask.addr32[2] == 0 &&
 	    super->src.addr.v.a.mask.addr32[3] == 0)
 		memset(&sub->src.addr, 0, sizeof(sub->src.addr));
 	else if (super->src.addr.type == PF_ADDR_ADDRMASK &&
 	    sub->src.addr.type == PF_ADDR_ADDRMASK &&
 	    super->src.neg == sub->src.neg &&
 	    super->af == sub->af &&
 	    unmask(&super->src.addr.v.a.mask, super->af) <
 	    unmask(&sub->src.addr.v.a.mask, sub->af) &&
 	    super->src.addr.v.a.addr.addr32[0] ==
 	    (sub->src.addr.v.a.addr.addr32[0] &
 	    super->src.addr.v.a.mask.addr32[0]) &&
 	    super->src.addr.v.a.addr.addr32[1] ==
 	    (sub->src.addr.v.a.addr.addr32[1] &
 	    super->src.addr.v.a.mask.addr32[1]) &&
 	    super->src.addr.v.a.addr.addr32[2] ==
 	    (sub->src.addr.v.a.addr.addr32[2] &
 	    super->src.addr.v.a.mask.addr32[2]) &&
 	    super->src.addr.v.a.addr.addr32[3] ==
 	    (sub->src.addr.v.a.addr.addr32[3] &
 	    super->src.addr.v.a.mask.addr32[3])) {
 		/* sub->src.addr is a subset of super->src.addr/mask */
 		memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr));
 	}
 
 	if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg &&
 	    !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 &&
 	    super->dst.addr.v.a.mask.addr32[1] == 0 &&
 	    super->dst.addr.v.a.mask.addr32[2] == 0 &&
 	    super->dst.addr.v.a.mask.addr32[3] == 0)
 		memset(&sub->dst.addr, 0, sizeof(sub->dst.addr));
 	else if (super->dst.addr.type == PF_ADDR_ADDRMASK &&
 	    sub->dst.addr.type == PF_ADDR_ADDRMASK &&
 	    super->dst.neg == sub->dst.neg &&
 	    super->af == sub->af &&
 	    unmask(&super->dst.addr.v.a.mask, super->af) <
 	    unmask(&sub->dst.addr.v.a.mask, sub->af) &&
 	    super->dst.addr.v.a.addr.addr32[0] ==
 	    (sub->dst.addr.v.a.addr.addr32[0] &
 	    super->dst.addr.v.a.mask.addr32[0]) &&
 	    super->dst.addr.v.a.addr.addr32[1] ==
 	    (sub->dst.addr.v.a.addr.addr32[1] &
 	    super->dst.addr.v.a.mask.addr32[1]) &&
 	    super->dst.addr.v.a.addr.addr32[2] ==
 	    (sub->dst.addr.v.a.addr.addr32[2] &
 	    super->dst.addr.v.a.mask.addr32[2]) &&
 	    super->dst.addr.v.a.addr.addr32[3] ==
 	    (sub->dst.addr.v.a.addr.addr32[3] &
 	    super->dst.addr.v.a.mask.addr32[3])) {
 		/* sub->dst.addr is a subset of super->dst.addr/mask */
 		memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr));
 	}
 
 	if (super->af == 0)
 		sub->af = 0;
 }
 
 
 void
 superblock_free(struct pfctl *pf, struct superblock *block)
 {
 	struct pf_opt_rule *por;
 	while ((por = TAILQ_FIRST(&block->sb_rules))) {
 		TAILQ_REMOVE(&block->sb_rules, por, por_entry);
 		if (por->por_src_tbl) {
 			if (por->por_src_tbl->pt_buf) {
 				pfr_buf_clear(por->por_src_tbl->pt_buf);
 				free(por->por_src_tbl->pt_buf);
 			}
 			free(por->por_src_tbl);
 		}
 		if (por->por_dst_tbl) {
 			if (por->por_dst_tbl->pt_buf) {
 				pfr_buf_clear(por->por_dst_tbl->pt_buf);
 				free(por->por_dst_tbl->pt_buf);
 			}
 			free(por->por_dst_tbl);
 		}
 		free(por);
 	}
 	if (block->sb_profiled_block)
 		superblock_free(pf, block->sb_profiled_block);
 	free(block);
 }
 
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index aa6d98d7cf91..2547caa1a8ce 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -1,344 +1,344 @@
 /*	$OpenBSD: pfctl_parser.h,v 1.86 2006/10/31 23:46:25 mcbride Exp $ */
 
 /*-
  * SPDX-License-Identifier: BSD-2-Clause
  *
  * 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.
  *
  * $FreeBSD$
  */
 
 #ifndef _PFCTL_PARSER_H_
 #define _PFCTL_PARSER_H_
 
 #define PF_OSFP_FILE		"/etc/pf.os"
 
 #define PF_OPT_DISABLE		0x0001
 #define PF_OPT_ENABLE		0x0002
 #define PF_OPT_VERBOSE		0x0004
 #define PF_OPT_NOACTION		0x0008
 #define PF_OPT_QUIET		0x0010
 #define PF_OPT_CLRRULECTRS	0x0020
 #define PF_OPT_USEDNS		0x0040
 #define PF_OPT_VERBOSE2		0x0080
 #define PF_OPT_DUMMYACTION	0x0100
 #define PF_OPT_DEBUG		0x0200
 #define PF_OPT_SHOWALL		0x0400
 #define PF_OPT_OPTIMIZE		0x0800
 #define PF_OPT_NUMERIC		0x1000
 #define PF_OPT_MERGE		0x2000
 #define PF_OPT_RECURSE		0x4000
 
 #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 {
 	int dev;
 	int opts;
 	int optimize;
 	int loadopt;
 	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_pooladdr paddr;
 	struct pfioc_altq *paltq;
 	struct pfioc_queue *pqueue;
 	struct pfr_buffer *trans;
 	struct pf_anchor *anchor, *alast;
 	const char *ruleset;
 
 	/* 'set foo' options */
 	u_int32_t	 timeout[PFTM_MAX];
 	u_int32_t	 limit[PF_LIMIT_MAX];
 	u_int32_t	 debug;
 	u_int32_t	 hostid;
 	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;
 };
 
 struct node_if {
 	char			 ifname[IFNAMSIZ];
 	u_int8_t		 not;
 	u_int8_t		 dynamic; /* antispoof */
 	u_int			 ifa_flags;
 	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 */
 	char			*ifname;
 	u_int			 ifa_flags;
 	struct node_host	*next;
 	struct node_host	*tail;
 };
 
 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_fairq_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_fairq_opts {
 	struct node_fairq_sc	linkshare;
 	struct node_queue_bw	hogs_bw;
 	u_int			nbuckets;
 	int			flags;
 };
 
 struct node_queue_opt {
 	int			 qtype;
 	union {
 		struct cbq_opts		cbq_opts;
 		struct codel_opts	codel_opts;
 		struct priq_opts	priq_opts;
 		struct node_hfsc_opts	hfsc_opts;
 		struct node_fairq_opts	fairq_opts;
 	}			 data;
 };
 
 #define QPRI_BITSET_SIZE	256
 BITSET_DEFINE(qpri_bitset, QPRI_BITSET_SIZE);
 LIST_HEAD(gen_sc, segment);
 
 struct pfctl_altq {
 	struct pf_altq	pa;
 	struct {
 		STAILQ_ENTRY(pfctl_altq)	link;
 		u_int64_t			bwsum;
 		struct qpri_bitset		qpris;
 		int				children;
 		int				root_classes;
 		int				default_classes;
 		struct gen_sc			lssc;
 		struct gen_sc			rtsc;
 	} meta;
 };
 
 #ifdef __FreeBSD__
 /*
  * XXX
  * Absolutely this is not correct location to define this.
  * Should we use an another sperate header file?
  */
 #define	SIMPLEQ_HEAD			STAILQ_HEAD
 #define	SIMPLEQ_HEAD_INITIALIZER	STAILQ_HEAD_INITIALIZER
 #define	SIMPLEQ_ENTRY			STAILQ_ENTRY
 #define	SIMPLEQ_FIRST			STAILQ_FIRST
 #define	SIMPLEQ_END(head)		NULL
 #define	SIMPLEQ_EMPTY			STAILQ_EMPTY
 #define	SIMPLEQ_NEXT			STAILQ_NEXT
 /*#define	SIMPLEQ_FOREACH			STAILQ_FOREACH*/
 #define	SIMPLEQ_FOREACH(var, head, field)		\
     for((var) = SIMPLEQ_FIRST(head);			\
 	(var) != SIMPLEQ_END(head);			\
 	(var) = SIMPLEQ_NEXT(var, field))
 #define	SIMPLEQ_INIT			STAILQ_INIT
 #define	SIMPLEQ_INSERT_HEAD		STAILQ_INSERT_HEAD
 #define	SIMPLEQ_INSERT_TAIL		STAILQ_INSERT_TAIL
 #define	SIMPLEQ_INSERT_AFTER		STAILQ_INSERT_AFTER
 #define	SIMPLEQ_REMOVE_HEAD		STAILQ_REMOVE_HEAD
 #endif
 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;
 	struct node_tinithead	 pt_nodes;
 	struct pfr_buffer	*pt_buf;
 };
 #define PF_OPT_TABLE_PREFIX	"__automatic_"
 
 /* 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);
 
 int	pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *);
 int	pfctl_optimize_ruleset(struct pfctl *, struct pf_ruleset *);
 
-int	pfctl_add_rule(struct pfctl *, struct pf_rule *, const char *);
+int	pfctl_append_rule(struct pfctl *, struct pf_rule *, const char *);
 int	pfctl_add_altq(struct pfctl *, struct pf_altq *);
 int	pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t);
 void	pfctl_move_pool(struct pf_pool *, struct pf_pool *);
 void	pfctl_clear_pool(struct pf_pool *);
 
 int	pfctl_set_timeout(struct pfctl *, const char *, int, int);
 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 *);
 int	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 *, struct pfr_buffer *);
 
 void	print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int);
 void	print_src_node(struct pf_src_node *, int);
 void	print_rule(struct pf_rule *, const char *, int, int);
 void	print_tabledef(const char *, int, int, struct node_tinithead *);
 void	print_status(struct pf_status *, int);
 void	print_running(struct pf_status *);
 
 int	eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
 	    struct node_queue_opt *);
 int	eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
 	    struct node_queue_opt *);
 
 void	 print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *,
 	    struct node_queue_opt *);
 void	 print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *,
 	    int, struct node_queue_opt *);
 
 int	pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *,
 	    u_int32_t);
 
 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;
 };
 
 const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t);
 const struct icmptypeent *geticmptypebyname(char *, u_int8_t);
 const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t);
 const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t);
 
 struct pf_timeout {
 	const char	*name;
 	int		 timeout;
 };
 
 #define PFCTL_FLAG_FILTER	0x02
 #define PFCTL_FLAG_NAT		0x04
 #define PFCTL_FLAG_OPTION	0x08
 #define PFCTL_FLAG_ALTQ		0x10
 #define PFCTL_FLAG_TABLE	0x20
 
 extern const struct pf_timeout pf_timeouts[];
 
 void			 set_ipmask(struct node_host *, u_int8_t);
 int			 check_netmask(struct node_host *, sa_family_t);
 int			 unmask(struct pf_addr *, sa_family_t);
 void			 ifa_load(void);
 int			 get_query_socket(void);
 struct node_host	*ifa_exists(char *);
 struct node_host	*ifa_grouplookup(char *ifa_name, int flags);
 struct node_host	*ifa_lookup(char *, int);
 struct node_host	*host(const char *);
 
 int			 append_addr(struct pfr_buffer *, char *, int);
 int			 append_addr_host(struct pfr_buffer *,
 			    struct node_host *, int, int);
 
 #endif /* _PFCTL_PARSER_H_ */
diff --git a/share/mk/src.libnames.mk b/share/mk/src.libnames.mk
index 1d3195f53eb1..259a65a7afd4 100644
--- a/share/mk/src.libnames.mk
+++ b/share/mk/src.libnames.mk
@@ -1,704 +1,709 @@
 # $FreeBSD$
 #
 # The include file <src.libnames.mk> define library names suitable
 # for INTERNALLIB and PRIVATELIB definition
 
 .if !target(__<bsd.init.mk>__)
 .error src.libnames.mk cannot be included directly.
 .endif
 
 .if !target(__<src.libnames.mk>__)
 __<src.libnames.mk>__:
 
 .include <src.opts.mk>
 
 _PRIVATELIBS=	\
 		atf_c \
 		atf_cxx \
 		auditd \
 		bsdstat \
 		devdctl \
 		event1 \
 		gmock \
 		gtest \
 		gmock_main \
 		gtest_main \
 		heimipcc \
 		heimipcs \
 		ldns \
 		sqlite3 \
 		ssh \
 		ucl \
 		unbound \
 		zstd
 
 _INTERNALLIBS=	\
 		amu \
 		bsnmptools \
 		c_nossp_pic \
 		cron \
 		elftc \
 		fifolog \
 		ifconfig \
 		ipf \
 		kyua_cli \
 		kyua_drivers \
 		kyua_engine \
 		kyua_model \
 		kyua_store \
 		kyua_utils \
 		lpr \
 		lua \
 		lutok \
 		netbsd \
 		ntp \
 		ntpevent \
 		openbsd \
 		opts \
 		parse \
 		pe \
+		pfctl \
 		pmcstat \
 		sl \
 		sm \
 		smdb \
 		smutil \
 		telnet \
 		vers
 
 _LIBRARIES=	\
 		${_PRIVATELIBS} \
 		${_INTERNALLIBS} \
 		${LOCAL_LIBRARIES} \
 		80211 \
 		9p \
 		alias \
 		archive \
 		asn1 \
 		avl \
 		be \
 		begemot \
 		bluetooth \
 		bsdxml \
 		bsm \
 		bsnmp \
 		bz2 \
 		c \
 		c_pic \
 		calendar \
 		cam \
 		casper \
 		cap_dns \
 		cap_fileargs \
 		cap_grp \
 		cap_net \
 		cap_pwd \
 		cap_sysctl \
 		cap_syslog \
 		com_err \
 		compiler_rt \
 		crypt \
 		crypto \
 		ctf \
 		cuse \
 		cxxrt \
 		devctl \
 		devdctl \
 		devinfo \
 		devstat \
 		dialog \
 		dl \
 		dpv \
 		dtrace \
 		dwarf \
 		edit \
 		efivar \
 		elf \
 		execinfo \
 		fetch \
 		figpar \
 		geom \
 		gpio \
 		gssapi \
 		gssapi_krb5 \
 		hdb \
 		heimbase \
 		heimntlm \
 		heimsqlite \
 		hx509 \
 		icp \
 		ipsec \
 		ipt \
 		jail \
 		kadm5clnt \
 		kadm5srv \
 		kafs5 \
 		kdc \
 		kiconv \
 		krb5 \
 		kvm \
 		l \
 		lzma \
 		m \
 		magic \
 		md \
 		memstat \
 		mp \
 		mt \
 		ncursesw \
 		netgraph \
 		netmap \
 		ngatm \
 		nv \
 		nvpair \
 		opencsd \
 		opie \
 		pam \
 		panel \
 		panelw \
 		pcap \
 		pcsclite \
 		pjdlog \
 		pmc \
 		proc \
 		procstat \
 		pthread \
 		radius \
 		regex \
 		roken \
 		rpcsec_gss \
 		rpcsvc \
 		rt \
 		rtld_db \
 		sbuf \
 		sdp \
 		sm \
 		smb \
 		spl \
 		ssl \
 		ssp_nonshared \
 		stats \
 		stdthreads \
 		supcplusplus \
 		sysdecode \
 		tacplus \
 		termcapw \
 		tpool \
 		ufs \
 		ugidfw \
 		ulog \
 		umem \
 		usb \
 		usbhid \
 		util \
 		uutil \
 		vmmapi \
 		wind \
 		wrap \
 		xo \
 		y \
 		ypclnt \
 		z \
 		zfs_core \
 		zfs \
 		zfsbootenv \
 		zpool \
 		zutil
 
 .if ${MK_BLACKLIST} != "no"
 _LIBRARIES+= \
 		blacklist \
 
 .endif
 
 .if ${MK_OFED} != "no"
 _LIBRARIES+= \
 		cxgb4 \
 		ibcm \
 		ibmad \
 		ibnetdisc \
 		ibumad \
 		ibverbs \
 		mlx4 \
 		mlx5 \
 		rdmacm \
 		osmcomp \
 		opensm \
 		osmvendor
 .endif
 
 .if ${MK_BEARSSL} == "yes"
 _LIBRARIES+= \
 		bearssl \
 		secureboot \
 
 LIBBEARSSL?=	${LIBBEARSSLDIR}/libbearssl.a
 LIBSECUREBOOT?=	${LIBSECUREBOOTDIR}/libsecureboot.a
 .endif
 
 .if ${MK_VERIEXEC} == "yes"
 _LIBRARIES+= veriexec
 
 LIBVERIEXEC?=	${LIBVERIEXECDIR}/libveriexec.a
 .endif
 
 # Each library's LIBADD needs to be duplicated here for static linkage of
 # 2nd+ order consumers.  Auto-generating this would be better.
 _DP_80211=	sbuf bsdxml
 _DP_9p=		sbuf
 _DP_archive=	z bz2 lzma bsdxml zstd
 _DP_zstd=	pthread
 .if ${MK_BLACKLIST} != "no"
 _DP_blacklist+=	pthread
 .endif
 _DP_crypto=	pthread
 .if ${MK_OPENSSL} != "no"
 _DP_archive+=	crypto
 .else
 _DP_archive+=	md
 .endif
 _DP_sqlite3=	pthread
 _DP_ssl=	crypto
 _DP_ssh=	crypto crypt z
 .if ${MK_LDNS} != "no"
 _DP_ssh+=	ldns
 .endif
 _DP_edit=	ncursesw
 .if ${MK_OPENSSL} != "no"
 _DP_bsnmp=	crypto
 .endif
 _DP_geom=	bsdxml sbuf
 _DP_cam=	sbuf
 _DP_kvm=	elf
 _DP_kyua_cli=		kyua_drivers kyua_engine kyua_model kyua_store kyua_utils
 _DP_kyua_drivers=	kyua_model kyua_engine kyua_store
 _DP_kyua_engine=	lutok kyua_utils
 _DP_kyua_model=		lutok
 _DP_kyua_utils=		lutok
 _DP_kyua_store=		kyua_model kyua_utils sqlite3
 _DP_casper=	nv
 _DP_cap_dns=	nv
 _DP_cap_fileargs=	nv
 _DP_cap_grp=	nv
 _DP_cap_pwd=	nv
 _DP_cap_sysctl=	nv
 _DP_cap_syslog=	nv
 .if ${MK_OFED} != "no"
 _DP_pcap=	ibverbs mlx5
 .endif
 _DP_pjdlog=	util
 _DP_opie=	md
 _DP_usb=	pthread
 _DP_unbound=	ssl crypto pthread
 _DP_rt=	pthread
 .if ${MK_OPENSSL} == "no"
 _DP_radius=	md
 .else
 _DP_radius=	crypto
 .endif
 _DP_rtld_db=	elf procstat
 _DP_procstat=	kvm util elf
 .if ${MK_CXX} == "yes"
 _DP_proc=	cxxrt
 .endif
 .if ${MK_CDDL} != "no"
 _DP_proc+=	ctf
 .endif
 _DP_proc+=	elf procstat rtld_db util
 _DP_mp=	crypto
 _DP_memstat=	kvm
 _DP_magic=	z
 _DP_mt=		sbuf bsdxml
 _DP_ldns=	ssl crypto
 _DP_lua=	m
 _DP_lutok=	lua
 .if ${MK_OPENSSL} != "no"
 _DP_fetch=	ssl crypto
 .else
 _DP_fetch=	md
 .endif
 _DP_execinfo=	elf
 _DP_dwarf=	elf
 _DP_dpv=	dialog figpar util ncursesw
 _DP_dialog=	ncursesw m
 _DP_cuse=	pthread
 _DP_atf_cxx=	atf_c
 _DP_gtest=	pthread regex
 _DP_gmock=	gtest
 _DP_gmock_main=	gmock
 _DP_gtest_main=	gtest
 _DP_devstat=	kvm
 _DP_pam=	radius tacplus opie md util
 .if ${MK_KERBEROS} != "no"
 _DP_pam+=	krb5
 .endif
 .if ${MK_OPENSSH} != "no"
 _DP_pam+=	ssh
 .endif
 .if ${MK_NIS} != "no"
 _DP_pam+=	ypclnt
 .endif
 _DP_roken=	crypt
 _DP_kadm5clnt=	com_err krb5 roken
 _DP_kadm5srv=	com_err hdb krb5 roken
 _DP_heimntlm=	crypto com_err krb5 roken
 _DP_hx509=	asn1 com_err crypto roken wind
 _DP_hdb=	asn1 com_err krb5 roken sqlite3
 _DP_asn1=	com_err roken
 _DP_kdc=	roken hdb hx509 krb5 heimntlm asn1 crypto
 _DP_wind=	com_err roken
 _DP_heimbase=	pthread
 _DP_heimipcc=	heimbase roken pthread
 _DP_heimipcs=	heimbase roken pthread
 _DP_kafs5=	asn1 krb5 roken
 _DP_krb5+=	asn1 com_err crypt crypto hx509 roken wind heimbase heimipcc
 _DP_gssapi_krb5+=	gssapi krb5 crypto roken asn1 com_err
 _DP_lzma=	md pthread
 _DP_ucl=	m
 _DP_vmmapi=	util
 _DP_opencsd=	cxxrt
 _DP_ctf=	spl z
 _DP_dtrace=	ctf elf proc pthread rtld_db
 _DP_xo=		util
 _DP_ztest=	geom m nvpair umem zpool pthread avl zfs_core spl zutil zfs uutil icp
 # The libc dependencies are not strictly needed but are defined to make the
 # assert happy.
 _DP_c=		compiler_rt
 .if ${MK_SSP} != "no" && \
     (${MACHINE_ARCH} == "i386" || ${MACHINE_ARCH:Mpower*} != "")
 _DP_c+=		ssp_nonshared
 .endif
 _DP_stats=	sbuf pthread
 _DP_stdthreads=	pthread
 _DP_tacplus=	md
 _DP_panelw=	ncursesw
 _DP_rpcsec_gss=	gssapi
 _DP_smb=	kiconv
 _DP_ulog=	md
 _DP_fifolog=	z
 _DP_ipf=	kvm
 _DP_tpool=	spl
 _DP_uutil=	avl spl
 _DP_zfs=	md pthread umem util uutil m avl bsdxml crypto geom nvpair \
 	z zfs_core zutil
 _DP_zfsbootenv= zfs nvpair
 _DP_zfs_core=	nvpair
 _DP_zpool=	md pthread z icp spl nvpair avl umem
 _DP_zutil=	avl tpool
 _DP_be=		zfs spl nvpair zfsbootenv
 _DP_netmap=
 _DP_ifconfig=	m
+_DP_pfctl=	nv
 
 # OFED support
 .if ${MK_OFED} != "no"
 _DP_cxgb4=	ibverbs pthread
 _DP_ibcm=	ibverbs
 _DP_ibmad=	ibumad
 _DP_ibnetdisc=	osmcomp ibmad ibumad
 _DP_ibumad=	
 _DP_ibverbs=
 _DP_mlx4=	ibverbs pthread
 _DP_mlx5=	ibverbs pthread
 _DP_rdmacm=	ibverbs
 _DP_osmcomp=	pthread
 _DP_opensm=	pthread
 _DP_osmvendor=	ibumad pthread
 .endif
 
 # Define special cases
 LDADD_supcplusplus=	-lsupc++
 LIBATF_C=	${LIBDESTDIR}${LIBDIR_BASE}/libprivateatf-c.a
 LIBATF_CXX=	${LIBDESTDIR}${LIBDIR_BASE}/libprivateatf-c++.a
 LDADD_atf_c=	-lprivateatf-c
 LDADD_atf_cxx=	-lprivateatf-c++
 
 LIBGMOCK=	${LIBDESTDIR}${LIBDIR_BASE}/libprivategmock.a
 LIBGMOCK_MAIN=	${LIBDESTDIR}${LIBDIR_BASE}/libprivategmock_main.a
 LIBGTEST=	${LIBDESTDIR}${LIBDIR_BASE}/libprivategtest.a
 LIBGTEST_MAIN=	${LIBDESTDIR}${LIBDIR_BASE}/libprivategtest_main.a
 LDADD_gmock=	-lprivategmock
 LDADD_gtest=	-lprivategtest
 LDADD_gmock_main= -lprivategmock_main
 LDADD_gtest_main= -lprivategtest_main
 
 .for _l in ${_PRIVATELIBS}
 LIB${_l:tu}?=	${LIBDESTDIR}${LIBDIR_BASE}/libprivate${_l}.a
 .endfor
 
 .if ${MK_PIE} != "no"
 PIE_SUFFIX=	_pie
 .endif
 
 .for _l in ${_LIBRARIES}
 .if ${_INTERNALLIBS:M${_l}} || !defined(SYSROOT)
 LDADD_${_l}_L+=		-L${LIB${_l:tu}DIR}
 .endif
 DPADD_${_l}?=	${LIB${_l:tu}}
 .if ${_PRIVATELIBS:M${_l}}
 LDADD_${_l}?=	-lprivate${_l}
 .elif ${_INTERNALLIBS:M${_l}}
 LDADD_${_l}?=	${LDADD_${_l}_L} -l${_l:S/${PIE_SUFFIX}//}${PIE_SUFFIX}
 .else
 LDADD_${_l}?=	${LDADD_${_l}_L} -l${_l}
 .endif
 # Add in all dependencies for static linkage.
 .if defined(_DP_${_l}) && (${_INTERNALLIBS:M${_l}} || \
     (defined(NO_SHARED) && ${NO_SHARED:tl} != "no"))
 .for _d in ${_DP_${_l}}
 DPADD_${_l}+=	${DPADD_${_d}}
 LDADD_${_l}+=	${LDADD_${_d}}
 .endfor
 .endif
 .endfor
 
 # These are special cases where the library is broken and anything that uses
 # it needs to add more dependencies.  Broken usually means that it has a
 # cyclic dependency and cannot link its own dependencies.  This is bad, please
 # fix the library instead.
 # Unless the library itself is broken then the proper place to define
 # dependencies is _DP_* above.
 
 # libatf-c++ exposes libatf-c abi hence we need to explicit link to atf_c for
 # atf_cxx
 DPADD_atf_cxx+=	${DPADD_atf_c}
 LDADD_atf_cxx+=	${LDADD_atf_c}
 
 DPADD_gmock+=	${DPADD_gtest}
 LDADD_gmock+=	${LDADD_gtest}
 
 DPADD_gmock_main+=	${DPADD_gmock}
 LDADD_gmock_main+=	${LDADD_gmock}
 
 DPADD_gtest_main+=	${DPADD_gtest}
 LDADD_gtest_main+=	${LDADD_gtest}
 
 # Detect LDADD/DPADD that should be LIBADD, before modifying LDADD here.
 _BADLDADD=
 .for _l in ${LDADD:M-l*:N-l*/*:C,^-l,,}
 .if ${_LIBRARIES:M${_l}} && !${_PRIVATELIBS:M${_l}}
 _BADLDADD+=	${_l}
 .endif
 .endfor
 .if !empty(_BADLDADD)
 .error ${.CURDIR}: These libraries should be LIBADD+=foo rather than DPADD/LDADD+=-lfoo: ${_BADLDADD}
 .endif
 
 .for _l in ${LIBADD}
 DPADD+=		${DPADD_${_l}}
 LDADD+=		${LDADD_${_l}}
 .endfor
 
 _LIB_OBJTOP?=	${OBJTOP}
 # INTERNALLIB definitions.
 LIBELFTCDIR=	${_LIB_OBJTOP}/lib/libelftc
 LIBELFTC?=	${LIBELFTCDIR}/libelftc${PIE_SUFFIX}.a
 
 LIBKYUA_CLIDIR=	${_LIB_OBJTOP}/lib/kyua/cli
 LIBKYUA_CLI?=	${LIBKYUA_CLIDIR}/libkyua_cli${PIE_SUFFIX}.a
 
 LIBKYUA_DRIVERSDIR=	${_LIB_OBJTOP}/lib/kyua/drivers
 LIBKYUA_DRIVERS?=	${LIBKYUA_DRIVERSDIR}/libkyua_drivers${PIE_SUFFIX}.a
 
 LIBKYUA_ENGINEDIR=	${_LIB_OBJTOP}/lib/kyua/engine
 LIBKYUA_ENGINE?=	${LIBKYUA_ENGINEDIR}/libkyua_engine${PIE_SUFFIX}.a
 
 LIBKYUA_MODELDIR=	${_LIB_OBJTOP}/lib/kyua/model
 LIBKYUA_MODEL?=		${LIBKYUA_MODELDIR}/libkyua_model${PIE_SUFFIX}.a
 
 LIBKYUA_STOREDIR=	${_LIB_OBJTOP}/lib/kyua/store
 LIBKYUA_STORE?=		${LIBKYUA_STOREDIR}/libkyua_store${PIE_SUFFIX}.a
 
 LIBKYUA_UTILSDIR=	${_LIB_OBJTOP}/lib/kyua/utils
 LIBKYUA_UTILS?=		${LIBKYUA_UTILSDIR}/libkyua_utils${PIE_SUFFIX}.a
 
 LIBLUADIR=	${_LIB_OBJTOP}/lib/liblua
 LIBLUA?=	${LIBLUADIR}/liblua${PIE_SUFFIX}.a
 
 LIBLUTOKDIR=	${_LIB_OBJTOP}/lib/liblutok
 LIBLUTOK?=	${LIBLUTOKDIR}/liblutok${PIE_SUFFIX}.a
 
 LIBPEDIR=	${_LIB_OBJTOP}/lib/libpe
 LIBPE?=		${LIBPEDIR}/libpe${PIE_SUFFIX}.a
 
 LIBOPENBSDDIR=	${_LIB_OBJTOP}/lib/libopenbsd
 LIBOPENBSD?=	${LIBOPENBSDDIR}/libopenbsd${PIE_SUFFIX}.a
 
 LIBSMDIR=	${_LIB_OBJTOP}/lib/libsm
 LIBSM?=		${LIBSMDIR}/libsm${PIE_SUFFIX}.a
 
 LIBSMDBDIR=	${_LIB_OBJTOP}/lib/libsmdb
 LIBSMDB?=	${LIBSMDBDIR}/libsmdb${PIE_SUFFIX}.a
 
 LIBSMUTILDIR=	${_LIB_OBJTOP}/lib/libsmutil
 LIBSMUTIL?=	${LIBSMUTILDIR}/libsmutil${PIE_SUFFIX}.a
 
 LIBNETBSDDIR?=	${_LIB_OBJTOP}/lib/libnetbsd
 LIBNETBSD?=	${LIBNETBSDDIR}/libnetbsd${PIE_SUFFIX}.a
 
 LIBVERSDIR?=	${_LIB_OBJTOP}/kerberos5/lib/libvers
 LIBVERS?=	${LIBVERSDIR}/libvers${PIE_SUFFIX}.a
 
 LIBSLDIR=	${_LIB_OBJTOP}/kerberos5/lib/libsl
 LIBSL?=		${LIBSLDIR}/libsl${PIE_SUFFIX}.a
 
 LIBIFCONFIGDIR=	${_LIB_OBJTOP}/lib/libifconfig
 LIBIFCONFIG?=	${LIBIFCONFIGDIR}/libifconfig${PIE_SUFFIX}.a
 
 LIBIPFDIR=	${_LIB_OBJTOP}/sbin/ipf/libipf
 LIBIPF?=	${LIBIPFDIR}/libipf${PIE_SUFFIX}.a
 
 LIBTELNETDIR=	${_LIB_OBJTOP}/lib/libtelnet
 LIBTELNET?=	${LIBTELNETDIR}/libtelnet${PIE_SUFFIX}.a
 
 LIBCRONDIR=	${_LIB_OBJTOP}/usr.sbin/cron/lib
 LIBCRON?=	${LIBCRONDIR}/libcron${PIE_SUFFIX}.a
 
 LIBNTPDIR=	${_LIB_OBJTOP}/usr.sbin/ntp/libntp
 LIBNTP?=	${LIBNTPDIR}/libntp${PIE_SUFFIX}.a
 
 LIBNTPEVENTDIR=	${_LIB_OBJTOP}/usr.sbin/ntp/libntpevent
 LIBNTPEVENT?=	${LIBNTPEVENTDIR}/libntpevent${PIE_SUFFIX}.a
 
 LIBOPTSDIR=	${_LIB_OBJTOP}/usr.sbin/ntp/libopts
 LIBOPTS?=	${LIBOPTSDIR}/libopts${PIE_SUFFIX}.a
 
 LIBPARSEDIR=	${_LIB_OBJTOP}/usr.sbin/ntp/libparse
 LIBPARSE?=	${LIBPARSEDIR}/libparse${PIE_SUFFIX}.a
 
+LIBPFCTL=	${_LIB_OBJTOP}/lib/libpfctl
+LIBPFCTL?=	${LIBPFCTLDIR}/libpfctl${PIE_SUFFIX}.a
+
 LIBLPRDIR=	${_LIB_OBJTOP}/usr.sbin/lpr/common_source
 LIBLPR?=	${LIBLPRDIR}/liblpr${PIE_SUFFIX}.a
 
 LIBFIFOLOGDIR=	${_LIB_OBJTOP}/usr.sbin/fifolog/lib
 LIBFIFOLOG?=	${LIBFIFOLOGDIR}/libfifolog${PIE_SUFFIX}.a
 
 LIBBSNMPTOOLSDIR=	${_LIB_OBJTOP}/usr.sbin/bsnmpd/tools/libbsnmptools
 LIBBSNMPTOOLS?=	${LIBBSNMPTOOLSDIR}/libbsnmptools${PIE_SUFFIX}.a
 
 LIBAMUDIR=	${_LIB_OBJTOP}/usr.sbin/amd/libamu
 LIBAMU?=	${LIBAMUDIR}/libamu${PIE_SUFFIX}.a
 
 LIBBE?=		${LIBBEDIR}/libbe${PIE_SUFFIX}.a
 
 LIBPMCSTATDIR=	${_LIB_OBJTOP}/lib/libpmcstat
 LIBPMCSTAT?=	${LIBPMCSTATDIR}/libpmcstat${PIE_SUFFIX}.a
 
 LIBC_NOSSP_PICDIR=	${_LIB_OBJTOP}/lib/libc
 LIBC_NOSSP_PIC?=	${LIBC_NOSSP_PICDIR}/libc_nossp_pic.a
 
 # Define a directory for each library.  This is useful for adding -L in when
 # not using a --sysroot or for meta mode bootstrapping when there is no
 # Makefile.depend.  These are sorted by directory.
 LIBAVLDIR=	${OBJTOP}/cddl/lib/libavl
 LIBCTFDIR=	${OBJTOP}/cddl/lib/libctf
 LIBDTRACEDIR=	${OBJTOP}/cddl/lib/libdtrace
 LIBICPDIR=	${OBJTOP}/cddl/lib/libicp
 LIBNVPAIRDIR=	${OBJTOP}/cddl/lib/libnvpair
 LIBUMEMDIR=	${OBJTOP}/cddl/lib/libumem
 LIBUUTILDIR=	${OBJTOP}/cddl/lib/libuutil
 LIBZFSDIR=	${OBJTOP}/cddl/lib/libzfs
 LIBZFS_COREDIR=	${OBJTOP}/cddl/lib/libzfs_core
 LIBZFSBOOTENVDIR=	${OBJTOP}/cddl/lib/libzfsbootenv
 LIBZPOOLDIR=	${OBJTOP}/cddl/lib/libzpool
 LIBZUTILDIR=	${OBJTOP}/cddl/lib/libzutil
 LIBTPOOLDIR=	${OBJTOP}/cddl/lib/libtpool
 
 # OFED support
 LIBCXGB4DIR=	${OBJTOP}/lib/ofed/libcxgb4
 LIBIBCMDIR=	${OBJTOP}/lib/ofed/libibcm
 LIBIBMADDIR=	${OBJTOP}/lib/ofed/libibmad
 LIBIBNETDISCDIR=${OBJTOP}/lib/ofed/libibnetdisc
 LIBIBUMADDIR=	${OBJTOP}/lib/ofed/libibumad
 LIBIBVERBSDIR=	${OBJTOP}/lib/ofed/libibverbs
 LIBMLX4DIR=	${OBJTOP}/lib/ofed/libmlx4
 LIBMLX5DIR=	${OBJTOP}/lib/ofed/libmlx5
 LIBRDMACMDIR=	${OBJTOP}/lib/ofed/librdmacm
 LIBOSMCOMPDIR=	${OBJTOP}/lib/ofed/complib
 LIBOPENSMDIR=	${OBJTOP}/lib/ofed/libopensm
 LIBOSMVENDORDIR=${OBJTOP}/lib/ofed/libvendor
 
 LIBDIALOGDIR=	${OBJTOP}/gnu/lib/libdialog
 LIBSSPDIR=	${OBJTOP}/lib/libssp
 LIBSSP_NONSHAREDDIR=	${OBJTOP}/lib/libssp_nonshared
 LIBASN1DIR=	${OBJTOP}/kerberos5/lib/libasn1
 LIBGSSAPI_KRB5DIR=	${OBJTOP}/kerberos5/lib/libgssapi_krb5
 LIBGSSAPI_NTLMDIR=	${OBJTOP}/kerberos5/lib/libgssapi_ntlm
 LIBGSSAPI_SPNEGODIR=	${OBJTOP}/kerberos5/lib/libgssapi_spnego
 LIBHDBDIR=	${OBJTOP}/kerberos5/lib/libhdb
 LIBHEIMBASEDIR=	${OBJTOP}/kerberos5/lib/libheimbase
 LIBHEIMIPCCDIR=	${OBJTOP}/kerberos5/lib/libheimipcc
 LIBHEIMIPCSDIR=	${OBJTOP}/kerberos5/lib/libheimipcs
 LIBHEIMNTLMDIR=	${OBJTOP}/kerberos5/lib/libheimntlm
 LIBHX509DIR=	${OBJTOP}/kerberos5/lib/libhx509
 LIBKADM5CLNTDIR=	${OBJTOP}/kerberos5/lib/libkadm5clnt
 LIBKADM5SRVDIR=	${OBJTOP}/kerberos5/lib/libkadm5srv
 LIBKAFS5DIR=	${OBJTOP}/kerberos5/lib/libkafs5
 LIBKDCDIR=	${OBJTOP}/kerberos5/lib/libkdc
 LIBKRB5DIR=	${OBJTOP}/kerberos5/lib/libkrb5
 LIBROKENDIR=	${OBJTOP}/kerberos5/lib/libroken
 LIBWINDDIR=	${OBJTOP}/kerberos5/lib/libwind
 LIBATF_CDIR=	${OBJTOP}/lib/atf/libatf-c
 LIBATF_CXXDIR=	${OBJTOP}/lib/atf/libatf-c++
 LIBGMOCKDIR=	${OBJTOP}/lib/googletest/gmock
 LIBGMOCK_MAINDIR=	${OBJTOP}/lib/googletest/gmock_main
 LIBGTESTDIR=	${OBJTOP}/lib/googletest/gtest
 LIBGTEST_MAINDIR=	${OBJTOP}/lib/googletest/gtest_main
 LIBALIASDIR=	${OBJTOP}/lib/libalias/libalias
 LIBBLACKLISTDIR=	${OBJTOP}/lib/libblacklist
 LIBBLOCKSRUNTIMEDIR=	${OBJTOP}/lib/libblocksruntime
 LIBBSNMPDIR=	${OBJTOP}/lib/libbsnmp/libbsnmp
 LIBCASPERDIR=	${OBJTOP}/lib/libcasper/libcasper
 LIBCAP_DNSDIR=	${OBJTOP}/lib/libcasper/services/cap_dns
 LIBCAP_GRPDIR=	${OBJTOP}/lib/libcasper/services/cap_grp
 LIBCAP_NETDIR=	${OBJTOP}/lib/libcasper/services/cap_net
 LIBCAP_PWDDIR=	${OBJTOP}/lib/libcasper/services/cap_pwd
 LIBCAP_SYSCTLDIR=	${OBJTOP}/lib/libcasper/services/cap_sysctl
 LIBCAP_SYSLOGDIR=	${OBJTOP}/lib/libcasper/services/cap_syslog
 LIBBSDXMLDIR=	${OBJTOP}/lib/libexpat
 LIBKVMDIR=	${OBJTOP}/lib/libkvm
 LIBPTHREADDIR=	${OBJTOP}/lib/libthr
 LIBMDIR=	${OBJTOP}/lib/msun
 LIBFORMWDIR=	${OBJTOP}/lib/ncurses/form
 LIBMENUWDIR=	${OBJTOP}/lib/ncurses/menu
 LIBNCURSESWDIR=	${OBJTOP}/lib/ncurses/ncurses
 LIBPANELWDIR=	${OBJTOP}/lib/ncurses/panel
 LIBCRYPTODIR=	${OBJTOP}/secure/lib/libcrypto
 LIBSPLDIR=	${OBJTOP}/cddl/lib/libspl
 LIBSSHDIR=	${OBJTOP}/secure/lib/libssh
 LIBSSLDIR=	${OBJTOP}/secure/lib/libssl
 LIBTEKENDIR=	${OBJTOP}/sys/teken/libteken
 LIBEGACYDIR=	${OBJTOP}/tools/build
 LIBLNDIR=	${OBJTOP}/usr.bin/lex/lib
 
 LIBTERMCAPWDIR=	${LIBNCURSESWDIR}
 
 # Default other library directories to lib/libNAME.
 .for lib in ${_LIBRARIES}
 LIB${lib:tu}DIR?=	${OBJTOP}/lib/lib${lib}
 .endfor
 
 # Validate that listed LIBADD are valid.
 .for _l in ${LIBADD}
 .if empty(_LIBRARIES:M${_l})
 _BADLIBADD+= ${_l}
 .endif
 .endfor
 .if !empty(_BADLIBADD)
 .error ${.CURDIR}: Invalid LIBADD used which may need to be added to ${_this:T}: ${_BADLIBADD}
 .endif
 
 # Sanity check that libraries are defined here properly when building them.
 .if defined(LIB) && ${_LIBRARIES:M${LIB}} != ""
 .if !empty(LIBADD) && \
     (!defined(_DP_${LIB}) || ${LIBADD:O:u} != ${_DP_${LIB}:O:u})
 .error ${.CURDIR}: Missing or incorrect _DP_${LIB} entry in ${_this:T}.  Should match LIBADD for ${LIB} ('${LIBADD}' vs '${_DP_${LIB}}')
 .endif
 # Note that OBJTOP is not yet defined here but for the purpose of the check
 # it is fine as it resolves to the SRC directory.
 .if !defined(LIB${LIB:tu}DIR) || !exists(${SRCTOP}/${LIB${LIB:tu}DIR:S,^${OBJTOP}/,,})
 .error ${.CURDIR}: Missing or incorrect value for LIB${LIB:tu}DIR in ${_this:T}: ${LIB${LIB:tu}DIR:S,^${OBJTOP}/,,}
 .endif
 .if ${_INTERNALLIBS:M${LIB}} != "" && !defined(LIB${LIB:tu})
 .error ${.CURDIR}: Missing value for LIB${LIB:tu} in ${_this:T}.  Likely should be: LIB${LIB:tu}?= $${LIB${LIB:tu}DIR}/lib${LIB}.a
 .endif
 .endif
 
 .endif	# !target(__<src.libnames.mk>__)