The branch main has been updated by ks:

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

commit 42441d342071767f32cfe507466135dc79052a56
Author:     Kajetan Staszkiewicz <k...@freebsd.org>
AuthorDate: 2025-09-07 10:48:15 +0000
Commit:     Kajetan Staszkiewicz <k...@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

Reply via email to