Skip to content

feat(esp_eth): consume Rx checksum offload engine verdict; expose lwIP checksum Kconfig (IDFGH-18027)#18883

Open
DatanoiseTV wants to merge 2 commits into
espressif:masterfrom
DatanoiseTV:feat/emac-s31-checksum-offload
Open

feat(esp_eth): consume Rx checksum offload engine verdict; expose lwIP checksum Kconfig (IDFGH-18027)#18883
DatanoiseTV wants to merge 2 commits into
espressif:masterfrom
DatanoiseTV:feat/emac-s31-checksum-offload

Conversation

@DatanoiseTV

@DatanoiseTV DatanoiseTV commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

The internal EMAC's Rx checksum offload engine is enabled unconditionally in emac_hal_init_mac_default() (emac_ll_checksum_offload_mode(hal->mac_regs, ETH_CHECKSUM_HW)), but its result is never consumed:

  • Hardware dropping of checksum-error frames (emac_ll_drop_tcp_err_frame_enable) only takes effect in Receive Store Forward mode. RSF is disabled on all targets except the original ESP32 because the Rx FIFO is smaller than a full frame - verified on ESP32-S31: with RSF force-enabled, frames larger than the FIFO are never forwarded to memory at all.
  • In the threshold (cut-through) mode actually used, the engine still verifies IPv4 header and TCP/UDP/ICMP payload checksums and reports its per-frame verdict in the enhanced Rx descriptor extended status (RDES4: IPHeadErr, IPPayloadErr, qualified by IPChecksumBypass) - but the DMA driver only checks RDES0.ErrSummary, which does not include the checksum engine bits.

Consequences:

  1. With CONFIG_LWIP_CHECKSUM_CHECK_UDP disabled (the IDF default), corrupted UDP datagrams that pass the Ethernet CRC are silently delivered to the application.
  2. lwIP's software TCP receive checksum runs over every segment even though the hardware has already verified it - measurable CPU cost on high-throughput targets.

Changes

Commit 1 - esp_eth: consume the checksum engine verdict in emac_esp_dma_get_valid_recv_len(): when extended status is available and the engine was not bypassed, drop frames flagged with IPHeadErr or IPPayloadErr, the same way ErrSummary frames are dropped. Non-IP frames, IPv6 frames with unsupported extension headers, and zero-checksum UDP are reported as bypass by the engine and are unaffected. Comments in emac_hal.c document the store-forward constraints (including why Tx checksum insertion via TDES0.CIC cannot work: it requires Tx store-and-forward, which stalls all transmission on ESP32-S31, and in threshold mode CIC is ignored - frames leave with a zeroed IP header checksum, verified with tcpdump on the link partner).

Commit 2 - lwip: expose LWIP_CHECKSUM_CHECK_TCP and LWIP_CHECKSUM_GEN_IP/UDP/TCP/ICMP in Kconfig (all defaulting to current behavior). With commit 1, disabling the TCP receive check on an internal-EMAC-only system is safe and removes redundant per-segment software checksumming.

Measurements (ESP32-S31 @ 320 MHz, RGMII gigabit link, wired iperf2 peer, 0.1 ms RTT)

  • TCP Rx throughput with LWIP_CHECKSUM_CHECK_TCP=n: 160 -> 186 Mbit/s (+16 %).
  • Correctness, verified on hardware with a raw-socket sender:
    • 5000 valid-checksum UDP datagrams: 100 % delivered.
    • 5000 zero-checksum UDP datagrams (RFC-legal): 100 % delivered - no false-positive drops; the engine treats checksum 0 as "not present".
    • 5000 corrupt-checksum UDP datagrams (checksum field 0xDEAD, valid CRC): 0 delivered - previously these were all accepted with the default CHECKSUM_CHECK_UDP=n.
  • DHCP, ARP, ICMP, TCP sessions regression-tested on hardware.

Precedent

