git: 7215aed7974c - stable/14 - kern: wg: remove overly-restrictive address family check

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Sat, 15 Mar 2025 02:52:08 UTC
The branch stable/14 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=7215aed7974cc4b7d3197ca5e5fcf545d3a28c0f

commit 7215aed7974cc4b7d3197ca5e5fcf545d3a28c0f
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2025-03-04 19:57:34 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2025-03-15 02:52:02 +0000

    kern: wg: remove overly-restrictive address family check
    
    IPv4 packets can be routed via an IPv6 nexthop, so the handling of the
    parsed address family is more strict than it needs to be.  If we have a
    valid header that matches a known peer, then we have no reason to
    decline the packet.
    
    Convert it to an assertion that it matches the destination as viewed by
    the stack below it, instead.  `dst` may be the gateway instead of the
    destination in the case of a nexthop, so the `af` assignment must be
    switched to use the destination in all cases.
    
    Add a test case that approximates a setup like in the PR and
    demonstrates the issue.
    
    PR:             284857
    Reviewed by:    markj (earlier version), zlei
    
    (cherry picked from commit 2bef0d54f74dad6962ef7d1dfa407e95cb4fb4ad)
---
 sys/dev/wg/if_wg.c     |  8 ++---
 tests/sys/net/if_wg.sh | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 5 deletions(-)

diff --git a/sys/dev/wg/if_wg.c b/sys/dev/wg/if_wg.c
index b0ad94e284f1..ed23baf33a17 100644
--- a/sys/dev/wg/if_wg.c
+++ b/sys/dev/wg/if_wg.c
@@ -2333,7 +2333,7 @@ wg_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro
 	if (dst->sa_family == AF_UNSPEC || dst->sa_family == pseudo_AF_HDRCMPLT)
 		memcpy(&af, dst->sa_data, sizeof(af));
 	else
-		af = dst->sa_family;
+		af = RO_GET_FAMILY(ro, dst);
 	if (af == AF_UNSPEC) {
 		xmit_err(ifp, m, NULL, af);
 		return (EAFNOSUPPORT);
@@ -2358,10 +2358,8 @@ wg_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro
 		xmit_err(ifp, m, NULL, AF_UNSPEC);
 		return (ret);
 	}
-	if (parsed_af != af) {
-		xmit_err(ifp, m, NULL, AF_UNSPEC);
-		return (EAFNOSUPPORT);
-	}
+
+	MPASS(parsed_af == af);
 	mtu = (ro != NULL && ro->ro_mtu > 0) ? ro->ro_mtu : if_getmtu(ifp);
 	return (wg_xmit(ifp, m, parsed_af, mtu));
 }
diff --git a/tests/sys/net/if_wg.sh b/tests/sys/net/if_wg.sh
index b43b40f25018..e5df6afface1 100644
--- a/tests/sys/net/if_wg.sh
+++ b/tests/sys/net/if_wg.sh
@@ -92,6 +92,84 @@ wg_basic_cleanup()
 	vnet_cleanup
 }
 
+atf_test_case "wg_basic_crossaf" "cleanup"
+wg_basic_crossaf_head()
+{
+	atf_set descr 'Create a wg(4) tunnel and pass IPv4 traffic over an IPv6 nexthop'
+	atf_set require.user root
+}
+
+wg_basic_crossaf_body()
+{
+	local epair pri1 pri2 pub1 pub2 wg1 wg2
+	local endpoint1 endpoint2 tunnel1 tunnel2
+	local testnet testlocal testremote
+
+	kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
+
+	pri1=$(wg genkey)
+	pri2=$(wg genkey)
+
+	endpoint1=192.168.2.1
+	endpoint2=192.168.2.2
+	tunnel1=2001:db8:1::1
+	tunnel2=2001:db8:1::2
+
+	testnet=192.168.3.0/24
+	testlocal=192.168.3.1
+	testremote=192.168.3.2
+
+	epair=$(vnet_mkepair)
+
+	vnet_init
+
+	vnet_mkjail wgtest1 ${epair}a
+	vnet_mkjail wgtest2 ${epair}b
+
+	jexec wgtest1 ifconfig ${epair}a ${endpoint1}/24 up
+	jexec wgtest2 ifconfig ${epair}b ${endpoint2}/24 up
+
+	wg1=$(jexec wgtest1 ifconfig wg create)
+	echo "$pri1" | jexec wgtest1 wg set $wg1 listen-port 12345 \
+	    private-key /dev/stdin
+	pub1=$(jexec wgtest1 wg show $wg1 public-key)
+	wg2=$(jexec wgtest2 ifconfig wg create)
+	echo "$pri2" | jexec wgtest2 wg set $wg2 listen-port 12345 \
+	    private-key /dev/stdin
+	pub2=$(jexec wgtest2 wg show $wg2 public-key)
+
+	atf_check -s exit:0 -o ignore \
+	    jexec wgtest1 wg set $wg1 peer "$pub2" \
+	    endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/128,${testnet}
+	atf_check -s exit:0 \
+	    jexec wgtest1 ifconfig $wg1 inet6 ${tunnel1}/64 up
+
+	atf_check -s exit:0 -o ignore \
+	    jexec wgtest2 wg set $wg2 peer "$pub1" \
+	    endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/128,${testnet}
+	atf_check -s exit:0 \
+	    jexec wgtest2 ifconfig $wg2 inet6 ${tunnel2}/64 up
+
+	atf_check -s exit:0 jexec wgtest1 ifconfig $wg1 inet ${testlocal}/32
+	atf_check -s exit:0 jexec wgtest2 ifconfig $wg2 inet ${testremote}/32
+
+	# Generous timeout since the handshake takes some time.
+	atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 5 "$tunnel2"
+
+	# Setup our IPv6 endpoint and routing
+	atf_check -s exit:0 -o ignore \
+		jexec wgtest1 route add -inet ${testnet} -inet6 "$tunnel2"
+	atf_check -s exit:0 -o ignore \
+		jexec wgtest2 route add -inet ${testnet} -inet6 "$tunnel1"
+	# Now ping an address on the other side
+	atf_check -s exit:0 -o ignore jexec wgtest1 ping -c 1 -t 3 ${testremote}
+}
+
+wg_basic_crossaf_cleanup()
+{
+	vnet_cleanup
+}
+
 atf_test_case "wg_basic_netmap" "cleanup"
 wg_basic_netmap_head()
 {
@@ -349,6 +427,7 @@ wg_vnet_parent_routing_cleanup()
 atf_init_test_cases()
 {
 	atf_add_test_case "wg_basic"
+	atf_add_test_case "wg_basic_crossaf"
 	atf_add_test_case "wg_basic_netmap"
 	atf_add_test_case "wg_key_peerdev_shared"
 	atf_add_test_case "wg_key_peerdev_makeshared"