Repository navigation
RP1 GEM TX checksum offload emits 0x0000 when an IPv4 UDP checksum calculates to zero #7550
Description
Describe the bug
On a Raspberry Pi 5, the RP1 GEM TX checksum-offload path emitted an IPv4 UDP
checksum of 0x0000 when the calculated checksum was zero. Because the socket
had requested a UDP checksum, RFC 768 requires this result to be encoded as
0xffff; 0x0000 instead means that no checksum was generated.
The packet originated in a VM bridged through the Pi host. A capture in the VM
showed a CHECKSUM_PARTIAL-style seed, while a capture on a separate Internet
host showed 0x0000 on the wire. When checksum calculation was performed in
software, the same test produced 0xffff before NAT and a valid adjusted
checksum after NAT.
This matters even though a zero checksum is legal for IPv4 UDP receivers: it
silently removes the integrity check requested by the sender, and packet
transformations may treat it as an intentionally absent checksum. In the
original workload this caused one deterministic sequence gap and eventually a
reliable transport timeout.
Steps to reproduce the behaviour
First create the temporary reproducer below. It chooses a two-byte payload so
the IPv4 UDP checksum for the supplied source/destination tuple calculates to
zero, then sends that payload ten times:
cat >/tmp/udp-zero-checksum-repro.py <<'PY'
#!/usr/bin/env python3
import ipaddress
import socket
import struct
import sys
import time
def fold_sum(data):
if len(data) % 2:
data += b'\x00'
total = sum(struct.unpack(f'!{len(data) // 2}H', data))
while total >> 16:
total = (total & 0xffff) + (total >> 16)
return total
source, destination = sys.argv[1:3]
source_port, destination_port = map(int, sys.argv[3:5])
udp_length = 8 + 2
protocol = struct.pack('!BBH', 0, socket.IPPROTO_UDP, udp_length)
pseudo_header = (
ipaddress.IPv4Address(source).packed
+ ipaddress.IPv4Address(destination).packed
+ protocol
)
udp_header = struct.pack(
'!HHHH', source_port, destination_port, udp_length, 0
)
payload = struct.pack('!H', (~fold_sum(pseudo_header + udp_header)) & 0xffff)
assert fold_sum(pseudo_header + udp_header + payload) == 0xffff
print(f'payload={payload.hex()}')
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((source, source_port))
sock.connect((destination, destination_port))
for _ in range(10):
sock.send(payload)
time.sleep(0.1)
PY-
On a separate receiver, start a UDP listener and capture on the receiving
interface. The capture must be taken on another machine because a sender
capture can show an unfinished checksum before hardware offload:nc -u -l 41000 >/dev/null sudo tcpdump -ni <receiver-interface> -vvv -XX 'udp port 41000'
-
On the Pi 5, identify its source address and egress interface:
ip route get <receiver-ip>
-
Enable TX checksum offload and send the crafted packets:
sudo ethtool -K <egress-interface> tx on python3 /tmp/udp-zero-checksum-repro.py \ <pi-source-ip> <receiver-ip> 43000 41000
-
Inspect the receiver capture. The suspected failure is a UDP checksum field
of0x0000(tcpdumpreportsno cksum). The expected field is0xffff
when there is no NAT, or a valid nonzero checksum if NAT changes an address
or port. -
Repeat with software checksumming:
sudo ethtool -K <egress-interface> tx off python3 /tmp/udp-zero-checksum-repro.py \ <pi-source-ip> <receiver-ip> 43001 41000
Restore the original offload setting after the test.
In the original bridged-VM reproduction, the exact tuple and observed values
were:
| Guest TX checksum offload | Guest capture | External capture |
|---|---|---|
| enabled | partial seed 0xd26d |
0x0000 |
| disabled | valid 0xffff |
valid NAT-adjusted checksum 0x306b |
The enabled case used
192.168.0.36:43000 -> 51.15.222.118:41000 with payload e566. The external
capture contained:
... a7f8 a028 000a 0000 e566
^^^^ UDP checksum
Device (s)
Raspberry Pi 5
System
Raspberry Pi 5 Model B Rev 1.0
Kernel release: 6.12.93+rpt-rpi-2712
Architecture: aarch64
Network topology: VM connected through host bridge br0, then RP1 Ethernet
The matching Raspberry Pi archive source package is 6.12.93-1+rpt1.
Logs
No response
Additional context
Linux 6.12.93's macb driver advertises NETIF_F_HW_CSUM for GEM devices and
clears the checksum field for CHECKSUM_PARTIAL packets before hardware
completion:
- https://github.com/gregkh/linux/blob/v6.12.93/drivers/net/ethernet/cadence/macb_main.c#L2333-L2348
- https://github.com/gregkh/linux/blob/v6.12.93/drivers/net/ethernet/cadence/macb_main.c#L4302-L4314
The generic software fallback explicitly substitutes CSUM_MANGLED_0
(0xffff) when the calculated result is zero:
This suggests that RP1/GEM checksum completion returns raw zero, or that the
driver needs an RP1-specific workaround before advertising this offload. I have
not tested a kernel patch and am not yet proposing whether the appropriate fix
is a hardware setting, an RP1 quirk, or software UDP checksum completion.
There is an older report with the same zero-result failure class, but it
concerns different Raspberry Pi hardware, a different driver, and IPv6:
Activity
nbuchwitz commentedon Aug 13, 2026
Once the builds have been finished, could you please test #7552 ?
(please read https://github.com/raspberrypi/rpi-update/blob/master/README.md#caveats first)
net: macb: fix zero UDP checksum on transmit
marcus-pousette-hp commentedon Aug 13, 2026
I don't have hardware I can test this build on unfortunately, thanks for the swift work!
P33M commentedon Aug 18, 2026
Within the Cadence GEM IP, sitting on top of the checksum engine is a mangler which does (in effect - not confirmed with a sim):
Which decodes to
Which is a bit concerning.
However in the UDPv4 case, the checksum should be mangled correctly. Is the header detection insufficient - there are writeback bits in the TX DMA descriptor that capture the result of the TX checksum engine, and/or reasons for failing to compute a checksum. I will look at adding those to net stats.
nbuchwitz commentedon Aug 18, 2026
Thanks for looking into this!
Unfortunately your table contradicts both the reporter's and my own repro, but if I invert ipv4_frame_detected, it matches exactly. So iy possibly got flipped somewhere? Anyway the writeback bits will add some more insight.
P33M commentedon Aug 18, 2026
Hmm it's actually a bitwise negation, and the un-negated net is used in blocks that would only make sense if the
ipv4_frame_detectedsignal was in positive logic. So I'm confused. Or it doesn't assert on a UFO buffer even if it's only got 2 bytes of payload.If you turn off GSO/LSO with ethtool, does the checksum behaviour change?
nbuchwitz commentedon Aug 19, 2026
Did some further testing:
UDPv4 (tso/gso off) => 0x0000 seen in tcpdump
UDPv4 => 0x0000 seen in tcpdump
UDPv6 => 0xffff (mangler did its job)
TCP => correct
So exactly the v4+udp+zero case goes out raw, which matches your table with
ipv4_frame_detectedinverted.Do you know / can find out if that mangler block is common across GEM revisions?
If so we cpi;d narrow the fallback to UDPv4 and let UDPv6 keep the offload.
P33M commentedon Aug 19, 2026
I spun up a sim - the logic is "inverted" because the mangle has the effect of skipping the final 1's complement checksum step. So the sense of mangled/not mangled inside the IF statement should be reversed, which matches the (wonky) intent and the observed behaviour.
I have a changelog for the IP for releases earlier than were used on RP1, and there is no mention of a bug relating to checksumming, or a feature that explicitly added this offload support. So it's safe to assume that UDPv4 is broken on all versions of the hardware <=RP1 unless a chip vendor made a local change to the design (very unlikely).
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
2 remaining items
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit
net: macb: fix zero UDPv4 checksum on transmit