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
of 0x0000 (tcpdump reports no cksum). The expected field is 0xffff
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:
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:
Describe the bug
On a Raspberry Pi 5, the RP1 GEM TX checksum-offload path emitted an IPv4 UDP
checksum of
0x0000when the calculated checksum was zero. Because the sockethad requested a UDP checksum, RFC 768 requires this result to be encoded as
0xffff;0x0000instead 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 Internethost showed
0x0000on the wire. When checksum calculation was performed insoftware, the same test produced
0xffffbefore NAT and a valid adjustedchecksum 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:
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:
On the Pi 5, identify its source address and egress interface:
Enable TX checksum offload and send the crafted packets:
Inspect the receiver capture. The suspected failure is a UDP checksum field
of
0x0000(tcpdumpreportsno cksum). The expected field is0xffffwhen there is no NAT, or a valid nonzero checksum if NAT changes an address
or port.
Repeat with software checksumming:
Restore the original offload setting after the test.
In the original bridged-VM reproduction, the exact tuple and observed values
were:
0xd26d0x00000xffff0x306bThe enabled case used
192.168.0.36:43000 -> 51.15.222.118:41000with payloade566. The externalcapture contained:
Device (s)
Raspberry Pi 5
System
Raspberry Pi 5 Model B Rev 1.0
Kernel release:
6.12.93+rpt-rpi-2712Architecture:
aarch64Network topology: VM connected through host bridge
br0, then RP1 EthernetThe matching Raspberry Pi archive source package is
6.12.93-1+rpt1.Logs
No response
Additional context
Linux 6.12.93's
macbdriver advertisesNETIF_F_HW_CSUMfor GEM devices andclears the checksum field for
CHECKSUM_PARTIALpackets before hardwarecompletion:
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: