git: c72fb110e47f - main - pf: convert state limiter interface to netlink

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 14 Jan 2026 08:05:59 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=c72fb110e47f5a52e64683a8759a11eb69b34bd3

commit c72fb110e47f5a52e64683a8759a11eb69b34bd3
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2026-01-06 21:33:31 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-01-14 06:44:39 +0000

    pf: convert state limiter interface to netlink
    
    This is a new feature with new ioctl calls, so we can safely remove them
    right now.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 lib/libpfctl/libpfctl.c   | 314 +++++++++++++++++++++++++++++++++++
 lib/libpfctl/libpfctl.h   |  99 +++++++++++
 sbin/pfctl/pfctl.c        | 189 +++++++++------------
 sbin/pfctl/pfctl_parser.c |   4 +-
 sbin/pfctl/pfctl_parser.h |   8 +-
 sys/net/pfvar.h           | 108 +++++-------
 sys/netpfil/pf/pf_ioctl.c | 163 ++-----------------
 sys/netpfil/pf/pf_nl.c    | 407 ++++++++++++++++++++++++++++++++++++++++++++++
 sys/netpfil/pf/pf_nl.h    |  79 +++++++++
 9 files changed, 1035 insertions(+), 336 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index c3fdaf70ad0d..a5abe1cadd64 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -3915,3 +3915,317 @@ pfctl_clr_astats(struct pfctl_handle *h, const struct pfr_table *tbl,
 	return (ret);
 }
 
+static void
+snl_add_msg_attr_limit_rate(struct snl_writer *nw, uint32_t type,
+    const struct pfctl_limit_rate *rate)
+{
+	int off;
+
+	off = snl_add_msg_attr_nested(nw, type);
+
+	snl_add_msg_attr_u32(nw, PF_LR_LIMIT, rate->limit);
+	snl_add_msg_attr_u32(nw, PF_LR_SECONDS, rate->seconds);
+
+	snl_end_attr_nested(nw, off);
+}
+
+#define _OUT(_field)	offsetof(struct pfctl_limit_rate, _field)
+static const struct snl_attr_parser ap_limit_rate[] = {
+	{ .type = PF_LR_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
+	{ .type = PF_LR_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 },
+};
+SNL_DECLARE_ATTR_PARSER(limit_rate_parser, ap_limit_rate);
+#undef _OUT
+
+#define _OUT(_field)	offsetof(struct pfctl_state_lim, _field)
+static struct snl_attr_parser ap_statelim[] = {
+	{ .type = PF_SL_NAME, .off = _OUT(name), .arg_u32 = PF_STATELIM_NAME_LEN, .cb = snl_attr_copy_string },
+	{ .type = PF_SL_ID, .off = _OUT(id), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SL_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SL_RATE, .off = _OUT(rate), .arg = &limit_rate_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_SL_DESCR, .off = _OUT(description), .arg_u32 = PF_STATELIM_DESCR_LEN, .cb = snl_attr_copy_string },
+	{ .type = PF_SL_INUSE, .off = _OUT(inuse), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SL_ADMITTED, .off = _OUT(admitted), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SL_HARDLIMITED, .off = _OUT(hardlimited), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SL_RATELIMITED, .off = _OUT(ratelimited), .cb = snl_attr_get_uint64 },
+};
+#undef _OUT
+SNL_DECLARE_PARSER(statelim_parser, struct genlmsghdr, snl_f_p_empty, ap_statelim);
+
+int
+pfctl_state_limiter_nget(struct pfctl_handle *h, struct pfctl_state_lim *lim)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_STATE_LIMITER_NGET);
+
+	snl_add_msg_attr_u32(&nw, PF_SL_ID, lim->id);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		if (! snl_parse_nlmsg(&h->ss, hdr, &statelim_parser, lim))
+			continue;
+	}
+
+	return (e.error);
+}
+
+int
+pfctl_state_limiter_add(struct pfctl_handle *h, struct pfctl_state_lim *lim)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_STATE_LIMITER_ADD);
+
+	snl_add_msg_attr_u32(&nw, PF_SL_ID, lim->id);
+	snl_add_msg_attr_u32(&nw, PF_SL_TICKET, lim->ticket);
+	snl_add_msg_attr_string(&nw, PF_SL_NAME, lim->name);
+	snl_add_msg_attr_u32(&nw, PF_SL_LIMIT, lim->limit);
+	snl_add_msg_attr_limit_rate(&nw, PF_SL_RATE, &lim->rate);
+	snl_add_msg_attr_string(&nw, PF_SL_DESCR, lim->description);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		if (! snl_parse_nlmsg(&h->ss, hdr, &statelim_parser, &lim))
+			continue;
+	}
+
+	return (e.error);
+}
+
+#define _OUT(_field)	offsetof(struct pfctl_source_lim, _field)
+static struct snl_attr_parser ap_sourcelim[] = {
+	{ .type = PF_SCL_NAME, .off = _OUT(name), .arg_u32 = PF_SOURCELIM_NAME_LEN, .cb = snl_attr_copy_string },
+	{ .type = PF_SCL_ID, .off = _OUT(id), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_ENTRIES, .off = _OUT(entries), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_RATE, .off = _OUT(rate), .arg = &limit_rate_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_SCL_OVERLOAD_TBL_NAME, .off = _OUT(overload_tblname), .arg_u32 = PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_SCL_OVERLOAD_HIGH_WM, .off = _OUT(overload_hwm), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_OVERLOAD_LOW_WM, .off = _OUT(overload_lwm), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_INET_PREFIX, .off = _OUT(inet_prefix), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_INET6_PREFIX, .off = _OUT(inet6_prefix), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_DESCR, .off = _OUT(description), .arg_u32 = PF_SOURCELIM_DESCR_LEN, .cb = snl_attr_copy_string },
+	{ .type = PF_SCL_NENTRIES, .off = _OUT(nentries), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_INUSE, .off = _OUT(inuse), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SCL_ADDR_ALLOCS, .off = _OUT(addrallocs), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SCL_ADDR_NOMEM, .off = _OUT(addrnomem), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SCL_ADMITTED, .off = _OUT(admitted), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SCL_ADDRLIMITED, .off = _OUT(addrlimited), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SCL_HARDLIMITED, .off = _OUT(hardlimited), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SCL_RATELIMITED, .off = _OUT(ratelimited), .cb = snl_attr_get_uint64 },
+};
+#undef _OUT
+SNL_DECLARE_PARSER(sourcelim_parser, struct genlmsghdr, snl_f_p_empty, ap_sourcelim);
+
+int
+pfctl_source_limiter_add(struct pfctl_handle *h, struct pfctl_source_lim *lim)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_SOURCE_LIMITER_ADD);
+
+	snl_add_msg_attr_u32(&nw, PF_SCL_TICKET, lim->ticket);
+	snl_add_msg_attr_string(&nw, PF_SCL_NAME, lim->name);
+	snl_add_msg_attr_u32(&nw, PF_SCL_ID, lim->id);
+	snl_add_msg_attr_u32(&nw, PF_SCL_ENTRIES, lim->entries);
+	snl_add_msg_attr_u32(&nw, PF_SCL_LIMIT, lim->limit);
+	snl_add_msg_attr_limit_rate(&nw, PF_SCL_RATE, &lim->rate);
+	snl_add_msg_attr_string(&nw, PF_SCL_OVERLOAD_TBL_NAME, lim->overload_tblname);
+	snl_add_msg_attr_u32(&nw, PF_SCL_OVERLOAD_HIGH_WM, lim->overload_hwm);
+	snl_add_msg_attr_u32(&nw, PF_SCL_OVERLOAD_LOW_WM, lim->overload_lwm);
+	snl_add_msg_attr_u32(&nw, PF_SCL_INET_PREFIX, lim->inet_prefix);
+	snl_add_msg_attr_u32(&nw, PF_SCL_INET6_PREFIX, lim->inet6_prefix);
+	snl_add_msg_attr_string(&nw, PF_SCL_DESCR, lim->description);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		if (! snl_parse_nlmsg(&h->ss, hdr, &sourcelim_parser, &lim))
+			continue;
+	}
+
+	return (e.error);
+}
+
+static int
+_pfctl_source_limiter_get(struct pfctl_handle *h, int cmd, struct pfctl_source_lim *lim)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, cmd);
+
+	snl_add_msg_attr_u32(&nw, PF_SCL_ID, lim->id);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		if (! snl_parse_nlmsg(&h->ss, hdr, &sourcelim_parser, lim))
+			continue;
+	}
+
+	return (e.error);
+}
+
+int
+pfctl_source_limiter_get(struct pfctl_handle *h, struct pfctl_source_lim *lim)
+{
+	return (_pfctl_source_limiter_get(h, PFNL_CMD_SOURCE_LIMITER_GET, lim));
+}
+
+int
+pfctl_source_limiter_nget(struct pfctl_handle *h, struct pfctl_source_lim *lim)
+{
+	return (_pfctl_source_limiter_get(h, PFNL_CMD_SOURCE_LIMITER_NGET, lim));
+}
+
+#define _OUT(_field)	offsetof(struct pfctl_source, _field)
+static struct snl_attr_parser ap_source[] = {
+	{ .type = PF_SRC_AF, .off = _OUT(af), .cb = snl_attr_get_uint8 },
+	{ .type = PF_SRC_RDOMAIN, .off = _OUT(rdomain), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SRC_ADDR, .off = _OUT(addr), .cb = snl_attr_get_in6_addr },
+	{ .type = PF_SRC_INUSE, .off = _OUT(inuse), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SRC_ADMITTED, .off = _OUT(admitted), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SRC_HARDLIMITED, .off = _OUT(hardlimited), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SRC_RATELIMITED, .off = _OUT(ratelimited), .cb = snl_attr_get_uint64 },
+	{ .type = PF_SRC_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
+	{ .type = PF_SRC_INET_PREFIX, .off = _OUT(inet_prefix), .cb = snl_attr_get_uint32 },
+	{. type = PF_SRC_INET6_PREFIX, .off = _OUT(inet6_prefix), .cb = snl_attr_get_uint32 },
+};
+#undef _OUT
+SNL_DECLARE_PARSER(source_parser, struct genlmsghdr, snl_f_p_empty, ap_source);
+
+int
+pfctl_source_get(struct pfctl_handle *h, int id, pfctl_get_source_fn fn, void *arg)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id, error;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_SOURCE_NGET);
+
+	snl_add_msg_attr_u32(&nw, PF_SRC_ID, id);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+		struct pfctl_source src;
+
+		if (! snl_parse_nlmsg(&h->ss, hdr, &source_parser, &src))
+			continue;
+
+		error = fn(&src, arg);
+		if (error != 0) {
+			e.error = error;
+			break;
+		}
+	}
+
+	return (e.error);
+}
+
+int
+pfctl_source_clear(struct pfctl_handle *h, struct pfctl_source_clear *kill)
+{
+	struct snl_writer nw;
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	uint32_t seq_id;
+	int family_id;
+
+	family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME);
+	if (family_id == 0)
+		return (ENOTSUP);
+
+	snl_init_writer(&h->ss, &nw);
+	hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_SOURCE_CLEAR);
+
+	snl_add_msg_attr_string(&nw, PF_SC_NAME, kill->name);
+	snl_add_msg_attr_u32(&nw, PF_SC_ID, kill->id);
+	snl_add_msg_attr_u32(&nw, PF_SC_RDOMAIN, kill->rdomain);
+	snl_add_msg_attr_u8(&nw, PF_SC_AF, kill->af);
+	snl_add_msg_attr_ip6(&nw, PF_SC_ADDR, &kill->addr.v6);
+
+	if ((hdr = snl_finalize_msg(&nw)) == NULL)
+		return (ENXIO);
+	seq_id = hdr->nlmsg_seq;
+
+	if (! snl_send_message(&h->ss, hdr))
+		return (ENXIO);
+
+	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
+	}
+
+	return (e.error);
+}
+
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 785ac2bc7fd7..670688893a6a 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -592,4 +592,103 @@ int	pfctl_get_astats(struct pfctl_handle *h, const struct pfr_table *tbl,
 int	pfctl_clr_astats(struct pfctl_handle *h, const struct pfr_table *tbl,
 	    struct pfr_addr *addr, int size, int *nzero, int flags);
 
+struct pfctl_limit_rate {
+	unsigned int	 limit;
+	unsigned int	 seconds;
+};
+
+struct pfctl_state_lim {
+	uint32_t		 ticket;
+	char			 name[PF_STATELIM_NAME_LEN];
+	uint32_t		 id;
+	unsigned int		 limit;
+
+	struct pfctl_limit_rate	 rate;
+
+	char			 description[PF_STATELIM_DESCR_LEN];
+
+	unsigned int		 inuse;
+	uint64_t		 admitted;
+	uint64_t		 hardlimited;
+	uint64_t		 ratelimited;
+};
+
+int	pfctl_state_limiter_nget(struct pfctl_handle *h, struct pfctl_state_lim *lim);
+int	pfctl_state_limiter_add(struct pfctl_handle *h, struct pfctl_state_lim *lim);
+
+struct pfctl_source_lim {
+	uint32_t	 ticket;
+
+	char		 name[PF_SOURCELIM_NAME_LEN];
+	uint32_t	 id;
+
+	/* limit on the total number of address entries */
+	unsigned int	 entries;
+
+	/* limit on the number of states per address entry */
+	unsigned int	 limit;
+
+	/* rate limit on the creation of states by an address entry */
+	struct pfctl_limit_rate	 rate;
+
+	/*
+	 * when the number of states on an entry exceeds hwm, add
+	 * the address to the specified table. when the number of
+	 * states goes below lwm, remove it from the table.
+	 */
+	char		 overload_tblname[PF_TABLE_NAME_SIZE];
+	unsigned int	 overload_hwm;
+	unsigned int	 overload_lwm;
+
+	/*
+	 * mask addresses before they're used for entries. /64s
+	 * everywhere for inet6 makes it easy to use too much memory.
+	 */
+	unsigned int	 inet_prefix;
+	unsigned int	 inet6_prefix;
+
+	char	 description[PF_SOURCELIM_DESCR_LEN];
+
+	unsigned int	 nentries;
+	unsigned int	 inuse;
+
+	uint64_t	 addrallocs;
+	uint64_t	 addrnomem;
+	uint64_t	 admitted;
+	uint64_t	 addrlimited;
+	uint64_t	 hardlimited;
+	uint64_t	 ratelimited;
+};
+
+int	pfctl_source_limiter_get(struct pfctl_handle *h, struct pfctl_source_lim *lim);
+int	pfctl_source_limiter_nget(struct pfctl_handle *h, struct pfctl_source_lim *lim);
+int	pfctl_source_limiter_add(struct pfctl_handle *h, struct pfctl_source_lim *lim);
+
+struct pfctl_source {
+	sa_family_t	 af;
+	unsigned int	 rdomain;
+	struct pf_addr	 addr;
+
+	unsigned int	 inet_prefix;
+	unsigned int	 inet6_prefix;
+
+	unsigned int	 limit;
+	unsigned int	 inuse;
+	uint64_t	 admitted;
+	uint64_t	 hardlimited;
+	uint64_t	 ratelimited;
+};
+typedef int (*pfctl_get_source_fn)(struct pfctl_source *, void *);
+int	pfctl_source_get(struct pfctl_handle *h, int id,
+	    pfctl_get_source_fn fn, void *arg);
+
+struct pfctl_source_clear {
+	char		 name[PF_SOURCELIM_NAME_LEN];
+	uint32_t	 id;
+	sa_family_t	 af;
+	unsigned int	 rdomain;
+	struct pf_addr	 addr;
+};
+int	pfctl_source_clear(struct pfctl_handle *h, struct pfctl_source_clear *);
+
 #endif
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 04deccf7e890..256868a399d2 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -126,7 +126,7 @@ int	 pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool);
 void	 pfctl_load_statelims(struct pfctl *);
 void	 pfctl_load_statelim(struct pfctl *, struct pfctl_statelim *);
 void	 pfctl_load_sourcelims(struct pfctl *);
-void	 pfctl_load_sourcelim(struct pfctl *, struct pfctl_sourcelim *);
+void	 pfctl_load_sourcelim(struct pfctl *, struct pfctl_source_lim *);
 int	 pfctl_eth_ruleset_trans(struct pfctl *, char *,
 	    struct pfctl_eth_anchor *);
 int	 pfctl_load_eth_ruleset(struct pfctl *, char *,
@@ -1260,8 +1260,9 @@ pfctl_print_title(char *title)
 int
 pfctl_show_statelims(int dev, enum pfctl_show format)
 {
-	struct pfioc_statelim stlim;
+	struct pfctl_state_lim stlim;
 	uint32_t id = PF_STATELIM_ID_MIN;
+	int error;
 
 	if (format == PFCTL_SHOW_LABELS) {
 		printf("%3s %8s/%-8s %5s/%-5s %8s %8s %8s\n", "ID", "USE",
@@ -1272,12 +1273,13 @@ pfctl_show_statelims(int dev, enum pfctl_show format)
 		memset(&stlim, 0, sizeof(stlim));
 		stlim.id = id;
 
-		if (ioctl(dev, DIOCGETNSTATELIM, &stlim) == -1) {
-			if (errno == ENOENT) {
+		error = pfctl_state_limiter_nget(pfh, &stlim);
+		if (error != 0) {
+			if (error == ENOENT) {
 				/* we're done */
 				return (0);
 			}
-			warn("DIOCGETNSTATELIM %u", stlim.id);
+			warnc(error, "DIOCGETNSTATELIM %u", stlim.id);
 			return (-1);
 		}
 
@@ -1323,100 +1325,51 @@ pf_addr_inc(struct pf_addr *addr)
 }
 
 static int
-pfctl_show_sources(int dev, const struct pfioc_sourcelim *srlim,
-    enum pfctl_show format, int opts)
+pfctl_print_source(struct pfctl_source *e, void *arg)
 {
-	struct pfioc_source sr = { .id = srlim->id };
-	struct pfioc_source_entry *entries, *e;
-	unsigned int nentries;
-	size_t len, used;
-
-	if (format != PFCTL_SHOW_LABELS)
-		errx(1, "%s format is not PFCTL_SHOW_LABELS", __func__);
-
-	nentries = srlim->nentries;
-	if (nentries == 0)
-		return (0);
-	if (nentries > 128) /* arbitrary */
-		nentries = 128;
-
-	entries = reallocarray(NULL, nentries, sizeof(*entries));
-	if (entries == NULL)
-		err(1, "alloc %u source limiter entries", nentries);
-
-	len = nentries * sizeof(*entries);
-
-	e = entries;
-
-	/* start from af 0 address 0 */
-	memset(e, 0, sizeof(*e));
-
-	sr.entry_size = sizeof(*e);
-	sr.key = e;
-
-	for (;;) {
-		sr.entries = entries;
-		sr.entrieslen = len;
-
-		if (ioctl(dev, DIOCGETNSOURCE, &sr) == -1) {
-			switch (errno) {
-			case ESRCH:	    /* can't find the sourcelim */
-			case ENOENT:	    /* no more sources */
-				return (0); /* we're done */
-			}
-			warn("DIOCGETNSOURCE %u", sr.id);
-			return (-1);
-		}
-
-		used = 0;
-		if (sr.entrieslen > len)
-			errx(1, "DIOCGETNSOURCE used too much buffer");
-
-		e = entries;
-		for (;;) {
-			if (used > sr.entrieslen)
-				errx(1, "DIOCGETNSOURCE weird entrieslen");
-
-			print_addr_str(e->af, &e->addr);
-			switch (e->af) {
-			case AF_INET:
-				printf("/%u ", sr.inet_prefix);
-				break;
-			case AF_INET6:
-				printf("/%u ", sr.inet6_prefix);
-				break;
-			default:
-				printf("/af? ");
-				break;
-			}
-			printf("rdomain %u ", e->rdomain);
+	print_addr_str(e->af, &e->addr);
+	switch (e->af) {
+	case AF_INET:
+		printf("/%u ", e->inet_prefix);
+		break;
+	case AF_INET6:
+		printf("/%u ", e->inet6_prefix);
+		break;
+	default:
+		printf("/af? ");
+		break;
+	}
+	printf("rdomain %u ", e->rdomain);
 
-			printf("inuse %u/%u ", e->inuse, sr.limit);
-			printf("admit %ju hardlim %ju ratelim %ju\n",
-			    e->admitted, e->hardlimited, e->ratelimited);
+	printf("inuse %u/%u ", e->inuse, e->limit);
+	printf("admit %ju hardlim %ju ratelim %ju\n",
+	    e->admitted, e->hardlimited, e->ratelimited);
 
-			used += sizeof(*e);
-			if (used == sr.entrieslen)
-				break;
+	return (0);
+}
 
-			e++;
-		}
+static int
+pfctl_show_sources(int dev, const struct pfctl_source_lim *srlim,
+    enum pfctl_show format, int opts)
+{
+	int error;
 
-		/* reuse the last entry as the next key */
-		e->af += pf_addr_inc(&e->addr);
-		sr.key = e;
-	}
+	if (format != PFCTL_SHOW_LABELS)
+		errx(1, "%s format is not PFCTL_SHOW_LABELS", __func__);
 
-	return (0);
+	error = pfctl_source_get(pfh, srlim->id, pfctl_print_source, NULL);
+	if (error != 0)
+		warnc(error, "DIOCGETNSOURCE %u", srlim->id);
+	return (error);
 }
 
 int
 pfctl_show_sourcelims(int dev, enum pfctl_show format, int opts,
     const char *idopt)
 {
-	struct pfioc_sourcelim srlim;
+	struct pfctl_source_lim srlim;
 	uint32_t id = PF_SOURCELIM_ID_MIN;
-	unsigned long cmd = DIOCGETNSOURCELIM;
+	int error;
 
 	if (idopt != NULL) {
 		const char *errstr;
@@ -1425,8 +1378,6 @@ pfctl_show_sourcelims(int dev, enum pfctl_show format, int opts,
 		    &errstr);
 		if (errstr != NULL)
 			errx(1, "source limiter id: %s", errstr);
-
-		cmd = DIOCGETSOURCELIM;
 	}
 
 	if (format == PFCTL_SHOW_LABELS) {
@@ -1439,12 +1390,18 @@ pfctl_show_sourcelims(int dev, enum pfctl_show format, int opts,
 		memset(&srlim, 0, sizeof(srlim));
 		srlim.id = id;
 
-		if (ioctl(dev, cmd, &srlim) == -1) {
-			if (errno == ESRCH) {
+		if (idopt != NULL) {
+			error = pfctl_source_limiter_get(pfh, &srlim);
+		} else {
+			error = pfctl_source_limiter_nget(pfh, &srlim);
+		}
+
+		if (error != 0) {
+			if (error == ESRCH) {
 				/* we're done */
 				return (0);
 			}
-			warn("DIOCGETNSOURCELIM %u", srlim.id);
+			warnc(error, "DIOCGETNSOURCELIM %u", srlim.id);
 			return (-1);
 		}
 
@@ -1485,7 +1442,7 @@ pfctl_show_sourcelims(int dev, enum pfctl_show format, int opts,
 void
 pfctl_kill_source(int dev, const char *idopt, const char *source, int opts)
 {
-	struct pfioc_source_kill ioc;
+	struct pfctl_source_clear clear = { 0 };
 	unsigned int id;
 	const char *errstr;
 	struct addrinfo hints, *res;
@@ -1508,22 +1465,22 @@ pfctl_kill_source(int dev, const char *idopt, const char *source, int opts)
 	if (error != 0)
 		errx(1, "source limiter address: %s", gai_strerror(error));
 
-	ioc.id = id;
-	ioc.af = res->ai_family;
-	copy_satopfaddr(&ioc.addr, res->ai_addr);
-	ioc.rmstates = 0;
+	clear.id = id;
+	clear.af = res->ai_family;
+	copy_satopfaddr(&clear.addr, res->ai_addr);
 
 	freeaddrinfo(res);
 
-	if (ioctl(dev, DIOCCLRSOURCE, &ioc) == -1) {
-		switch (errno) {
-		case ESRCH:
-			errx(1, "source limiter %u not found", id);
-		case ENOENT:
-			errx(1, "source limiter %u: %s not found", id, source);
-		default:
-			err(1, "kill source limiter %u entry %s", id, source);
-		}
+	error = pfctl_source_clear(pfh, &clear);
+	switch (error) {
+	case 0:
+		break;
+	case ESRCH:
+		errx(1, "source limiter %u not found", id);
+	case ENOENT:
+		errx(1, "source limiter %u: %s not found", id, source);
+	default:
+		err(1, "kill source limiter %u entry %s", id, source);
 	}
 }
 
@@ -2325,14 +2282,17 @@ pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool d
 void
 pfctl_load_statelim(struct pfctl *pf, struct pfctl_statelim *stlim)
 {
+	int error;
+
 	if (pf->opts & PF_OPT_VERBOSE)
 		print_statelim(&stlim->ioc);
 
 	if (pf->opts & PF_OPT_NOACTION)
 		return;
 
-	if (ioctl(pf->dev, DIOCADDSTATELIM, &stlim->ioc) == -1) {
-		err(1, "DIOCADDSTATELIM %s id %u", stlim->ioc.name,
+	error = pfctl_state_limiter_add(pf->h, &stlim->ioc);
+	if (error) {
+		errc(1, error, "DIOCADDSTATELIM %s id %u", stlim->ioc.name,
 		    stlim->ioc.id);
 	}
 }
@@ -2356,17 +2316,20 @@ pfctl_load_statelims(struct pfctl *pf)
 }
 
 void
-pfctl_load_sourcelim(struct pfctl *pf, struct pfctl_sourcelim *srlim)
+pfctl_load_sourcelim(struct pfctl *pf, struct pfctl_source_lim *srlim)
 {
+	int error;
+
 	if (pf->opts & PF_OPT_VERBOSE)
-		print_sourcelim(&srlim->ioc);
+		print_sourcelim(srlim);
 
 	if (pf->opts & PF_OPT_NOACTION)
 		return;
 
-	if (ioctl(pf->dev, DIOCADDSOURCELIM, &srlim->ioc) == -1) {
-		err(1, "DIOCADDSOURCELIM %s id %u", srlim->ioc.name,
-		    srlim->ioc.id);
+	error = pfctl_source_limiter_add(pf->h, srlim);
+	if (error != 0) {
+		errc(1, error, "DIOCADDSOURCELIM %s id %u", srlim->name,
+		    srlim->id);
 	}
 }
 
@@ -2382,7 +2345,7 @@ pfctl_load_sourcelims(struct pfctl *pf)
 	RB_FOREACH(srlim, pfctl_sourcelim_ids, &pf->sourcelim_ids)
 	{
 		srlim->ioc.ticket = ticket;
-		pfctl_load_sourcelim(pf, srlim);
+		pfctl_load_sourcelim(pf, &srlim->ioc);
 	}
 
 	/* Don't free the sourcelims because we're about to exit anyway. */
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 617d3f8e0733..25d52f4ec823 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -856,7 +856,7 @@ print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call,
 }
 
 void
-print_statelim(const struct pfioc_statelim *ioc)
+print_statelim(const struct pfctl_state_lim *ioc)
 {
 	printf("state limiter %s id %u limit %u", ioc->name, ioc->id,
 	    ioc->limit);
@@ -867,7 +867,7 @@ print_statelim(const struct pfioc_statelim *ioc)
 }
 
 void
-print_sourcelim(const struct pfioc_sourcelim *ioc)
+print_sourcelim(const struct pfctl_source_lim *ioc)
 {
 	printf("source limiter %s id %u limit %u states %u", ioc->name,
 	    ioc->id, ioc->entries, ioc->limit);
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 6d0417cde061..8934238da148 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -76,7 +76,7 @@
 struct pfr_buffer;	/* forward definition */
 
 struct pfctl_statelim {
-	struct pfioc_statelim		 ioc;
+	struct pfctl_state_lim		 ioc;
 	RB_ENTRY(pfctl_statelim)	 entry;
 };
 
@@ -84,7 +84,7 @@ RB_HEAD(pfctl_statelim_ids, pfctl_statelim);
 RB_HEAD(pfctl_statelim_nms, pfctl_statelim);
 
 struct pfctl_sourcelim {
-	struct pfioc_sourcelim		 ioc;
+	struct pfctl_source_lim		 ioc;
 	RB_ENTRY(pfctl_sourcelim)	 entry;
 };
 
@@ -343,8 +343,8 @@ int	pfctl_load_anchors(int, struct pfctl *);
 
 void	print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, int);
 void	print_src_node(struct pfctl_src_node *, int);
-void	print_statelim(const struct pfioc_statelim *);
-void	print_sourcelim(const struct pfioc_sourcelim *);
+void	print_statelim(const struct pfctl_state_lim *);
+void	print_sourcelim(const struct pfctl_source_lim *);
 void	print_eth_rule(struct pfctl_eth_rule *, const char *, int);
 void	print_rule(struct pfctl_rule *, const char *, int, int);
 void	print_tabledef(const char *, int, int, struct node_tinithead *);
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 025a30378f1f..5329c5ebdd9e 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1205,6 +1205,11 @@ struct pf_kstate {
  * State limiter
  */
 
+struct pf_limiter_rate {
+	unsigned int	 limit;
+	unsigned int	 seconds;
+};
+
 struct pf_statelim {
 	RB_ENTRY(pf_statelim)	 pfstlim_id_tree;
 	RB_ENTRY(pf_statelim)	 pfstlim_nm_tree;
@@ -1217,10 +1222,7 @@ struct pf_statelim {
 	/* config */
 
 	unsigned int		 pfstlim_limit;
-	struct {
-		unsigned int	 limit;
-		unsigned int	 seconds;
-	}			 pfstlim_rate;
+	struct pf_limiter_rate	 pfstlim_rate;
 
 	/* run state */
 	struct mtx		 pfstlim_lock;
@@ -1340,10 +1342,7 @@ struct pf_sourcelim {
 	unsigned int			 pfsrlim_ipv4_prefix;
 	unsigned int			 pfsrlim_ipv6_prefix;
 
-	struct {
-		unsigned int		 limit;
-		unsigned int		 seconds;
-	}				 pfsrlim_rate;
+	struct pf_limiter_rate		 pfsrlim_rate;
 
 	struct {
 		char			 name[PF_TABLE_NAME_SIZE];
@@ -2074,25 +2073,29 @@ enum pf_syncookies_mode {
 #define	PF_SYNCOOKIES_HIWATPCT	25
 #define	PF_SYNCOOKIES_LOWATPCT	(PF_SYNCOOKIES_HIWATPCT / 2)
 
+#define	PF_STATELIM_ID_NONE	0
+#define	PF_STATELIM_ID_MIN	1
+#define	PF_STATELIM_ID_MAX	255 /* fits in pf_state uint8_t */
+#define	PF_STATELIM_LIMIT_MIN	1
+#define	PF_STATELIM_LIMIT_MAX	(1 << 24) /* pf is pretty scalable */
+
+#define	PF_SOURCELIM_ID_NONE	0
+#define	PF_SOURCELIM_ID_MIN	1
+#define	PF_SOURCELIM_ID_MAX	255 /* fits in pf_state uint8_t */
+
+#ifdef _KERNEL
+
 struct pfioc_statelim {
 	uint32_t	 ticket;
 
 	char		 name[PF_STATELIM_NAME_LEN];
 	uint32_t	 id;
-#define	PF_STATELIM_ID_NONE	0
-#define	PF_STATELIM_ID_MIN	1
-#define	PF_STATELIM_ID_MAX	255 /* fits in pf_state uint8_t */
 
 	/* limit on the total number of states */
 	unsigned int	 limit;
-#define	PF_STATELIM_LIMIT_MIN	1
-#define	PF_STATELIM_LIMIT_MAX	(1 << 24) /* pf is pretty scalable */
 
 	/* rate limit on the creation of states */
-	struct {
-		unsigned int	 limit;
-		unsigned int	 seconds;
-	} rate;
+	struct pf_limiter_rate	 rate;
 
 	char		 description[PF_STATELIM_DESCR_LEN];
 
@@ -2108,9 +2111,6 @@ struct pfioc_sourcelim {
 
 	char		 name[PF_SOURCELIM_NAME_LEN];
 	uint32_t	 id;
-#define	PF_SOURCELIM_ID_NONE	0
-#define	PF_SOURCELIM_ID_MIN	1
-#define	PF_SOURCELIM_ID_MAX	255 /* fits in pf_state uint8_t */
 
 	/* limit on the total number of address entries */
 	unsigned int	 entries;
@@ -2119,10 +2119,7 @@ struct pfioc_sourcelim {
 	unsigned int	 limit;
 
 	/* rate limit on the creation of states by an address entry */
-	struct {
-		unsigned int	 limit;
-		unsigned int	 seconds;
-	} rate;
+	struct pf_limiter_rate	 rate;
 
 	/*
 	 * when the number of states on an entry exceeds hwm, add
@@ -2154,37 +2151,6 @@ struct pfioc_sourcelim {
 	uint64_t	 ratelimited;	/* counter */
 };
 
-struct pfioc_source_entry {
-	sa_family_t	 af;
-	unsigned int	 rdomain;
-	struct pf_addr	 addr;
-
-	/* stats */
-
-	unsigned int	 inuse;		/* gauge */
-	uint64_t	 admitted;	/* counter */
-	uint64_t	 hardlimited;	/* counter */
-	uint64_t	 ratelimited;	/* counter */
-};
-
-struct pfioc_source {
-	char		 name[PF_SOURCELIM_NAME_LEN];
-	uint32_t	 id;
-
-	/* copied from the parent source limiter */
-
-	unsigned int	 inet_prefix;
-	unsigned int	 inet6_prefix;
-	unsigned int	 limit;
-
-	/* source entries */
-	size_t		 entry_size;	/* sizeof(struct pfioc_source_entry) */
-
-	struct pfioc_source_entry	*key;
-	struct pfioc_source_entry	*entries;
-	size_t		 entrieslen;	/* bytes */
-};
-
 struct pfioc_source_kill {
 	char		 name[PF_SOURCELIM_NAME_LEN];
 	uint32_t	 id;
@@ -2195,7 +2161,28 @@ struct pfioc_source_kill {
 	unsigned int	 rmstates; /* kill the states too? */
 };
 
-#ifdef _KERNEL
+int pf_statelim_add(const struct pfioc_statelim *);
+struct pf_statelim *pf_statelim_rb_find(struct pf_statelim_id_tree *,
+    struct pf_statelim *);
+struct pf_statelim *pf_statelim_rb_nfind(struct pf_statelim_id_tree *,
+    struct pf_statelim *);
+int pf_statelim_get(struct pfioc_statelim *,
+    struct pf_statelim *(*rbt_op)(struct pf_statelim_id_tree *,
+     struct pf_statelim *));
+int pf_sourcelim_add(const struct pfioc_sourcelim *);
+struct pf_sourcelim *pf_sourcelim_rb_find(struct pf_sourcelim_id_tree *,
+    struct pf_sourcelim *);
+struct pf_sourcelim *pf_sourcelim_rb_nfind(struct pf_sourcelim_id_tree *,
+    struct pf_sourcelim *);
+int pf_sourcelim_get(struct pfioc_sourcelim *,
+    struct pf_sourcelim *(*rbt_op)(struct pf_sourcelim_id_tree *,
+     struct pf_sourcelim *));
+struct pf_source *pf_source_rb_find(struct pf_source_ioc_tree *,
+    struct pf_source *);
+struct pf_source *pf_source_rb_nfind(struct pf_source_ioc_tree *,
+    struct pf_source *);
+int pf_source_clr(struct pfioc_source_kill *);
+
 struct pf_kstatus {
 	counter_u64_t	counters[PFRES_MAX]; /* reason for passing/dropping */
 	counter_u64_t	lcounters[KLCNT_MAX]; /* limit counters */
*** 810 LINES SKIPPED ***