Netdev List
 help / color / mirror / Atom feed
* [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets
@ 2026-09-26 14:04 Willem de Bruijn
  2026-09-29  0:07 ` netdev-bot+sashiko
  2026-09-30  1:20 ` patchwork-bot+netdevbpf
  0 siblings, 2 replies; 4+ messages in thread
From: Willem de Bruijn @ 2026-09-26 14:04 UTC (permalink / raw)
  To: netdev
  Cc: davem, kuba, edumazet, pabeni, horms, andrew+netdev, xietangxin,
	Willem de Bruijn, stable

From: Willem de Bruijn <willemb@google.com>

Commit c4336a07eb6b ("net: correctly handle tunneled traffic on IPV6_CSUM
GSO fallback") split skb_gso_has_extension_hdr() into mutually exclusive
branches on skb->encapsulation. This did not yet address all paths:

1. With skb->encapsulation set, the outer header is not checked. IPv6
   tunnels such as ip6_gre and ip6_tunnel add an outer Destination
   Options header by default (encap_limit). Their GSO packets skip
   software GSO, then skb_csum_hwoffload_help() sees the outer extension
   header and calls skb_checksum_help() on the GSO skb, which warns and
   drops it.

2. Tunnels over IPv6 without an outer transport header, such as
   ip6_tunnel, leave skb->transport_header at the inner transport
   header. skb_network_header_len() then spans the outer IPv6, tunnel
   and inner IP headers, a false positive.

3. UDP tunnels without an inner network header, such as SCTP-in-UDP or
   PSP, have no inner IPv6 header to check. Decide on the outer header
   alone.

4. Directly dereferencing inner_ip_hdr(skb)->version without
   skb_header_pointer() is unsafe.

Instead, check ipv6_ext_hdr(nexthdr) on the outer IPv6 header and, if
set, on the inner IPv6 header. Read the headers with skb_header_pointer().
Keep the skb_network_header_len() check when !skb->encapsulation to also
catch encapsulated packets without skb->encapsulation (e.g., virtio).

Use the same helper in skb_csum_hwoffload_help(). Its open coded test
has the false positive of (2) and ignores the inner header.

Background: checksum offload of tunneled packets invariants:

Non-GSO skb:
- If the inner packet is CHECKSUM_PARTIAL, Local Checksum Offload computes
  the outer checksum in software and the device offloads only the inner L4
  checksum.
- If the inner packet is CHECKSUM_NONE (e.g., SCTP-in-UDP, ESP-in-UDP,
  Remote Checksum Offload), the device offloads the outer UDP or GRE
  checksum instead.

GSO skb:
- A device with NETIF_F_GSO_UDP_TUNNEL_CSUM or NETIF_F_GSO_GRE_CSUM
  computes both inner and outer checksums per segment.
  The outer checksum is seeded with only the pseudo-header checksum.

A NETIF_F_IPV6_CSUM device must parse through the outer headers to
reach the inner ones.

Fixes: c4336a07eb6b ("net: correctly handle tunneled traffic on IPV6_CSUM GSO fallback")
Cc: stable@vger.kernel.org
Signed-off-by: Willem de Bruijn <willemb@google.com>

---

  v1 -> v2
  - Keep skb_network_header_len() check when !skb->encapsulation to
    support tunnels without skb->encapsulation (e.g., virtio).

  v1: https://lore.kernel.org/netdev/20260924192128.1197118-1-willemdebruijn.kernel@gmail.com/

Stable: trees before commit 1676ebba391d ("net/ipv6: Remove jumbo_remove
step from TX path") must keep the BIG TCP jumbo exemption on the outer
check, or BIG TCP loses TSO (see 68e068cabd2c):

	if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) &&
	    !ipv6_has_hopopt_jumbo(skb)) {
		if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb)))
			return true;

		/* Catch tunnels without skb->encapsulation (e.g., virtio). */
		if (!skb->encapsulation &&
		    skb_transport_header_was_set(skb) &&
		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
			return true;
	}

