Skip to content

Enforce the TCP receive window to bound per-session memory - #90

Open
IntellyCode wants to merge 2 commits into
narrowlink:mainfrom
IntellyCode:enforce-recv-window
Open

Enforce the TCP receive window to bound per-session memory#90
IntellyCode wants to merge 2 commits into
narrowlink:mainfrom
IntellyCode:enforce-recv-window

Conversation

@IntellyCode

Copy link
Copy Markdown
Contributor

ipstack advertises a receive window but never enforces it: the reassembly buffer and the data_tx handoff channel are both unbounded, so a peer that outpaces the reader (or holds a reassembly gap open) grows a session's memory without limit — an OOM risk on memory-constrained hosts.

This makes the window real:

  • add_unordered_packet drops out-of-order segments once the reassembly buffer reaches read_buffer_size (head-of-line always admitted).
  • data_tx/data_rx are bounded; extract_data_n_write_upstream reserves a slot before consuming, so a stalled reader shrinks the advertised window and backpressures the peer. poll_read signals a Notify to re-drive the flush when space frees.
  • SWS avoidance: advertise zero below one MSS, so a full receiver uses the peer's persist mode.
  • A FIN is accepted only once ack reaches its sequence, so a full channel can't strand the tail.
  • consume_unordered_packets trims a segment a retransmission left straddling ack instead of wedging.

Covered by unit tests and an async test; validated live against a real TCP connection (reader stalled → server throughput drops to zero, recovers on resume, clean close). If you'd prefer an in-repo integration test, I can add ~200 lines to tests/ that build a packet-framed in-memory device and assert the window closes under a stalled reader and reopens on drain.

The receive window ipstack advertises now holds. A segment beyond it is
dropped for the peer to resend, the head-of-line segment always admitted
so the stream advances. The handoff channel to the reader is bounded and
filled by reserving a slot before consuming, so buffered data leaves the
reassembly map only once it has a home; a reader that frees space wakes
the loop to flush more and the follow-up ACK carries the reopened window.
The window is advertised as zero below one segment, so a stalled reader
puts the peer into persist mode until space frees.

Consuming trims a stale head entry a re-segmented retransmission left
below the ack, and a FIN is accepted only once the data before it has
been consumed, so a full channel never strands the tail.
The stream's first tokio test drives extract_data_n_write_upstream
against a full handoff channel: buffered data stays in the reassembly
map and the ack holds until the reader drains a slot, then the tail
flushes and the ack advances.
@IntellyCode
IntellyCode force-pushed the enforce-recv-window branch 2 times, most recently from 323baf0 to d0785d6 Compare August 25, 2026 17:01
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.

1 participant