Skip to content

RP1 GEM TX checksum offload emits 0x0000 when an IPv4 UDP checksum calculates to zero #7550

Description

@marcus-pousette-hp

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
  1. 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'
  2. On the Pi 5, identify its source address and egress interface:

    ip route get <receiver-ip>
  3. 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
  4. 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.

  5. 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:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions