git: 42441d342071 - main - pf: Fix interface binding for af-to with route-to
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 23 Sep 2025 10:11:58 UTC
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