Skip to content

net_tap: include the L4 length in the LSO checksum seed#3812

Open
bitranox wants to merge 1 commit into
microsoft:mainfrom
bitranox:fix-net-tap-lso-checksum
Open

net_tap: include the L4 length in the LSO checksum seed#3812
bitranox wants to merge 1 commit into
microsoft:mainfrom
bitranox:fix-net-tap-lso-checksum

Conversation

@bitranox

Copy link
Copy Markdown
Contributor

Problem

A guest TCP stream sent with TSO/LSO to an off-host receiver collapses to
a few Mbit/s with heavy retransmits. The same guest is fine with
segmentation offload disabled, to a same-host peer, and on receive.

Cause

For a segmentation-offload (GSO) frame, net_tap forwards the frontend's
partial TCP/UDP checksum to the kernel unchanged. The netvsp LSO path
supplies a length-less pseudo-header seed (the guest cannot know the
per-segment length). The kernel GSO path expects th->check to be the
pseudo-header over the full L4 length, as __tcp_v4_send_check writes for a
GSO skb; tcp_gso_segment and the hardware TSO drivers then adjust it down
per segment. With a length-less seed the adjustment is short by the
segment length, so every segment goes out with a wrong checksum and the
receiver drops it. Checksum offload without segmentation is unaffected,
because the non-segmented seed already includes the length.

Fix

For segmentation frames, recompute the L4 pseudo-header seed in net_tap
from the packet's own IP header, including the full L4 length. The payload
is left untouched; the kernel or NIC still completes the per-segment
checksum.

Testing

Unit test for the seed value. On a Linux guest over the TAP backend, an
off-host TCP TX recovered from a few Mbit/s to ~9 Gbit/s, and a checksum
capture at the egress NIC showed zero bad checksums across ~1.8M segments
(previously ~16% bad).

@bitranox bitranox requested a review from a team as a code owner June 24, 2026 20:17
Copilot AI review requested due to automatic review settings June 24, 2026 20:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Linux TAP/GSO interoperability issue for Windows/NDIS LSO by rewriting the TCP/UDP checksum seed on segmentation-offload frames so the kernel’s GSO path sees a pseudo-header seed that includes the full L4 length, enabling correct per-segment checksum adjustment during segmentation.

Changes:

  • Add fixup_gso_pseudo_header() to recompute the L4 pseudo-header checksum seed (including full L4 length) for TCP/UDP segmentation-offload frames.
  • Invoke the fixup for outgoing packets when TCP or UDP segmentation offload is requested.
  • Add a unit test validating the rewritten pseudo-header seed value (currently IPv4/TCP).

Comment thread vm/devices/net/net_tap/src/lib.rs Outdated
Comment thread vm/devices/net/net_tap/src/lib.rs
Comment thread vm/devices/net/net_tap/src/lib.rs
@bitranox bitranox force-pushed the fix-net-tap-lso-checksum branch from 6a3e7c9 to b0716e1 Compare June 24, 2026 20:48
@bitranox

Copy link
Copy Markdown
Contributor Author

Thanks. Addressed all three:

  • Widened the IPv6 payload-length fixup to also cover UDP segmentation (USO), matching the checksum-seed fixup and the comment.
  • Reworded the doc: the seed is recomputed from the IP header addresses and protocol plus the L4 length taken from the frame size.
  • Added an IPv4/UDP test (gso_pseudo_header_seed_includes_length_udp) covering the offset-6 / UDP-header-length path.

@bitranox

Copy link
Copy Markdown
Contributor Author

@smalis-msft - uh that was bad - You dont do performance tests before merging ?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread vm/devices/net/net_tap/src/lib.rs Outdated
Comment thread vm/devices/net/net_tap/src/lib.rs
A segmentation-offload frame carries a partial TCP/UDP checksum in the
checksum field. The netvsp LSO path supplies a length-less pseudo-header
there, because the guest cannot know the per-segment length. The kernel
GSO path expects the opposite: th->check is the pseudo-header over the
full L4 length, as __tcp_v4_send_check writes for a GSO skb, which
tcp_gso_segment and the hardware TSO drivers adjust down per segment.

Passing the length-less seed straight through leaves the per-segment
adjustment short by the segment length, so every emitted segment carries
a wrong checksum and an off-host receiver drops it. A guest TCP stream
over TSO to an off-host peer collapses to a few Mbit/s of retransmits.
Checksum offload without segmentation and same-host traffic are
unaffected, since their seed already includes the length.

Recompute the seed for segmentation frames from the packet's own IP
header. The payload is untouched; the kernel or NIC still completes the
per-segment checksum.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@github-actions

Copy link
Copy Markdown

@bitranox

Copy link
Copy Markdown
Contributor Author

@smalis-msft - I guess make that sooner then later - NIC TX Performance was down from around 10GBit to 30MBit because of checksum errors due to #3730

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants