git: 777a4702c591 - main - pf: implement addrule via netlink

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 02 Feb 2024 16:55:41 UTC
The branch main has been updated by kp:

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

commit 777a4702c591154c5a844d43c32f588f371ae80a
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-01-12 10:54:18 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-02-02 16:55:16 +0000

    pf: implement addrule via netlink
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 lib/libpfctl/libpfctl.c     | 282 ++++++++++++++++++++++++++++++++++++++-
 lib/libpfctl/libpfctl.h     |   3 +
 sbin/pfctl/pfctl.c          |   4 +-
 sys/net/pfvar.h             |   5 +-
 sys/netlink/netlink_snl.h   |  12 ++
 sys/netpfil/pf/pf_ioctl.c   |  22 ++--
 sys/netpfil/pf/pf_nl.c      | 312 ++++++++++++++++++++++++++++++++++++++++++++
 sys/netpfil/pf/pf_nl.h      |  14 ++
 sys/netpfil/pf/pf_ruleset.c |  68 +++-------
 9 files changed, 659 insertions(+), 63 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 2db3f0ede99f..cb9b377f7b6c 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1229,7 +1229,287 @@ pfctl_get_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor,
 	    anchor_call, false));
 }
 
-int	pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket,
+#define _OUT(_field)	offsetof(struct pf_addr_wrap, _field)
+static const struct snl_attr_parser ap_addr_wrap[] = {
+	{ .type = PF_AT_ADDR, .off = _OUT(v.a.addr), .cb = snl_attr_get_in6_addr },
+	{ .type = PF_AT_MASK, .off = _OUT(v.a.mask), .cb = snl_attr_get_in6_addr },
+	{ .type = PF_AT_IFNAME, .off = _OUT(v.ifname), .arg = (void *)IFNAMSIZ,.cb = snl_attr_copy_string },
+	{ .type = PF_AT_TABLENAME, .off = _OUT(v.tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_AT_TYPE, .off = _OUT(type), .cb = snl_attr_get_uint8 },
+	{ .type = PF_AT_IFLAGS, .off = _OUT(iflags), .cb = snl_attr_get_uint8 },
+	{ .type = PF_AT_TBLCNT, .off = _OUT(p.tblcnt), .cb = snl_attr_get_uint32 },
+	{ .type = PF_AT_DYNCNT, .off = _OUT(p.dyncnt), .cb = snl_attr_get_uint32 },
+};
+SNL_DECLARE_ATTR_PARSER(addr_wrap_parser, ap_addr_wrap);
+#undef _OUT
+
+#define _OUT(_field)	offsetof(struct pf_rule_addr, _field)
+static struct snl_attr_parser ap_rule_addr[] = {
+	{ .type = PF_RAT_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_RAT_SRC_PORT, .off = _OUT(port[0]), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RAT_DST_PORT, .off = _OUT(port[1]), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RAT_NEG, .off = _OUT(neg), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RAT_OP, .off = _OUT(port_op), .cb = snl_attr_get_uint8 },
+};
+#undef _OUT
+SNL_DECLARE_ATTR_PARSER(rule_addr_parser, ap_rule_addr);
+
+struct snl_parsed_labels
+{
+	char		labels[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE];
+	uint32_t	i;
+};
+
+static bool
+snl_attr_get_pf_rule_labels(struct snl_state *ss, struct nlattr *nla,
+    const void *arg __unused, void *target)
+{
+	struct snl_parsed_labels *l = (struct snl_parsed_labels *)target;
+	bool ret;
+
+	if (l->i >= PF_RULE_MAX_LABEL_COUNT)
+		return (E2BIG);
+
+	ret = snl_attr_copy_string(ss, nla, (void *)PF_RULE_LABEL_SIZE,
+	    l->labels[l->i]);
+	if (ret)
+		l->i++;
+
+	return (ret);
+}
+
+#define _OUT(_field)	offsetof(struct nl_parsed_labels, _field)
+static const struct snl_attr_parser ap_labels[] = {
+	{ .type = PF_LT_LABEL, .off = 0, .cb = snl_attr_get_pf_rule_labels },
+};
+SNL_DECLARE_ATTR_PARSER(rule_labels_parser, ap_labels);
+#undef _OUT
+
+static bool
+snl_attr_get_nested_pf_rule_labels(struct snl_state *ss, struct nlattr *nla,
+    const void *arg __unused, void *target)
+{
+	struct snl_parsed_labels parsed_labels = { };
+	bool error;
+
+	/* Assumes target points to the beginning of the structure */
+	error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &rule_labels_parser, &parsed_labels);
+	if (! error)
+		return (error);
+
+	memcpy(target, parsed_labels.labels, sizeof(parsed_labels));
+
+	return (true);
+}
+
+#define _OUT(_field)	offsetof(struct pf_mape_portset, _field)
+static const struct snl_attr_parser ap_mape_portset[] = {
+	{ .type = PF_MET_OFFSET, .off = _OUT(offset), .cb = snl_attr_get_uint8 },
+	{ .type = PF_MET_PSID_LEN, .off = _OUT(psidlen), .cb = snl_attr_get_uint8 },
+	{. type = PF_MET_PSID, .off = _OUT(psid), .cb = snl_attr_get_uint16 },
+};
+SNL_DECLARE_ATTR_PARSER(mape_portset_parser, ap_mape_portset);
+#undef _OUT
+
+#define _OUT(_field)	offsetof(struct pfctl_pool, _field)
+static const struct snl_attr_parser ap_pool[] = {
+	{ .type = PF_PT_KEY, .off = _OUT(key), .arg = (void *)sizeof(struct pf_poolhashkey), .cb = snl_attr_get_bytes },
+	{ .type = PF_PT_COUNTER, .off = _OUT(counter), .cb = snl_attr_get_in6_addr },
+	{ .type = PF_PT_TBLIDX, .off = _OUT(tblidx), .cb = snl_attr_get_uint32 },
+	{ .type = PF_PT_PROXY_SRC_PORT, .off = _OUT(proxy_port[0]), .cb = snl_attr_get_uint16 },
+	{ .type = PF_PT_PROXY_DST_PORT, .off = _OUT(proxy_port[1]), .cb = snl_attr_get_uint16 },
+	{ .type = PF_PT_OPTS, .off = _OUT(opts), .cb = snl_attr_get_uint8 },
+	{ .type = PF_PT_MAPE, .off = _OUT(mape), .arg = &mape_portset_parser, .cb = snl_attr_get_nested },
+};
+SNL_DECLARE_ATTR_PARSER(pool_parser, ap_pool);
+#undef _OUT
+
+struct nl_parsed_timeouts
+{
+	uint32_t	timeouts[PFTM_MAX];
+	uint32_t	i;
+};
+
+static bool
+snl_attr_get_pf_timeout(struct snl_state *ss, struct nlattr *nla,
+    const void *arg __unused, void *target)
+{
+	struct nl_parsed_timeouts *t = (struct nl_parsed_timeouts *)target;
+	bool ret;
+
+	if (t->i >= PFTM_MAX)
+		return (E2BIG);
+
+	ret = snl_attr_get_uint32(ss, nla, NULL, &t->timeouts[t->i]);
+	if (ret)
+		t->i++;
+
+	return (ret);
+}
+
+#define _OUT(_field)	offsetof(struct nl_parsed_timeout, _field)
+static const struct snl_attr_parser ap_timeouts[] = {
+	{ .type = PF_TT_TIMEOUT, .off = 0, .cb = snl_attr_get_pf_timeout },
+};
+SNL_DECLARE_ATTR_PARSER(timeout_parser, ap_timeouts);
+#undef _OUT
+
+static bool
+snl_attr_get_nested_timeouts(struct snl_state *ss, struct nlattr *nla,
+    const void *arg __unused, void *target)
+{
+	struct nl_parsed_timeouts parsed_timeouts = { };
+	bool error;
+
+	/* Assumes target points to the beginning of the structure */
+	error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &timeout_parser, &parsed_timeouts);
+	if (! error)
+		return (error);
+
+	memcpy(target, parsed_timeouts.timeouts, sizeof(parsed_timeouts.timeouts));
+
+	return (true);
+}
+
+#define _OUT(_field)	offsetof(struct pf_rule_uid, _field)
+static const struct snl_attr_parser ap_rule_uid[] = {
+	{ .type = PF_RUT_UID_LOW, .off = _OUT(uid[0]), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RUT_UID_HIGH, .off = _OUT(uid[1]), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RUT_OP, .off = _OUT(op), .cb = snl_attr_get_uint8 },
+};
+SNL_DECLARE_ATTR_PARSER(rule_uid_parser, ap_rule_uid);
+#undef _OUT
+
+struct pfctl_nl_get_rule {
+	struct pfctl_rule r;
+	char anchor_call[MAXPATHLEN];
+};
+#define	_OUT(_field)	offsetof(struct pfctl_nl_get_rule, _field)
+static struct snl_attr_parser ap_getrule[] = {
+	{ .type = PF_RT_SRC, .off = _OUT(r.src), .arg = &rule_addr_parser,.cb = snl_attr_get_nested },
+	{ .type = PF_RT_DST, .off = _OUT(r.dst), .arg = &rule_addr_parser,.cb = snl_attr_get_nested },
+	{ .type = PF_RT_RIDENTIFIER, .off = _OUT(r.ridentifier), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_LABELS, .off = _OUT(r.label), .arg = &rule_labels_parser,.cb = snl_attr_get_nested_pf_rule_labels },
+	{ .type = PF_RT_IFNAME, .off = _OUT(r.ifname), .arg = (void *)IFNAMSIZ, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_QNAME, .off = _OUT(r.qname), .arg = (void *)PF_QNAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_PQNAME, .off = _OUT(r.pqname), .arg = (void *)PF_QNAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_TAGNAME, .off = _OUT(r.tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_MATCH_TAGNAME, .off = _OUT(r.match_tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_OVERLOAD_TBLNAME, .off = _OUT(r.overload_tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string },
+	{ .type = PF_RT_RPOOL, .off = _OUT(r.rpool), .arg = &pool_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_RT_OS_FINGERPRINT, .off = _OUT(r.os_fingerprint), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_RTABLEID, .off = _OUT(r.rtableid), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_TIMEOUT, .off = _OUT(r.timeout), .arg = &timeout_parser, .cb = snl_attr_get_nested_timeouts },
+	{ .type = PF_RT_MAX_STATES, .off = _OUT(r.max_states), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_MAX_SRC_NODES, .off = _OUT(r.max_src_nodes), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_MAX_SRC_STATES, .off = _OUT(r.max_src_states), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_MAX_SRC_CONN_RATE_LIMIT, .off = _OUT(r.max_src_conn_rate.limit), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_MAX_SRC_CONN_RATE_SECS, .off = _OUT(r.max_src_conn_rate.seconds), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_DNPIPE, .off = _OUT(r.dnpipe), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_DNRPIPE, .off = _OUT(r.dnrpipe), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_DNFLAGS, .off = _OUT(r.free_flags), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_NR, .off = _OUT(r.nr), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_PROB, .off = _OUT(r.prob), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_CUID, .off = _OUT(r.cuid), .cb = snl_attr_get_uint32 },
+	{. type = PF_RT_CPID, .off = _OUT(r.cpid), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_RETURN_ICMP, .off = _OUT(r.return_icmp), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_RETURN_ICMP6, .off = _OUT(r.return_icmp6), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_MAX_MSS, .off = _OUT(r.max_mss), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_SCRUB_FLAGS, .off = _OUT(r.scrub_flags), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_UID, .off = _OUT(r.uid), .arg = &rule_uid_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_RT_GID, .off = _OUT(r.gid), .arg = &rule_uid_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_RT_RULE_FLAG, .off = _OUT(r.rule_flag), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_ACTION, .off = _OUT(r.action), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_DIRECTION, .off = _OUT(r.direction), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_LOG, .off = _OUT(r.log), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_LOGIF, .off = _OUT(r.logif), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_QUICK, .off = _OUT(r.quick), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_IF_NOT, .off = _OUT(r.ifnot), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_MATCH_TAG_NOT, .off = _OUT(r.match_tag_not), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_NATPASS, .off = _OUT(r.natpass), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_KEEP_STATE, .off = _OUT(r.keep_state), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_AF, .off = _OUT(r.af), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_PROTO, .off = _OUT(r.proto), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_TYPE, .off = _OUT(r.type), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_CODE, .off = _OUT(r.code), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_FLAGS, .off = _OUT(r.flags), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_FLAGSET, .off = _OUT(r.flagset), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_MIN_TTL, .off = _OUT(r.min_ttl), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_ALLOW_OPTS, .off = _OUT(r.allow_opts), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_RT, .off = _OUT(r.rt), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_RETURN_TTL, .off = _OUT(r.return_ttl), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_TOS, .off = _OUT(r.tos), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_SET_TOS, .off = _OUT(r.set_tos), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_ANCHOR_RELATIVE, .off = _OUT(r.anchor_relative), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_ANCHOR_WILDCARD, .off = _OUT(r.anchor_wildcard), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_FLUSH, .off = _OUT(r.flush), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_PRIO, .off = _OUT(r.prio), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_SET_PRIO, .off = _OUT(r.set_prio[0]), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_SET_PRIO_REPLY, .off = _OUT(r.set_prio[1]), .cb = snl_attr_get_uint8 },
+	{ .type = PF_RT_DIVERT_ADDRESS, .off = _OUT(r.divert.addr), .cb = snl_attr_get_in6_addr },
+	{ .type = PF_RT_DIVERT_PORT, .off = _OUT(r.divert.port), .cb = snl_attr_get_uint16 },
+	{ .type = PF_RT_PACKETS_IN, .off = _OUT(r.packets[0]), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_PACKETS_OUT, .off = _OUT(r.packets[1]), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_BYTES_IN, .off = _OUT(r.bytes[0]), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_BYTES_OUT, .off = _OUT(r.bytes[1]), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_EVALUATIONS, .off = _OUT(r.evaluations), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_TIMESTAMP, .off = _OUT(r.last_active_timestamp), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_STATES_CUR, .off = _OUT(r.states_cur), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_STATES_TOTAL, .off = _OUT(r.states_tot), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_SRC_NODES, .off = _OUT(r.src_nodes), .cb = snl_attr_get_uint64 },
+	{ .type = PF_RT_ANCHOR_CALL, .off = _OUT(anchor_call), .arg = (void*)MAXPATHLEN, .cb = snl_attr_copy_string },
+};
+static struct snl_field_parser fp_getrule[] = {};
+#undef _OUT
+SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, fp_getrule, ap_getrule);
+
+int
+pfctl_get_clear_rule_h(struct pfctl_handle *h, uint32_t nr, uint32_t ticket,
+    const char *anchor, uint32_t ruleset, struct pfctl_rule *rule,
+    char *anchor_call, bool clear)
+{
+	struct pfctl_nl_get_rule attrs = {};
+	struct snl_errmsg_data e = {};
+	struct nlmsghdr *hdr;
+	struct snl_writer nw;
+	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_GETRULE);
+	hdr->nlmsg_flags |= NLM_F_DUMP;
+
+	snl_add_msg_attr_string(&nw, PF_GR_ANCHOR, anchor);
+	snl_add_msg_attr_u8(&nw, PF_GR_ACTION, ruleset);
+	snl_add_msg_attr_u32(&nw, PF_GR_NR, nr);
+	snl_add_msg_attr_u32(&nw, PF_GR_TICKET, ticket);
+	snl_add_msg_attr_u8(&nw, PF_GR_CLEAR, clear);
+
+	hdr = snl_finalize_msg(&nw);
+	if (hdr == NULL)
+		return (ENOMEM);
+
+	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, &getrule_parser, &attrs))
+			continue;
+	}
+
+	memcpy(rule, &attrs.r, sizeof(attrs.r));
+	strlcpy(anchor_call, attrs.anchor_call, MAXPATHLEN);
+
+	return (e.error);
+}
+
+int
+pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket,
 	    const char *anchor, uint32_t ruleset, struct pfctl_rule *rule,
 	    char *anchor_call, bool clear)
 {
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index cd72d04d6715..f05044a9a985 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -418,6 +418,9 @@ int	pfctl_get_rule(int dev, uint32_t nr, uint32_t ticket,
 int	pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket,
 	    const char *anchor, uint32_t ruleset, struct pfctl_rule *rule,
 	    char *anchor_call, bool clear);
+int	pfctl_get_clear_rule_h(struct pfctl_handle *h, uint32_t nr, uint32_t ticket,
+	    const char *anchor, uint32_t ruleset, struct pfctl_rule *rule,
+	    char *anchor_call, bool clear);
 int	pfctl_add_rule(int dev, const struct pfctl_rule *r,
 	    const char *anchor, const char *anchor_call, uint32_t ticket,
 	    uint32_t pool_ticket);
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 217bf31b3301..c583279750f1 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1303,7 +1303,7 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
 	}
 
 	for (nr = 0; nr < ri.nr; ++nr) {
-		if (pfctl_get_clear_rule(dev, nr, ri.ticket, path, PF_SCRUB,
+		if (pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_SCRUB,
 		    &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) {
 			warn("DIOCGETRULENV");
 			goto error;
@@ -1334,7 +1334,7 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
 		goto error;
 	}
 	for (nr = 0; nr < ri.nr; ++nr) {
-		if (pfctl_get_clear_rule(dev, nr, ri.ticket, path, PF_PASS,
+		if (pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_PASS,
 		    &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) {
 			warn("DIOCGETRULE");
 			goto error;
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index ff3370bc105e..f3a808f8ad26 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2470,10 +2470,10 @@ void			 pf_init_kruleset(struct pf_kruleset *);
 void			 pf_init_keth(struct pf_keth_ruleset *);
 int			 pf_kanchor_setup(struct pf_krule *,
 			    const struct pf_kruleset *, const char *);
+int			 pf_kanchor_copyout(const struct pf_kruleset *,
+			    const struct pf_krule *, char *);
 int			 pf_kanchor_nvcopyout(const struct pf_kruleset *,
 			    const struct pf_krule *, nvlist_t *);
-int			 pf_kanchor_copyout(const struct pf_kruleset *,
-			    const struct pf_krule *, struct pfioc_rule *);
 void			 pf_kanchor_remove(struct pf_krule *);
 void			 pf_remove_if_empty_kruleset(struct pf_kruleset *);
 struct pf_kruleset	*pf_find_kruleset(const char *);
@@ -2501,6 +2501,7 @@ int			 pf_ioctl_addrule(struct pf_krule *, uint32_t,
 			    pid_t);
 
 void			 pf_krule_free(struct pf_krule *);
+void			 pf_krule_clear_counters(struct pf_krule *);
 #endif
 
 /* The fingerprint functions can be linked into userland programs (tcpdump) */
diff --git a/sys/netlink/netlink_snl.h b/sys/netlink/netlink_snl.h
index 7bdf5424ddf3..6636d53f2353 100644
--- a/sys/netlink/netlink_snl.h
+++ b/sys/netlink/netlink_snl.h
@@ -513,6 +513,18 @@ snl_attr_get_flag(struct snl_state *ss __unused, struct nlattr *nla, const void
 	return (false);
 }
 
+static inline bool
+snl_attr_get_bytes(struct snl_state *ss __unused, struct nlattr *nla, const void *arg,
+    void *target)
+{
+	if ((size_t)NLA_DATA_LEN(nla) != (size_t)arg)
+		return (false);
+
+	memcpy(target, NLA_DATA_CONST(nla), (size_t)arg);
+
+	return (true);
+}
+
 static inline bool
 snl_attr_get_uint8(struct snl_state *ss __unused, struct nlattr *nla,
     const void *arg __unused, void *target)
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 7f70321ea560..d83933cd293f 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -1867,6 +1867,17 @@ pf_krule_free(struct pf_krule *rule)
 	free(rule, M_PFRULE);
 }
 
+void
+pf_krule_clear_counters(struct pf_krule *rule)
+{
+	pf_counter_u64_zero(&rule->evaluations);
+	for (int i = 0; i < 2; i++) {
+		pf_counter_u64_zero(&rule->packets[i]);
+		pf_counter_u64_zero(&rule->bytes[i]);
+	}
+	counter_u64_zero(rule->states_tot);
+}
+
 static void
 pf_kpooladdr_to_pooladdr(const struct pf_kpooladdr *kpool,
     struct pf_pooladdr *pool)
@@ -3266,14 +3277,9 @@ DIOCADDRULENV_error:
 			ERROUT(ENOSPC);
 		}
 
-		if (clear_counter) {
-			pf_counter_u64_zero(&rule->evaluations);
-			for (int i = 0; i < 2; i++) {
-				pf_counter_u64_zero(&rule->packets[i]);
-				pf_counter_u64_zero(&rule->bytes[i]);
-			}
-			counter_u64_zero(rule->states_tot);
-		}
+		if (clear_counter)
+			pf_krule_clear_counters(rule);
+
 		PF_RULES_WUNLOCK();
 
 		error = copyout(nvlpacked, nv->data, nv->len);
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index e74d4773b1e7..120ce88f8720 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -401,6 +401,42 @@ static const struct nlattr_parser nla_p_addr_wrap[] = {
 NL_DECLARE_ATTR_PARSER(addr_wrap_parser, nla_p_addr_wrap);
 #undef _OUT
 
+static bool
+nlattr_add_addr_wrap(struct nl_writer *nw, int attrtype, struct pf_addr_wrap *a)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+	int num;
+
+	nlattr_add_in6_addr(nw, PF_AT_ADDR, &a->v.a.addr.v6);
+	nlattr_add_in6_addr(nw, PF_AT_MASK, &a->v.a.mask.v6);
+	nlattr_add_u8(nw, PF_AT_TYPE, a->type);
+	nlattr_add_u8(nw, PF_AT_IFLAGS, a->iflags);
+
+	if (a->type == PF_ADDR_DYNIFTL) {
+		nlattr_add_string(nw, PF_AT_IFNAME, a->v.ifname);
+		num = 0;
+		if (a->p.dyn != NULL)
+			num = a->p.dyn->pfid_acnt4 + a->p.dyn->pfid_acnt6;
+		nlattr_add_u32(nw, PF_AT_DYNCNT, num);
+	} else if (a->type == PF_ADDR_TABLE) {
+		struct pfr_ktable *kt;
+
+		nlattr_add_string(nw, PF_AT_TABLENAME, a->v.tblname);
+		num = -1;
+		kt = a->p.tbl;
+		if ((kt->pfrkt_flags & PFR_TFLAG_ACTIVE) &&
+		    kt->pfrkt_root != NULL)
+			kt = kt->pfrkt_root;
+		if (kt->pfrkt_flags & PFR_TFLAG_ACTIVE)
+			num = kt->pfrkt_cnt;
+		nlattr_add_u32(nw, PF_AT_TBLCNT, num);
+	}
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 #define _OUT(_field)	offsetof(struct pf_rule_addr, _field)
 static const struct nlattr_parser nla_p_ruleaddr[] = {
 	{ .type = PF_RAT_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = nlattr_get_nested },
@@ -412,6 +448,22 @@ static const struct nlattr_parser nla_p_ruleaddr[] = {
 NL_DECLARE_ATTR_PARSER(rule_addr_parser, nla_p_ruleaddr);
 #undef _OUT
 
+static bool
+nlattr_add_rule_addr(struct nl_writer *nw, int attrtype, struct pf_rule_addr *r)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+
+	nlattr_add_addr_wrap(nw, PF_RAT_ADDR, &r->addr);
+	nlattr_add_u16(nw, PF_RAT_SRC_PORT, r->port[0]);
+	nlattr_add_u16(nw, PF_RAT_DST_PORT, r->port[1]);
+	nlattr_add_u8(nw, PF_RAT_NEG, r->neg);
+	nlattr_add_u8(nw, PF_RAT_OP, r->port_op);
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 #define _OUT(_field)	offsetof(struct pf_mape_portset, _field)
 static const struct nlattr_parser nla_p_mape_portset[] = {
 	{ .type = PF_MET_OFFSET, .off = _OUT(offset), .cb = nlattr_get_uint8 },
@@ -421,6 +473,20 @@ static const struct nlattr_parser nla_p_mape_portset[] = {
 NL_DECLARE_ATTR_PARSER(mape_portset_parser, nla_p_mape_portset);
 #undef _OUT
 
+static bool
+nlattr_add_mape_portset(struct nl_writer *nw, int attrtype, const struct pf_mape_portset *m)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+
+	nlattr_add_u8(nw, PF_MET_OFFSET, m->offset);
+	nlattr_add_u8(nw, PF_MET_PSID_LEN, m->psidlen);
+	nlattr_add_u16(nw, PF_MET_PSID, m->psid);
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 struct nl_parsed_labels
 {
 	char		labels[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE];
@@ -468,6 +534,23 @@ nlattr_get_nested_pf_rule_labels(struct nlattr *nla, struct nl_pstate *npt, cons
 	return (0);
 }
 
+static bool
+nlattr_add_labels(struct nl_writer *nw, int attrtype, const struct pf_krule *r)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+	int i = 0;
+
+	while (r->label[i][0] != 0
+	    && i < PF_RULE_MAX_LABEL_COUNT) {
+		nlattr_add_string(nw, PF_LT_LABEL, r->label[i]);
+		i++;
+	}
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 #define _OUT(_field)	offsetof(struct pf_kpool, _field)
 static const struct nlattr_parser nla_p_pool[] = {
 	{ .type = PF_PT_KEY, .off = _OUT(key), .arg = (void *)sizeof(struct pf_poolhashkey), .cb = nlattr_get_bytes },
@@ -481,6 +564,24 @@ static const struct nlattr_parser nla_p_pool[] = {
 NL_DECLARE_ATTR_PARSER(pool_parser, nla_p_pool);
 #undef _OUT
 
+static bool
+nlattr_add_pool(struct nl_writer *nw, int attrtype, const struct pf_kpool *pool)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+
+	nlattr_add(nw, PF_PT_KEY, sizeof(struct pf_poolhashkey), &pool->key);
+	nlattr_add_in6_addr(nw, PF_PT_COUNTER, (const struct in6_addr *)&pool->counter);
+	nlattr_add_u32(nw, PF_PT_TBLIDX, pool->tblidx);
+	nlattr_add_u16(nw, PF_PT_PROXY_SRC_PORT, pool->proxy_port[0]);
+	nlattr_add_u16(nw, PF_PT_PROXY_DST_PORT, pool->proxy_port[1]);
+	nlattr_add_u8(nw, PF_PT_OPTS, pool->opts);
+	nlattr_add_mape_portset(nw, PF_PT_MAPE, &pool->mape);
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 #define _OUT(_field)	offsetof(struct pf_rule_uid, _field)
 static const struct nlattr_parser nla_p_rule_uid[] = {
 	{ .type = PF_RUT_UID_LOW, .off = _OUT(uid[0]), .cb = nlattr_get_uint32 },
@@ -490,6 +591,20 @@ static const struct nlattr_parser nla_p_rule_uid[] = {
 NL_DECLARE_ATTR_PARSER(rule_uid_parser, nla_p_rule_uid);
 #undef _OUT
 
+static bool
+nlattr_add_rule_uid(struct nl_writer *nw, int attrtype, const struct pf_rule_uid *u)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+
+	nlattr_add_u32(nw, PF_RUT_UID_LOW, u->uid[0]);
+	nlattr_add_u32(nw, PF_RUT_UID_HIGH, u->uid[1]);
+	nlattr_add_u8(nw, PF_RUT_OP, u->op);
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 struct nl_parsed_timeouts
 {
 	uint32_t	timeouts[PFTM_MAX];
@@ -536,6 +651,19 @@ nlattr_get_nested_timeouts(struct nlattr *nla, struct nl_pstate *npt, const void
 	return (0);
 }
 
+static bool
+nlattr_add_timeout(struct nl_writer *nw, int attrtype, uint32_t *timeout)
+{
+	int off = nlattr_add_nested(nw, attrtype);
+
+	for (int i = 0; i < PFTM_MAX; i++)
+		nlattr_add_u32(nw, PF_RT_TIMEOUT, timeout[i]);
+
+	nlattr_set_len(nw, off);
+
+	return (true);
+}
+
 #define _OUT(_field)	offsetof(struct pf_krule, _field)
 static const struct nlattr_parser nla_p_rule[] = {
 	{ .type = PF_RT_SRC, .off = _OUT(src), .arg = &rule_addr_parser,.cb = nlattr_get_nested },
@@ -654,6 +782,7 @@ static const struct nlattr_parser nla_p_getrules[] = {
 };
 static const struct nlfield_parser nlf_p_getrules[] = {
 };
+#undef _OUT
 NL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, nlf_p_getrules, nla_p_getrules);
 
 static int
@@ -695,6 +824,182 @@ out:
 	return (error);
 }
 
+struct nl_parsed_get_rule {
+	char anchor[MAXPATHLEN];
+	uint8_t action;
+	uint32_t nr;
+	uint32_t ticket;
+	uint8_t clear;
+};
+#define	_IN(_field)	offsetof(struct genlmsghdr, _field)
+#define	_OUT(_field)	offsetof(struct nl_parsed_get_rule, _field)
+static const struct nlattr_parser nla_p_getrule[] = {
+	{ .type = PF_GR_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara },
+	{ .type = PF_GR_ACTION, .off = _OUT(action), .cb = nlattr_get_uint8 },
+	{ .type = PF_GR_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 },
+	{ .type = PF_GR_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 },
+	{ .type = PF_GR_CLEAR, .off = _OUT(clear), .cb = nlattr_get_uint8 },
+};
+static const struct nlfield_parser nlf_p_getrule[] = {
+};
+NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_getrule, nla_p_getrule);
+
+static int
+pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+	char				 anchor_call[MAXPATHLEN];
+	struct nl_parsed_get_rule	 attrs = {};
+	struct nl_writer		*nw = npt->nw;
+	struct genlmsghdr		*ghdr_new;
+	struct pf_kruleset		*ruleset;
+	struct pf_krule			*rule;
+	int				 rs_num;
+	int				 error;
+
+	error = nl_parse_nlmsg(hdr, &getrule_parser, npt, &attrs);
+	if (error != 0)
+		return (error);
+
+	if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
+		return (ENOMEM);
+
+	ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
+	ghdr_new->cmd = PFNL_CMD_GETRULE;
+	ghdr_new->version = 0;
+	ghdr_new->reserved = 0;
+
+	PF_RULES_WLOCK();
+	ruleset = pf_find_kruleset(attrs.anchor);
+	if (ruleset == NULL) {
+		PF_RULES_WUNLOCK();
+		error = ENOENT;
+		goto out;
+	}
+
+	rs_num = pf_get_ruleset_number(attrs.action);
+	if (rs_num >= PF_RULESET_MAX) {
+		PF_RULES_WUNLOCK();
+		error = EINVAL;
+		goto out;
+	}
+
+	if (attrs.ticket != ruleset->rules[rs_num].active.ticket) {
+		PF_RULES_WUNLOCK();
+		error = EBUSY;
+		goto out;
+	}
+
+	rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr);
+	while ((rule != NULL) && (rule->nr != attrs.nr))
+		rule = TAILQ_NEXT(rule, entries);
+	if (rule == NULL) {
+		PF_RULES_WUNLOCK();
+		error = EBUSY;
+		goto out;
+	}
+
+	nlattr_add_rule_addr(nw, PF_RT_SRC, &rule->src);
+	nlattr_add_rule_addr(nw, PF_RT_DST, &rule->dst);
+	nlattr_add_u32(nw, PF_RT_RIDENTIFIER, rule->ridentifier);
+	nlattr_add_labels(nw, PF_RT_LABELS, rule);
+	nlattr_add_string(nw, PF_RT_IFNAME, rule->ifname);
+	nlattr_add_string(nw, PF_RT_QNAME, rule->qname);
+	nlattr_add_string(nw, PF_RT_PQNAME, rule->pqname);
+	nlattr_add_string(nw, PF_RT_TAGNAME, rule->tagname);
+	nlattr_add_string(nw, PF_RT_MATCH_TAGNAME, rule->match_tagname);
+	nlattr_add_string(nw, PF_RT_OVERLOAD_TBLNAME, rule->overload_tblname);
+	nlattr_add_pool(nw, PF_RT_RPOOL, &rule->rpool);
+	nlattr_add_u32(nw, PF_RT_OS_FINGERPRINT, rule->os_fingerprint);
+	nlattr_add_u32(nw, PF_RT_RTABLEID, rule->rtableid);
+	nlattr_add_timeout(nw, PF_RT_TIMEOUT, rule->timeout);
+	nlattr_add_u32(nw, PF_RT_MAX_STATES, rule->max_states);
+	nlattr_add_u32(nw, PF_RT_MAX_SRC_NODES, rule->max_src_nodes);
+	nlattr_add_u32(nw, PF_RT_MAX_SRC_STATES, rule->max_src_states);
+	nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_LIMIT, rule->max_src_conn_rate.limit);
+	nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_SECS, rule->max_src_conn_rate.seconds);
+
+	nlattr_add_u16(nw, PF_RT_DNPIPE, rule->dnpipe);
+	nlattr_add_u16(nw, PF_RT_DNRPIPE, rule->dnrpipe);
+	nlattr_add_u32(nw, PF_RT_DNFLAGS, rule->free_flags);
+
+	nlattr_add_u32(nw, PF_RT_NR, rule->nr);
+	nlattr_add_u32(nw, PF_RT_PROB, rule->prob);
+	nlattr_add_u32(nw, PF_RT_CUID, rule->cuid);
+	nlattr_add_u32(nw, PF_RT_CPID, rule->cpid);
+
+	nlattr_add_u16(nw, PF_RT_RETURN_ICMP, rule->return_icmp);
+	nlattr_add_u16(nw, PF_RT_RETURN_ICMP6, rule->return_icmp6);
+	nlattr_add_u16(nw, PF_RT_RETURN_ICMP6, rule->return_icmp6);
+	nlattr_add_u16(nw, PF_RT_MAX_MSS, rule->max_mss);
+	nlattr_add_u16(nw, PF_RT_SCRUB_FLAGS, rule->scrub_flags);
+
+	nlattr_add_rule_uid(nw, PF_RT_UID, &rule->uid);
+	nlattr_add_rule_uid(nw, PF_RT_GID, (const struct pf_rule_uid *)&rule->gid);
+
+	nlattr_add_u32(nw, PF_RT_RULE_FLAG, rule->rule_flag);
+	nlattr_add_u8(nw, PF_RT_ACTION, rule->action);
+	nlattr_add_u8(nw, PF_RT_DIRECTION, rule->direction);
+	nlattr_add_u8(nw, PF_RT_LOG, rule->log);
+	nlattr_add_u8(nw, PF_RT_LOGIF, rule->logif);
+	nlattr_add_u8(nw, PF_RT_QUICK, rule->quick);
+	nlattr_add_u8(nw, PF_RT_IF_NOT, rule->ifnot);
+	nlattr_add_u8(nw, PF_RT_MATCH_TAG_NOT, rule->match_tag_not);
+	nlattr_add_u8(nw, PF_RT_NATPASS, rule->natpass);
+	nlattr_add_u8(nw, PF_RT_KEEP_STATE, rule->keep_state);
+
+	nlattr_add_u8(nw, PF_RT_AF, rule->af);
+	nlattr_add_u8(nw, PF_RT_PROTO, rule->proto);
+	nlattr_add_u8(nw, PF_RT_TYPE, rule->type);
+	nlattr_add_u8(nw, PF_RT_CODE, rule->code);
+	nlattr_add_u8(nw, PF_RT_FLAGS, rule->flags);
+	nlattr_add_u8(nw, PF_RT_FLAGSET, rule->flagset);
+	nlattr_add_u8(nw, PF_RT_MIN_TTL, rule->min_ttl);
+	nlattr_add_u8(nw, PF_RT_ALLOW_OPTS, rule->allow_opts);
+	nlattr_add_u8(nw, PF_RT_RT, rule->rt);
+	nlattr_add_u8(nw, PF_RT_RETURN_TTL, rule->return_ttl);
+	nlattr_add_u8(nw, PF_RT_TOS, rule->tos);
+	nlattr_add_u8(nw, PF_RT_SET_TOS, rule->set_tos);
+	nlattr_add_u8(nw, PF_RT_ANCHOR_RELATIVE, rule->anchor_relative);
+	nlattr_add_u8(nw, PF_RT_ANCHOR_WILDCARD, rule->anchor_wildcard);
+	nlattr_add_u8(nw, PF_RT_FLUSH, rule->flush);
+	nlattr_add_u8(nw, PF_RT_PRIO, rule->prio);
+	nlattr_add_u8(nw, PF_RT_SET_PRIO, rule->set_prio[0]);
+	nlattr_add_u8(nw, PF_RT_SET_PRIO_REPLY, rule->set_prio[1]);
+
+	nlattr_add_in6_addr(nw, PF_RT_DIVERT_ADDRESS, &rule->divert.addr.v6);
+	nlattr_add_u16(nw, PF_RT_DIVERT_PORT, rule->divert.port);
+
+	nlattr_add_u64(nw, PF_RT_PACKETS_IN, pf_counter_u64_fetch(&rule->packets[0]));
+	nlattr_add_u64(nw, PF_RT_PACKETS_OUT, pf_counter_u64_fetch(&rule->packets[1]));
+	nlattr_add_u64(nw, PF_RT_BYTES_IN, pf_counter_u64_fetch(&rule->bytes[0]));
+	nlattr_add_u64(nw, PF_RT_BYTES_OUT, pf_counter_u64_fetch(&rule->bytes[1]));
+	nlattr_add_u64(nw, PF_RT_EVALUATIONS, pf_counter_u64_fetch(&rule->evaluations));
+	nlattr_add_u64(nw, PF_RT_TIMESTAMP, pf_get_timestamp(rule));
+	nlattr_add_u64(nw, PF_RT_STATES_CUR, counter_u64_fetch(rule->states_cur));
+	nlattr_add_u64(nw, PF_RT_STATES_TOTAL, counter_u64_fetch(rule->states_tot));
+	nlattr_add_u64(nw, PF_RT_SRC_NODES, counter_u64_fetch(rule->src_nodes));
+
+	error = pf_kanchor_copyout(ruleset, rule, anchor_call);
+	MPASS(error == 0);
+
+	nlattr_add_string(nw, PF_RT_ANCHOR_CALL, anchor_call);
+
+	if (attrs.clear)
+		pf_krule_clear_counters(rule);
+
+	PF_RULES_WUNLOCK();
+
+	if (!nlmsg_end(nw)) {
+		error = ENOMEM;
+		goto out;
+	}
+
+	return (0);
+out:
+	nlmsg_abort(nw);
+	return (error);
+}
+
 static const struct nlhdr_parser *all_parsers[] = {
 	&state_parser,
 	&addrule_parser,
@@ -746,6 +1051,13 @@ static const struct genl_cmd pf_cmds[] = {
 		.cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
 		.cmd_priv = PRIV_NETINET_PF,
 	},
+	{
+		.cmd_num = PFNL_CMD_GETRULE,
+		.cmd_name = "GETRULE",
+		.cmd_cb = pf_handle_getrule,
+		.cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
+		.cmd_priv = PRIV_NETINET_PF,
+	},
 };
 
 void
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index d8b494a54cf7..51df8b7aece9 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -42,6 +42,7 @@ enum {
 	PFNL_CMD_STOP = 4,
 	PFNL_CMD_ADDRULE = 5,
 	PFNL_CMD_GETRULES = 6,
+	PFNL_CMD_GETRULE = 7,
 	__PFNL_CMD_MAX,
 };
 #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1)
@@ -117,6 +118,8 @@ enum pf_addr_type_t {
 	PF_AT_TABLENAME		= 4, /* string */
 	PF_AT_TYPE		= 5, /* u8 */
 	PF_AT_IFLAGS		= 6, /* u8 */
+	PF_AT_TBLCNT		= 7, /* u32 */
+	PF_AT_DYNCNT		= 8, /* u32 */
 };
 
 enum pfrule_addr_type_t {
@@ -229,6 +232,16 @@ enum pf_rule_type_t {
 	PF_RT_SET_PRIO_REPLY	= 60, /* u8 */
 	PF_RT_DIVERT_ADDRESS	= 61, /* in6_addr */
 	PF_RT_DIVERT_PORT	= 62, /* u16 */
+	PF_RT_PACKETS_IN	= 63, /* u64 */
+	PF_RT_PACKETS_OUT	= 64, /* u64 */
+	PF_RT_BYTES_IN		= 65, /* u64 */
+	PF_RT_BYTES_OUT		= 66, /* u64 */
+	PF_RT_EVALUATIONS	= 67, /* u64 */
+	PF_RT_TIMESTAMP		= 68, /* u64 */
+	PF_RT_STATES_CUR	= 69, /* u64 */
+	PF_RT_STATES_TOTAL	= 70, /* u64 */
+	PF_RT_SRC_NODES		= 71, /* u64 */
+	PF_RT_ANCHOR_CALL	= 72, /* string */
 };
 
 enum pf_addrule_type_t {
@@ -246,6 +259,7 @@ enum pf_getrules_type_t {
 	PF_GR_ACTION		= 2, /* u8 */
 	PF_GR_NR		= 3, /* u32 */
 	PF_GR_TICKET		= 4, /* u32 */
+	PF_GR_CLEAR		= 5, /* u8 */
 };
 
 #ifdef _KERNEL
diff --git a/sys/netpfil/pf/pf_ruleset.c b/sys/netpfil/pf/pf_ruleset.c
index bdc205785bd4..38cc1eae419f 100644
--- a/sys/netpfil/pf/pf_ruleset.c
+++ b/sys/netpfil/pf/pf_ruleset.c
@@ -367,10 +367,10 @@ pf_kanchor_setup(struct pf_krule *r, const struct pf_kruleset *s,
 }
 
 int
-pf_kanchor_nvcopyout(const struct pf_kruleset *rs, const struct pf_krule *r,
-    nvlist_t *nvl)
+pf_kanchor_copyout(const struct pf_kruleset *rs, const struct pf_krule *r,
+    char *anchor_call)
 {
-	char anchor_call[MAXPATHLEN] = { 0 };
+	anchor_call[0] = 0;
 
 	if (r->anchor == NULL)
 		goto done;
@@ -408,11 +408,25 @@ pf_kanchor_nvcopyout(const struct pf_kruleset *rs, const struct pf_krule *r,
 		    sizeof(anchor_call));
 
 done:
-	nvlist_add_string(nvl, "anchor_call", anchor_call);
 
 	return (0);
 }
 
+int
+pf_kanchor_nvcopyout(const struct pf_kruleset *rs, const struct pf_krule *r,
+    nvlist_t *nvl)
+{
+	char anchor_call[MAXPATHLEN] = { 0 };
+	int ret;
+
+	ret = pf_kanchor_copyout(rs, r, anchor_call);
+	MPASS(ret == 0);
+
+	nvlist_add_string(nvl, "anchor_call", anchor_call);
+
+	return (ret);
+}
+
 int
 pf_keth_anchor_nvcopyout(const struct pf_keth_ruleset *rs,
     const struct pf_keth_rule *r, nvlist_t *nvl)
@@ -460,52 +474,6 @@ done:
 	return (0);
 }
 
-int
-pf_kanchor_copyout(const struct pf_kruleset *rs, const struct pf_krule *r,
-    struct pfioc_rule *pr)
-{
-	pr->anchor_call[0] = 0;
-	if (r->anchor == NULL)
-		return (0);
-	if (!r->anchor_relative) {
-		strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
-		strlcat(pr->anchor_call, r->anchor->path,
-		    sizeof(pr->anchor_call));
-	} else {
-		char	*a, *p;
-		int	 i;
-
-		a = (char *)rs_malloc(MAXPATHLEN);
-		if (a == NULL)
-			return (1);
-		if (rs->anchor == NULL)
-			a[0] = 0;
-		else
-			strlcpy(a, rs->anchor->path, MAXPATHLEN);
-		for (i = 1; i < r->anchor_relative; ++i) {
-			if ((p = strrchr(a, '/')) == NULL)
-				p = a;
-			*p = 0;
-			strlcat(pr->anchor_call, "../",
-			    sizeof(pr->anchor_call));
-		}
-		if (strncmp(a, r->anchor->path, strlen(a))) {
-			printf("pf_anchor_copyout: '%s' '%s'\n", a,
-			    r->anchor->path);
-			rs_free(a);
-			return (1);
-		}
-		if (strlen(r->anchor->path) > strlen(a))
-			strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
-			    strlen(a) + 1 : 0), sizeof(pr->anchor_call));
-		rs_free(a);
-	}
-	if (r->anchor_wildcard)
-		strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
-		    sizeof(pr->anchor_call));
-	return (0);
-}
-
 void
 pf_kanchor_remove(struct pf_krule *r)
 {