Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 23 Sep 2025 10:11:58 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: 42441d342071 - main - pf: Fix interface binding for af-to with route-to
Message-ID:  <202509231011.58NABwLv099521@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=42441d342071767f32cfe507466135dc79052a56

commit 42441d342071767f32cfe507466135dc79052a56
Author:     Kajetan Staszkiewicz <ks@FreeBSD.org>
AuthorDate: 2025-09-07 10:48:15 +0000
Commit:     Kajetan Staszkiewicz <ks@FreeBSD.org>
CommitDate: 2025-09-23 10:06:30 +0000

    pf: Fix interface binding for af-to with route-to
    
    States created by inbound af-to rules bypass outbound filtering and span
    both the inbound and outbound interfaces. When the first packet for such rule
    creates a state, this state has st->orig_kif set the original inbound interface
    and kif set to V_pfi_all. When the outbound interface is eventually
    known st->kif is updated to that interface. When not using route-to,
    the outbound route and its interface are determined for the new address family
    and st->kif is set to that interface. However when using route-to, ifp
    is explicitely given and the code for updating st->kif is not run for
    the first packet. When the returning packet matches the state, the code is
    run but updates st->kif to the original inbound interface, which is now
    the outbound interface. The state ends up with st->kif == st->orig_kif
    and won't forward any more returning packets.
    
    There is another block of code performing such update, but only for reply-to.
    
    Perform the update of st->kif in a single place no matter if ifp was
    explicitely given or found by routing lookup. For checks using pings
    check if really all pings have been replied to, because a single reply
    is enough to have ping exit with a successful exit code.
    
    Reviewed by:    kp
    Sponsored by:   InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D52445
---
 sys/netpfil/pf/pf.c               | 64 +++++++++++++++++----------------------
 tests/sys/netpfil/pf/nat64.sh     |  6 +++-
 tests/sys/netpfil/pf/src_track.sh |  6 ++--
 3 files changed, 36 insertions(+), 40 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 2705df61a1f7..a57905df0c69 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -9172,26 +9172,8 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 				} else {
 					dst->sin.sin_addr = ip->ip_dst;
 				}
-
-				/*
-				 * Bind to the correct interface if we're
-				 * if-bound. We don't know which interface
-				 * that will be until here, so we've inserted
-				 * the state on V_pf_all. Fix that now.
-				 */
-				if (s->kif == V_pfi_all && ifp != NULL &&
-				    r->rule_flag & PFRULE_IFBOUND)
-					s->kif = ifp->if_pf_kif;
 			}
 		}
-
-		if (r->rule_flag & PFRULE_IFBOUND &&
-		    pd->act.rt == PF_REPLYTO &&
-		    s->kif == V_pfi_all) {
-			s->kif = pd->act.rt_kif;
-			s->orig_kif = oifp->if_pf_kif;
-		}
-
 		PF_STATE_UNLOCK(s);
 	}
 
@@ -9206,6 +9188,20 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 		goto bad;
 	}
 
+	/*
+	 * Bind to the correct interface if we're if-bound. We don't know which
+	 * interface that will be until here, so we've inserted the state
+	 * on V_pf_all. Fix that now.
+	 */
+	if (s != NULL && s->kif == V_pfi_all && r->rule_flag & PFRULE_IFBOUND) {
+		/* Verify that we're here because of BOUND_IFACE */
+		MPASS(r->rt == PF_REPLYTO || (pd->af != pd->naf && s->direction == PF_IN));
+		s->kif = ifp->if_pf_kif;
+		if (pd->act.rt == PF_REPLYTO) {
+			s->orig_kif = oifp->if_pf_kif;
+		}
+	}
+
 	if (r->rt == PF_DUPTO)
 		skip_test = true;
 
@@ -9486,26 +9482,8 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 					    sizeof(dst.sin6_addr));
 				else
 					dst.sin6_addr = ip6->ip6_dst;
-
-				/*
-				 * Bind to the correct interface if we're
-				 * if-bound. We don't know which interface
-				 * that will be until here, so we've inserted
-				 * the state on V_pf_all. Fix that now.
-				 */
-				if (s->kif == V_pfi_all && ifp != NULL &&
-				    r->rule_flag & PFRULE_IFBOUND)
-					s->kif = ifp->if_pf_kif;
 			}
 		}
-
-		if (r->rule_flag & PFRULE_IFBOUND &&
-		    pd->act.rt == PF_REPLYTO &&
-		    s->kif == V_pfi_all) {
-			s->kif = pd->act.rt_kif;
-			s->orig_kif = oifp->if_pf_kif;
-		}
-
 		PF_STATE_UNLOCK(s);
 	}
 
@@ -9527,6 +9505,20 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 		goto bad;
 	}
 
+	/*
+	 * Bind to the correct interface if we're if-bound. We don't know which
+	 * interface that will be until here, so we've inserted the state
+	 * on V_pf_all. Fix that now.
+	 */
+	if (s != NULL && s->kif == V_pfi_all && r->rule_flag & PFRULE_IFBOUND) {
+		/* Verify that we're here because of BOUND_IFACE */
+		MPASS(r->rt == PF_REPLYTO || (pd->af != pd->naf && s->direction == PF_IN));
+		s->kif = ifp->if_pf_kif;
+		if (pd->act.rt == PF_REPLYTO) {
+			s->orig_kif = oifp->if_pf_kif;
+		}
+	}
+
 	if (r->rt == PF_DUPTO)
 		skip_test = true;
 
diff --git a/tests/sys/netpfil/pf/nat64.sh b/tests/sys/netpfil/pf/nat64.sh
index 4438ad6abb85..d930e2ee5763 100644
--- a/tests/sys/netpfil/pf/nat64.sh
+++ b/tests/sys/netpfil/pf/nat64.sh
@@ -1039,13 +1039,14 @@ route_to_body()
 	    "pass in on ${epair}b route-to (${epair_link}a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
 
 	atf_check -s exit:0 -o ignore \
+	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
 	    ping6 -c 3 64:ff9b::192.0.2.2
 
 	states=$(mktemp) || exit 1
 	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
 
 	for state_regexp in \
-		"${epair}b ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).*4:2 pkts.*route-to: 192.0.2.2@${epair_link}a" \
+		"${epair_link}a ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).*6:6 pkts.*route-to: 192.0.2.2@${epair_link}a origif: ${epair}b" \
 	; do
 		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
 	done
@@ -1094,6 +1095,7 @@ reply_to_body()
 	    "pass in on ${epair}b reply-to (${epair}b 2001:db8::2) inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.1"
 
 	atf_check -s exit:0 -o ignore \
+	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
 	    ping6 -c 3 64:ff9b::192.0.2.2
 }
 
@@ -1155,8 +1157,10 @@ v6_gateway_body()
 	    "pass in on ${epair_lan}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_wan_one}a)"
 
 	atf_check -s exit:0 -o ignore \
+	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
 	    ping6 -c 3 64:ff9b::192.0.2.2
 	atf_check -s exit:0 -o ignore \
+	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
 	    ping6 -c 3 64:ff9b::198.51.100.1
 }
 
diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh
index e12d0464ee8c..d86b4ce6c466 100755
--- a/tests/sys/netpfil/pf/src_track.sh
+++ b/tests/sys/netpfil/pf/src_track.sh
@@ -565,9 +565,9 @@ mixed_af_body()
 	# FIXME: Sticky-address is broken for af-to pools!
 	#        The SN is created but apparently not used, as seen in states.
 	for state_regexp in \
-		"${epair_tester}b tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \
-		"${epair_tester}b tcp 203.0.113.1:4202 \(2001:db8:44::1\[4202\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \
-		"${epair_tester}b tcp 203.0.113.2:4203 \(2001:db8:44::2\[4203\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a" \
+		"${epair_server2}a tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a origif: ${epair_tester}b" \
+		"${epair_server2}a tcp 203.0.113.1:4202 \(2001:db8:44::1\[4202\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a origif: ${epair_tester}b" \
+		"${epair_server1}a tcp 203.0.113.2:4203 \(2001:db8:44::2\[4203\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a origif: ${epair_tester}b" \
 	; do
 		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
 	done



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