Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE
@ 2026-10-31 22:50 Andrea Mayer
  2026-10-31 22:50 ` [PATCH net 1/6] seg6: fix skb checksum update helpers Andrea Mayer
                   ` (5 more replies)
  0 siblings, 6 replies; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

This series fixes the skb->csum updates with CHECKSUM_COMPLETE when the
SRv6 code processes a received packet. In several places skb->csum is not
updated correctly:
 - ipv6_srh_rcv() updates skb->csum with the wrong sign for the new
   Segments Left and destination address, and not at all for the Hop Limit
   decrement.
 - advance_nextseg() and seg6_next_csid_advance_arg() in seg6_local do
   not update skb->csum.
 - The End.BPF program and seg6_bpf_has_valid_srh() can change the
   packet without updating skb->csum.
 - In L2ENCAP and L2ENCAP_RED mode, the MAC header put back in front of
   the frame is not added to skb->csum.

When the packet is then delivered on the same node, the effect depends on
who computed skb->csum. With skb->csum computed in software, for example by
conntrack, UDP can drop the packet as a checksum error. With skb->csum
computed by the device, TCP can log a "hw csum failure", as if the device
had computed a wrong skb->csum. In the L2ENCAP case skb->csum can be read
on local delivery, for example by a raw socket with IPV6_CHECKSUM.

The series is organized as follows:
 - Patch 1/6 fixes the update_csum_diff4() and update_csum_diff16()
   helpers.
 - Patch 2/6 updates skb->csum for the Hop Limit decrement in
   ipv6_srh_rcv().
   It depends on patch 1/6.
 - Patch 3/6 updates skb->csum when SRv6 behaviors advance to the next SID.
   It depends on patch 1/6.
 - Patch 4/6 discards CHECKSUM_COMPLETE after the End.BPF program.
 - Patch 5/6 accounts for the MAC header in skb->csum on L2 encapsulation.
 - Patch 6/6 adds a selftest that uses a tc ct action to compute skb->csum.
   It covers the paths of patches 1/6 to 4/6, and a control datagram
   checks that the test detects a wrong skb->csum.

Tested with the srv6_*.sh selftests, including the new one, and with the
lwt_seg6local BPF selftest. The series was also tested with a ConnectX-6 Lx
NIC (mlx5) that provides CHECKSUM_COMPLETE. Without the series, TCP over
the SRv6 paths of patches 1/6 to 4/6 can log "hw csum failure". The same
happens for a raw socket with IPV6_CHECKSUM, on a packet delivered locally
after L2ENCAP. With this series, none of this happens.

Thanks,
Andrea

Andrea Mayer (6):
  seg6: fix skb checksum update helpers
  seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit
  seg6: update skb checksum when SRv6 behaviors advance to the next SID
  seg6: discard CHECKSUM_COMPLETE after the End.BPF program
  seg6: account for the MAC header in skb checksum on L2 encapsulation
  selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE

 include/net/seg6.h                            |   4 +-
 net/ipv6/exthdrs.c                            |   8 +
 net/ipv6/seg6_iptunnel.c                      |   2 +-
 net/ipv6/seg6_local.c                         |  24 +-
 tools/testing/selftests/net/Makefile          |   1 +
 .../selftests/net/srv6_csum_complete.bpf.c    |  65 ++
 .../selftests/net/srv6_csum_complete_test.sh  | 642 ++++++++++++++++++
 7 files changed, 742 insertions(+), 4 deletions(-)
 create mode 100644 tools/testing/selftests/net/srv6_csum_complete.bpf.c
 create mode 100755 tools/testing/selftests/net/srv6_csum_complete_test.sh

-- 
2.43.0


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

* [PATCH net 1/6] seg6: fix skb checksum update helpers
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  1:25   ` Hangbin Liu
  2026-10-31 22:50 ` [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit Andrea Mayer
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

With CHECKSUM_COMPLETE, ipv6_srh_rcv() updates skb->csum for the new
Segments Left and destination address through seg6_update_csum().
Replacing "from" with "to" in the packet must change skb->csum (C) to
C - from + to, but update_csum_diff4() and update_csum_diff16() compute:

  ~csum_partial({ ~from, to }, ~C) = ~(~C + ~from + to) = C + from - to

This does not affect packets forwarded by ip6_forward(), which sets
ip_summed to CHECKSUM_NONE. But when the packet is delivered on the same
node and skb->csum was computed in software, for instance by conntrack, UDP
can drop the packet as a checksum error. With skb->csum computed by the
device, TCP can instead log a "hw csum failure", as if the device had
computed a wrong skb->csum.

Fix update_csum_diff4() and update_csum_diff16() to compute C - from + to.

Found by manual code inspection, while checking that the skb->csum updates
in the SRv6 receive path are consistent. A following commit adds a selftest
that reproduces the problem with a tc ct action. The issue was confirmed in
a VM without kernel changes or error injection: with the rest of this
series and without this patch, that selftest fails, as UDP drops datagrams
as checksum errors. Also tested against the srv6 selftests, and with a
ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.

Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)")
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 include/net/seg6.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/net/seg6.h b/include/net/seg6.h
index 82b3fbbcbb93..01d8034e48fc 100644
--- a/include/net/seg6.h
+++ b/include/net/seg6.h
@@ -19,7 +19,7 @@ static inline void update_csum_diff4(struct sk_buff *skb, __be32 from,
 {
 	__be32 diff[] = { ~from, to };
 
-	skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
+	skb->csum = csum_partial(diff, sizeof(diff), skb->csum);
 }
 
 static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
@@ -30,7 +30,7 @@ static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
 		to[0], to[1], to[2], to[3],
 	};
 
-	skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
+	skb->csum = csum_partial(diff, sizeof(diff), skb->csum);
 }
 
 struct seg6_pernet_data {
-- 
2.43.0


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

* [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
  2026-10-31 22:50 ` [PATCH net 1/6] seg6: fix skb checksum update helpers Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  1:27   ` Hangbin Liu
  2026-10-31 22:50 ` [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID Andrea Mayer
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

When the next segment is a local address, ipv6_srh_rcv() processes it
directly and also decrements the Hop Limit. With CHECKSUM_COMPLETE,
skb->csum is not updated for this change. If the packet is then delivered
on the same node and skb->csum was computed in software, for instance by
conntrack, UDP can drop the packet as a checksum error. With skb->csum
computed by the device, TCP can instead log a "hw csum failure", as if the
device had computed a wrong skb->csum.

Update skb->csum for the Hop Limit decrement with update_csum_diff4().

Depends on patch "seg6: fix skb checksum update helpers".

Found by manual code inspection, and reproduced in a VM without kernel
changes or error injection. A following commit adds a selftest that
reproduces the problem with a tc ct action. With this patch, the selftest
passes for the path with local SRH segments. Also tested against the srv6
selftests, and with a ConnectX-6 Lx NIC (mlx5) that provides
CHECKSUM_COMPLETE.

Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)")
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 net/ipv6/exthdrs.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 96c8402a27a5..9d3ba310065a 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -460,6 +460,10 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev)
 	}
 
 	if (skb_dst_dev(skb)->flags & IFF_LOOPBACK) {
+		/* payload_len, nexthdr and hop_limit */
+		__be32 *hlim_word = (__be32 *)ipv6_hdr(skb) + 1;
+		__be32 old;
+
 		if (ipv6_hdr(skb)->hop_limit <= 1) {
 			__IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
 			icmpv6_send(skb, ICMPV6_TIME_EXCEED,
@@ -467,8 +471,12 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev)
 			kfree_skb_reason(skb, SKB_DROP_REASON_IP_TTL_EXCEEDED);
 			return -1;
 		}
+		old = *hlim_word;
 		ipv6_hdr(skb)->hop_limit--;
 
+		if (skb->ip_summed == CHECKSUM_COMPLETE)
+			update_csum_diff4(skb, old, *hlim_word);
+
 		skb_pull(skb, skb_transport_offset(skb));
 		goto looped_back;
 	}
-- 
2.43.0


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

* [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
  2026-10-31 22:50 ` [PATCH net 1/6] seg6: fix skb checksum update helpers Andrea Mayer
  2026-10-31 22:50 ` [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  2:15   ` Hangbin Liu
  2026-10-31 22:50 ` [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program Andrea Mayer
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

advance_nextseg() decrements Segments Left and writes the next segment into
the destination address, and seg6_next_csid_advance_arg() advances the
NEXT-C-SID argument in the destination address.
With CHECKSUM_COMPLETE, neither updates skb->csum for these changes. The
stale skb->csum is read when the packet is delivered on the same node, e.g.
through End.B6.Encaps towards a local End.DT6 SID.
As a consequence, when skb->csum is computed in software, for instance by
conntrack, UDP can drop the packet as a checksum error. With skb->csum
computed by the device, TCP can instead log a "hw csum failure", as if the
device had computed a wrong skb->csum.

Update skb->csum in both functions with update_csum_diff16() for the
destination address. advance_nextseg() also uses update_csum_diff4() for
Segments Left.

Depends on patch "seg6: fix skb checksum update helpers".

Found by manual code inspection while working on
commit 8ae3f4067ba4 ("seg6: ensure packet data is writable before modifying
SRH and IPv6 DA"), and reproduced in a VM without kernel changes or error
injection. A following commit adds a selftest that reproduces the problem
with a tc ct action. With this patch, the selftest passes for the
End.B6.Encaps and next-csid paths. Also tested against the srv6 selftests,
and with a ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.

Fixes: 140f04c33bbc ("ipv6: sr: implement several seg6local actions")
Fixes: 848f3c0d4769 ("seg6: add NEXT-C-SID support for SRv6 End behavior")
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 net/ipv6/seg6_local.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 0c3d1be7f129..57a82477ce3f 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -291,7 +291,9 @@ static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb,
 					   struct ipv6_sr_hdr *srh,
 					   enum skb_drop_reason *reason)
 {
+	struct in6_addr *daddr;
 	struct in6_addr *addr;
+	__be32 old;
 	int srhoff;
 	int wlen;
 
@@ -309,10 +311,19 @@ static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb,
 
 	/* skb_ensure_writable() may change skb pointers; evaluate srh again */
 	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
+	daddr = &ipv6_hdr(skb)->daddr;
+	/* nexthdr, hdrlen, type and segments_left */
+	old = *(__be32 *)srh;
 
 	srh->segments_left--;
 	addr = srh->segments + srh->segments_left;
-	ipv6_hdr(skb)->daddr = *addr;
+
+	if (skb->ip_summed == CHECKSUM_COMPLETE) {
+		update_csum_diff4(skb, old, *(__be32 *)srh);
+		update_csum_diff16(skb, (__be32 *)daddr, (__be32 *)addr);
+	}
+
+	*daddr = *addr;
 
 	return srh;
 }
@@ -427,11 +438,13 @@ seg6_next_csid_advance_arg(struct sk_buff *skb,
 	__u8 fnc_octects = seg6_flv_lcnode_func_octects(finfo);
 	__u8 blk_octects = seg6_flv_lcblock_octects(finfo);
 	struct in6_addr *addr;
+	struct in6_addr old;
 
 	if (unlikely(skb_ensure_writable(skb, sizeof(struct ipv6hdr))))
 		return SKB_DROP_REASON_NOMEM;
 
 	addr = &ipv6_hdr(skb)->daddr;
+	old = *addr;
 
 	/* advance DA.Argument */
 	memmove(&addr->s6_addr[blk_octects],
@@ -440,6 +453,9 @@ seg6_next_csid_advance_arg(struct sk_buff *skb,
 
 	memset(&addr->s6_addr[16 - fnc_octects], 0x00, fnc_octects);
 
+	if (skb->ip_summed == CHECKSUM_COMPLETE)
+		update_csum_diff16(skb, (__be32 *)&old, (__be32 *)addr);
+
 	return SKB_NOT_DROPPED_YET;
 }
 
-- 
2.43.0


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

* [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
                   ` (2 preceding siblings ...)
  2026-10-31 22:50 ` [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  2:28   ` Hangbin Liu
  2026-10-31 22:50 ` [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation Andrea Mayer
  2026-10-31 22:50 ` [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE Andrea Mayer
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

The End.BPF program can change the packet through the seg6 BPF helpers,
for instance the SRH Tag with bpf_lwt_seg6_store_bytes(). After the
program, seg6_bpf_has_valid_srh() can also rewrite the SRH Hdr Ext Len.

With CHECKSUM_COMPLETE, the program does not always update skb->csum for
the bytes it writes, and seg6_bpf_has_valid_srh() does not either. If the
packet is then delivered on the same node and skb->csum was computed in
software, for instance by conntrack, UDP can drop the packet as a checksum
error. With skb->csum computed by the device, TCP can instead log a
"hw csum failure", as if the device had computed a wrong skb->csum.

Discard CHECKSUM_COMPLETE with skb_checksum_complete_unset() after the
program, since input_action_end_bpf() does not know which bytes the program
changed. The checksum is then verified in software.

Found by manual code inspection and reproduced in a VM without kernel
changes or error injection: with skb->csum computed by a tc ct action, an
End.BPF program that writes the SRH Tag, followed by End.B6.Encaps and a
local End.DT6 SID, makes UDP drop the packet. With this patch, the packet
is delivered. Also tested against the lwt_seg6local BPF selftest, and with
a ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.

Fixes: 004d4b274e2a ("ipv6: sr: Add seg6local action End.BPF")
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 net/ipv6/seg6_local.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 57a82477ce3f..3045a351f4e9 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -1582,6 +1582,12 @@ static int input_action_end_bpf(struct sk_buff *skb,
 		goto drop;
 	}
 
+	/* The program may have changed the packet and seg6_bpf_has_valid_srh()
+	 * may rewrite the SRH Hdr Ext Len below.
+	 * skb->csum may then no longer be the checksum of the full packet.
+	 */
+	skb_checksum_complete_unset(skb);
+
 	if (srh_state->srh && !seg6_bpf_has_valid_srh(skb))
 		goto drop;
 	local_unlock_nested_bh(&seg6_bpf_srh_states.bh_lock);
-- 
2.43.0


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

* [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
                   ` (3 preceding siblings ...)
  2026-10-31 22:50 ` [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  2:37   ` Hangbin Liu
  2026-10-31 22:50 ` [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE Andrea Mayer
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

In L2ENCAP and L2ENCAP_RED mode, seg6_do_srh() puts the MAC header back
in front of the frame with skb_push(), without adding it to skb->csum.
The encapsulation then calls skb_postpush_rcsum() only for the outer
headers. With CHECKSUM_COMPLETE the MAC header is missing from
skb->csum, which no longer covers the whole packet as required.

For a forwarded packet, ip6_forward() sets ip_summed to CHECKSUM_NONE with
skb_forward_csum(), and End.DX2 does the same before sending the frame.
When the packet is delivered on the same node, a raw socket with
IPV6_CHECKSUM still reads skb->csum. With skb->csum computed by the device,
the kernel can then log a "hw csum failure", as if the device had computed
a wrong skb->csum.

Use skb_push_rcsum(), which also calls skb_postpush_rcsum() for the MAC
header.

Found by manual code inspection of the skb->csum updates in the SRv6 code,
and observed in a VM without kernel changes or error injection: with
skb->csum computed by a tc ct action, a BPF tracing program on
ip6_forward() shows skb->csum missing the MAC header. With this patch,
skb->csum covers it. Also tested against the srv6 selftests, and with a
ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.

Fixes: 38ee7f2d4756 ("ipv6: sr: add support for encapsulation of L2 frames")
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 net/ipv6/seg6_iptunnel.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c
index 0d39a38a5dd2..3821d8e1409e 100644
--- a/net/ipv6/seg6_iptunnel.c
+++ b/net/ipv6/seg6_iptunnel.c
@@ -456,7 +456,7 @@ static int seg6_do_srh(struct sk_buff *skb, struct dst_entry *cache_dst)
 			return err;
 
 		skb_mac_header_rebuild(skb);
-		skb_push(skb, skb->mac_len);
+		skb_push_rcsum(skb, skb->mac_len);
 
 		if (tinfo->mode == SEG6_IPTUN_MODE_L2ENCAP)
 			err = __seg6_do_srh_encap(skb, tinfo->srh,
-- 
2.43.0


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

* [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE
  2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
                   ` (4 preceding siblings ...)
  2026-10-31 22:50 ` [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation Andrea Mayer
@ 2026-10-31 22:50 ` Andrea Mayer
  2026-11-03  3:26   ` Hangbin Liu
  5 siblings, 1 reply; 13+ messages in thread
From: Andrea Mayer @ 2026-10-31 22:50 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan
  Cc: linux-kernel, netdev, linux-kselftest, bpf, David Lebrun,
	Daniel Borkmann, Mathieu Xhonneux, David Lebrun, Hangbin Liu,
	Stefano Salsano, Andrea Mayer, Andrea Mayer

Add a selftest for received packets with CHECKSUM_COMPLETE that the SRv6
code processes before they are delivered on the same node. The test checks
that, after the node changes the packet, UDP does not read a wrong
skb->csum.

The test uses a host and a router connected through a veth pair. Since veth
does not provide CHECKSUM_COMPLETE, a tc ct action on the router computes
skb->csum in software.

The host sends a UDP datagram to the router along five paths: no SRH (the
reference), an SRH with local segments, End.B6.Encaps with End.DT6, End
with the next-csid flavor, and End.BPF. Each datagram is expected to be
delivered, not dropped as a checksum error. Without the previous patches,
every path except the reference fails. With them, every path passes.

The host also sends a control datagram. It checks that the test detects a
wrong skb->csum after a change to the packet. After the ct action, a BPF
program on the router changes the Flow Label of the control datagram
without updating skb->csum, and UDP must drop it. If UDP accepts it, the
paths above may report a false pass, and the test fails.

Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
 tools/testing/selftests/net/Makefile          |   1 +
 .../selftests/net/srv6_csum_complete.bpf.c    |  65 ++
 .../selftests/net/srv6_csum_complete_test.sh  | 642 ++++++++++++++++++
 3 files changed, 708 insertions(+)
 create mode 100644 tools/testing/selftests/net/srv6_csum_complete.bpf.c
 create mode 100755 tools/testing/selftests/net/srv6_csum_complete_test.sh

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index df215e1a6262..16e9fbf889fd 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -92,6 +92,7 @@ TEST_PROGS := \
 	rxtimestamp.sh \
 	sctp_vrf.sh \
 	skf_net_off.sh \
+	srv6_csum_complete_test.sh \
 	srv6_encap_lookup_l3vpn_test.sh \
 	srv6_end_dt46_l3vpn_test.sh \
 	srv6_end_dt4_l3vpn_test.sh \
diff --git a/tools/testing/selftests/net/srv6_csum_complete.bpf.c b/tools/testing/selftests/net/srv6_csum_complete.bpf.c
new file mode 100644
index 000000000000..281b0361f976
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_csum_complete.bpf.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Andrea Mayer <andrea.mayer@uniroma2.it>
+ */
+#include <stddef.h>
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/seg6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+/* On the tc ingress, skb->data points to the Ethernet header */
+#define TC_IPV6_OFF		ETH_HLEN
+/* Byte with the lowest 8 bits of the Flow Label */
+#define TC_FLOW_LBL_OFF		(TC_IPV6_OFF + \
+				 offsetof(struct ipv6hdr, flow_lbl) + 2)
+/* Bit of the Flow Label changed in the control datagram */
+#define CTRL_FLOW_LBL_BIT	0x01
+
+/* In End.BPF, skb->data points to the IPv6 header, followed by the SRH */
+#define SRH_TAG_OFF		(sizeof(struct ipv6hdr) + \
+				 offsetof(struct ipv6_sr_hdr, tag))
+/* Value written in the SRH Tag, test case (vi) */
+#define SRH_TAG			0x1234
+
+/* Test case (v) of srv6_csum_complete_test.sh. The program runs on the tc
+ * ingress of rt, after the ct action, only for the control datagram. It
+ * changes one bit of the Flow Label, without updating the receive checksum
+ * (skb->csum). The Flow Label is not part of the UDP checksum, which stays
+ * correct. UDP must drop the datagram because the receive checksum no longer
+ * matches the packet.
+ */
+SEC("tc")
+int ctrl_flow_label(struct __sk_buff *skb)
+{
+	__u8 lbl;
+
+	/* Read the byte with the lowest 8 bits of the Flow Label */
+	if (bpf_skb_load_bytes(skb, TC_FLOW_LBL_OFF, &lbl, sizeof(lbl)))
+		return TC_ACT_SHOT;
+
+	lbl ^= CTRL_FLOW_LBL_BIT;
+
+	/* Write the byte back, flags 0 do not update the receive checksum */
+	if (bpf_skb_store_bytes(skb, TC_FLOW_LBL_OFF, &lbl, sizeof(lbl), 0))
+		return TC_ACT_SHOT;
+
+	return TC_ACT_OK;
+}
+
+/* Test case (vi), the End.BPF program writes the SRH Tag */
+SEC("lwt_seg6local")
+int end_bpf_tag(struct __sk_buff *skb)
+{
+	__u16 tag = bpf_htons(SRH_TAG);
+
+	if (bpf_lwt_seg6_store_bytes(skb, SRH_TAG_OFF, &tag, sizeof(tag)))
+		return BPF_DROP;
+
+	return BPF_OK;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/net/srv6_csum_complete_test.sh b/tools/testing/selftests/net/srv6_csum_complete_test.sh
new file mode 100755
index 000000000000..0b9c92931362
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_csum_complete_test.sh
@@ -0,0 +1,642 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Andrea Mayer <andrea.mayer@uniroma2.it>
+
+# This test checks the receive checksum of an SRv6 packet that is processed and
+# then delivered on the same node. The node changes the Segments Left and the
+# destination address of the packet, and can also change the Hop Limit.
+#
+# The receive checksum (CHECKSUM_COMPLETE) is the checksum of the whole packet,
+# kept with the received packet. When the node changes the packet, it must also
+# update this sum, or discard it.
+#
+# Network topology
+# ================
+#
+# The test uses a host (hs) and a router (rt) connected through a veth pair. In
+# each test case, hs sends one UDP datagram to an address of rt.
+#
+# +--------------------+                    +--------------------------+
+# |        hs          |                    |           rt             |
+# |                    |                    |                          |
+# |  veth-hs           +--------------------+ veth-rt                  |
+# |  fd00::1/64        |                    | fd00::2/64               |
+# |  tx checksum off   |                    | tc ingress: ct, then bpf |
+# +--------------------+                    |                          |
+#                                           | lo: fc00::1 ... fc00::4  |
+#                                           |     cafe::1 ... cafe::6  |
+#                                           +--------------------------+
+#
+# Receive checksum
+# ================
+#
+# veth does not provide the receive checksum. For this reason, a tc ct action
+# runs on the ingress of rt. The ct action checks the UDP checksum, and in this
+# way computes the receive checksum in software. If the receive checksum does
+# not match the packet, UDP drops the datagram as a checksum error.
+#
+# The ct action computes the receive checksum when hs computes the UDP checksum
+# in software, before the datagram crosses the veth. For this reason, hs has
+# transmit checksumming off.
+#
+# Each datagram is longer than 76 bytes, UDP header included. For datagrams of
+# 76 bytes or less, UDP computes the checksum again from the packet, and accepts
+# the datagram even with a wrong receive checksum.
+#
+# UDP counters
+# ============
+#
+# Each datagram goes to a port with no listener. If the UDP checksum check
+# passes, rt counts the datagram in Udp6NoPorts. If it fails, rt counts it in
+# Udp6InCsumErrors.
+#
+# Control datagram
+# ================
+#
+# A control datagram checks that the test detects a wrong receive checksum after
+# a change to the packet. After the ct action, a tc bpf filter on rt changes one
+# bit of its Flow Label, without updating the receive checksum. The Flow Label
+# is not part of the UDP checksum, which stays correct. UDP must drop the
+# datagram because the receive checksum no longer matches the packet. If UDP
+# accepts it, the test cannot detect a wrong receive checksum. The other test
+# cases may then pass even when their receive checksum is wrong, and for this
+# reason the test fails.
+#
+# Routes and tc filters
+# =====================
+#
+# hs: encap (main table)
+# +---------+-----------------------------------------------------+
+# | dst     | action                                              |
+# +---------+-----------------------------------------------------+
+# | cafe::1 | via fd00::2                                         |
+# | cafe::2 | encap seg6 mode inline segs fc00::1,fc00::2         |
+# | cafe::3 | encap seg6 mode inline segs fcbb:0:0200::           |
+# | cafe::4 | encap seg6 mode inline segs fcbb:0:0100:0200::      |
+# | cafe::5 | encap seg6 mode inline segs fc00::3,fc00::4         |
+# | cafe::6 | encap seg6 mode inline segs fc00::b0,fcbb:0:0200::  |
+# +---------+-----------------------------------------------------+
+#
+# rt: SRv6 behaviors (main table)
+# +------------------+------------------------------------------------+
+# | SID              | action                                         |
+# +------------------+------------------------------------------------+
+# | fcbb:0:0100::/48 | End flavors next-csid lblen 32 nflen 16        |
+# | fcbb:0:0200::/48 | End.B6.Encaps srh segs fc00::d6                |
+# | fc00::d6         | End.DT6 table 255 (local)                      |
+# | fc00::b0         | End.BPF, the program writes the SRH Tag        |
+# +------------------+------------------------------------------------+
+#
+# rt: tc ingress of veth-rt
+# +-------+------+---------------------------------------------------+
+# | chain | prio | filter                                            |
+# +-------+------+---------------------------------------------------+
+# | 0     | 1    | matchall, action ct, goto chain 1                 |
+# | 1     | 1    | flower dst_ip fc00::3, goto chain 2               |
+# | 2     | 1    | bpf, changes one bit of the Flow Label            |
+# +-------+------+---------------------------------------------------+
+#
+# Test cases
+# ==========
+#
+# hs applies the SRv6 Policies in inline mode, i.e. the original destination
+# becomes the last SID of the SID List.
+#
+#   i) cafe::1, no SRH, used as a reference.
+#
+#  ii) cafe::2, SID List=fc00::1,fc00::2,cafe::2
+#      fc00::1 and fc00::2 are addresses of rt. rt processes the SRH twice. Each
+#      time, it also decrements the Hop Limit.
+#
+# iii) cafe::3, SID List=fcbb:0:0200::,cafe::3
+#      End.B6.Encaps advances the SRH and adds an outer IPv6 header with an SRH.
+#      The outer header goes to the End.DT6 SID fc00::d6, which removes it.
+#
+#  iv) cafe::4, SID List=fcbb:0:0100:0200::,cafe::4
+#      End with the NEXT-C-SID flavor changes the DA to the next C-SID,
+#      fcbb:0:0200::. Then the datagram follows the same path as in (iii).
+#
+#   v) cafe::5, SID List=fc00::3,fc00::4,cafe::5, control.
+#      The tc flower filter matches the first segment, fc00::3, and the tc bpf
+#      filter changes the Flow Label. Then rt processes the SRH as in (ii).
+#
+#  vi) cafe::6, SID List=fc00::b0,fcbb:0:0200::,cafe::6
+#      End.BPF writes the SRH Tag. Then the datagram follows the same path as in
+#      (iii).
+#
+# In the test case (v), the datagram is expected to be dropped as a checksum
+# error. In all the other test cases, the datagram is expected to be delivered
+# on rt.
+
+# shellcheck source=lib.sh
+source lib.sh
+
+readonly IPv6_RT_NETWORK=fd00
+readonly IPv6_DST_NETWORK=cafe
+readonly SID_LOCATOR=fc00
+readonly CSID_BLOCK=fcbb:0
+readonly CSID_LBLEN=32
+readonly CSID_NFLEN=16
+readonly CSID_BLOCK_FUNC_LEN=$((CSID_LBLEN + CSID_NFLEN))
+readonly B6_FUNC=0200
+readonly CSID_FUNC=0100
+readonly DT6_FUNC=d6
+readonly BPF_FUNC=b0
+readonly DUMMY_DEVNAME=dum0
+readonly UDP_PORT=9
+readonly UDP_PAYLOAD_LEN=200
+readonly WAIT_TIMEOUT_SEC=5
+
+# destination of the control datagram on the veth, test case (v)
+readonly CTRL_DST="${SID_LOCATOR}::3"
+# tc chain of the flower filter that selects the control datagram
+readonly CTRL_CHAIN=1
+# tc chain of the bpf filter that changes the Flow Label
+readonly BPF_CHAIN=2
+
+readonly BPF_FILE=srv6_csum_complete.bpf.o
+readonly BPF_TC_SEC=tc
+readonly BPF_END_SEC=lwt_seg6local
+
+# return values of check_udp_delivery()
+readonly UDP_DELIVERED=0
+readonly UDP_LOST=1
+readonly UDP_CSUM_ERROR=2
+
+SETUP_ERR=1
+
+ret=${ksft_skip}
+nsuccess=0
+nfail=0
+
+log_test()
+{
+	log_test_expected "$1" "$2" "$3"
+}
+
+print_log_test_results()
+{
+	printf "\nTests passed: %3d\n" "${nsuccess}"
+	printf "Tests failed: %3d\n"   "${nfail}"
+
+	# when a test fails, the value of 'ret' is set to 1 (error code).
+	# Conversely, when all tests are passed successfully, the 'ret' value
+	# is set to 0 (success code).
+	if [ "${ret}" -ne 1 ]; then
+		ret=0
+	fi
+}
+
+log_section()
+{
+	echo
+	echo "################################################################################"
+	echo "TEST SECTION: $*"
+	echo "################################################################################"
+}
+
+cleanup()
+{
+	cleanup_all_ns
+
+	# check whether the setup phase was completed successfully or not. In
+	# case of an error during the setup phase of the testing environment,
+	# the selftest is considered as "skipped".
+	if [ "${SETUP_ERR}" -ne 0 ]; then
+		echo "SKIP: Setting up the testing environment failed"
+		exit "${ksft_skip}"
+	fi
+
+	exit "${ret}"
+}
+
+# add on hs the SRv6 Policy for cafe::<id>, in inline mode
+# args:
+#  $1 - destination id
+#  $2 - segments, comma separated
+set_hs_policy()
+{
+	local dstid="$1"
+	local segs="$2"
+
+	ip -netns "${NS_HS}" -6 route add "${IPv6_DST_NETWORK}::${dstid}/128" \
+		encap seg6 mode inline segs "${segs}" \
+		via "${IPv6_RT_NETWORK}::2" dev veth-hs
+}
+
+setup_hs()
+{
+	ip -netns "${NS_HS}" link set veth-hs up
+	ip -netns "${NS_HS}" addr add "${IPv6_RT_NETWORK}::1/64" \
+		dev veth-hs nodad
+
+	# with transmit checksumming on, conntrack on rt does not compute the
+	# receive checksum
+	ip netns exec "${NS_HS}" ethtool -K veth-hs tx off >/dev/null
+
+	# routes to the SIDs, used after the SRH is inserted
+	ip -netns "${NS_HS}" -6 route add "${SID_LOCATOR}::/16" \
+		via "${IPv6_RT_NETWORK}::2" dev veth-hs
+	ip -netns "${NS_HS}" -6 route add "${CSID_BLOCK}::/${CSID_LBLEN}" \
+		via "${IPv6_RT_NETWORK}::2" dev veth-hs
+
+	# test case (i), the reference, no SRH
+	ip -netns "${NS_HS}" -6 route add "${IPv6_DST_NETWORK}::1/128" \
+		via "${IPv6_RT_NETWORK}::2" dev veth-hs
+
+	# the SRv6 Policies of the test cases (ii) to (vi)
+	set_hs_policy 2 "${SID_LOCATOR}::1,${SID_LOCATOR}::2"
+	set_hs_policy 3 "${CSID_BLOCK}:${B6_FUNC}::"
+	set_hs_policy 4 "${CSID_BLOCK}:${CSID_FUNC}:${B6_FUNC}::"
+	set_hs_policy 5 "${SID_LOCATOR}::3,${SID_LOCATOR}::4"
+	set_hs_policy 6 "${SID_LOCATOR}::${BPF_FUNC},${CSID_BLOCK}:${B6_FUNC}::"
+}
+
+setup_rt()
+{
+	local dstid
+	local dev
+
+	ip -netns "${NS_RT}" link set veth-rt up
+	ip -netns "${NS_RT}" addr add "${IPv6_RT_NETWORK}::2/64" \
+		dev veth-rt nodad
+
+	# local segments of the test case (ii)
+	ip -netns "${NS_RT}" addr add "${SID_LOCATOR}::1/128" dev lo nodad
+	ip -netns "${NS_RT}" addr add "${SID_LOCATOR}::2/128" dev lo nodad
+	# local segments of the test case (v)
+	ip -netns "${NS_RT}" addr add "${SID_LOCATOR}::3/128" dev lo nodad
+	ip -netns "${NS_RT}" addr add "${SID_LOCATOR}::4/128" dev lo nodad
+	for dstid in 1 2 3 4 5 6; do
+		ip -netns "${NS_RT}" addr add \
+			"${IPv6_DST_NETWORK}::${dstid}/128" dev lo nodad
+	done
+
+	for dev in all veth-rt lo; do
+		ip netns exec "${NS_RT}" sysctl -wq \
+			net.ipv6.conf."${dev}".seg6_enabled=1
+	done
+
+	# SRv6 behaviors used by the test cases (iii), (iv) and (vi)
+	ip -netns "${NS_RT}" -6 route \
+		add "${CSID_BLOCK}:${CSID_FUNC}::/${CSID_BLOCK_FUNC_LEN}" \
+		encap seg6local action End flavors next-csid \
+		lblen "${CSID_LBLEN}" nflen "${CSID_NFLEN}" dev veth-rt
+	ip -netns "${NS_RT}" -6 route \
+		add "${CSID_BLOCK}:${B6_FUNC}::/${CSID_BLOCK_FUNC_LEN}" \
+		encap seg6local action End.B6.Encaps \
+		srh segs "${SID_LOCATOR}::${DT6_FUNC}" dev veth-rt
+	ip -netns "${NS_RT}" -6 route add "${SID_LOCATOR}::${DT6_FUNC}/128" \
+		encap seg6local action End.DT6 table 255 dev veth-rt
+	ip -netns "${NS_RT}" -6 route add "${SID_LOCATOR}::${BPF_FUNC}/128" \
+		encap seg6local action End.BPF endpoint obj "${BPF_FILE}" \
+		sec "${BPF_END_SEC}" dev veth-rt
+
+	tc -netns "${NS_RT}" qdisc add dev veth-rt clsact
+	# the bpf filter changes the Flow Label of the control datagram
+	tc -netns "${NS_RT}" filter add dev veth-rt ingress \
+		chain "${BPF_CHAIN}" prio 1 protocol ipv6 \
+		bpf object-file "${BPF_FILE}" section "${BPF_TC_SEC}" \
+		direct-action
+	# the control datagram goes to the bpf filter
+	tc -netns "${NS_RT}" filter add dev veth-rt ingress \
+		chain "${CTRL_CHAIN}" prio 1 protocol ipv6 \
+		flower dst_ip "${CTRL_DST}" \
+		action goto chain "${BPF_CHAIN}"
+	# every packet goes to the ct action, which computes the receive
+	# checksum, then to the chain of the flower filter
+	tc -netns "${NS_RT}" filter add dev veth-rt ingress prio 1 \
+		protocol ipv6 matchall \
+		action ct pipe action goto chain "${CTRL_CHAIN}"
+	ip netns exec "${NS_RT}" sysctl -wq \
+		net.netfilter.nf_conntrack_checksum=1
+}
+
+setup()
+{
+	setup_ns NS_HS NS_RT
+
+	ip -netns "${NS_HS}" link add veth-hs type veth \
+		peer name veth-rt netns "${NS_RT}"
+
+	setup_hs
+	setup_rt
+
+	# testing environment was set up successfully
+	SETUP_ERR=0
+}
+
+# print the value of a counter of rt in /proc/net/snmp6
+# args:
+#  $1 - counter name in /proc/net/snmp6
+get_rt_udp6_counter()
+{
+	local name="$1"
+
+	ip netns exec "${NS_RT}" cat /proc/net/snmp6 | \
+		awk -v k="${name}" '$1 == k { print $2 }'
+}
+
+# number of datagrams that UDP on rt counted in Udp6NoPorts or Udp6InCsumErrors
+get_rt_udp6_received()
+{
+	echo $(( $(get_rt_udp6_counter Udp6NoPorts) +
+		 $(get_rt_udp6_counter Udp6InCsumErrors) ))
+}
+
+check_hs_rt_connectivity()
+{
+	ip netns exec "${NS_HS}" ping -c 1 -W 1 "${IPv6_RT_NETWORK}::2" \
+		>/dev/null 2>&1
+}
+
+check_and_log_hs_rt_connectivity()
+{
+	check_hs_rt_connectivity
+	log_test $? 0 "Connectivity: hs -> rt"
+}
+
+connectivity_tests()
+{
+	log_section "IPv6 connectivity test between host and router"
+
+	check_and_log_hs_rt_connectivity
+}
+
+# Send one UDP datagram from hs to a port with no listener on rt.
+# args:
+#  $1 - destination id
+# return:
+#  - UDP_DELIVERED if rt counts the datagram as delivered
+#  - UDP_CSUM_ERROR if rt drops it for a bad checksum
+#  - UDP_LOST if the datagram cannot be sent, or if it does not arrive
+check_udp_delivery()
+{
+	local dstid="$1"
+	local noports
+	local payload
+	local errors
+	local recv
+	local dst
+
+	dst="${IPv6_DST_NETWORK}::${dstid}"
+	noports="$(get_rt_udp6_counter Udp6NoPorts)"
+	errors="$(get_rt_udp6_counter Udp6InCsumErrors)"
+	recv=$((noports + errors))
+	payload="$(printf '%*s' "${UDP_PAYLOAD_LEN}" '')"
+
+	# send the datagram with the /dev/udp redirection of bash
+	if ! ip netns exec "${NS_HS}" bash -c "printf '%s' '${payload}' > \
+			/dev/udp/${dst}/${UDP_PORT}"; then
+		return "${UDP_LOST}"
+	fi
+
+	# wait until rt counts the datagram in Udp6NoPorts or Udp6InCsumErrors
+	if ! slowwait "${WAIT_TIMEOUT_SEC}" until_counter_is \
+			">= $((recv + 1))" get_rt_udp6_received >/dev/null; then
+		return "${UDP_LOST}"
+	fi
+
+	if [ "$(get_rt_udp6_counter Udp6InCsumErrors)" -ne "${errors}" ]; then
+		return "${UDP_CSUM_ERROR}"
+	fi
+
+	if [ "$(get_rt_udp6_counter Udp6NoPorts)" -ne "$((noports + 1))" ]; then
+		return "${UDP_LOST}"
+	fi
+
+	return "${UDP_DELIVERED}"
+}
+
+# check the delivery of a datagram to cafe::<id> and log the result
+# args:
+#  $1 - destination id
+#  $2 - expected return value of check_udp_delivery()
+#  $3 - description of the path
+check_and_log_udp_delivery()
+{
+	local dstid="$1"
+	local expected="$2"
+	local desc="$3"
+
+	check_udp_delivery "${dstid}"
+	log_test $? "${expected}" \
+		"UDP: hs -> ${IPv6_DST_NETWORK}::${dstid} (${desc})"
+}
+
+udp_delivery_tests()
+{
+	log_section "UDP delivery test with CHECKSUM_COMPLETE"
+
+	check_and_log_udp_delivery 1 "${UDP_DELIVERED}" "no SRH"
+	check_and_log_udp_delivery 2 "${UDP_DELIVERED}" "SRH, local segments"
+	check_and_log_udp_delivery 3 "${UDP_DELIVERED}" "End.B6.Encaps, End.DT6"
+	check_and_log_udp_delivery 4 "${UDP_DELIVERED}" \
+		"End next-csid, End.B6.Encaps, End.DT6"
+	check_and_log_udp_delivery 5 "${UDP_CSUM_ERROR}" "control, csum error"
+	check_and_log_udp_delivery 6 "${UDP_DELIVERED}" \
+		"End.BPF, End.B6.Encaps, End.DT6"
+}
+
+test_command_or_ksft_skip()
+{
+	local cmd="$1"
+
+	if [ ! -x "$(command -v "${cmd}")" ]; then
+		echo "SKIP: Could not run test without \"${cmd}\" tool"
+		exit "${ksft_skip}"
+	fi
+}
+
+test_dummy_dev_or_ksft_skip()
+{
+	local test_netns
+
+	setup_ns test_netns
+
+	modprobe dummy &>/dev/null || true
+	if ! ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" \
+			type dummy; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: dummy dev not supported"
+		exit "${ksft_skip}"
+	fi
+
+	cleanup_ns "${test_netns}"
+}
+
+test_iproute2_supp_or_ksft_skip()
+{
+	if ! ip route help 2>&1 | grep -qo next-csid; then
+		echo "SKIP: Missing SRv6 next-csid flavor support in iproute2"
+		exit "${ksft_skip}"
+	fi
+}
+
+test_kernel_supp_or_ksft_skip()
+{
+	local test_netns
+
+	setup_ns test_netns
+
+	ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" type dummy
+	ip -netns "${test_netns}" link set "${DUMMY_DEVNAME}" up
+
+	if ! ip -netns "${test_netns}" -6 route \
+			add "${CSID_BLOCK}::/${CSID_LBLEN}" encap seg6local \
+			action End flavors next-csid dev "${DUMMY_DEVNAME}" \
+			2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: SRv6 next-csid flavor not supported"
+		exit "${ksft_skip}"
+	fi
+
+	cleanup_ns "${test_netns}"
+}
+
+test_veth_tx_csum_off_or_ksft_skip()
+{
+	local test_netns
+
+	setup_ns test_netns
+
+	if ! ip -netns "${test_netns}" link add veth-t0 type veth \
+			peer name veth-t1 2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: veth dev not supported"
+		exit "${ksft_skip}"
+	fi
+
+	# setup_hs() turns off transmit checksumming on veth-hs. If it stays on,
+	# conntrack does not compute the receive checksum and the test cannot
+	# see the error.
+	ip netns exec "${test_netns}" ethtool -K veth-t0 tx off >/dev/null 2>&1
+
+	if ! ip netns exec "${test_netns}" ethtool -k veth-t0 | \
+			grep -q "^tx-checksumming: off"; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: Cannot turn off transmit checksumming on veth"
+		exit "${ksft_skip}"
+	fi
+
+	cleanup_ns "${test_netns}"
+}
+
+test_bpf_file_or_ksft_skip()
+{
+	if [ ! -f "${BPF_FILE}" ]; then
+		echo "SKIP: Missing ${BPF_FILE}, run 'make' first"
+		exit "${ksft_skip}"
+	fi
+}
+
+test_tc_or_ksft_skip()
+{
+	local test_netns
+
+	setup_ns test_netns
+
+	ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" type dummy
+
+	if ! tc -netns "${test_netns}" qdisc add dev "${DUMMY_DEVNAME}" \
+			clsact 2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: tc clsact qdisc not supported"
+		exit "${ksft_skip}"
+	fi
+
+	if ! tc -netns "${test_netns}" filter add dev "${DUMMY_DEVNAME}" \
+			ingress chain "${BPF_CHAIN}" protocol ipv6 \
+			bpf object-file "${BPF_FILE}" section "${BPF_TC_SEC}" \
+			direct-action 2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: tc bpf filter not supported"
+		exit "${ksft_skip}"
+	fi
+
+	if ! tc -netns "${test_netns}" filter add dev "${DUMMY_DEVNAME}" \
+			ingress chain "${CTRL_CHAIN}" protocol ipv6 \
+			flower dst_ip "${CTRL_DST}" \
+			action goto chain "${BPF_CHAIN}" 2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: tc flower filter or goto chain action not supported"
+		exit "${ksft_skip}"
+	fi
+
+	if ! tc -netns "${test_netns}" filter add dev "${DUMMY_DEVNAME}" \
+			ingress protocol ipv6 matchall \
+			action ct pipe action goto chain "${CTRL_CHAIN}" \
+			2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: tc matchall filter or ct action not supported"
+		exit "${ksft_skip}"
+	fi
+
+	# setup_rt() turns on nf_conntrack_checksum. If it is off, conntrack
+	# does not check the UDP checksum and does not compute the receive
+	# checksum. The test cannot see the error.
+	if ! ip netns exec "${test_netns}" sysctl -wq \
+			net.netfilter.nf_conntrack_checksum=1 2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: nf_conntrack_checksum sysctl not available"
+		exit "${ksft_skip}"
+	fi
+
+	cleanup_ns "${test_netns}"
+}
+
+test_end_bpf_or_ksft_skip()
+{
+	local test_netns
+
+	setup_ns test_netns
+
+	ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" type dummy
+	ip -netns "${test_netns}" link set "${DUMMY_DEVNAME}" up
+
+	if ! ip -netns "${test_netns}" -6 route \
+			add "${SID_LOCATOR}::${BPF_FUNC}/128" encap seg6local \
+			action End.BPF endpoint obj "${BPF_FILE}" \
+			sec "${BPF_END_SEC}" dev "${DUMMY_DEVNAME}" \
+			2>/dev/null; then
+		cleanup_ns "${test_netns}"
+		echo "SKIP: SRv6 End.BPF not supported"
+		exit "${ksft_skip}"
+	fi
+
+	cleanup_ns "${test_netns}"
+}
+
+if [ "$(id -u)" -ne 0 ]; then
+	echo "SKIP: Need root privileges"
+	exit "${ksft_skip}"
+fi
+
+# required programs to carry out this selftest
+test_command_or_ksft_skip ip
+test_command_or_ksft_skip tc
+test_command_or_ksft_skip ethtool
+test_command_or_ksft_skip ping
+test_command_or_ksft_skip sysctl
+test_command_or_ksft_skip awk
+test_command_or_ksft_skip grep
+
+test_dummy_dev_or_ksft_skip
+test_iproute2_supp_or_ksft_skip
+test_kernel_supp_or_ksft_skip
+test_veth_tx_csum_off_or_ksft_skip
+test_bpf_file_or_ksft_skip
+test_tc_or_ksft_skip
+test_end_bpf_or_ksft_skip
+
+set -e
+trap cleanup EXIT
+
+setup
+set +e
+
+connectivity_tests
+udp_delivery_tests
+
+print_log_test_results
-- 
2.43.0


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

* Re: [PATCH net 1/6] seg6: fix skb checksum update helpers
  2026-10-31 22:50 ` [PATCH net 1/6] seg6: fix skb checksum update helpers Andrea Mayer
@ 2026-11-03  1:25   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  1:25 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

On Sat, Oct 31, 2026 at 11:50:28PM +0100, Andrea Mayer wrote:
> With CHECKSUM_COMPLETE, ipv6_srh_rcv() updates skb->csum for the new
> Segments Left and destination address through seg6_update_csum().
> Replacing "from" with "to" in the packet must change skb->csum (C) to
> C - from + to, but update_csum_diff4() and update_csum_diff16() compute:
> 
>   ~csum_partial({ ~from, to }, ~C) = ~(~C + ~from + to) = C + from - to
> 
> This does not affect packets forwarded by ip6_forward(), which sets
> ip_summed to CHECKSUM_NONE. But when the packet is delivered on the same
> node and skb->csum was computed in software, for instance by conntrack, UDP
> can drop the packet as a checksum error. With skb->csum computed by the
> device, TCP can instead log a "hw csum failure", as if the device had
> computed a wrong skb->csum.
> 
> Fix update_csum_diff4() and update_csum_diff16() to compute C - from + to.
> 
> Found by manual code inspection, while checking that the skb->csum updates
> in the SRv6 receive path are consistent. A following commit adds a selftest
> that reproduces the problem with a tc ct action. The issue was confirmed in
> a VM without kernel changes or error injection: with the rest of this
> series and without this patch, that selftest fails, as UDP drops datagrams
> as checksum errors. Also tested against the srv6 selftests, and with a
> ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.
> 
> Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)")
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  include/net/seg6.h | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/include/net/seg6.h b/include/net/seg6.h
> index 82b3fbbcbb93..01d8034e48fc 100644
> --- a/include/net/seg6.h
> +++ b/include/net/seg6.h
> @@ -19,7 +19,7 @@ static inline void update_csum_diff4(struct sk_buff *skb, __be32 from,
>  {
>  	__be32 diff[] = { ~from, to };
>  
> -	skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
> +	skb->csum = csum_partial(diff, sizeof(diff), skb->csum);
>  }
>  
>  static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
> @@ -30,7 +30,7 @@ static inline void update_csum_diff16(struct sk_buff *skb, __be32 *from,
>  		to[0], to[1], to[2], to[3],
>  	};
>  
> -	skb->csum = ~csum_partial((char *)diff, sizeof(diff), ~skb->csum);
> +	skb->csum = csum_partial(diff, sizeof(diff), skb->csum);
>  }
>  
>  struct seg6_pernet_data {
> -- 
> 2.43.0
> 

Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>

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

* Re: [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit
  2026-10-31 22:50 ` [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit Andrea Mayer
@ 2026-11-03  1:27   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  1:27 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

On Sat, Oct 31, 2026 at 11:50:29PM +0100, Andrea Mayer wrote:
> When the next segment is a local address, ipv6_srh_rcv() processes it
> directly and also decrements the Hop Limit. With CHECKSUM_COMPLETE,
> skb->csum is not updated for this change. If the packet is then delivered
> on the same node and skb->csum was computed in software, for instance by
> conntrack, UDP can drop the packet as a checksum error. With skb->csum
> computed by the device, TCP can instead log a "hw csum failure", as if the
> device had computed a wrong skb->csum.
> 
> Update skb->csum for the Hop Limit decrement with update_csum_diff4().
> 
> Depends on patch "seg6: fix skb checksum update helpers".
> 
> Found by manual code inspection, and reproduced in a VM without kernel
> changes or error injection. A following commit adds a selftest that
> reproduces the problem with a tc ct action. With this patch, the selftest
> passes for the path with local SRH segments. Also tested against the srv6
> selftests, and with a ConnectX-6 Lx NIC (mlx5) that provides
> CHECKSUM_COMPLETE.
> 
> Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)")
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  net/ipv6/exthdrs.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
> index 96c8402a27a5..9d3ba310065a 100644
> --- a/net/ipv6/exthdrs.c
> +++ b/net/ipv6/exthdrs.c
> @@ -460,6 +460,10 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev)
>  	}
>  
>  	if (skb_dst_dev(skb)->flags & IFF_LOOPBACK) {
> +		/* payload_len, nexthdr and hop_limit */
> +		__be32 *hlim_word = (__be32 *)ipv6_hdr(skb) + 1;
> +		__be32 old;
> +
>  		if (ipv6_hdr(skb)->hop_limit <= 1) {
>  			__IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS);
>  			icmpv6_send(skb, ICMPV6_TIME_EXCEED,
> @@ -467,8 +471,12 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev)
>  			kfree_skb_reason(skb, SKB_DROP_REASON_IP_TTL_EXCEEDED);
>  			return -1;
>  		}
> +		old = *hlim_word;
>  		ipv6_hdr(skb)->hop_limit--;
>  
> +		if (skb->ip_summed == CHECKSUM_COMPLETE)
> +			update_csum_diff4(skb, old, *hlim_word);
> +
>  		skb_pull(skb, skb_transport_offset(skb));
>  		goto looped_back;
>  	}
> -- 
> 2.43.0
> 

Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>

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

* Re: [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID
  2026-10-31 22:50 ` [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID Andrea Mayer
@ 2026-11-03  2:15   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  2:15 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

Hi Andrea,
On Sat, Oct 31, 2026 at 11:50:30PM +0100, Andrea Mayer wrote:
> advance_nextseg() decrements Segments Left and writes the next segment into
> the destination address, and seg6_next_csid_advance_arg() advances the
> NEXT-C-SID argument in the destination address.
> With CHECKSUM_COMPLETE, neither updates skb->csum for these changes. The
> stale skb->csum is read when the packet is delivered on the same node, e.g.
> through End.B6.Encaps towards a local End.DT6 SID.
> As a consequence, when skb->csum is computed in software, for instance by
> conntrack, UDP can drop the packet as a checksum error. With skb->csum
> computed by the device, TCP can instead log a "hw csum failure", as if the
> device had computed a wrong skb->csum.
> 
> Update skb->csum in both functions with update_csum_diff16() for the
> destination address. advance_nextseg() also uses update_csum_diff4() for
> Segments Left.
> 
> Depends on patch "seg6: fix skb checksum update helpers".
> 
> Found by manual code inspection while working on
> commit 8ae3f4067ba4 ("seg6: ensure packet data is writable before modifying
> SRH and IPv6 DA"), and reproduced in a VM without kernel changes or error
> injection. A following commit adds a selftest that reproduces the problem
> with a tc ct action. With this patch, the selftest passes for the
> End.B6.Encaps and next-csid paths. Also tested against the srv6 selftests,
> and with a ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.
> 
> Fixes: 140f04c33bbc ("ipv6: sr: implement several seg6local actions")
> Fixes: 848f3c0d4769 ("seg6: add NEXT-C-SID support for SRv6 End behavior")
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  net/ipv6/seg6_local.c | 18 +++++++++++++++++-
>  1 file changed, 17 insertions(+), 1 deletion(-)
> 
> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 0c3d1be7f129..57a82477ce3f 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c
> @@ -291,7 +291,9 @@ static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb,
>  					   struct ipv6_sr_hdr *srh,
>  					   enum skb_drop_reason *reason)
>  {
> +	struct in6_addr *daddr;
>  	struct in6_addr *addr;
> +	__be32 old;
>  	int srhoff;
>  	int wlen;
>  
> @@ -309,10 +311,19 @@ static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb,
>  
>  	/* skb_ensure_writable() may change skb pointers; evaluate srh again */
>  	srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
> +	daddr = &ipv6_hdr(skb)->daddr;
> +	/* nexthdr, hdrlen, type and segments_left */
> +	old = *(__be32 *)srh;
>  
>  	srh->segments_left--;
>  	addr = srh->segments + srh->segments_left;
> -	ipv6_hdr(skb)->daddr = *addr;
> +
> +	if (skb->ip_summed == CHECKSUM_COMPLETE) {
> +		update_csum_diff4(skb, old, *(__be32 *)srh);
> +		update_csum_diff16(skb, (__be32 *)daddr, (__be32 *)addr);
> +	}
> +
> +	*daddr = *addr;

Nit: we can re-use seg6_update_csum() if the transport header set correctly.
But that function is static and we need adjust header files.
So the current change looks good for me.

>  
>  	return srh;
>  }
> @@ -427,11 +438,13 @@ seg6_next_csid_advance_arg(struct sk_buff *skb,
>  	__u8 fnc_octects = seg6_flv_lcnode_func_octects(finfo);
>  	__u8 blk_octects = seg6_flv_lcblock_octects(finfo);
>  	struct in6_addr *addr;
> +	struct in6_addr old;
>  
>  	if (unlikely(skb_ensure_writable(skb, sizeof(struct ipv6hdr))))
>  		return SKB_DROP_REASON_NOMEM;
>  
>  	addr = &ipv6_hdr(skb)->daddr;
> +	old = *addr;
>  
>  	/* advance DA.Argument */
>  	memmove(&addr->s6_addr[blk_octects],
> @@ -440,6 +453,9 @@ seg6_next_csid_advance_arg(struct sk_buff *skb,
>  
>  	memset(&addr->s6_addr[16 - fnc_octects], 0x00, fnc_octects);
>  
> +	if (skb->ip_summed == CHECKSUM_COMPLETE)
> +		update_csum_diff16(skb, (__be32 *)&old, (__be32 *)addr);
> +
>  	return SKB_NOT_DROPPED_YET;
>  }
>  
> -- 
> 2.43.0
> 

Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>

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

* Re: [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program
  2026-10-31 22:50 ` [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program Andrea Mayer
@ 2026-11-03  2:28   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  2:28 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

Hi Andrea,
On Sat, Oct 31, 2026 at 11:50:31PM +0100, Andrea Mayer wrote:
> The End.BPF program can change the packet through the seg6 BPF helpers,
> for instance the SRH Tag with bpf_lwt_seg6_store_bytes(). After the
> program, seg6_bpf_has_valid_srh() can also rewrite the SRH Hdr Ext Len.
> 
> With CHECKSUM_COMPLETE, the program does not always update skb->csum for
> the bytes it writes, and seg6_bpf_has_valid_srh() does not either. If the
> packet is then delivered on the same node and skb->csum was computed in
> software, for instance by conntrack, UDP can drop the packet as a checksum
> error. With skb->csum computed by the device, TCP can instead log a
> "hw csum failure", as if the device had computed a wrong skb->csum.
> 
> Discard CHECKSUM_COMPLETE with skb_checksum_complete_unset() after the
> program, since input_action_end_bpf() does not know which bytes the program
> changed. The checksum is then verified in software.
> 
> Found by manual code inspection and reproduced in a VM without kernel
> changes or error injection: with skb->csum computed by a tc ct action, an
> End.BPF program that writes the SRH Tag, followed by End.B6.Encaps and a
> local End.DT6 SID, makes UDP drop the packet. With this patch, the packet
> is delivered. Also tested against the lwt_seg6local BPF selftest, and with
> a ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.
> 
> Fixes: 004d4b274e2a ("ipv6: sr: Add seg6local action End.BPF")
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  net/ipv6/seg6_local.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 57a82477ce3f..3045a351f4e9 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c
> @@ -1582,6 +1582,12 @@ static int input_action_end_bpf(struct sk_buff *skb,
>  		goto drop;
>  	}
>  
> +	/* The program may have changed the packet and seg6_bpf_has_valid_srh()
> +	 * may rewrite the SRH Hdr Ext Len below.
> +	 * skb->csum may then no longer be the checksum of the full packet.
> +	 */
> +	skb_checksum_complete_unset(skb);
> +

Should we move this a bit later?

>  	if (srh_state->srh && !seg6_bpf_has_valid_srh(skb))
>  		goto drop;
>  	local_unlock_nested_bh(&seg6_bpf_srh_states.bh_lock);
> -- 

e.g. after this. As there is not need to do the setting if the skb is dropped.

Thanks
Hangbin

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

* Re: [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation
  2026-10-31 22:50 ` [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation Andrea Mayer
@ 2026-11-03  2:37   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  2:37 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

Hi Andrea,
On Sat, Oct 31, 2026 at 11:50:32PM +0100, Andrea Mayer wrote:
> In L2ENCAP and L2ENCAP_RED mode, seg6_do_srh() puts the MAC header back
> in front of the frame with skb_push(), without adding it to skb->csum.
> The encapsulation then calls skb_postpush_rcsum() only for the outer
> headers. With CHECKSUM_COMPLETE the MAC header is missing from
> skb->csum, which no longer covers the whole packet as required.
> 
> For a forwarded packet, ip6_forward() sets ip_summed to CHECKSUM_NONE with
> skb_forward_csum(), and End.DX2 does the same before sending the frame.
> When the packet is delivered on the same node, a raw socket with
> IPV6_CHECKSUM still reads skb->csum. With skb->csum computed by the device,
> the kernel can then log a "hw csum failure", as if the device had computed
> a wrong skb->csum.
> 
> Use skb_push_rcsum(), which also calls skb_postpush_rcsum() for the MAC
> header.
> 
> Found by manual code inspection of the skb->csum updates in the SRv6 code,
> and observed in a VM without kernel changes or error injection: with
> skb->csum computed by a tc ct action, a BPF tracing program on
> ip6_forward() shows skb->csum missing the MAC header. With this patch,
> skb->csum covers it. Also tested against the srv6 selftests, and with a
> ConnectX-6 Lx NIC (mlx5) that provides CHECKSUM_COMPLETE.
> 
> Fixes: 38ee7f2d4756 ("ipv6: sr: add support for encapsulation of L2 frames")
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  net/ipv6/seg6_iptunnel.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c
> index 0d39a38a5dd2..3821d8e1409e 100644
> --- a/net/ipv6/seg6_iptunnel.c
> +++ b/net/ipv6/seg6_iptunnel.c
> @@ -456,7 +456,7 @@ static int seg6_do_srh(struct sk_buff *skb, struct dst_entry *cache_dst)
>  			return err;
>  
>  		skb_mac_header_rebuild(skb);
> -		skb_push(skb, skb->mac_len);
> +		skb_push_rcsum(skb, skb->mac_len);
>  
>  		if (tinfo->mode == SEG6_IPTUN_MODE_L2ENCAP)
>  			err = __seg6_do_srh_encap(skb, tinfo->srh,
> -- 
> 2.43.0
> 

Should we put the skb_push_rcsum() inside of __seg6_do_srh_encap() and
seg6_do_srh_encap_red()? They all do skb_mac_header_rebuild() again.

Thanks
Hangbin

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

* Re: [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE
  2026-10-31 22:50 ` [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE Andrea Mayer
@ 2026-11-03  3:26   ` Hangbin Liu
  0 siblings, 0 replies; 13+ messages in thread
From: Hangbin Liu @ 2026-11-03  3:26 UTC (permalink / raw)
  To: Andrea Mayer
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, Shuah Khan, linux-kernel,
	netdev, linux-kselftest, bpf, David Lebrun, Daniel Borkmann,
	Mathieu Xhonneux, David Lebrun, Stefano Salsano, Andrea Mayer

Hi Andrea,
On Sat, Oct 31, 2026 at 11:50:33PM +0100, Andrea Mayer wrote:
> Add a selftest for received packets with CHECKSUM_COMPLETE that the SRv6
> code processes before they are delivered on the same node. The test checks
> that, after the node changes the packet, UDP does not read a wrong
> skb->csum.
> 
> The test uses a host and a router connected through a veth pair. Since veth
> does not provide CHECKSUM_COMPLETE, a tc ct action on the router computes
> skb->csum in software.
> 
> The host sends a UDP datagram to the router along five paths: no SRH (the
> reference), an SRH with local segments, End.B6.Encaps with End.DT6, End
> with the next-csid flavor, and End.BPF. Each datagram is expected to be
> delivered, not dropped as a checksum error. Without the previous patches,
> every path except the reference fails. With them, every path passes.
> 
> The host also sends a control datagram. It checks that the test detects a
> wrong skb->csum after a change to the packet. After the ct action, a BPF
> program on the router changes the Flow Label of the control datagram
> without updating skb->csum, and UDP must drop it. If UDP accepts it, the
> paths above may report a false pass, and the test fails.
> 
> Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
> ---
>  tools/testing/selftests/net/Makefile          |   1 +
>  .../selftests/net/srv6_csum_complete.bpf.c    |  65 ++
>  .../selftests/net/srv6_csum_complete_test.sh  | 642 ++++++++++++++++++
>  3 files changed, 708 insertions(+)
>  create mode 100644 tools/testing/selftests/net/srv6_csum_complete.bpf.c
>  create mode 100755 tools/testing/selftests/net/srv6_csum_complete_test.sh
> 

[...]

> +
> +log_test()
> +{
> +	log_test_expected "$1" "$2" "$3"
> +}

We'd better use log_test_expected directly, otherwise the log_test in
lib.sh will be overwrite.

> +
> +test_command_or_ksft_skip()
> +{
> +	local cmd="$1"
> +
> +	if [ ! -x "$(command -v "${cmd}")" ]; then
> +		echo "SKIP: Could not run test without \"${cmd}\" tool"
> +		exit "${ksft_skip}"
> +	fi
> +}

Then we can use require_command() in lib.sh here, which will call
log_test() in lib.sh.

[...]

> +
> +if [ "$(id -u)" -ne 0 ]; then
> +	echo "SKIP: Need root privileges"
> +	exit "${ksft_skip}"
> +fi

I wonder if there is any one run the selftest with none root privileges.
Cause almost all the selftests need ip configurations.

Thanks
Hangbin

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

end of thread, other threads:[~2026-11-03  3:26 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-31 22:50 [PATCH net 0/6] seg6: fix skb checksum updates with CHECKSUM_COMPLETE Andrea Mayer
2026-10-31 22:50 ` [PATCH net 1/6] seg6: fix skb checksum update helpers Andrea Mayer
2026-11-03  1:25   ` Hangbin Liu
2026-10-31 22:50 ` [PATCH net 2/6] seg6: update skb checksum when ipv6_srh_rcv() decrements the Hop Limit Andrea Mayer
2026-11-03  1:27   ` Hangbin Liu
2026-10-31 22:50 ` [PATCH net 3/6] seg6: update skb checksum when SRv6 behaviors advance to the next SID Andrea Mayer
2026-11-03  2:15   ` Hangbin Liu
2026-10-31 22:50 ` [PATCH net 4/6] seg6: discard CHECKSUM_COMPLETE after the End.BPF program Andrea Mayer
2026-11-03  2:28   ` Hangbin Liu
2026-10-31 22:50 ` [PATCH net 5/6] seg6: account for the MAC header in skb checksum on L2 encapsulation Andrea Mayer
2026-11-03  2:37   ` Hangbin Liu
2026-10-31 22:50 ` [PATCH net 6/6] selftests: seg6: add test for skb checksum with CHECKSUM_COMPLETE Andrea Mayer
2026-11-03  3:26   ` Hangbin Liu

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