* [PATCH] udp: resubmit encapsulation packets on all multicast listeners
@ 2026-08-15 14:11 Mariano Baragiola
2026-08-17 23:29 ` Jakub Kicinski
2026-08-22 19:08 ` Willem de Bruijn
0 siblings, 2 replies; 4+ messages in thread
From: Mariano Baragiola @ 2026-08-15 14:11 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, horms, willemb, kuniyu,
littlesmilingcloud, Mariano Baragiola
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 <mbaragiola@linux.com>
---
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
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH] udp: resubmit encapsulation packets on all multicast listeners
2026-08-15 14:11 [PATCH] udp: resubmit encapsulation packets on all multicast listeners Mariano Baragiola
@ 2026-08-17 23:29 ` Jakub Kicinski
2026-08-22 19:08 ` Willem de Bruijn
1 sibling, 0 replies; 4+ messages in thread
From: Jakub Kicinski @ 2026-08-17 23:29 UTC (permalink / raw)
To: Mariano Baragiola
Cc: netdev, davem, edumazet, pabeni, horms, willemb, kuniyu,
littlesmilingcloud
On Sat, 15 Aug 2026 11:11:28 -0300 Mariano Baragiola wrote:
> 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.
Wait a week, rebase and repost against net.
> 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.
Please include in the commit description how the issue was found.
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] udp: resubmit encapsulation packets on all multicast listeners
2026-08-15 14:11 [PATCH] udp: resubmit encapsulation packets on all multicast listeners Mariano Baragiola
2026-08-17 23:29 ` Jakub Kicinski
@ 2026-08-22 19:08 ` Willem de Bruijn
2026-08-24 13:14 ` Mariano Baragiola
1 sibling, 1 reply; 4+ messages in thread
From: Willem de Bruijn @ 2026-08-22 19:08 UTC (permalink / raw)
To: Mariano Baragiola, netdev
Cc: davem, edumazet, kuba, pabeni, horms, willemb, kuniyu,
littlesmilingcloud, Mariano Baragiola
Mariano Baragiola wrote:
> 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.
It is now, so this will conflict.
> Secondary listeners still clone the skb and drop it on a
> positive return, and net itself still drops the primary socket path too.
When I asked about additional listeners Anton responded:
"
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.
"
https://lore.kernel.org/netdev/alAAXv60KUZ9KYx1@dau-home-pc/
> Resubmit secondary clones inline via ip_protocol_deliver_rcu() /
So is this a real path?
Small caveat on the SO_REUSEADDR/SO_REUSEPORT: SO_BINDTODEVICE
is another way to skip the equality check in udp_lib_lport_inuse2.
> 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")
Fixes should be the above patch if that is incomplete.
> Signed-off-by: Mariano Baragiola <mbaragiola@linux.com>
> ---
> 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);
> }
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] udp: resubmit encapsulation packets on all multicast listeners
2026-08-22 19:08 ` Willem de Bruijn
@ 2026-08-24 13:14 ` Mariano Baragiola
0 siblings, 0 replies; 4+ messages in thread
From: Mariano Baragiola @ 2026-08-24 13:14 UTC (permalink / raw)
To: Willem de Bruijn, netdev
Cc: davem, edumazet, kuba, pabeni, horms, kuniyu, littlesmilingcloud,
Mariano Baragiola
Thanks for checking this.
I fetched current net and confirmed that 3cb8d4b9bfeb is now present, so the primary-path issue is fixed there.
I also checked the SO_BINDTODEVICE case. Although different bound devices can bypass the port-use equality check, multicast delivery requires the socket's bound device to match the packet's ingress device. Those sockets therefore cannot become multiple matching listeners for the same multicast packet. FOU/GUE sockets also do not enable SO_REUSEADDR or SO_REUSEPORT.
The secondary clone path is therefore not reachable for the positive encapsulation return. I will withdraw this patch rather than repost it.
For completeness, I found the original issue while comparing the multicast receive path with the unicast and GSO paths, then confirmed it with a focused FOU probe against clean and patched kernels. The clean kernel dropped the multicast encapsulated packets; the patched kernel resubmitted them to the IP protocol handler.
Thanks again for the review.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-24 13:14 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-15 14:11 [PATCH] udp: resubmit encapsulation packets on all multicast listeners Mariano Baragiola
2026-08-17 23:29 ` Jakub Kicinski
2026-08-22 19:08 ` Willem de Bruijn
2026-08-24 13:14 ` Mariano Baragiola
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox