git: f05af59fba69 - main - pf: return per-address feedback from netlink table test
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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"
}