[PATCH] udp: resubmit encapsulation packets on all multicast listeners
From: Mariano Baragiola <hidden>
Date: 2026-08-15 14:11:35
Subsystem:
networking [general], the rest, user datagram protocol (udp) · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Willem de Bruijn
UDP encapsulation handlers (FOU/GUE and similar) return a positive
protocol number from udp_queue_rcv_skb()/udpv6_queue_rcv_skb() when the
UDP header has been consumed and the packet must be resubmitted to the
IP protocol handler. Unicast paths already propagate that return value.
Multicast delivery called consume_skb() on every positive return, so the
inner packet was dropped instead of being reinjected.
Commit 3cb8d4b9bfeb ("udp: fix encapsulation packet resubmit in multicast
deliver") fixed only the primary ("first") socket path on net-next and is
not yet in net. Secondary listeners still clone the skb and drop it on a
positive return, and net itself still drops the primary socket path too.
Resubmit secondary clones inline via ip_protocol_deliver_rcu() /
ip6_protocol_deliver_rcu() (matching the GSO segment path in
udp_queue_rcv_skb()/udpv6_queue_rcv_skb()), and propagate the primary
socket return value with the same IPv4/IPv6 sign convention as the
unicast helpers.
Fixes: ca065d0cf80f ("udp: no longer use SLAB_DESTROY_BY_RCU")
Signed-off-by: Mariano Baragiola <redacted>
---
net/ipv4/udp.c | 16 ++++++++++++----
net/ipv6/udp.c | 16 ++++++++++++----
2 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 70f6cbd4ef73..d6a17b0462b6 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c@@ -2475,6 +2475,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;
@@ -2508,8 +2509,13 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb, __UDP_INC_STATS(net, UDP_MIB_INERRORS); continue; } - if (udp_queue_rcv_skb(sk, nskb) > 0) - consume_skb(nskb); + /* >0 means the encap handler wants IP-level resubmit. Do that + * inline for secondary listeners; only the first socket can + * propagate the protocol number to the caller. + */ + ret = udp_queue_rcv_skb(sk, nskb); + if (ret > 0) + ip_protocol_deliver_rcu(net, nskb, ret); } /* Also lookup *:port if we are using hash2 and haven't done so yet. */
@@ -2519,8 +2525,10 @@ 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); + /* Match udp_unicast_rcv_skb(): return -protocol for IPv4. */ + if (ret > 0) + return -ret; } else { kfree_skb(skb); __UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 15e032194ecc..65dd944211e9 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c@@ -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;
@@ -987,8 +988,13 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb, continue; } - if (udpv6_queue_rcv_skb(sk, nskb) > 0) - consume_skb(nskb); + /* >0 means the encap handler wants IP-level resubmit. Do that + * inline for secondary listeners; only the first socket can + * propagate the nexthdr to the caller. + */ + ret = udpv6_queue_rcv_skb(sk, nskb); + if (ret > 0) + ip6_protocol_deliver_rcu(net, nskb, ret, true); } /* Also lookup *:port if we are using hash2 and haven't done so yet. */
@@ -998,8 +1004,10 @@ 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); + /* Match udp6_unicast_rcv_skb(): return protocol for IPv6. */ + if (ret > 0) + return ret; } else { kfree_skb(skb); __UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
--
2.55.0