tcp: mss honoring; accept syn with ECE/CWR flags; add RSTQueue type (#41)

* tcp: mss honoring; accept syn with ECE/CWR flags; add RSTQueue type

* tcp: move option logic to own file

* remove prints in pcap

* add MSS send threshold inspired by linux/freebsd/lwip thresh
This commit is contained in:
Pat Whittingslow
2026-02-25 16:58:55 +01:00
committed by GitHub
parent 58a9bf57e5
commit be949d960c
10 changed files with 570 additions and 191 deletions
+24 -9
View File
@@ -176,7 +176,7 @@ func (h *Handler) Recv(incomingPacket []byte) error {
if err != nil {
if h.scb.State() == StateClosed {
// TODO(soypat): Should return EOF/ErrClosed?
err = err // Connection closed by reset.
err = net.ErrClosed //err // Connection closed by reset.
}
return err
}
@@ -198,10 +198,22 @@ func (h *Handler) Recv(incomingPacket []byte) error {
// Update TX ring buffer to free up acked data.
h.bufTx.RecvACK(segIncoming.ACK)
}
if segIncoming.Flags.HasAny(FlagSYN) && h.remotePort == 0 {
// Remote reached out and has given us their port, set it on our side.
h.debug("tcp.Handler:rx-remoteport-set", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)))
h.remotePort = remotePort
if segIncoming.Flags.HasAny(FlagSYN) {
// Parse remote MSS from TCP options.
h.optcodec.ForEachOption(tfrm.Options(), func(kind OptionKind, data []byte) error {
if kind == OptMaxSegmentSize && len(data) == 2 {
mss := uint16(data[0])<<8 | uint16(data[1])
if mss > 0 {
h.scb.snd.MSS = Size(mss)
}
}
return nil
})
if h.remotePort == 0 {
// Remote reached out and has given us their port, set it on our side.
h.debug("tcp.Handler:rx-remoteport-set", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)))
h.remotePort = remotePort
}
}
if h.logenabled(internal.LevelTrace) {
h.trace("tcp.Handler:rx-done", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)), slog.String("seg", segIncoming.String()))
@@ -343,16 +355,19 @@ func (h *Handler) Read(b []byte) (n int, err error) {
// is still 0 after we've Read() data from the buffer.
//
// Per RFC 9293 §3.8.6.2.2 (SWS avoidance), the window is updated when freed
// space >= min(bufferSize/2, MSS). Since we don't track MSS, we use bufferSize/2.
// Zero-window openings always trigger an update.
// space >= min(bufferSize/2, MSS). This applies uniformly including zero-window
// recovery — the remote uses zero-window probes until enough space opens.
func (h *Handler) maybeQueueWindowUpdate() {
currentFree := Size(h.bufRx.Free())
lastAdvertised := h.scb.RecvWindow()
if currentFree <= lastAdvertised {
return // Window hasn't grown.
}
bufSize := Size(h.bufRx.Size())
if lastAdvertised == 0 || currentFree-lastAdvertised >= bufSize/2 {
thresh := Size(h.bufRx.Size()) / 2
if mss := h.scb.snd.MSS; mss > 0 && mss < thresh {
thresh = mss
}
if currentFree-lastAdvertised >= thresh {
h.scb.pending[0] |= FlagACK
}
}