git: ac92792bbdb2 - stable/14 - pfctl: clear statistic for specified addresses

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Sat, 14 Dec 2024 14:41:37 UTC
The branch stable/14 has been updated by kp:

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

commit ac92792bbdb297d7da90d510387e8528b87899c7
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-11-21 20:33:41 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-12-14 10:03:27 +0000

    pfctl: clear statistic for specified addresses
    
    The ioctl DIOCRCLRASTATS provides the functionality of clearing stats
    not only for the whole table for for addresses stored in that table. The
    functionality was missing from pfctl, though. Add it now.
    
    PR:             282877
    Obtained from:  OpenBSD, kirill <kirill@openbsd.org>, e496dff3a7
    MFC after:      3 weeks
    
    (cherry picked from commit 6463b6b59152fb1695bbe0de78f6e2675c5a765a)
---
 sbin/pfctl/pfctl.8            |  6 ++---
 sbin/pfctl/pfctl.h            |  1 +
 sbin/pfctl/pfctl_radix.c      | 23 +++++++++++++++++
 sbin/pfctl/pfctl_table.c      | 17 +++++++++++--
 tests/sys/netpfil/pf/table.sh | 57 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 99 insertions(+), 5 deletions(-)

diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8
index ac0106fb560b..3a75c9a62eec 100644
--- a/sbin/pfctl/pfctl.8
+++ b/sbin/pfctl/pfctl.8
@@ -24,7 +24,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd July 23, 2024
+.Dd November 20, 2024
 .Dt PFCTL 8
 .Os
 .Sh NAME
@@ -501,8 +501,8 @@ Automatically create a nonexisting table.
 Show the content (addresses) of a table.
 .It Fl T Cm test
 Test if the given addresses match a table.
-.It Fl T Cm zero
-Clear all the statistics of a table.
+.It Fl T Cm zero Op Ar address ...
+Clear all the statistics of a table, or only for specified addresses.
 .It Fl T Cm load
 Load only the table definitions from
 .Xr pf.conf 5 .
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
index b9da5e96a90e..e29af0eb5eee 100644
--- a/sbin/pfctl/pfctl.h
+++ b/sbin/pfctl/pfctl.h
@@ -60,6 +60,7 @@ int	 pfr_del_tables(struct pfr_table *, int, int *, int);
 int	 pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int);
 int	 pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int);
 int	 pfr_clr_tstats(struct pfr_table *, int, int *, int);
+int	 pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int);
 int	 pfr_clr_addrs(struct pfr_table *, int *, int);
 int	 pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
 int	 pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c
index 1e93a8972d9e..7d8c146c1dda 100644
--- a/sbin/pfctl/pfctl_radix.c
+++ b/sbin/pfctl/pfctl_radix.c
@@ -287,6 +287,29 @@ pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size,
 	return (0);
 }
 
+int
+pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+    int *nzero, int flags)
+{
+	struct pfioc_table io;
+
+	if (size < 0 || (size && !tbl) || addr == NULL) {
+		errno = EINVAL;
+		return (-1);
+	}
+	bzero(&io, sizeof io);
+	io.pfrio_flags = flags;
+	io.pfrio_table = *tbl;
+	io.pfrio_buffer = addr;
+	io.pfrio_esize = sizeof(*addr);
+	io.pfrio_size = size;
+	if (ioctl(dev, DIOCRCLRASTATS, &io) == -1)
+		return (-1);
+	if (nzero)
+		*nzero = io.pfrio_nzero;
+	return (0);
+}
+
 int
 pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags)
 {
diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c
index fe934a8d2ea2..95a0d0c9171c 100644
--- a/sbin/pfctl/pfctl_table.c
+++ b/sbin/pfctl/pfctl_table.c
@@ -345,9 +345,22 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command,
 		}
 		if (nmatch < b.pfrb_size)
 			rv = 2;
