diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8 index ac0106fb560..3a75c9a62ee 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 b9da5e96a90..e29af0eb5ee 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 1e93a8972d9..7d8c146c1dd 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 fe934a8d2ea..95a0d0c9171 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 32943e659bd..828d76a998b 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 counters { 192.0.2.1, 192.0.2.3 }" \ + "block all" \ + "pass in from to any" \ + "pass out from any to " \ + "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"