]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: correctly handle tunneled traffic on IPV6_CSUM GSO fallback
authorWillem de Bruijn <willemb@google.com>
Fri, 20 Mar 2026 19:01:46 +0000 (15:01 -0400)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 24 Mar 2026 11:35:19 +0000 (12:35 +0100)
NETIF_F_IPV6_CSUM only advertises support for checksum offload of
packets without IPv6 extension headers. Packets with extension
headers must fall back onto software checksumming. Since TSO
depends on checksum offload, those must revert to GSO.

The below commit introduces that fallback. It always checks
network header length. For tunneled packets, the inner header length
must be checked instead. Extend the check accordingly.

A special case is tunneled packets without inner IP protocol. Such as
RFC 6951 SCTP in UDP. Those are not standard IPv6 followed by
transport header either, so also must revert to the software GSO path.

Cc: stable@vger.kernel.org
Fixes: 864e3396976e ("net: gso: Forbid IPv6 TSO with extensions on devices with only IPV6_CSUM")
Reported-by: Tangxin Xie <xietangxin@yeah.net>
Closes: https://lore.kernel.org/netdev/0414e7e2-9a1c-4d7c-a99d-b9039cf68f40@yeah.net/
Suggested-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20260320190148.2409107-1-willemdebruijn.kernel@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
net/core/dev.c

index 14a83f2035b9338850647798206dc257aefb8409..fc555706241436d790b7910451ec10de310ccce1 100644 (file)
@@ -3769,6 +3769,22 @@ static netdev_features_t dflt_features_check(struct sk_buff *skb,
        return vlan_features_check(skb, features);
 }
 
+static bool skb_gso_has_extension_hdr(const struct sk_buff *skb)
+{
+       if (!skb->encapsulation)
+               return ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 ||
+                        (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 &&
+                         vlan_get_protocol(skb) == htons(ETH_P_IPV6))) &&
+                       skb_transport_header_was_set(skb) &&
+                       skb_network_header_len(skb) != sizeof(struct ipv6hdr));
+       else
+               return (!skb_inner_network_header_was_set(skb) ||
+                       ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 ||
+                         (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 &&
+                          inner_ip_hdr(skb)->version == 6)) &&
+                        skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr)));
+}
+
 static netdev_features_t gso_features_check(const struct sk_buff *skb,
                                            struct net_device *dev,
                                            netdev_features_t features)
@@ -3816,11 +3832,7 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb,
         * so neither does TSO that depends on it.
         */
        if (features & NETIF_F_IPV6_CSUM &&
-           (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 ||
-            (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 &&
-             vlan_get_protocol(skb) == htons(ETH_P_IPV6))) &&
-           skb_transport_header_was_set(skb) &&
-           skb_network_header_len(skb) != sizeof(struct ipv6hdr))
+           skb_gso_has_extension_hdr(skb))
                features &= ~(NETIF_F_IPV6_CSUM | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4);
 
        return features;