From: Anton Danilov <littlesmilingcloud@gmail.com>
To: netdev@vger.kernel.org
Cc: willemdebruijn.kernel@gmail.com, davem@davemloft.net,
dsahern@kernel.org, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, shuah@kernel.org,
linux-kselftest@vger.kernel.org
Subject: [RFC PATCH net-next v2 2/2] selftests: net: add FOU multicast encapsulation resubmit test
Date: Wed, 15 Apr 2026 21:52:08 +0300 [thread overview]
Message-ID: <ad_eWBo76OLcgPSA@dau-home-pc> (raw)
In-Reply-To: <ad_dal164gVmImWl@dau-home-pc>
Add a selftest to verify that FOU-encapsulated packets addressed to a
multicast destination are correctly resubmitted to the inner protocol
handler (GRE) via the UDP multicast delivery path.
The test creates two network namespaces connected by a veth pair. The
receiver namespace has a FOU/GRETAP tunnel with a multicast remote
address (239.0.0.1). A Python helper script (fou_mcast_encap.py)
crafts GRE-over-UDP packets and sends them to the multicast address.
The early demux optimization (net.ipv4.ip_early_demux) is disabled on
the receiver to force packets through __udp4_lib_mcast_deliver(),
which is the code path that was previously broken.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
tools/testing/selftests/net/Makefile | 2 +
.../testing/selftests/net/fou_mcast_encap.py | 81 ++++++++++++++
.../testing/selftests/net/fou_mcast_encap.sh | 105 ++++++++++++++++++
3 files changed, 188 insertions(+)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.py
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index a275ed584026..2b463dda5ac6 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -38,6 +38,7 @@ TEST_PROGS := \
fib_rule_tests.sh \
fib_tests.sh \
fin_ack_lat.sh \
+ fou_mcast_encap.sh \
fq_band_pktlimit.sh \
gre_gso.sh \
gre_ipv6_lladdr.sh \
@@ -195,6 +196,7 @@ TEST_GEN_PROGS := \
TEST_FILES := \
fcnal-test.sh \
+ fou_mcast_encap.py \
in_netns.sh \
lib.sh \
settings \
diff --git a/tools/testing/selftests/net/fou_mcast_encap.py b/tools/testing/selftests/net/fou_mcast_encap.py
new file mode 100755
index 000000000000..0fd836c454a8
--- /dev/null
+++ b/tools/testing/selftests/net/fou_mcast_encap.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Send FOU/GRE encapsulated packets to a multicast destination.
+
+Build and send GRE-over-UDP (FOU) packets with a multicast outer
+destination IP. Used by fou_mcast_encap.sh to test that the UDP
+multicast delivery path correctly resubmits encapsulated packets
+to the inner protocol handler.
+"""
+
+import argparse
+import socket
+import struct
+
+
+def checksum(data):
+ """Compute Internet checksum (RFC 1071)."""
+ csum = 0
+ for i in range(0, len(data) - 1, 2):
+ csum += (data[i] << 8) + data[i + 1]
+ if len(data) % 2:
+ csum += data[-1] << 8
+ while csum >> 16:
+ csum = (csum & 0xFFFF) + (csum >> 16)
+ return ~csum & 0xFFFF
+
+
+def build_gre_encap_packet(dst_addr):
+ """Build a GRE/Ethernet/IP/ICMP payload for FOU encapsulation."""
+ gre_key = socket.inet_aton(dst_addr)
+ gre_hdr = struct.pack("!HH", 0x2000, 0x6558) + gre_key
+
+ dst_mac = b"\xff\xff\xff\xff\xff\xff"
+ src_mac = b"\x02\x00\x00\x00\x00\x01"
+ eth_hdr = dst_mac + src_mac + struct.pack("!H", 0x0800)
+
+ inner_ip_src = socket.inet_aton("192.168.99.1")
+ inner_ip_dst = socket.inet_aton("192.168.99.2")
+
+ icmp_payload = b"TESTFOU!" * 4
+ icmp_hdr = struct.pack("!BBHHH", 8, 0, 0, 0x1234, 1) + icmp_payload
+ icmp_csum = checksum(icmp_hdr)
+ icmp_hdr = struct.pack("!BBHHH", 8, 0, icmp_csum, 0x1234, 1) + icmp_payload
+
+ ip_len = 20 + len(icmp_hdr)
+ ip_hdr = struct.pack("!BBHHHBBH", 0x45, 0, ip_len, 0x1234, 0, 64, 1, 0)
+ ip_hdr += inner_ip_src + inner_ip_dst
+ ip_csum = checksum(ip_hdr)
+ ip_hdr = ip_hdr[:10] + struct.pack("!H", ip_csum) + ip_hdr[12:]
+
+ return gre_hdr + eth_hdr + ip_hdr + icmp_hdr
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Send FOU/GRE encapsulated packets to a multicast address"
+ )
+ parser.add_argument(
+ "-c", "--count", type=int, required=True,
+ help="number of packets to send",
+ )
+ parser.add_argument(
+ "-d", "--dst", default="239.0.0.1",
+ help="destination multicast address (default: 239.0.0.1)",
+ )
+ parser.add_argument(
+ "-p", "--port", type=int, default=4797,
+ help="destination UDP port (default: 4797)",
+ )
+ args = parser.parse_args()
+
+ payload = build_gre_encap_packet(args.dst)
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ for _ in range(args.count):
+ sock.sendto(payload, (args.dst, args.port))
+ sock.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
new file mode 100755
index 000000000000..1f9dbe7092f3
--- /dev/null
+++ b/tools/testing/selftests/net/fou_mcast_encap.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test that UDP encapsulation (FOU) correctly handles packet resubmit
+# when packets are delivered via the multicast UDP delivery path.
+#
+# When a FOU-encapsulated packet arrives with a multicast destination IP,
+# __udp4_lib_mcast_deliver() must resubmit it to the inner protocol
+# handler (e.g., GRE) rather than consuming it. This test verifies that
+# by creating a FOU/GRETAP tunnel with a multicast remote address, sending
+# encapsulated packets, and checking that they are correctly decapsulated.
+#
+# The early demux optimization can mask this issue by routing packets via
+# the unicast path (udp_unicast_rcv_skb), so we disable it to force
+# packets through __udp4_lib_mcast_deliver().
+
+source lib.sh
+
+NSENDER=""
+NRECV=""
+
+cleanup() {
+ cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+setup() {
+ setup_ns NSENDER NRECV
+
+ ip link add veth_s type veth peer name veth_r
+ ip link set veth_s netns "$NSENDER"
+ ip link set veth_r netns "$NRECV"
+
+ ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
+ ip -n "$NSENDER" link set veth_s up
+
+ ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r
+ ip -n "$NRECV" link set veth_r up
+
+ # Disable early demux to force multicast delivery path
+ ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add 239.0.0.1/32 dev veth_r autojoin
+
+ # Multicast routes
+ ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r
+ ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s
+
+ # FOU listener
+ ip netns exec "$NRECV" ip fou add port 4797 ipproto 47
+
+ # GRETAP with multicast remote - this triggers __udp4_lib_mcast_deliver
+ ip -n "$NRECV" link add eoudp0 type gretap \
+ remote 239.0.0.1 local 10.0.0.2 \
+ encap fou encap-sport 4797 encap-dport 4797 \
+ key 239.0.0.1
+ ip -n "$NRECV" link set eoudp0 up
+ ip -n "$NRECV" addr add 192.168.99.2/24 dev eoudp0
+}
+
+send_fou_gre_packets() {
+ local count=$1
+ local script_dir
+ script_dir=$(dirname "$(readlink -f "$0")")
+
+ ip netns exec "$NSENDER" python3 "$script_dir/fou_mcast_encap.py" \
+ -c "$count" -d 239.0.0.1 -p 4797
+}
+
+get_rx_packets() {
+ ip -n "$NRECV" -s link show eoudp0 | awk '/RX:/{getline; print $2}'
+}
+
+test_fou_mcast_encap() {
+ local count=100
+ local rx_before
+ local rx_after
+ local rx_delta
+
+ rx_before=$(get_rx_packets)
+ send_fou_gre_packets $count
+ sleep 1
+ rx_after=$(get_rx_packets)
+
+ rx_delta=$((rx_after - rx_before))
+
+ if [ "$rx_delta" -ge "$count" ]; then
+ echo "PASS: received $rx_delta/$count packets via multicast FOU/GRETAP"
+ return "$ksft_pass"
+ elif [ "$rx_delta" -gt 0 ]; then
+ echo "FAIL: only $rx_delta/$count packets received (partial delivery)"
+ return "$ksft_fail"
+ else
+ echo "FAIL: 0/$count packets received (multicast encap resubmit broken)"
+ return "$ksft_fail"
+ fi
+}
+
+echo "TEST: FOU/GRETAP multicast encapsulation resubmit"
+
+setup
+test_fou_mcast_encap
+exit $?
--
2.47.3
next prev parent reply other threads:[~2026-04-15 18:52 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-15 18:48 [RFC PATCH net-next v2 0/2] udp: fix FOU/GUE over multicast Anton Danilov
2026-04-15 18:50 ` [RFC PATCH net-next v2 1/2] udp: fix encapsulation packet resubmit in multicast deliver Anton Danilov
2026-04-15 20:35 ` Kuniyuki Iwashima
2026-04-15 18:52 ` Anton Danilov [this message]
2026-05-04 22:53 ` [RFC PATCH net-next v3 0/2] udp: fix FOU/GUE over multicast Anton Danilov
2026-05-04 22:53 ` [RFC PATCH net-next v3 1/2] udp: fix encapsulation packet resubmit in multicast deliver Anton Danilov
2026-05-04 22:53 ` [RFC PATCH net-next v3 2/2] selftests: net: add FOU multicast encapsulation resubmit test Anton Danilov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ad_eWBo76OLcgPSA@dau-home-pc \
--to=littlesmilingcloud@gmail.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
--cc=willemdebruijn.kernel@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.