Skip to content

Commit 4247d93

Browse files
ccie18643claude
andcommitted
docs(rfc): record TCP buffer auto-tuning in RFC 9293 / RFC 7323 adherence
Refresh the adherence records for the Tier-3 buffer auto-tuning work (steps 1-6), per the lockstep rule that RFC-governed runtime changes update the relevant adherence record. RFC 9293: - New §3.8.6.4 "Advertised-window sizing (receive-buffer auto-tuning)": documents that rcv_wnd_max is seeded from SO_RCVBUF / tcp.rmem.default and grown by Dynamic Right-Sizing (_maybe_adjust_rcv_space toward 2*copied + 16*advmss, clamped at min(tcp.rmem.max, 0xFFFF << rcv_wsc)), gated on tcp.moderate_rcvbuf and disabled under SOCK_RCVBUF_LOCK — Linux tcp_rcv_space_adjust parity. - Coverage-table rows for §3.8.6.4 DRS and the renamed §3.10.2 SEND / SO_SNDBUF auto-tune (tcp_sndbuf_expand). RFC 7323: - §2.2 "Maximum shift count": the offered rcv_wsc is now derived at SYN from tcp.rmem.max via derive_rcv_wscale (Linux tcp_select_initial_window), capped at WINDOW_SCALE__MAX_SHIFT = 14, strengthening the max-14 MUST; default 6 MiB rmem.max still derives shift 7. - New §4 extension "Receiver-side RTT estimator (DRS)": the TSecr-echo EWMA (RcvRttState.observe) and the no-timestamps window-time min-fold fallback (observe_window, Linux tcp_rcv_rtt_measure win_dep=1) that feed DRS without perturbing the RFC 6298 RTO. - Coverage + overall-assessment rows for both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6dc3bd5 commit 4247d93

2 files changed

Lines changed: 93 additions & 11 deletions

File tree

docs/rfc/tcp/rfc7323__timestamps_wscale_paws/adherence.md

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,23 @@ option presence:
6464
> the error but MUST use 14 instead of the specified
6565
> value."
6666
67-
**Adherence:** met (need to verify the clamp). The
68-
default `_rcv_wsc = 7`
69-
(`packages/pytcp/pytcp/protocols/tcp/tcp__session.py:165`) yields a
70-
~8 MB max receive window (65535 << 7), well within
71-
the 14 cap. The wire-level option's `shift_count`
72-
field accepts up to 255 at the assembler level
73-
(uint8); the protocol-level clamp to 14 happens at
74-
the inbound adoption site. A regression that
75-
adopted shift > 14 would manifest as oversized
76-
window field shifts.
67+
**Adherence:** met. The offered receive shift is no
68+
longer a flat default — `TcpSession.__init__` sets
69+
`rcv_wsc = derive_rcv_wscale(tcp.rmem.max)`
70+
(`packages/pytcp/pytcp/protocols/tcp/state/tcp__state__window.py`,
71+
`derive_rcv_wscale`), sizing the shift for the
72+
receive-buffer DRS ceiling (Linux
73+
`tcp_select_initial_window`). The derivation returns
74+
the smallest shift whose `0xFFFF << shift` covers
75+
`tcp.rmem.max` and is itself **capped at 14**
76+
(`WINDOW_SCALE__MAX_SHIFT`), so the outbound offer can
77+
never exceed the RFC limit regardless of how large an
78+
operator sets `tcp.rmem.max`. The default 6 MiB
79+
`tcp.rmem.max` derives shift 7 (65535 << 7 ≈ 8 MiB),
80+
the canonical Linux value. The wire-level option's
81+
`shift_count` field accepts up to 255 at the assembler
82+
level (uint8); the protocol-level clamp to 14 on the
83+
*inbound* adoption side is separate and unchanged.
7784

7885
### Window field unscaled on SYN / SYN+ACK
7986

@@ -253,6 +260,31 @@ multiple per-RTT samples from TSecr-based RTTM
253260
correctly; the alpha=1/8 / beta=1/4 weights work
254261
correctly with multiple samples per RTT.
255262

263+
### §4 (extension) Receiver-side RTT estimator (DRS)
264+
265+
Beyond the RTO sample above (which needs *our* data to
266+
be acked), PyTCP keeps a separate **receiver-side** RTT
267+
estimate for receive-buffer Dynamic Right-Sizing —
268+
mirroring Linux `rcv_rtt_est` / `tcp_rcv_rtt_measure_ts`
269+
— so a pure receiver that only sends ACKs still measures
270+
a round trip.
271+
272+
**Adherence:** met (Linux-parity extension). On an
273+
inbound new-data segment carrying a valid TSecr,
274+
`RcvRttState.observe`
275+
(`packages/pytcp/pytcp/protocols/tcp/state/tcp__state__rcv_rtt.py`)
276+
folds `now_ms - TSecr` via an alpha=1/8 EWMA, deduped on
277+
the echoed TSecr so a within-RTT burst yields one
278+
sample (the phase-5 hook in
279+
`session/tcp__session__ack.py`). When TSopt was **not**
280+
negotiated, the fallback `observe_window` measures the
281+
wall-time to receive one advertised window of data
282+
(anchor at `rcv_nxt + rcv_wnd`) and takes the minimum,
283+
matching Linux `tcp_rcv_rtt_measure` (`win_dep=1`). The
284+
estimate feeds the DRS cadence gate (RFC 9293 §3.8.6.4)
285+
but never the RFC 6298 RTO, so it cannot perturb
286+
retransmission timing.
287+
256288
### §4.3 Which Timestamp to Echo (cases A, B, C)
257289

258290
The §4.3 algorithm specifies the `TS.Recent` /
@@ -480,6 +512,23 @@ typical PyTCP use cases.
480512

481513
**Status:** locked in.
482514

515+
### §4 (extension) Receiver-side RTT estimator (DRS)
516+
517+
- **Unit:**
518+
`test__tcp__state__rcv_rtt.py::TestRcvRttState`
519+
pins the TSecr EWMA fold + within-RTT dedup;
520+
`::TestRcvRttStateWindowFallback` pins the
521+
no-timestamps `observe_window` min-fold.
522+
- **Integration:**
523+
`test__tcp__session__rcv_rtt.py` pins the RX-path
524+
TSecr sample (seed / EWMA / dedup, and that a no-TS
525+
connection stays unset after a single segment);
526+
`test__tcp__session__rcv_rtt_no_ts.py` pins the
527+
window-based fallback seeding the estimate and
528+
enabling DRS growth on a timestamp-less connection.
529+
530+
**Status:** locked in.
531+
483532
### §4.3 Which TSval to echo
484533

485534
- **Integration:**
@@ -563,6 +612,7 @@ regression guard.
563612
| §3.2 TSopt on RST | n/a (gap) |
564613
| §3.2 SHOULD drop missing-TSopt | n/a (gap) |
565614
| §4 RTTM rule | locked in |
615+
| §4 Receiver-side RTT estimator (DRS) | locked in (unit + integration, TS + no-TS) |
566616
| §4.2 EWMA with multiple samples | locked in (covered by RFC 6298 audit) |
567617
| §4.3 Which TSval to echo (A/B/C) | locked in (Last.ACK.sent gate test) |
568618
| §5.2 PAWS basic | locked in |
@@ -578,7 +628,7 @@ regression guard.
578628
|-------------------------------------------------|-----------------------------------------|
579629
| §2.2 WSCALE wire format | met |
580630
| §2.2 Bilateral negotiation | met |
581-
| §2.2 Max shift count = 14 | met (default 7) |
631+
| §2.2 Max shift count = 14 | met (derived from tcp.rmem.max, cap 14) |
582632
| §2.2 SYN window unscaled | met |
583633
| §2.3 Snd/Rcv.Wind.Shift state | met |
584634
| §2.4 Window retraction handling | met (cross-cut RFC 1122) |
@@ -587,6 +637,7 @@ regression guard.
587637
| §3.2 TSopt on RST | met (synchronized states) |
588638
| §3.2 SHOULD drop missing-TSopt | met |
589639
| §4 RTTM rule | met |
640+
| §4 Receiver-side RTT estimator (DRS) | met (Linux-parity; TS + no-TS) |
590641
| §4.2 EWMA multi-sample | met |
591642
| §4.3 TSval-to-echo (A/B/C) | met (Last.ACK.sent gate via RCV.NXT) |
592643
| §5.2 PAWS | met |

docs/rfc/tcp/rfc9293__tcp/adherence.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,35 @@ Cross-cut with RFC 1122 §4.2.3.2 (audited).
380380

381381
**Adherence:** met.
382382

383+
#### §3.8.6.4 Advertised-window sizing (receive-buffer auto-tuning)
384+
385+
RFC 9293 §3.8.6 leaves the *size* of the advertised
386+
receive window to the implementation. PyTCP sizes it
387+
dynamically, mirroring Linux receive-buffer Dynamic
388+
Right-Sizing (`tcp_rcv_space_adjust`).
389+
390+
**Adherence:** met (Linux-parity extension). The
391+
advertised window is `max(0, rcv_wnd_max -
392+
len(rx_buffer))` (`session/tcp__session.py` `_rcv_wnd`
393+
property), scaled by `rcv_wsc` (RFC 7323). `rcv_wnd_max`
394+
is seeded from the socket's `SO_RCVBUF`, else
395+
`tcp.rmem.default`, and grown at runtime by DRS
396+
(`_maybe_adjust_rcv_space`, run from `receive()`): once
397+
per receiver-RTT, when the application has drained more
398+
than the previous per-RTT measurement, `rcv_wnd_max`
399+
grows toward `2*copied + 16*advmss` plus a sender-rate
400+
term, clamped at `min(tcp.rmem.max, 0xFFFF << rcv_wsc)`.
401+
DRS is gated on the `tcp.moderate_rcvbuf` sysctl and
402+
disabled when `SO_RCVBUF` is set explicitly
403+
(SOCK_RCVBUF_LOCK), so an explicit option pins the
404+
window. The receiver RTT it needs comes from the RFC
405+
7323 TSecr echo on inbound data, or — without negotiated
406+
timestamps — from the wall-time to receive one
407+
advertised window (Linux `tcp_rcv_rtt_measure`). The
408+
offered `rcv_wsc` is derived at SYN from `tcp.rmem.max`
409+
(RFC 7323 §2.2) so the negotiated scaling can express
410+
the whole grown window.
411+
383412
---
384413

385414
## §3.9 Interfaces
@@ -578,7 +607,9 @@ locations:
578607
| §3.8.6.1 Persist timer | data_transfer__send / window persist tests |
579608
| §3.8.6.2 SWS | window tests |
580609
| §3.8.6.3 Delayed ACK | data_transfer__recv tests |
610+
| §3.8.6.4 Advertised-window DRS | `test__tcp__session__so_rcvbuf.py` (SO_RCVBUF sizing); `test__tcp__session__drs.py` (grow toward BDP, cadence gate, rmem.max clamp, moderate_rcvbuf/SO_RCVBUF lock, receive()-path trigger); `test__tcp__session__rcv_rtt.py` / `test__tcp__session__rcv_rtt_no_ts.py` (RTT source); `test__tcp__session__rcv_wscale.py` (WSCALE ceiling) |
581611
| §3.9 Interfaces | socket tests + harness_smoke; `sendmsg` in `test__socket__tcp__socket.py::TestTcpSocketSendmsg`; SO_LINGER setsockopt/getsockopt in `::TestTcpSocketSoLinger` + close-path in `test__tcp__session__so_linger.py` |
612+
| §3.10.2 SEND / SO_SNDBUF autotune | `test__tcp__session__sndbuf_autotune.py` (grows with cwnd, wmem.max clamp, grow-only, SO_SNDBUF lock); `test__tcp__session__buffer_defaults.py` (wmem.default floor) |
582613
| §3.10.2 SEND / SO_SNDBUF backpressure | `test__tcp__session__so_sndbuf.py` (partial-write, non-blocking EAGAIN, oversized-into-empty, getsockopt parity, ACK-drain wake, close wake) |
583614
| §3.10.7 Per-state SEGMENT ARRIVES | per-state test files |
584615
| §3.10.8 Timeouts | RTO + persist + keep-alive + TIME-WAIT tests |

0 commit comments

Comments
 (0)