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

Open
#7,550 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
c, linux

Research direction

Start with the RP1/GEM checksum paths in drivers/net/ethernet/cadence/macb_main.c, especially the cited TX handling, and compare them with the zero-result handling in net/core/dev.c. Use the supplied Python reproducer with ethtool TX offload enabled and disabled, then verify that the external capture reports 0xffff rather than 0x0000 and that the software path remains valid.

Written by the indexing model from the issue text.

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

Dominant language
C
Stars
13.2k
Forks
5.5k
Avg merge
2d 21h
Merged PRs (30d)
21

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from raspberrypi/linux

All issues in raspberrypi/linux

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.