Files
lneto/tcp/policy.go
T
Pat Whittingslow 3eb1f39f1d feat(tcp): implement window scaling (RFC 7323) (rebased) (#197)
* feat(tcp): implement window scaling (RFC 7323)

The window-scale option was parsed for length validation but never applied:
the effective receive window was capped at the 16-bit field, which caps
throughput at 65535/RTT (about 4MB/s on a 15ms path) no matter how large the
receive buffer is. Worse, a receive buffer over 64KiB silently WRAPPED the
advertised window on the SYN (uint16 truncation): a 128KiB buffer went on
the wire as a near-zero window.

Design: scaling lives purely at the wire seam in Handler. The ControlBlock
always holds real octet counts; conversion happens on frame read (peer
windows shifted up by the peer's offer, never on SYN segments) and frame
write (wireWnd: our shift down, SYN never scaled, saturation instead of
wrap when the value still does not fit). The local shift derives from the
receive buffer size in SetBuffers; every active SYN offers it (a zero
shift still lets the peer scale, RFC 7323 §2.5) and a SYN-ACK echoes it
only when the peer's SYN carried the option. The ControlBlock's three
2**16 window caps move to the scaled maximum (65535<<14).

Tests: on-wire negotiation with asymmetric buffers (shift values, unscaled
saturated SYN windows, first scaled advertisement, peer scaling back up);
a transfer proving more than 64KiB genuinely in flight without a single
ACK, received intact; wire-safety corners (no echo without an offer,
saturation not wrap, so the pre-existing 128KiB SYN wrap bug stays pinned).
Fuzzers clean: 9.1M TCB execs, 4.9M full-stack HTTP execs, 7.2M TCB
actions.

* use min instead of if statement

* omitting use of min saves 8 bytes

* fix option offset bug and add OptionCodec.Next method

---------

Co-authored-by: Derek den Haas <d.haas@directcode.com>
2026-09-07 13:15:29 -07:00

34 lines
1.9 KiB
Go

package tcp
// TransmitUnlimited size returned by [Policy.PreTx] to signal no new data transmit limit (no congestion control).
const TransmitUnlimited = ^Size(0)
// Policy observes segment traffic and steers transmit behaviour: RTO,
// congestion control and the like (discussion #157). The tcp package holds no
// clock, so a Policy needing time carries its own (issue #140).
type Policy interface {
// Reset returns the Policy to its pre-connection state. Should be called on every
// Open/Listen on connection creation. Configuration like clock setting and fine tuning
// should persist throughout the Policy lifetime after Reset calls.
Reset()
// PreTx is called before writing to a frame.
// The outgoing frame options can be set by the Policy and will be respected if Frame offset >5.
// Keep in mind Handler will add options PreTx already added, these options are best overwritten in PostTx.
// retransmitFrom is ignored unless within [snd.UNA, snd.NXT] and returned retransmit==true.
// newTransmitLimit sets the maximum number of new bytes to send over the wire (congestion control).
// If not implementing congestion control then newTransmitLimit=[TransmitUnlimited].
PreTx(h *Handler, outgoingOpts Frame) (newTransmitLimit Size, retransmitFrom Value, retransmit bool)
// PostTx called on leaving the transmit path with the fully written frame.
// PostTx can strategically overwrite options normally set by Handler like MSS, Window scaling which
// ends up being more ergonomic than adding them in PreTx and then de-duplicating them in PostTx.
PostTx(h *Handler, outgoing Frame)
// PreRx is called by [Handler] on every incoming segment.
// PreRx can choose to drop segment if it returns keep=false.
PreRx(h *Handler, incoming Frame) (keep bool)
// PostRx is called by [Handler] after accepting an incoming segment.
// To access [ControlBlock.SendUNA] before incoming frame was processed save UNA in PreRx.
PostRx(h *Handler, prevState State, accepted Frame)
}