+	} else if (!strcmp(command, "zero") && (argc || file != NULL)) {
+		b.pfrb_type = PFRB_ADDRS;
+		if (load_addr(&b, argc, argv, file, 0))
+			goto _error;
+		if (opts & PF_OPT_VERBOSE)
+			flags |= PFR_FLAG_FEEDBACK;
+		RVTEST(pfr_clr_astats(&table, b.pfrb_caddr, b.pfrb_size,
+		    &nzero, flags));
+		xprintf(opts, "%d/%d addresses cleared", nzero, b.pfrb_size);
+		if (opts & PF_OPT_VERBOSE)
+			PFRB_FOREACH(a, &b)
+				if (opts & PF_OPT_VERBOSE2 ||
+				    a->pfra_fback != PFR_FB_NONE)
+					print_addrx(a, NULL,
+					    opts & PF_OPT_USEDNS);
 	} else if (!strcmp(command, "zero")) {
-		if (argc || file != NULL)
-			usage();
 		flags |= PFR_FLAG_ADDRSTOO;
 		RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags));
 		xprintf(opts, "%d table/stats cleared", nzero);
diff --git a/tests/sys/netpfil/pf/table.sh b/tests/sys/netpfil/pf/table.sh
index 32943e659bd0..828d76a998be 100644
--- a/tests/sys/netpfil/pf/table.sh
+++ b/tests/sys/netpfil/pf/table.sh
@@ -109,6 +109,62 @@ v6_counters_cleanup()
 	pft_cleanup
 }
 
+atf_test_case "zero_one" "cleanup"
+zero_one_head()
+{
+	atf_set descr 'Test zeroing a single address in a table'
+	atf_set require.user root
+}
+
+zero_one_body()
+{
+	epair_send=$(vnet_mkepair)
+	ifconfig ${epair_send}a 192.0.2.1/24 up
+	ifconfig ${epair_send}a inet alias 192.0.2.3/24
+
+	vnet_mkjail alcatraz ${epair_send}b
+	jexec alcatraz ifconfig ${epair_send}b 192.0.2.2/24 up
+	jexec alcatraz pfctl -e
+
+	pft_set_rules alcatraz \
+	    "table <foo> counters { 192.0.2.1, 192.0.2.3 }" \
+	    "block all" \
+	    "pass in from <foo> to any" \
+	    "pass out from any to <foo>" \
+	    "set skip on lo"
+
+	atf_check -s exit:0 -o ignore ping -c 3 -S 192.0.2.1 192.0.2.2
+	atf_check -s exit:0 -o ignore ping -c 3 -S 192.0.2.3 192.0.2.2
+
+	jexec alcatraz pfctl -t foo -T show -vv
+
+	atf_check -s exit:0 -e ignore \
+	    -o match:'In/Block:.*'"$TABLE_STATS_ZERO_REGEXP" \
+	    -o match:'In/Pass:.*'"$TABLE_STATS_NONZERO_REGEXP" \
+	    -o match:'Out/Block:.*'"$TABLE_STATS_ZERO_REGEXP" \
+	    -o match:'Out/Pass:.*'"$TABLE_STATS_NONZERO_REGEXP" \
+	    jexec alcatraz pfctl -t foo -T show -vv
+
+	atf_check -s exit:0 -e ignore \
+	    jexec alcatraz pfctl -t foo -T zero 192.0.2.3
+
+	# We now have a zeroed and a non-zeroed counter, so both patterns
+	# should match
+	atf_check -s exit:0 -e ignore \
+	    -o match:'In/Pass:.*'"$TABLE_STATS_NONZERO_REGEXP" \
+	    -o match:'Out/Pass:.*'"$TABLE_STATS_NONZERO_REGEXP" \
+	    jexec alcatraz pfctl -t foo -T show -vv
+	atf_check -s exit:0 -e ignore \
+	    -o match:'In/Pass:.*'"$TABLE_STATS_ZERO_REGEXP" \
+	    -o match:'Out/Pass:.*'"$TABLE_STATS_ZERO_REGEXP" \
+	    jexec alcatraz pfctl -t foo -T show -vv
+}
+
+zero_one_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "pr251414" "cleanup"
 pr251414_head()
 {
@@ -324,6 +380,7 @@ atf_init_test_cases()
 {
 	atf_add_test_case "v4_counters"
 	atf_add_test_case "v6_counters"
+	atf_add_test_case "zero_one"
 	atf_add_test_case "pr251414"
 	atf_add_test_case "automatic"
 	atf_add_test_case "network"