git: f05af59fba69 - main - pf: return per-address feedback from netlink table test

From: R. Christian McDonald <rcm_at_FreeBSD.org>
Date: Thu, 01 Oct 2026 10:39:53 UTC
The branch main has been updated by rcm:

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

commit f05af59fba6912bdde8c911b0820871224ed16db
Author:     R. Christian McDonald <rcm@FreeBSD.org>
AuthorDate: 2026-10-01 10:39:01 +0000
Commit:     R. Christian McDonald <rcm@FreeBSD.org>
CommitDate: 2026-10-01 10:39:01 +0000

    pf: return per-address feedback from netlink table test
    
    The PFNL_CMD_TABLE_TEST_ADDRS reply carries only the match count, so
    the per-address feedback from pfr_tst_addrs() is lost:
    "pfctl -v -T test" lists nothing and "pfctl -vv -T test" reports
    every address as "nomatch".
    
    Return each address, as updated by pfr_tst_addrs(), in a nested
    PF_TAS_ADDR attribute, and decode them into the caller's array in
    libpfctl.  PF_TA_ADDR is not reused: it shares its value with
    PF_TAS_ASTATS, which older libpfctl would decode into an
    uninitialised target.  That target was also read when no reply was
    parsed, so the match count could be garbage; initialise it.
    
    Add a regression test.
    
    Reviewed by:            kp
    Approved by:            kp (mentor)
    Fixes:                  281282e9357b ("pf: convert DIOCRTSTADDRS to netlink")
    See also:               https://redmine.pfsense.org/issues/17135
    Sponsored by:           Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D60146
---
 lib/libpfctl/libpfctl.c       | 12 ++++++++--
 sys/netpfil/pf/pf_nl.c        |  4 ++++
 sys/netpfil/pf/pf_nl.h        |  1 +
 tests/sys/netpfil/pf/table.sh | 55 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index aa3f4ec30f74..65c1a77b4d87 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -3896,6 +3896,14 @@ static struct snl_attr_parser ap_table_get_astats[] = {
 #undef _OUT
 SNL_DECLARE_PARSER(table_astats_parser, struct genlmsghdr, snl_f_p_empty, ap_table_get_astats);
 
+#define _OUT(_field)	offsetof(struct nl_addrs, _field)
+static struct snl_attr_parser ap_table_test_addrs[] = {
+	{ .type = PF_TAS_ASTATS_COUNT, .off = _OUT(total_count), .cb = snl_attr_get_uint32 },
+	{ .type = PF_TAS_ADDR, .off = 0, .cb = snl_attr_get_pfr_addrs },
+};
+#undef _OUT
+SNL_DECLARE_PARSER(table_test_addrs_parser, struct genlmsghdr, snl_f_p_empty, ap_table_test_addrs);
+
 int
 pfctl_get_astats(struct pfctl_handle *h, const struct pfr_table *tbl,
     struct pfr_astats *as, int *size, int flags)
@@ -4022,7 +4030,7 @@ _pfctl_test_addrs(struct pfctl_handle *h, const struct pfr_table *tbl,
 	struct snl_errmsg_data e = {};
 	struct nlmsghdr *hdr;
 	uint32_t seq_id;
-	struct nl_astats attrs;
+	struct nl_addrs attrs = { .addrs = addrs, .max = size };
 
 	snl_init_writer(&h->ss, &nw);
 	hdr = snl_create_genl_msg_request(&nw, h->family_id,
@@ -4046,7 +4054,7 @@ _pfctl_test_addrs(struct pfctl_handle *h, const struct pfr_table *tbl,
 	}
 
 	while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) {
-		if (! snl_parse_nlmsg(&h->ss, hdr, &table_astats_parser, &attrs))
+		if (! snl_parse_nlmsg(&h->ss, hdr, &table_test_addrs_parser, &attrs))
 			continue;
 	}
 
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index ad060ea3230a..80d12c3abae3 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -2553,6 +2553,10 @@ pf_handle_table_test_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	ghdr_new->cmd = PFNL_CMD_TABLE_TEST_ADDRS;
 
 	nlattr_add_u32(nw, PF_TAS_ASTATS_COUNT, attrs.nchange);
+	if (error == 0) {
+		for (size_t i = 0; i < attrs.addr_count; i++)
+			nlattr_add_pfr_addr(nw, PF_TAS_ADDR, &attrs.addrs[i]);
+	}
 
 	if (!nlmsg_end(nw))
 		return (ENOMEM);
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index d795d33c085a..c3c8da3571d3 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -527,6 +527,7 @@ enum pf_table_astats_t {
 	PF_TAS_FLAGS		= 3, /* u32 */
 	PF_TAS_ASTATS_COUNT	= 4, /* u32 */
 	PF_TAS_ASTATS_ZEROED	= 5, /* u32 */
+	PF_TAS_ADDR		= 6, /* nested, pfr_addr_t */
 };
 
 enum pf_limit_rate_t {
diff --git a/tests/sys/netpfil/pf/table.sh b/tests/sys/netpfil/pf/table.sh
index 9c93df781852..e21d998b4ece 100644
--- a/tests/sys/netpfil/pf/table.sh
+++ b/tests/sys/netpfil/pf/table.sh
@@ -986,6 +986,60 @@ test_cleanup()
 	pft_cleanup
 }
 
+atf_test_case "test_verbose" "cleanup"
+test_verbose_head()
+{
+	atf_set descr 'Test pfctl -v -T test per-address feedback'
+	atf_set require.user root
+}
+
+test_verbose_body()
+{
+	pft_init
+
+	vnet_mkjail alcatraz
+	jexec alcatraz pfctl -e
+
+	pft_set_rules alcatraz \
+	    "table <foo> persist { 192.0.2.1 198.51.100.0/24 !198.51.100.7 }" \
+	    "pass all"
+
+	# -v lists only the matching addresses.
+	atf_check -s exit:2 -e match:"2/4 addresses match." \
+	    -o match:"^M  192\.0\.2\.1$" \
+	    -o match:"^M  198\.51\.100\.5$" \
+	    -o not-match:"198\.51\.100\.7" \
+	    -o not-match:"1\.2\.3\.4" \
+	    jexec alcatraz pfctl -t foo -v -T test \
+	    192.0.2.1 198.51.100.5 198.51.100.7 1.2.3.4
+
+	# -vv lists every address and the table entry it matched.
+	atf_check -s exit:2 -e match:"2/4 addresses match." \
+	    -o match:"^M  192\.0\.2\.1	 192\.0\.2\.1$" \
+	    -o match:"^M  198\.51\.100\.5	 198\.51\.100\.0/24$" \
+	    -o match:"^   198\.51\.100\.7	!198\.51\.100\.7$" \
+	    -o match:"^   1\.2\.3\.4	 nomatch$" \
+	    jexec alcatraz pfctl -t foo -vv -T test \
+	    192.0.2.1 198.51.100.5 198.51.100.7 1.2.3.4
+
+	# libpfctl tests 256 addresses per request, check across requests.
+	for i in `seq 1 255`; do
+		echo "203.0.113.${i}"
+	done > addrs
+	echo "1.2.3.4" >> addrs
+	echo "198.51.100.5" >> addrs
+	atf_check -s exit:2 -e match:"1/257 addresses match." \
+	    -o match:"^   203\.0\.113\.255	 nomatch$" \
+	    -o match:"^   1\.2\.3\.4	 nomatch$" \
+	    -o match:"^M  198\.51\.100\.5	 198\.51\.100\.0/24$" \
+	    jexec alcatraz pfctl -t foo -vv -T test -f $(pwd)/addrs
+}
+
+test_verbose_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "show_no_counters" "cleanup"
 show_no_counters_head()
 {
@@ -1042,5 +1096,6 @@ atf_init_test_cases()
 	atf_add_test_case "replace_create"
 	atf_add_test_case "load"
 	atf_add_test_case "test"
+	atf_add_test_case "test_verbose"
 	atf_add_test_case "show_no_counters"
 }