git: 56b7685ae328 - main - pf: handle IPv6 fragmentation for route-to

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 26 Nov 2024 14:07:34 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=56b7685ae3285aa8c29574f116c6bc2c4fe17cc8

commit 56b7685ae3285aa8c29574f116c6bc2c4fe17cc8
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-11-26 08:45:13 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-11-26 14:06:52 +0000

    pf: handle IPv6 fragmentation for route-to
    
    If a fragmented IPv6 packet hits a route-to rule we have to first prevent
    the pf_test(PF_OUT) check in pf_route6() from refragmenting (and calling
    ip6_output()/ip6_forward()). We then have to refragment in pf_route6() and
    transmit the packets on the route-to interface.
    
    Split pf_refragment6() into two parts, the first to perform the refragmentation,
    the second to call ip6_output()/ip6_forward() and call the former from
    pf_route6().
    
    Add a test case for route-to-ing fragmented IPv6 packets to verify this works
    as expected.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D47684
---
 sys/net/pfvar.h                            |  5 ++-
 sys/netpfil/pf/pf.c                        | 18 ++++++--
 sys/netpfil/pf/pf_norm.c                   | 34 ++++++++++-----
 tests/sys/netpfil/pf/fragmentation_pass.sh | 70 ++++++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 15 deletions(-)

diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 0dc5d4363867..e00101ba2b78 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -70,6 +70,8 @@
 
 #ifdef _KERNEL
 
+#define        PF_PFIL_NOREFRAGMENT    0x80000000
+
 #if defined(__arm__)
 #define PF_WANT_32_TO_64_COUNTER
 #endif
@@ -2372,7 +2374,8 @@ void	pf_poolmask(struct pf_addr *, struct pf_addr*,
 	    struct pf_addr *, struct pf_addr *, sa_family_t);
 void	pf_addr_inc(struct pf_addr *, sa_family_t);
 int	pf_max_frag_size(struct mbuf *);
-int	pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *, bool);
+int	pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *,
+	    struct ifnet *, bool);
 #endif /* INET6 */
 
 int	pf_multihome_scan_init(int, int, struct pf_pdesc *);
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 5a0cad132340..fdec1cecb350 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -7927,6 +7927,7 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
     struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp)
 {
 	struct mbuf		*m0, *md;
+	struct m_tag		*mtag;
 	struct sockaddr_in6	dst;
 	struct ip6_hdr		*ip6;
 	struct pfi_kkif		*nkif = NULL;
@@ -8053,8 +8054,8 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 	}
 
 	if (pd->dir == PF_IN) {
-		if (pf_test(AF_INET6, PF_OUT, PFIL_FWD, ifp, &m0, inp,
-		    &pd->act) != PF_PASS) {
+		if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
+		    ifp, &m0, inp, &pd->act) != PF_PASS) {
 			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 			goto bad;
 		} else if (m0 == NULL) {
@@ -8087,6 +8088,14 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 	 */
 	if (IN6_IS_SCOPE_EMBED(&dst.sin6_addr))
 		dst.sin6_addr.s6_addr16[1] = htons(ifp->if_index);
+	mtag = m_tag_find(m0, PACKET_TAG_PF_REASSEMBLED, NULL);
+	if (mtag != NULL) {
+		int ret;
+		ret = pf_refragment6(ifp, &m0, mtag, ifp, true);
+		SDT_PROBE2(pf, ip6, route_to, output, ifp, ret);
+		goto done;
+	}
+
 	if ((u_long)m0->m_pkthdr.len <= ifp->if_mtu) {
 		md = m0;
 		pf_dummynet_route(pd, s, r, ifp, sintosa(&dst), &md);
@@ -9474,14 +9483,15 @@ eat_pkt:
 	if (s)
 		PF_STATE_UNLOCK(s);
 
+out:
 #ifdef INET6
 	/* If reassembled packet passed, create new fragments. */
 	if (af == AF_INET6 && action == PF_PASS && *m0 && dir == PF_OUT &&
+	    (! (pflags & PF_PFIL_NOREFRAGMENT)) &&
 	    (mtag = m_tag_find(pd.m, PACKET_TAG_PF_REASSEMBLED, NULL)) != NULL)
-		action = pf_refragment6(ifp, m0, mtag, pflags & PFIL_FWD);
+		action = pf_refragment6(ifp, m0, mtag, NULL, pflags & PFIL_FWD);
 #endif
 
-out:
 	pf_sctp_multihome_delayed(&pd, kif, s, action);
 
 	return (action);
diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c
index 8779d599900e..6e19c23d0b73 100644
--- a/sys/netpfil/pf/pf_norm.c
+++ b/sys/netpfil/pf/pf_norm.c
@@ -42,6 +42,7 @@
 #include <sys/socket.h>
 
 #include <net/if.h>
+#include <net/if_var.h>
 #include <net/vnet.h>
 #include <net/pfvar.h>
 #include <net/if_pflog.h>
@@ -49,6 +50,8 @@
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/ip_var.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
 #include <netinet6/ip6_var.h>
 #include <netinet6/scope6_var.h>
 #include <netinet/tcp.h>
@@ -958,7 +961,7 @@ pf_max_frag_size(struct mbuf *m)
 
 int
 pf_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag,
-    bool forward)
+    struct ifnet *rt, bool forward)
 {
 	struct mbuf		*m = *m0, *t;
 	struct ip6_hdr		*hdr;
@@ -1029,16 +1032,27 @@ pf_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag,
 		m->m_flags |= M_SKIP_FIREWALL;
 		memset(&pd, 0, sizeof(pd));
 		pd.pf_mtag = pf_find_mtag(m);
-		if (error == 0)
-			if (forward) {
-				MPASS(m->m_pkthdr.rcvif != NULL);
-				ip6_forward(m, 0);
-			} else {
-				(void)ip6_output(m, NULL, NULL, 0, NULL, NULL,
-				    NULL);
-			}
-		else
+		if (error != 0) {
 			m_freem(m);
+			continue;
+		}
+		if (rt != NULL) {
+			struct sockaddr_in6	dst;
+			hdr = mtod(m, struct ip6_hdr *);
+
+			bzero(&dst, sizeof(dst));
+			dst.sin6_family = AF_INET6;
+			dst.sin6_len = sizeof(dst);
+			dst.sin6_addr = hdr->ip6_dst;
+
+			nd6_output_ifp(rt, rt, m, &dst, NULL);
+		} else if (forward) {
+			MPASS(m->m_pkthdr.rcvif != NULL);
+			ip6_forward(m, 0);
+		} else {
+			(void)ip6_output(m, NULL, NULL, 0, NULL, NULL,
+			    NULL);
+		}
 	}
 
 	return (action);
diff --git a/tests/sys/netpfil/pf/fragmentation_pass.sh b/tests/sys/netpfil/pf/fragmentation_pass.sh
index 2399eb0374bb..68b078d42ddc 100644
--- a/tests/sys/netpfil/pf/fragmentation_pass.sh
+++ b/tests/sys/netpfil/pf/fragmentation_pass.sh
@@ -155,6 +155,75 @@ v6_cleanup()
 	pft_cleanup
 }
 
+atf_test_case "v6_route_to" "cleanup"
+v6_route_to_head()
+{
+	atf_set descr 'Test IPv6 reassembly combined with route-to'
+	atf_set require.user root
+}
+
+v6_route_to_body()
+{
+	pft_init
+}
+
+v6_route_to_cleanup()
+{
+	pft_cleanup
+
+	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 ifconfig ${epair_send}b inet6 -ifdisabled
+	jexec alcatraz ifconfig ${epair_link}a inet6 -ifdisabled
+	jexec singsing ifconfig ${epair_link}b inet6 -ifdisabled
+	ifconfig ${epair_send}a inet6 -ifdisabled
+
+	jexec alcatraz pfctl -e
+	pft_set_rules alcatraz \
+		"set reassemble yes" \
+		"pass" \
+		"pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state"
+
+	# Forwarding test
+	atf_check -s exit:0 -o ignore \
+		ping -6 -c 1 2001:db8:43::3
+
+	atf_check -s exit:0 -o ignore \
+		ping -6 -c 1 -s 4500 2001:db8:43::3
+
+	atf_check -s exit:0 -o ignore\
+		ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3
+
+	# Now test this without fragmentation
+	pft_set_rules alcatraz \
+		"set reassemble no" \
+		"pass" \
+		"pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state"
+
+	atf_check -s exit:0 -o ignore \
+		ping -6 -c 1 2001:db8:43::3
+
+	atf_check -s exit:0 -o ignore \
+		ping -6 -c 1 -s 4500 2001:db8:43::3
+
+	atf_check -s exit:0 -o ignore\
+		ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3
+}
+
 atf_test_case "mtu_diff" "cleanup"
 mtu_diff_head()
 {
@@ -544,6 +613,7 @@ atf_init_test_cases()
 {
 	atf_add_test_case "too_many_fragments"
 	atf_add_test_case "v6"
+	atf_add_test_case "v6_route_to"
 	atf_add_test_case "mtu_diff"
 	atf_add_test_case "overreplace"
 	atf_add_test_case "overindex"