git: 8c73c0f1230d - main - netinet6: Set PFIL_FWD in ip6_tryforward()

From: R. Christian McDonald <rcm_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 17:34:16 UTC
The branch main has been updated by rcm:

URL: https://cgit.FreeBSD.org/src/commit/?id=8c73c0f1230dde0a65474b1ae9a94f27f6568bda

commit 8c73c0f1230dde0a65474b1ae9a94f27f6568bda
Author:     R. Christian McDonald <rcm@FreeBSD.org>
AuthorDate: 2026-10-30 17:31:37 +0000
Commit:     R. Christian McDonald <rcm@FreeBSD.org>
CommitDate: 2026-10-30 17:31:37 +0000

    netinet6: Set PFIL_FWD in ip6_tryforward()
    
    ip6_forward() has run the outbound hooks with pfil_mbuf_fwd() since
    b52b61c0b6b1, and ip_tryforward() since 1f4c3887e3fa, but
    ip6_tryforward() still calls pfil_mbuf_out().  pf therefore handles a
    packet on the IPv6 fast path as locally generated: it skips the
    packet-too-big error it sends with the untranslated addresses, and
    pf_refragment6() sends reassembled packets out through ip6_output()
    instead of ip6_forward(), so their fragments leave with the hop limit
    not decremented.
    
    Use pfil_mbuf_fwd() in ip6_tryforward() as well.  The new
    fragmentation_pass:v6_refrag_hlim test sends a packet that pf
    reassembles and refragments through the fast path, and checks that its
    fragments leave with the hop limit decremented once; before this change
    they leave with it unchanged.
    
    Reviewed by:            kp
    Fixes:                  b52b61c0 ("pf: distinguish forwarding and output cases for ...")
    MFC after:              1 week
    Sponsored by:           Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D61033
---
 sys/netinet6/ip6_fastfwd.c                 |  2 +-
 tests/sys/netpfil/pf/fragmentation_pass.sh | 55 ++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/sys/netinet6/ip6_fastfwd.c b/sys/netinet6/ip6_fastfwd.c
index 8a288eb19891..f6d6df4c7b96 100644
--- a/sys/netinet6/ip6_fastfwd.c
+++ b/sys/netinet6/ip6_fastfwd.c
@@ -218,7 +218,7 @@ passin:
 	/*
 	 * Outgoing packet firewall processing.
 	 */
-	if (pfil_mbuf_out(V_inet6_pfil_head, &m, nh->nh_ifp,
+	if (pfil_mbuf_fwd(V_inet6_pfil_head, &m, nh->nh_ifp,
 	    NULL) != PFIL_PASS)
 		goto dropout;
 
diff --git a/tests/sys/netpfil/pf/fragmentation_pass.sh b/tests/sys/netpfil/pf/fragmentation_pass.sh
index c749aac793ee..d5310d6eeb58 100644
--- a/tests/sys/netpfil/pf/fragmentation_pass.sh
+++ b/tests/sys/netpfil/pf/fragmentation_pass.sh
@@ -155,6 +155,60 @@ v6_cleanup()
 	pft_cleanup
 }
 
+# refrag_hlim REDIRECT: with net.inet6.ip6.redirect at REDIRECT on the router
+# (0: ip6_tryforward(), 1: ip6_forward()), a forwarded packet that pf
+# reassembles and refragments loses one hop: the echo reply comes back
+# through alcatraz in fragments, with the hop limit singsing sent minus one.
+refrag_hlim()
+{
+	jexec alcatraz sysctl net.inet6.ip6.redirect=$1
+	atf_check -o match:"icmp_seq=0 hlim=63 " \
+	    ping -6 -c 1 -m 64 -s 4500 2001:db8:43::3
+}
+
+atf_test_case "v6_refrag_hlim" "cleanup"
+v6_refrag_hlim_head()
+{
+	atf_set descr 'Refragmented forwarded IPv6 loses one hop'
+	atf_set require.user root
+}
+
+v6_refrag_hlim_body()
+{
+	pft_init
+
+	epair_send=$(vnet_mkepair)
+	epair_link=$(vnet_mkepair)
+
+	vnet_mkjail alcatraz ${epair_send}b ${epair_link}a
+	vnet_mkjail singsing ${epair_link}b
+
+	ifconfig ${epair_send}a inet6 2001:db8:42::1/64 no_dad up
+
+	jexec alcatraz ifconfig ${epair_send}b inet6 2001:db8:42::2/64 no_dad up
+	jexec alcatraz ifconfig ${epair_link}a inet6 2001:db8:43::2/64 no_dad up
+	jexec alcatraz sysctl net.inet6.ip6.forwarding=1
+
+	jexec singsing ifconfig ${epair_link}b inet6 2001:db8:43::3/64 no_dad up
+	jexec singsing route add -6 2001:db8:42::/64 2001:db8:43::2
+	route add -6 2001:db8:43::/64 2001:db8:42::2
+
+	jexec alcatraz pfctl -e
+	pft_set_rules alcatraz \
+		"set reassemble yes" \
+		"pass"
+
+	atf_check -o ignore ping -6 -c 1 2001:db8:43::3
+
+	# The fast path: pf must refragment through ip6_forward().
+	refrag_hlim 0
+}
+
+v6_refrag_hlim_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "v6_route_to" "cleanup"
 v6_route_to_head()
 {
@@ -718,6 +772,7 @@ atf_init_test_cases()
 {
 	atf_add_test_case "too_many_fragments"
 	atf_add_test_case "v6"
+	atf_add_test_case "v6_refrag_hlim"
 	atf_add_test_case "v6_route_to"
 	atf_add_test_case "mtu_diff"
 	atf_add_test_case "overreplace"