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.

Suggested-by: Kuniyuki Iwashima <[email protected]>
Signed-off-by: Anton Danilov <[email protected]>
Assisted-by: Claude:claude-opus-4-6
Reviewed-by: Willem de Bruijn <[email protected]>
---
 net/ipv4/udp.c | 6 ++++--
 net/ipv6/udp.c | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 59248a59358c..d3ddcbfc8477 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -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);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 392e18b97045..0910cc171776 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;
@@ -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);
-- 
2.47.3


Reply via email to