Netdev List
 help / color / mirror / Atom feed
* [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