Skip to content

os/ip: TCP's usable window no longer wraps past the peer's window - #1155

Open
pdfinn wants to merge 2 commits into
masterfrom
fix/tcp-usable-underflow
Open

pdfinn wants to merge 2 commits into
masterfrom
fix/tcp-usable-underflow

Conversation

@pdfinn

@pdfinn pdfinn commented Nov 10, 2026

Copy link
Copy Markdown
Member

Fixes #1151.

Cause

In tcpoutput, usable is a u32int:

usable = min(cwind, snd.wnd);
usable -= sent;

When more is in flight than the window allows (sent > snd.wnd), the subtraction wraps to about 4G, so the if(usable < ssize) clamp never applies. The sender then sends a full segment every time it gets the chance.

It takes very little to get there. Scaled windows are rounded to their granularity (8 bytes at scale 3, Linux's included), so the peer's right edge can draw back by a few bytes between ACKs. The sender is then a few bytes over its window, and from that moment it runs away.

What the traces showed

Pi 4, devssl over loopback, using diagnostic kernels with timestamped netlog lines:

  • SENDER SAW SHRINK by 2, then OVERRUN by 16346 ... usable -2, continuing to OVERRUN by 392258: the sender kept sending full segments ever further past the window.
  • 174 of 178 refused segments lay beyond the receiver's advertised edge. Its queue was full (qlen 524280, rcv.wnd 0), so tcptrim dropped them.
  • After that, every segment past the hole drew a duplicate ACK (up to 31 in a row), followed by a fast retransmit. Recovery went one segment per round trip, and with the 50 ms delayed ACK that was about 1 MB/s.

Fix

Clamp at zero: if(usable > sent) usable -= sent; else usable = 0;. The sender now waits for the window, as it should.

Measured

Pi 4, crypto_bench over loopback, three 2 s runs per record size. Each block was a fresh tryboot. All kernels include #1150's loopback queue.

kernel 32K and 24K records, SHA-256 same, AES-256-CBC + SHA-256 16K records
fixed 42–67 MB/s 18–35 MB/s 65–66 / 35 MB/s
unfixed 1.1–1.4 MB/s 0.6–1.0 MB/s 66 / 28–34 MB/s
fixed again 23–67 MB/s 18–34 MB/s 66 / 35 MB/s

Scope

This isn't specific to loopback or devssl. It affects any native-kernel TCP connection whose receiver falls behind its sender and fills its window: a slow consumer, a Pi serving a fast client, or a Linux peer whose scaled window rounds down. It doesn't affect the hosted emulator, which uses the host's TCP.

Tests

tests/host/kernelpins_test.sh pins the clamp: every usable -= sent in tcpoutput must be guarded. I checked that it fails on master's tcp.c.

Not in this PR, possible follow-ups:

  • The receiver could round its advertised window up, so the edge never retreats (RFC 7323 §2.4); it isn't needed now that the sender copes.
  • The diagnostic tracing lives only on the bench.

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.

Native TCP: spurious retransmissions pin devssl over loopback at ~1 MB/s for records > 16K

1 participant