The same Synopsys DWMAC IP in the Linux stmmac driver handles this identically in spirit: Rx frames flagged by the checksum engine must not be treated as verified (CVE-2025-40337, fixed in commit 63fbe0e64132 by routing flagged frames to software re-validation; lwIP has no per-packet re-validation path, so dropping - the same policy the hardware applies in store-forward mode - is the equivalent). The Tx-side FIFO constraint is also known there: boards with Tx FIFO smaller than the frame size disable Tx checksum offload and fall back to software checksums.

Testing

  • ESP32-S31 (RGMII, 1000M): all measurements above, plus continuous iperf2 TCP/UDP operation.
  • Build-tested: examples/ethernet/basic for esp32 and esp32p4; full application build for esp32s31.
  • On targets whose EMAC was synthesized without the checksum engine the extended status bits stay zero, making the new check a no-op.

Note

Medium Risk
Changes Ethernet receive filtering and optional lwIP checksum behavior—misconfiguration (disabling software checks without hardware validation) could admit corrupted traffic; defaults and EMAC-only guidance mitigate this.

Overview
Internal EMAC Rx path now drops frames the hardware checksum engine flags as bad. In emac_esp_dma_get_valid_recv_len(), when enhanced Rx descriptor status is present and the engine did not bypass verification, frames with IPHeadErr or IPPayloadErr are flushed like ErrSummary frames—so lwIP can trust hardware-checked IP/TCP/UDP/ICMP on cut-through targets where Receive Store Forward stays off.

emac_hal.c comments document why RSF and Tx store-and-forward stay disabled (small FIFOs; full-frame Tx offload not viable on ESP32-S31).

lwIP gains Kconfig toggles for TCP receive checksum checking and software generation of IP/UDP/TCP/ICMP checksums (lwipopts.h maps them to CHECKSUM_*). Defaults preserve current behavior; with the DMA change, disabling redundant TCP Rx checks on internal-EMAC-only builds is documented as safe.

Reviewed by Cursor Bugbot for commit 5fd2584. Bugbot is set up for automated code reviews on this repo. Configure here.

The EMAC Rx checksum offload engine is enabled unconditionally
(emac_hal_init_mac_default), but its verdict was never consumed: the
DMA cannot drop erroneous frames in hardware because that requires
Receive Store Forward, and the Rx FIFO is smaller than a full frame
on most targets (verified on ESP32-S31: with store-and-forward
enabled, full-size frames are never forwarded to memory).

Consume the engine's per-frame verdict from the enhanced descriptor
extended status (IPHeadErr/IPPayloadErr, qualified by IPChecksumBypass)
in the Rx path and drop bad frames there. This lets the network stack
rely on hardware-verified IP/TCP/UDP/ICMP checksums; with lwIP's
software TCP receive checksum disabled, TCP Rx throughput on ESP32-S31
at 1000M improves from 160 to 186 Mbit/s (iperf2, wired peer,
320 MHz single core).

Also document why Tx checksum insertion (TDES0.CIC) cannot be used:
it requires Tx store-and-forward, which stops all transmission on
ESP32-S31 (Tx FIFO smaller than a full frame); in threshold mode CIC
is ignored and frames leave with a zeroed IP header checksum
(verified on the wire).
Add LWIP_CHECKSUM_CHECK_TCP and LWIP_CHECKSUM_GEN_IP/UDP/TCP/ICMP
options (all defaulting to the current behavior). Disabling the TCP
receive check is useful when the network interface hardware verifies
checksums and drops corrupted frames, e.g. the internal EMAC checksum
offload engine on ESP32-S31; the generation options prepare for
interfaces with transmit checksum insertion.
@github-actions github-actions Bot changed the title feat(esp_eth): consume Rx checksum offload engine verdict; expose lwIP checksum Kconfig feat(esp_eth): consume Rx checksum offload engine verdict; expose lwIP checksum Kconfig (IDFGH-18027) Jul 24, 2026
@espressif-bot espressif-bot added the Status: Opened Issue is new label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Opened Issue is new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants