Skip to contentSkip to conversation

udp: fix FOU/GUE over multicast

PATCH Applied Anton Danilov 3 participants 8 messages Jul 8 – Jul 21

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v6 cover udp: fix FOU/GUE over multicast

UDP encapsulation (FOU, GUE) has never worked correctly with multicast destination addresses. When a FOU-encapsulated packet arrives at a multicast address, it enters __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() which call consume_skb() on packets that need resubmission to the inner protocol handler, silently dropping them instead.

The unicast delivery paths handle this correctly by propagating the return value up to ip[6]_protocol_deliver_rcu() for resubmission, but the multicast paths were never updated to support UDP encapsulation resubmit.

This causes silent packet loss for FOU/GRETAP tunnels configured with multicast remote addresses (both IPv4 and IPv6).

Reproducing the issue (IPv4):

ip netns add ns_a && ip netns add ns_b
ip link add veth0 netns ns_a type veth peer name veth1 netns ns_b

  ip -n ns_a addr add 10.0.0.1/24 dev veth0 && ip -n ns_a link set veth0 up
  ip -n ns_b addr add 10.0.0.2/24 dev veth1 && ip -n ns_b link set veth1 up

ip -n ns_a route add 239.0.0.0/8 dev veth0
ip -n ns_b route add 239.0.0.0/8 dev veth1

# Disable early demux to expose the issue (otherwise it's partially masked) ip netns exec ns_b sysctl -w net.ipv4.ip_early_demux=0

# Join multicast group on receiver
ip -n ns_b addr add 239.0.0.1/32 dev veth1 autojoin

  # Sender: GRETAP with FOU encap
  ip -n ns_a link add eoudp0 type gretap \
      remote 239.0.0.1 local 10.0.0.1 \
      encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
  ip -n ns_a link set eoudp0 up
  ip -n ns_a addr add 192.168.99.1/24 dev eoudp0

  # Receiver: FOU listener + GRETAP
  ip netns exec ns_b ip fou add port 4797 ipproto 47
  ip -n ns_b 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 ns_b link set eoudp0 up
  ip -n ns_b addr add 192.168.99.2/24 dev eoudp0

  # Static neigh: ARP replies can't traverse unidirectional mcast tunnel
  recv_mac=$(ip -n ns_b link show eoudp0 | awk '/ether/{print $2}')
  ip -n ns_a neigh add 192.168.99.2 lladdr $recv_mac dev eoudp0

# Test: ping through the FOU/GRETAP tunnel ip netns exec ns_a ping -c 100 192.168.99.2 # -> without this patch: 0 packets received on eoudp0 # -> with this patch: all packets received on eoudp0

IPv6 (using fou6 + ip6gretap) exhibits the same silent drop with a different fix (see 1/2 for the sign-of-ret difference between ip_protocol_deliver_rcu() and ip6_protocol_deliver_rcu()).

AI assistance (Claude, claude-opus-4-6) was used during root cause analysis of the kernel source code (tracing the call chain from udp[6]_queue_rcv_skb through encap_rcv to ip[6]_protocol_deliver_rcu, comparing unicast/GSO/multicast paths) and during patch and selftest authoring.

v6:
- selftest: use the lib.sh ktap helpers (log_test / log_test_skip)
so the IPv6-unavailable case reports KSFT_SKIP properly (Willem
de Bruijn)
- selftest: add CONFIG_IPV6_FOU / CONFIG_IPV6_FOU_TUNNEL to tools/testing/selftests/net/config (Willem de Bruijn)
- selftest: speed up the ping loop with -i 0.01
v5: https://lore.kernel.org/netdev/[email protected]/
- Fix IPv6 patch: return ret, not -ret (Willem de Bruijn)
- selftest: add IPv6 test case
- selftest: create the veth pair inside the namespaces (Willem de Bruijn)
v4: https://lore.kernel.org/netdev/[email protected]/
- Promoted from RFC to PATCH; no functional changes since v3.
v3: https://lore.kernel.org/netdev/[email protected]/
- Use return -ret instead of calling ip_protocol_deliver_rcu() directly, matching the unicast path (Kuniyuki Iwashima)
- Replace Python packet generator with ping through a real
FOU/GRETAP tunnel in the selftest
v2: https://lore.kernel.org/netdev/ad_dal164gVmImWl@dau-home-pc/
- Moved inline Python packet generator into a separate helper
- Fixed author email typo in Signed-off-by
v1 (RFC): https://lore.kernel.org/netdev/ad7MsSJOuUU6EGwS@dau-home-pc/

Anton Danilov (2):
udp: fix encapsulation packet resubmit in multicast deliver
selftests: net: add FOU multicast encapsulation resubmit test

 net/ipv4/udp.c                                |   6 +-
 net/ipv6/udp.c                                |   6 +-
 tools/testing/selftests/net/Makefile          |   1 +
 tools/testing/selftests/net/config            |   2 +
 .../testing/selftests/net/fou_mcast_encap.sh  | 172 ++++++++++++++++++
 5 files changed, 183 insertions(+), 4 deletions(-)
 create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
Signature · 1 line
2.47.3
#1 lore raw

PATCH v6 1/2 udp: fix encapsulation packet resubmit in multicast deliver

When a UDP encapsulation socket (e.g., FOU) receives a multicast packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver() call consume_skb() when udp_queue_rcv_skb() returns a positive value. A positive return value from udp_queue_rcv_skb() indicates that the encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header and wants the packet to be resubmitted to the IP protocol handler for further processing (e.g., as a GRE packet).

The unicast paths handle this correctly by propagating the return value up to ip_protocol_deliver_rcu() / ip6_protocol_deliver_rcu() for resubmission. However, the multicast paths destroy the packet via consume_skb() instead of resubmitting it, causing silent packet loss.

This affects any UDP encapsulation (FOU, GUE) combined with multicast destination addresses.

Fix this by returning the value from udp_queue_rcv_skb() when it is positive, matching the behavior of the corresponding unicast paths. Note the sign difference between IPv4 and IPv6:

- IPv4: udp_unicast_rcv_skb() returns -ret, and ip_protocol_deliver_rcu() resubmits when ret < 0 (using -ret as the protocol number).
- IPv6: udp6_unicast_rcv_skb() returns ret, and ip6_protocol_deliver_rcu() resubmits when ret > 0 (using ret as the nexthdr).

Both mcast paths now follow the same convention as their respective unicast paths.

Assisted-by Claude:claude-opus-4-6
---
 net/ipv4/udp.c | 6 ++++--
 net/ipv6/udp.c | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)
net/ipv4/udp.c +4 -2
index 59248a59358c..d3ddcbfc8477 100644
@@ -2476,6 +2476,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	struct udp_hslot *hslot;
 	struct sk_buff *nskb;
 	bool use_hash2;
+	int ret;
 
 	hash2_any = 0;
 	hash2 = 0;
@@ -2520,8 +2521,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	}
 
 	if (first) {
-		if (udp_queue_rcv_skb(first, skb) > 0)
-			consume_skb(skb);
+		ret = udp_queue_rcv_skb(first, skb);
+		if (ret > 0)
+			return -ret;
 	} else {
 		kfree_skb(skb);
 		__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
net/ipv6/udp.c +4 -2
index 392e18b97045..0910cc171776 100644
@@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	struct udp_hslot *hslot;
 	struct sk_buff *nskb;
 	bool use_hash2;
+	int ret;
 
 	hash2_any = 0;
 	hash2 = 0;
@@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	}
 
 	if (first) {
-		if (udpv6_queue_rcv_skb(first, skb) > 0)
-			consume_skb(skb);
+		ret = udpv6_queue_rcv_skb(first, skb);
+		if (ret > 0)
+			return ret;
 	} else {
 		kfree_skb(skb);
 		__UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
Signature · 1 line
2.47.3
maintainer
#2 lore raw

Re: [PATCH net-next v6 1/2] udp: fix encapsulation packet resubmit in multicast deliver

Quoted 53 lines · Anton Danilov

When a UDP encapsulation socket (e.g., FOU) receives a multicast packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver() call consume_skb() when udp_queue_rcv_skb() returns a positive value. A positive return value from udp_queue_rcv_skb() indicates that the encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header and wants the packet to be resubmitted to the IP protocol handler for further processing (e.g., as a GRE packet).

The unicast paths handle this correctly by propagating the return value up to ip_protocol_deliver_rcu() / ip6_protocol_deliver_rcu() for resubmission. However, the multicast paths destroy the packet via consume_skb() instead of resubmitting it, causing silent packet loss.

This affects any UDP encapsulation (FOU, GUE) combined with multicast destination addresses.

Fix this by returning the value from udp_queue_rcv_skb() when it is positive, matching the behavior of the corresponding unicast paths. Note the sign difference between IPv4 and IPv6:

- IPv4: udp_unicast_rcv_skb() returns -ret, and ip_protocol_deliver_rcu() resubmits when ret < 0 (using -ret as the protocol number).
- IPv6: udp6_unicast_rcv_skb() returns ret, and ip6_protocol_deliver_rcu() resubmits when ret > 0 (using ret as the nexthdr).

Both mcast paths now follow the same convention as their respective unicast paths.

Assisted-by Claude:claude-opus-4-6
---
 net/ipv4/udp.c | 6 ++++--
 net/ipv6/udp.c | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)
net/ipv4/udp.c +1 -0
index 59248a59358c..d3ddcbfc8477 100644
@@ -2476,6 +2476,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	struct udp_hslot *hslot;
 	struct sk_buff *nskb;
 	bool use_hash2;
+	int ret;
 
 	hash2_any = 0;
 	hash2 = 0;

net/ipv4/udp.c static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, · line 2526

Quoted 8 lines
net/ipv4/udp.c +3 -2
@@ -2520,8 +2521,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
 	}
 
 	if (first) {
-		if (udp_queue_rcv_skb(first, skb) > 0)
-			consume_skb(skb);
+		ret = udp_queue_rcv_skb(first, skb);
+		if (ret > 0)
+			return -ret;

This helps the case of one encap_rcv socket in the multicast receiver group, so is a useful fix on its own.

But is Sashiko correct that this would still leave the same issue for other sockets in the group? If so, something to address in this series or leave for later?

Might be worthwhile to extend the test to capture that case too.

#3 lore raw

Re: [PATCH net-next v6 1/2] udp: fix encapsulation packet resubmit in multicast deliver

Quoted 8 lines · Willem de Bruijn

This helps the case of one encap_rcv socket in the multicast receiver group, so is a useful fix on its own.

But is Sashiko correct that this would still leave the same issue for other sockets in the group? If so, something to address in this series or leave for later?

Might be worthwhile to extend the test to capture that case too.

The clone loop is not reachable for encapsulation sockets, so there is no remaining gap.

FOU/GUE tunnel sockets are created via udp_sock_create() / setup_udp_tunnel_sock() and do not set SO_REUSEADDR or SO_REUSEPORT. Without either, a UDP socket cannot share its port, so an encap socket is always the only socket bound to its port. In __udp[46]_lib_mcast_deliver() it is therefore always delivered as 'first', and the clone loop -- which handles the second and subsequent sockets in the group -- never runs for it. The positive (resubmit) return from udp_queue_rcv_skb() only happens for encap sockets; plain UDP sockets return 0 or a negative value there. So the resubmit case in the clone loop cannot occur.

For the same reason a selftest cannot exercise it: a second socket cannot bind to the FOU port.

If useful, I can add a brief comment above the clone loop documenting why no resubmit is needed there.

maintainer
#4 lore raw

Re: [PATCH net-next v6 1/2] udp: fix encapsulation packet resubmit in multicast deliver

Quoted 24 lines · Anton Danilov
Quoted 8 lines · Willem de Bruijn

This helps the case of one encap_rcv socket in the multicast receiver group, so is a useful fix on its own.

But is Sashiko correct that this would still leave the same issue for other sockets in the group? If so, something to address in this series or leave for later?

Might be worthwhile to extend the test to capture that case too.

The clone loop is not reachable for encapsulation sockets, so there is no remaining gap.

FOU/GUE tunnel sockets are created via udp_sock_create() / setup_udp_tunnel_sock() and do not set SO_REUSEADDR or SO_REUSEPORT. Without either, a UDP socket cannot share its port, so an encap socket is always the only socket bound to its port. In __udp[46]_lib_mcast_deliver() it is therefore always delivered as 'first', and the clone loop -- which handles the second and subsequent sockets in the group -- never runs for it. The positive (resubmit) return from udp_queue_rcv_skb() only happens for encap sockets; plain UDP sockets return 0 or a negative value there. So the resubmit case in the clone loop cannot occur.

Quoted 5 lines

For the same reason a selftest cannot exercise it: a second socket cannot bind to the FOU port.

If useful, I can add a brief comment above the clone loop documenting why no resubmit is needed there.

Thanks for that context. No need to respin for that.

#5 lore raw

PATCH v6 2/2 selftests: net: add FOU multicast encapsulation resubmit test

In reply to Anton Danilov

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. Both IPv4 and IPv6 paths are tested.

The test creates two network namespaces connected by a veth pair with a FOU/GRETAP (IPv4) and FOU/ip6gretap (IPv6) tunnel using multicast remote addresses (239.0.0.1 and ff0e::1). Ping is sent through each tunnel and received packets are counted on the receiver's tunnel interface.

The veth pair is created directly inside the namespaces to avoid possible name collisions with devices in the root namespace.

Static neighbor entries are configured on the sender because ARP/ND replies from the receiver cannot traverse the unidirectional multicast tunnel back to the sender.

The early demux optimization (net.ipv4.ip_early_demux, which controls both IPv4 and IPv6) is disabled on the receiver to force packets through __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver(), which is the code path being tested.

Assisted-by Claude:claude-opus-4-6
---
 tools/testing/selftests/net/Makefile          |   1 +
 tools/testing/selftests/net/config            |   2 +
 .../testing/selftests/net/fou_mcast_encap.sh  | 172 ++++++++++++++++++
 3 files changed, 175 insertions(+)
 create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
tools/testing/selftests/net/Makefile +1 -0
index 708d960ae07d..7e9ae937cffa 100644
@@ -39,6 +39,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 \
tools/testing/selftests/net/config +2 -0
index e1ce35c2abbe..96fffca6547c 100644
@@ -38,6 +38,8 @@ CONFIG_IP_NF_TARGET_REJECT=m
 CONFIG_IP_NF_TARGET_TTL=m
 CONFIG_IP_SCTP=m
 CONFIG_IPV6=y
+CONFIG_IPV6_FOU=m
+CONFIG_IPV6_FOU_TUNNEL=m
 CONFIG_IPV6_GRE=m
 CONFIG_IPV6_ILA=m
 CONFIG_IPV6_IOAM6_LWTUNNEL=y
tools/testing/selftests/net/fou_mcast_encap.sh new file +172 -0

172 lines not on this page · raw message

Signature · 1 line
2.47.3
maintainer Reviewed-by on v6 2/2
#6 lore raw

Re: [PATCH net-next v6 2/2] selftests: net: add FOU multicast encapsulation resubmit test

Quoted 18 lines · Anton Danilov

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. Both IPv4 and IPv6 paths are tested.

The test creates two network namespaces connected by a veth pair with a FOU/GRETAP (IPv4) and FOU/ip6gretap (IPv6) tunnel using multicast remote addresses (239.0.0.1 and ff0e::1). Ping is sent through each tunnel and received packets are counted on the receiver's tunnel interface.

The veth pair is created directly inside the namespaces to avoid possible name collisions with devices in the root namespace.

Static neighbor entries are configured on the sender because ARP/ND replies from the receiver cannot traverse the unidirectional multicast tunnel back to the sender.

Quoted 7 lines

The early demux optimization (net.ipv4.ip_early_demux, which controls both IPv4 and IPv6) is disabled on the receiver to force packets through __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver(), which is the code path being tested.

Assisted-by Claude:claude-opus-4-6

tools/testing/selftests/net/fou_mcast_encap.sh line 54

Quoted 2 lines
tools/testing/selftests/net/fou_mcast_encap.sh new file +2 -0
+setup_ipv4() {
+	# IPv4 FOU (CONFIG_NET_FOU) is built in on kernels configured for

Instead of this distinction and modprobe for IPv6, also set those new CONFIGs to =Y, so that the two are equivalent?

Not important enough to respin.

#7 lore raw
In reply to Anton Danilov
This series was applied to netdev/net-next.git (main) by Paolo Abeni <[email protected]>:

Hello:

This series was applied to netdev/net-next.git (main) by Paolo Abeni <[email protected]>:

Quoted 8 lines · On Wed, 8 Jul 2026 03:35:02 +0300 you wrote:

UDP encapsulation (FOU, GUE) has never worked correctly with multicast destination addresses. When a FOU-encapsulated packet arrives at a multicast address, it enters __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() which call consume_skb() on packets that need resubmission to the inner protocol handler, silently dropping them instead.

[...]

Here is the summary with links:
- [net-next,v6,1/2] udp: fix encapsulation packet resubmit in multicast deliver

    https://git.kernel.org/netdev/net-next/c/3cb8d4b9bfeb
  - [net-next,v6,2/2] selftests: net: add FOU multicast encapsulation resubmit test
    https://git.kernel.org/netdev/net-next/c/e5382133c51c

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html