Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 28 Sep 2025 17:25:45 GMT
From:      Kajetan Staszkiewicz <ks@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 7cd3854f827f - main - pf: Fix interface counters for af-to rules
Message-ID:  <202509281725.58SHPj2B027429@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by ks:

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

commit 7cd3854f827faaad1ecf414d20bdf6802cfa60f8
Author:     Kajetan Staszkiewicz <ks@FreeBSD.org>
AuthorDate: 2025-09-08 17:53:48 +0000
Commit:     Kajetan Staszkiewicz <ks@FreeBSD.org>
CommitDate: 2025-09-28 17:23:02 +0000

    pf: Fix interface counters for af-to rules
    
    An inbound af-to rule creates a state bypassing outbound pf_test().
    In such case increase counters of the outbound interface directly in
    pf_route() for post-af-to address family.
    
    For outbound af-to rules ensure that post-af-to address family is used
    to increase interface counters.
    
    Reviewed by:    kp
    Sponsored by:   InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D52448
---
 sys/netpfil/pf/pf.c              | 151 +++++++++++++++++++++++++--------------
 tests/sys/netpfil/pf/counters.sh |  18 ++++-
 2 files changed, 115 insertions(+), 54 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 9f250476cb75..2c6d62078e6a 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -9231,25 +9231,38 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 	if (r->rt == PF_DUPTO || (pd->af != pd->naf && s->direction == PF_IN))
 		skip_test = true;
 
-	if (pd->dir == PF_IN && !skip_test) {
-		if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp,
-		    &pd->act) != PF_PASS) {
-			action = PF_DROP;
-			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
-			goto bad;
-		} else if (m0 == NULL) {
-			action = PF_DROP;
-			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
-			goto done;
-		}
-		if (m0->m_len < sizeof(struct ip)) {
-			DPFPRINTF(PF_DEBUG_URGENT,
-			    "%s: m0->m_len < sizeof(struct ip)", __func__);
-			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
-			action = PF_DROP;
-			goto bad;
+	if (pd->dir == PF_IN) {
+		if (skip_test) {
+			struct pfi_kkif *out_kif = (struct pfi_kkif *)ifp->if_pf_kif;
+			MPASS(s != NULL);
+			pf_counter_u64_critical_enter();
+			pf_counter_u64_add_protected(
+			    &out_kif->pfik_bytes[pd->naf == AF_INET6][1]
+			    [action != PF_PASS && action != PF_AFRT], pd->tot_len);
+			pf_counter_u64_add_protected(
+			    &out_kif->pfik_packets[pd->naf == AF_INET6][1]
+			    [action != PF_PASS && action != PF_AFRT], 1);
+			pf_counter_u64_critical_exit();
+		} else {
+			if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp,
+			    &pd->act) != PF_PASS) {
+				action = PF_DROP;
+				SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+				goto bad;
+			} else if (m0 == NULL) {
+				action = PF_DROP;
+				SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+				goto done;
+			}
+			if (m0->m_len < sizeof(struct ip)) {
+				DPFPRINTF(PF_DEBUG_URGENT,
+				    "%s: m0->m_len < sizeof(struct ip)", __func__);
+				SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+				action = PF_DROP;
+				goto bad;
+			}
+			ip = mtod(m0, struct ip *);
 		}
-		ip = mtod(m0, struct ip *);
 	}
 
 	if (ifp->if_flags & IFF_LOOPBACK)
@@ -9548,26 +9561,39 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 	if (r->rt == PF_DUPTO || (pd->af != pd->naf && s->direction == PF_IN))
 		skip_test = true;
 
-	if (pd->dir == PF_IN && !skip_test) {
-		if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
-		    ifp, &m0, inp, &pd->act) != PF_PASS) {
-			action = PF_DROP;
-			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
-			goto bad;
-		} else if (m0 == NULL) {
-			action = PF_DROP;
-			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
-			goto done;
-		}
-		if (m0->m_len < sizeof(struct ip6_hdr)) {
-			DPFPRINTF(PF_DEBUG_URGENT,
-			    "%s: m0->m_len < sizeof(struct ip6_hdr)",
-			    __func__);
-			action = PF_DROP;
-			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
-			goto bad;
+	if (pd->dir == PF_IN) {
+		if (skip_test) {
+			struct pfi_kkif *out_kif = (struct pfi_kkif *)ifp->if_pf_kif;
+			MPASS(s != NULL);
+			pf_counter_u64_critical_enter();
+			pf_counter_u64_add_protected(
+			    &out_kif->pfik_bytes[pd->naf == AF_INET6][1]
+			    [action != PF_PASS && action != PF_AFRT], pd->tot_len);
+			pf_counter_u64_add_protected(
+			    &out_kif->pfik_packets[pd->naf == AF_INET6][1]
+			    [action != PF_PASS && action != PF_AFRT], 1);
+			pf_counter_u64_critical_exit();
+		} else {
+			if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
+			    ifp, &m0, inp, &pd->act) != PF_PASS) {
+				action = PF_DROP;
+				SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
+				goto bad;
+			} else if (m0 == NULL) {
+				action = PF_DROP;
+				SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
+				goto done;
+			}
+			if (m0->m_len < sizeof(struct ip6_hdr)) {
+				DPFPRINTF(PF_DEBUG_URGENT,
+				    "%s: m0->m_len < sizeof(struct ip6_hdr)",
+				    __func__);
+				action = PF_DROP;
+				SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
+				goto bad;
+			}
+			ip6 = mtod(m0, struct ip6_hdr *);
 		}
-		ip6 = mtod(m0, struct ip6_hdr *);
 	}
 
 	if (ifp->if_flags & IFF_LOOPBACK)
@@ -10720,21 +10746,32 @@ pf_counters_inc(int action, struct pf_pdesc *pd, struct pf_kstate *s,
 	struct pf_addr		*dst_host = pd->dst;
 	struct pf_state_key	*key;
 	int			 dir_out = (pd->dir == PF_OUT);
-	int			 op_pass = (r->action == PF_PASS);
-	sa_family_t		 af = pd->af;
+	int			 op_r_pass = (r->action == PF_PASS);
+	int			 op_pass = (action == PF_PASS || action == PF_AFRT);
 	int			 s_dir_in, s_dir_out, s_dir_rev;
+	sa_family_t		 af = pd->af;
 
 	pf_counter_u64_critical_enter();
 
+	/*
+	 * Set AF for interface counters, it will be later overwritten for
+	 * rule and state counters with value from proper state key.
+	 */
+	if (action == PF_AFRT) {
+		MPASS(s != NULL);
+		if (s->direction == PF_OUT && dir_out)
+			af = pd->naf;
+	}
+
 	pf_counter_u64_add_protected(
-	    &pd->kif->pfik_bytes[pd->af == AF_INET6][dir_out][action != PF_PASS],
+	    &pd->kif->pfik_bytes[af == AF_INET6][dir_out][!op_pass],
 	    pd->tot_len);
 	pf_counter_u64_add_protected(
-	    &pd->kif->pfik_packets[pd->af == AF_INET6][dir_out][action != PF_PASS],
+	    &pd->kif->pfik_packets[af == AF_INET6][dir_out][!op_pass],
 	    1);
 
 	/* If the rule has failed to apply, don't increase its counters */
-	if (!(action == PF_PASS || action == PF_AFRT || r->action == PF_DROP)) {
+	if (!(op_pass || r->action == PF_DROP)) {
 		pf_counter_u64_critical_exit();
 		return;
 	}
@@ -10745,22 +10782,32 @@ pf_counters_inc(int action, struct pf_pdesc *pd, struct pf_kstate *s,
 
 		/*
 		 * For af-to on the inbound direction we can determine
-		 * the direction only by checking direction of AF translation,
-		 * since the state is always "in" and so is packet's direction.
+		 * the direction of passing packet only by checking direction
+		 * of AF translation. The af-to in "in" direction covers both
+		 * the inbound and the outbound side of state tracking,
+		 * so pd->dir is always PF_IN. We set dir_out and s_dir_rev
+		 * in a way to count packets as if the state was outbound,
+		 * because pfctl -ss shows the state with "->", as if it was
+		 * oubound.
 		 */
-		if (pd->af != pd->naf && s->direction == PF_IN) {
+		if (action == PF_AFRT && s->direction == PF_IN) {
 			dir_out = (pd->naf == s->rule->naf);
 			s_dir_in = 1;
 			s_dir_out = 0;
-			s_dir_rev = (pd->naf != s->rule->naf);
-		}
-		else {
+			s_dir_rev = (pd->naf == s->rule->af);
+		} else {
 			dir_out = (pd->dir == PF_OUT);
 			s_dir_in = (s->direction == PF_IN);
 			s_dir_out = (s->direction == PF_OUT);
 			s_dir_rev = (pd->dir != s->direction);
 		}
 
+		/* pd->tot_len is a problematic with af-to rules. Sure, we can
+		 * agree that it's the post-af-to packet length that was
+		 * forwarded through a state, but what about tables which match
+		 * on pre-af-to addresses? We don't have access the the original
+		 * packet length anymore.
+		 */
 		s->packets[s_dir_rev]++;
 		s->bytes[s_dir_rev] += pd->tot_len;
 
@@ -10792,7 +10839,7 @@ pf_counters_inc(int action, struct pf_pdesc *pd, struct pf_kstate *s,
 			    s->nat_rule->action == PF_BINAT) {
 				nr = s->nat_rule;
 				pf_rule_counters_inc(pd, s->nat_rule, dir_out,
-				    op_pass, af, src_host, dst_host);
+				    op_r_pass, af, src_host, dst_host);
 				/* Use post-NAT addresses from now on */
 				key = s->key[s_dir_in];
 				src_host = &(key->addr[s_dir_out]);
@@ -10803,7 +10850,7 @@ pf_counters_inc(int action, struct pf_pdesc *pd, struct pf_kstate *s,
 	}
 
 	SLIST_FOREACH(ri, mr, entry) {
-		pf_rule_counters_inc(pd, ri->r, dir_out, op_pass, af,
+		pf_rule_counters_inc(pd, ri->r, dir_out, op_r_pass, af,
 		    src_host, dst_host);
 		if (s && s->nat_rule == ri->r) {
 			/* Use post-NAT addresses after a match NAT rule */
@@ -10819,12 +10866,12 @@ pf_counters_inc(int action, struct pf_pdesc *pd, struct pf_kstate *s,
 	}
 
 	if (a != NULL) {
-		pf_rule_counters_inc(pd, a, dir_out, op_pass, af,
+		pf_rule_counters_inc(pd, a, dir_out, op_r_pass, af,
 		    src_host, dst_host);
 	}
 
 	if (r != nr) {
-		pf_rule_counters_inc(pd, r, dir_out, op_pass, af,
+		pf_rule_counters_inc(pd, r, dir_out, op_r_pass, af,
 		    src_host, dst_host);
 	}
 
diff --git a/tests/sys/netpfil/pf/counters.sh b/tests/sys/netpfil/pf/counters.sh
index a0119b4710c1..20d7dc3c6d89 100644
--- a/tests/sys/netpfil/pf/counters.sh
+++ b/tests/sys/netpfil/pf/counters.sh
@@ -684,7 +684,7 @@ nat64_in_body()
 		"table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \
 		"table <tbl_src_pass>  { ${net_tester_6_host_tester} }" \
 		"table <tbl_dst_pass>  { 64:ff9b::${net_server1_4_host_server} }" \
-		"block" \
+		"block log" \
 		"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
 		"match  in on ${epair_tester}b inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \
 		"pass   in on ${epair_tester}b inet6 proto tcp from <tbl_src_pass>  to <tbl_dst_pass> \
@@ -726,6 +726,13 @@ nat64_in_body()
 	; do
 		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
 	done
+
+	echo " === interfaces === "
+	echo " === tester === "
+	jexec router pfctl -qvvsI -i ${epair_tester}b
+	echo " === server === "
+	jexec router pfctl -qvvsI -i ${epair_server1}a
+	echo " === "
 }
 
 nat64_in_cleanup()
@@ -753,7 +760,7 @@ nat64_out_body()
 		"table <tbl_dst_match> { 64:ff9b::${net_server1_4_host_server} }" \
 		"table <tbl_src_pass>  { ${net_tester_6_host_tester} }" \
 		"table <tbl_dst_pass>  { 64:ff9b::${net_server1_4_host_server} }" \
-		"block" \
+		"block log " \
 		"pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
 		"pass  in  on ${epair_tester}b inet6 proto tcp keep state" \
 		"match out on ${epair_server1}a inet6 proto tcp from <tbl_src_match> to <tbl_dst_match> scrub (random-id)" \
@@ -794,6 +801,13 @@ nat64_out_body()
 	; do
 		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
 	done
+
+	echo " === interfaces === "
+	echo " === tester === "
+	jexec router pfctl -qvvsI -i ${epair_tester}b
+	echo " === server === "
+	jexec router pfctl -qvvsI -i ${epair_server1}a
+	echo " === "
 }
 
 nat64_out_cleanup()



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202509281725.58SHPj2B027429>