Tested with gre_gso.sh over veth with NETIF_F_IPV6_CSUM:
- GREv6 without extension headers
- GREv6 with inner dstopts
- GREv6 with outer encaplimit, and
- SCTP-in-UDPv6.

That needs a test-only feature added to veth to advertise
NETIF_F_IPV6_CSUM (mutually exclusive with NETIF_F_HW_CSUM).

If no one objects, we can follow up with those veth and selftest patches to
net-next later.
---
 net/core/dev.c | 49 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 32 insertions(+), 17 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index 0292a16e16c2..91b8a57fad96 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3818,20 +3818,36 @@ 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 bool __skb_has_ipv6_ext_hdr(const struct sk_buff *skb, int nhoff)
+{
+	const struct ipv6hdr *ip6h;
+	struct ipv6hdr _ip6h;
+
+	ip6h = skb_header_pointer(skb, nhoff, sizeof(_ip6h), &_ip6h);
+	return ip6h && ip6h->version == 6 && ipv6_ext_hdr(ip6h->nexthdr);
+}
+
+static bool skb_has_ipv6_extension_hdr(const struct sk_buff *skb)
+{
+	if (vlan_get_protocol(skb) == htons(ETH_P_IPV6)) {
+		if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb)))
+			return true;
+
+		/* Catch tunnels without skb->encapsulation (e.g., virtio). */
+		if (!skb->encapsulation &&
+		    skb_transport_header_was_set(skb) &&
+		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
+			return true;
+	}
+
+	/* Tunnels without an inner network header, such as SCTP-in-UDP or
+	 * PSP, have no inner IP header and thus no inner extension header.
+	 */
+	if (skb->encapsulation && skb_inner_network_header_was_set(skb) &&
+	    __skb_has_ipv6_ext_hdr(skb, skb_inner_network_offset(skb)))
+		return true;
+
+	return false;
 }
 
 static netdev_features_t gso_features_check(const struct sk_buff *skb,
@@ -3886,7 +3902,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_gso_has_extension_hdr(skb))
+	    skb_has_ipv6_extension_hdr(skb))
 		features &= ~(NETIF_F_IPV6_CSUM | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4);
 
 	return features;
@@ -3988,8 +4004,7 @@ int skb_csum_hwoffload_help(struct sk_buff *skb,
 		return 0;
 
 	if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) {
-		if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) &&
-		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
+		if (skb_has_ipv6_extension_hdr(skb))
 			goto sw_checksum;
 
 		switch (skb->csum_offset) {
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets
  2026-09-26 14:04 [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets Willem de Bruijn
@ 2026-09-29  0:07 ` netdev-bot+sashiko
  2026-09-29  3:23   ` Willem de Bruijn
  2026-09-30  1:20 ` patchwork-bot+netdevbpf
  1 sibling, 1 reply; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-09-29  0:07 UTC (permalink / raw)
  To: willemdebruijn.kernel
  Cc: netdev, davem, kuba, edumazet, pabeni, horms, andrew+netdev,
	xietangxin, willemb, stable

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] net: unnecessary software checksum fallback for outer UDP
  tunnels
- [Low] The old non-encapsulated branch cleared features only for
  SKB_GSO_TCPV6, or for SKB_GSO_UDP_L4 over IPv6.
- [Low] The in-tree NETIF_F_IPV6_CSUM contract
  (include/linux/skbuff.h:77-86) still says the feature covers only…

--

net: extend IPv6 exthdr detection of tunneled packets

This patch replaces skb_gso_has_extension_hdr() with
skb_has_ipv6_extension_hdr(). The new helper checks ipv6_ext_hdr() on the
outer IPv6 header and, when skb->encapsulation is set, on the inner IPv6
header, reading both through skb_header_pointer(). gso_features_check()
and skb_csum_hwoffload_help() now both use it.

> diff --git a/net/core/dev.c b/net/core/dev.c
> index f660fccfc0dbc..aa74aa60ca2a7 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -3818,20 +3818,36 @@ 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));

[ ... ]

> +static bool skb_has_ipv6_extension_hdr(const struct sk_buff *skb)
> +{
> +	if (vlan_get_protocol(skb) == htons(ETH_P_IPV6)) {
> +		if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb)))
> +			return true;

[Severity: Low]

The removed non-encapsulated branch cleared features only for
SKB_GSO_TCPV6, or for SKB_GSO_UDP_L4 over IPv6. The new outer check has no
gso_type gate. Is it meant to apply to every IPv6 GSO type?

SKB_GSO_SCTP is one type whose behaviour changes. sctp_packet_pack()
always makes the GSO head CHECKSUM_PARTIAL:

net/sctp/output.c:sctp_packet_pack() {
    ...
	if (gso) {
		...
		goto chksum;
	}
    ...
chksum:
		head->ip_summed = CHECKSUM_PARTIAL;
		head->csum_not_inet = 1;
    ...
}

Take SCTP over IPv6 with IPV6_DSTOPTS or IPV6_RTHDR set.
gso_features_check() now clears NETIF_F_IPV6_CSUM. harmonize_features()
then sees ip_summed != CHECKSUM_NONE and
!can_checksum_protocol(features, ETH_P_IPV6). It strips all of
NETIF_F_CSUM_MASK, including NETIF_F_SCTP_CRC, plus NETIF_F_GSO_MASK:

validate_xmit_skb()
  netif_skb_features()
    gso_features_check()    /* clears NETIF_F_IPV6_CSUM */
    harmonize_features()    /* clears NETIF_F_CSUM_MASK, NETIF_F_GSO_MASK */
  skb_gso_segment()
    sctp_gso_segment()      /* CRC32c computed in software per segment */

Some devices have NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SCTP_CRC
but no NETIF_F_HW_CSUM. One example is hns3 without
HNAE3_DEV_SUPPORT_HW_TX_CSUM_B. On such a device the CRC32c was offloaded
before this patch and is computed in software after it. The output is
still correct, so only performance is affected.

Could the gso_type restriction be kept here? If not, could the commit
message say that the check now covers all IPv6 GSO types?

> +
> +		/* Catch tunnels without skb->encapsulation (e.g., virtio). */
> +		if (!skb->encapsulation &&
> +		    skb_transport_header_was_set(skb) &&
> +		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
> +			return true;
> +	}

[ ... ]

> @@ -3988,8 +4004,7 @@ int skb_csum_hwoffload_help(struct sk_buff *skb,
>  		return 0;
>  
>  	if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) {
> -		if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) &&
> -		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
> +		if (skb_has_ipv6_extension_hdr(skb))
>  			goto sw_checksum;
>  
>  		switch (skb->csum_offset) {

[Severity: Medium]
skb_has_ipv6_extension_hdr() checks the inner IPv6 header whenever
skb->encapsulation and the inner network header are set. That is right
for gso_features_check(), where the device computes the inner checksum
of every segment. Here it is applied regardless of which checksum is
being offloaded.

The commit message describes the case where this matters: if the inner
packet is CHECKSUM_NONE, the device offloads the outer UDP checksum.
csum_start then points at the outer UDP header and the device does not
need to parse any further.

Suppose the inner packet is IPv6 with, say, a Destination Options
header. The inner check still returns true, and the outer checksum is
now computed in software:

validate_xmit_skb()
  skb_csum_hwoffload_help()
    skb_has_ipv6_extension_hdr()
      __skb_has_ipv6_ext_hdr(skb, skb_inner_network_offset(skb))
                          /* inner nexthdr == NEXTHDR_DEST */
    skb_checksum_help()   /* outer UDP csum in software */

Before this patch, skb_csum_hwoffload_help() only compared the outer
network header length, so the outer checksum was offloaded.

The same regression affects an IPv4 outer header on a NETIF_F_IP_CSUM
device. The old test never looked at those packets, because
vlan_get_protocol() returned ETH_P_IP.

The output is still correct, so this is a performance issue. Whether
it triggers depends on the contents of the inner packet.

For non-GSO skbs that are not CHECKSUM_PARTIAL,
iptunnel_handle_offloads() clears skb->encapsulation. That likely hides
this for the common in-tree tunnels, but the helper should not depend
on it.

Could the inner check be skipped here when the checksum being
offloaded is not the inner one? For example, it could be skipped when
skb_checksum_start_offset(skb) is below skb_inner_network_offset(skb).

Note that validate_xmit_skb() sets inner_transport_header to csum_start
just before calling skb_csum_hwoffload_help(). That field therefore
cannot tell the two cases apart.

[Severity: Low]

The NETIF_F_IPV6_CSUM description in include/linux/skbuff.h still says:

 *     - Driver (device) is only able to checksum plain
 *	 TCP or UDP packets over IPv6. These are specifically
 *	 unencapsulated packets of the form IPv6|TCP or
 *	 IPv6|UDP where the Next Header field in the IPv6
 *	 header is either TCP or UDP. IPv6 extension headers
 *	 are not supported with this feature.

skb_has_ipv6_extension_hdr() rejects only extension headers in the outer
and first inner IPv6 header. It also skips the length check when
skb->encapsulation is set. As a result, skb_csum_hwoffload_help() returns
0 for encapsulated packets whose outer nexthdr is GRE, UDP or IPv6.

Encapsulated GRE and UDP over IPv6 could already be offloaded before this
patch. The new case is IPv6|IPv6|TCP from ip6_tunnel with encaplimit
none. The old network-to-transport length check sent it to
skb_checksum_help(). Now it goes to the device.

The commit message says:

  A NETIF_F_IPV6_CSUM device must parse through the outer headers to
  reach the inner ones.

Should this requirement also go into the NETIF_F_IPV6_CSUM documentation
in skbuff.h? Driver authors who set the bit in hw_enc_features would then
know about it.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926140506.2335137-1-willemdebruijn.kernel%40gmail.com

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets
  2026-09-29  0:07 ` netdev-bot+sashiko
@ 2026-09-29  3:23   ` Willem de Bruijn
  0 siblings, 0 replies; 4+ messages in thread
From: Willem de Bruijn @ 2026-09-29  3:23 UTC (permalink / raw)
  To: netdev-bot+sashiko, willemdebruijn.kernel
  Cc: netdev, davem, kuba, edumazet, pabeni, horms, andrew+netdev,
	xietangxin, willemb, stable

netdev-bot+sashiko@ wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 0 · Medium: 1 · Low: 2
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [Medium] net: unnecessary software checksum fallback for outer UDP
>   tunnels
> - [Low] The old non-encapsulated branch cleared features only for
>   SKB_GSO_TCPV6, or for SKB_GSO_UDP_L4 over IPv6.
> - [Low] The in-tree NETIF_F_IPV6_CSUM contract
>   (include/linux/skbuff.h:77-86) still says the feature covers only…
> 
> --
> 
> > +static bool skb_has_ipv6_extension_hdr(const struct sk_buff *skb)
> > +{
> > +	if (vlan_get_protocol(skb) == htons(ETH_P_IPV6)) {
> > +		if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb)))
> > +			return true;
> 
> [Severity: Low]
> 
> The removed non-encapsulated branch cleared features only for
> SKB_GSO_TCPV6, or for SKB_GSO_UDP_L4 over IPv6. The new outer check has no
> gso_type gate. Is it meant to apply to every IPv6 GSO type?
> 
> SKB_GSO_SCTP is one type whose behaviour changes. sctp_packet_pack()
> always makes the GSO head CHECKSUM_PARTIAL:
> 
> net/sctp/output.c:sctp_packet_pack() {
>     ...
> 	if (gso) {
> 		...
> 		goto chksum;
> 	}
>     ...
> chksum:
> 		head->ip_summed = CHECKSUM_PARTIAL;
> 		head->csum_not_inet = 1;
>     ...
> }
> 
> Take SCTP over IPv6 with IPV6_DSTOPTS or IPV6_RTHDR set.
> gso_features_check() now clears NETIF_F_IPV6_CSUM. harmonize_features()
> then sees ip_summed != CHECKSUM_NONE and
> !can_checksum_protocol(features, ETH_P_IPV6). It strips all of
> NETIF_F_CSUM_MASK, including NETIF_F_SCTP_CRC, plus NETIF_F_GSO_MASK:

NETIF_F_SCTP_CRC is not part of NETIF_F_CSUM_MASK:

#define NETIF_F_CSUM_MASK       (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | \
                                 NETIF_F_HW_CSUM)

 > validate_xmit_skb()
>   netif_skb_features()
>     gso_features_check()    /* clears NETIF_F_IPV6_CSUM */
>     harmonize_features()    /* clears NETIF_F_CSUM_MASK, NETIF_F_GSO_MASK */
>   skb_gso_segment()
>     sctp_gso_segment()      /* CRC32c computed in software per segment */
> 
> Some devices have NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_SCTP_CRC
> but no NETIF_F_HW_CSUM. One example is hns3 without
> HNAE3_DEV_SUPPORT_HW_TX_CSUM_B. On such a device the CRC32c was offloaded
> before this patch and is computed in software after it. The output is
> still correct, so only performance is affected.
> 
> Could the gso_type restriction be kept here? If not, could the commit
> message say that the check now covers all IPv6 GSO types?
> 
> > +
> > +		/* Catch tunnels without skb->encapsulation (e.g., virtio). */
> > +		if (!skb->encapsulation &&
> > +		    skb_transport_header_was_set(skb) &&
> > +		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
> > +			return true;
> > +	}
> 
> [ ... ]
> 
> > @@ -3988,8 +4004,7 @@ int skb_csum_hwoffload_help(struct sk_buff *skb,
> >  		return 0;
> >  
> >  	if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) {
> > -		if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) &&
> > -		    skb_network_header_len(skb) != sizeof(struct ipv6hdr))
> > +		if (skb_has_ipv6_extension_hdr(skb))
> >  			goto sw_checksum;
> >  
> >  		switch (skb->csum_offset) {
> 
> [Severity: Medium]
> skb_has_ipv6_extension_hdr() checks the inner IPv6 header whenever
> skb->encapsulation and the inner network header are set. That is right
> for gso_features_check(), where the device computes the inner checksum
> of every segment. Here it is applied regardless of which checksum is
> being offloaded.
> 
> The commit message describes the case where this matters: if the inner
> packet is CHECKSUM_NONE, the device offloads the outer UDP checksum.
> csum_start then points at the outer UDP header and the device does not
> need to parse any further.
> 
> Suppose the inner packet is IPv6 with, say, a Destination Options
> header. The inner check still returns true, and the outer checksum is
> now computed in software:
> 
> validate_xmit_skb()
>   skb_csum_hwoffload_help()
>     skb_has_ipv6_extension_hdr()
>       __skb_has_ipv6_ext_hdr(skb, skb_inner_network_offset(skb))
>                           /* inner nexthdr == NEXTHDR_DEST */
>     skb_checksum_help()   /* outer UDP csum in software */
> 
> Before this patch, skb_csum_hwoffload_help() only compared the outer
> network header length, so the outer checksum was offloaded.
> 
> The same regression affects an IPv4 outer header on a NETIF_F_IP_CSUM
> device. The old test never looked at those packets, because
> vlan_get_protocol() returned ETH_P_IP.
> 
> The output is still correct, so this is a performance issue. Whether
> it triggers depends on the contents of the inner packet.
> 
> For non-GSO skbs that are not CHECKSUM_PARTIAL,
> iptunnel_handle_offloads() clears skb->encapsulation. That likely hides
> this for the common in-tree tunnels, but the helper should not depend
> on it.

This is the key bit: tunnels disable skb->encapsulation when
checksumming of outer packet is requested.

This is not only something the "common in-tree tunnels" do, but
required.

For one, validate_xmit_skb sets either the inner or outer transport
header to csum_start depending on skb->encapsulation just before
calling this function.

        if (skb->ip_summed == CHECKSUM_PARTIAL) {
                if (skb->encapsulation)
                        skb_set_inner_transport_header(skb,
                                                       skb_checksum_start_offset(skb));
                else
                        skb_set_transport_header(skb,
                                                 skb_checksum_start_offset(skb));
                if (skb_csum_hwoffload_help(skb, features))
                        goto out_kfree_skb;
        }

 
> Could the inner check be skipped here when the checksum being
> offloaded is not the inner one? For example, it could be skipped when
> skb_checksum_start_offset(skb) is below skb_inner_network_offset(skb).
> 
> Note that validate_xmit_skb() sets inner_transport_header to csum_start
> just before calling skb_csum_hwoffload_help(). That field therefore
> cannot tell the two cases apart.
> 
> [Severity: Low]
> 
> The NETIF_F_IPV6_CSUM description in include/linux/skbuff.h still says:
> 
>  *     - Driver (device) is only able to checksum plain
>  *	 TCP or UDP packets over IPv6. These are specifically
>  *	 unencapsulated packets of the form IPv6|TCP or
>  *	 IPv6|UDP where the Next Header field in the IPv6
>  *	 header is either TCP or UDP. IPv6 extension headers
>  *	 are not supported with this feature.
> 
> skb_has_ipv6_extension_hdr() rejects only extension headers in the outer
> and first inner IPv6 header. It also skips the length check when
> skb->encapsulation is set. As a result, skb_csum_hwoffload_help() returns
> 0 for encapsulated packets whose outer nexthdr is GRE, UDP or IPv6.
> 
> Encapsulated GRE and UDP over IPv6 could already be offloaded before this
> patch. The new case is IPv6|IPv6|TCP from ip6_tunnel with encaplimit
> none. The old network-to-transport length check sent it to
> skb_checksum_help(). Now it goes to the device.
> 
> The commit message says:
> 
>   A NETIF_F_IPV6_CSUM device must parse through the outer headers to
>   reach the inner ones.
> 
> Should this requirement also go into the NETIF_F_IPV6_CSUM documentation
> in skbuff.h? Driver authors who set the bit in hw_enc_features would then
> know about it.
> 
> -- 
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260926140506.2335137-1-willemdebruijn.kernel%40gmail.com



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets
  2026-09-26 14:04 [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets Willem de Bruijn
  2026-09-29  0:07 ` netdev-bot+sashiko
@ 2026-09-30  1:20 ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 4+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-30  1:20 UTC (permalink / raw)
  To: Willem de Bruijn
  Cc: netdev, davem, kuba, edumazet, pabeni, horms, andrew+netdev,
	xietangxin, willemb, stable

Hello:

This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Sat, 26 Sep 2026 10:04:54 -0400 you wrote:
> From: Willem de Bruijn <willemb@google.com>
> 
> Commit c4336a07eb6b ("net: correctly handle tunneled traffic on IPV6_CSUM
> GSO fallback") split skb_gso_has_extension_hdr() into mutually exclusive
> branches on skb->encapsulation. This did not yet address all paths:
> 
> 1. With skb->encapsulation set, the outer header is not checked. IPv6
>    tunnels such as ip6_gre and ip6_tunnel add an outer Destination
>    Options header by default (encap_limit). Their GSO packets skip
>    software GSO, then skb_csum_hwoffload_help() sees the outer extension
>    header and calls skb_checksum_help() on the GSO skb, which warns and
>    drops it.
> 
> [...]

Here is the summary with links:
  - [net,v2] net: extend IPv6 exthdr detection of tunneled packets
    https://git.kernel.org/netdev/net/c/5f0764cb1c81

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-30  1:20 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-26 14:04 [PATCH net v2] net: extend IPv6 exthdr detection of tunneled packets Willem de Bruijn
2026-09-29  0:07 ` netdev-bot+sashiko
2026-09-29  3:23   ` Willem de Bruijn
2026-09-30  1:20 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox