git: 55a69b9be886 - main - libpfctl: zero the counters before summing per-chunk results

From: R. Christian McDonald <rcm_at_FreeBSD.org>
Date: Wed, 30 Sep 2026 23:31:21 UTC
The branch main has been updated by rcm:

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

commit 55a69b9be886731087894ab0b3851215ff791ef9
Author:     R. Christian McDonald <rcm@FreeBSD.org>
AuthorDate: 2026-09-30 23:23:21 +0000
Commit:     R. Christian McDonald <rcm@FreeBSD.org>
CommitDate: 2026-09-30 23:23:21 +0000

    libpfctl: zero the counters before summing per-chunk results
    
    The chunked table address functions (set, add, del, clr_astats) add
    each chunk's result to the caller's counter without initialising it.
    pfctl reuses nadd for the number of tables created, so a replace that
    also creates the table is off by one:
    
    pfctl -t foo -T replace 192.0.2.1
    
    reports "2 addresses added".
    
    Zero the counters first, as pfctl_test_addrs() already does. Remove
    the workaround for the add case from pfctl (da64f6e047b5), which is
    no longer needed.
    
    Add a regression test.
    
    Reviewed by:            kp
    Approved by:            kp (mentor)
    Fixes:                  08ed87a4a276 ("pf: convert DIOCRSETADDRS to netlink")
    MFC after:              1 week
    Sponsored by:           Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D60174
---
 lib/libpfctl/libpfctl.c       | 16 ++++++++++++++++
 sbin/pfctl/pfctl_radix.c      |  3 ---
 tests/sys/netpfil/pf/table.sh | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index a7dbc0a412aa..aa3f4ec30f74 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -2517,6 +2517,9 @@ pfctl_table_add_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pf
 	int partial_added;
 	int chunk_size;
 
+	if (nadd)
+		*nadd = 0;
+
 	do {
 		chunk_size = MIN(size - off, 256);
 		ret = _pfctl_table_add_addrs_h(h, tbl, &addr[off], chunk_size, &partial_added, flags);
@@ -2609,6 +2612,9 @@ pfctl_table_del_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pf
 	int partial_deleted;
 	int chunk_size;
 
+	if (ndel)
+		*ndel = 0;
+
 	do {
 		chunk_size = MIN(size - off, 256);
 		ret = _pfctl_table_del_addrs_h(h, tbl, &addr[off], chunk_size,
@@ -2694,6 +2700,13 @@ pfctl_table_set_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl,
 	int partial_add, partial_del, partial_change;
 	int chunk_size;
 
+	if (nadd)
+		*nadd = 0;
+	if (ndel)
+		*ndel = 0;
+	if (nchange)
+		*nchange = 0;
+
 	do {
 		flags &= ~(PFR_FLAG_START | PFR_FLAG_DONE);
 		if (off == 0)
@@ -3984,6 +3997,9 @@ pfctl_clr_astats(struct pfctl_handle *h, const struct pfr_table *tbl,
 	int partial_zeroed;
 	int chunk_size;
 
+	if (nzero)
+		*nzero = 0;
+
 	do {
 		chunk_size = MIN(size - off, 256);
 		ret = _pfctl_clr_astats(h, tbl, &addrs[off], chunk_size,
diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c
index d99923e4fb67..088c41e0583e 100644
--- a/sbin/pfctl/pfctl_radix.c
+++ b/sbin/pfctl/pfctl_radix.c
@@ -136,9 +136,6 @@ pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
 {
 	int ret;
 
-	if (*nadd)
-		*nadd = 0;
-
 	ret = pfctl_table_add_addrs_h(pfh, tbl, addr, size, nadd, flags);
 	if (ret) {
 		errno = ret;
diff --git a/tests/sys/netpfil/pf/table.sh b/tests/sys/netpfil/pf/table.sh
index 24201588ddbf..9c93df781852 100644
--- a/tests/sys/netpfil/pf/table.sh
+++ b/tests/sys/netpfil/pf/table.sh
@@ -878,6 +878,43 @@ replace_verbose_cleanup()
 	pft_cleanup
 }
 
+atf_test_case "replace_create" "cleanup"
+replace_create_head()
+{
+	atf_set descr 'Test the counts of a replace that creates the table'
+	atf_set require.user root
+}
+
+replace_create_body()
+{
+	pft_init
+	pwd=$(pwd)
+
+	vnet_mkjail alcatraz
+	jexec alcatraz pfctl -e
+
+	# libpfctl used to add the number of addresses to whatever the
+	# caller's counter held, which here is the number of tables created.
+	atf_check -s exit:0 -e "match:^1 table created\.$" \
+	    -e "match:^1 addresses added\.$" \
+	    jexec alcatraz pfctl -t foo -T replace 192.0.2.1
+
+	# More than one chunk of addresses.
+	for i in `seq 1 2`; do
+		for j in `seq 1 150`; do
+			echo "1.${i}.${j}.1" >> ${pwd}/bar.lst
+		done
+	done
+	atf_check -s exit:0 -e "match:^1 table created\.$" \
+	    -e "match:^300 addresses added\.$" \
+	    jexec alcatraz pfctl -t bar -T replace -f ${pwd}/bar.lst
+}
+
+replace_create_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "load" "cleanup"
 load_head()
 {
@@ -1002,6 +1039,7 @@ atf_init_test_cases()
 	atf_add_test_case "in_anchor"
 	atf_add_test_case "replace"
 	atf_add_test_case "replace_verbose"
+	atf_add_test_case "replace_create"
 	atf_add_test_case "load"
 	atf_add_test_case "test"
 	atf_add_test_case "show_no_counters"