mirror of
https://github.com/soypat/lneto.git
synced 2026-08-31 11:59:05 +00:00
21f477b86e
ControlBlock.Send refused any outgoing segment carrying data in
FIN-WAIT-1, citing RFC 9293's "no further SENDs from the user will be
accepted by the TCP implementation". That rule bounds what the
application may queue, which Handler.Write already enforces, and not the
retransmission of data the connection has already accepted from it.
The consequence is that write-then-close, which is what nearly every
server does with a response, cannot recover from losing its last data
segment. The FIN occupies a sequence number above that data, so the peer
cannot cross the gap to process the close: it waits for bytes that are
never resent while the sender waits for an ACK that cannot arrive.
Neither side times out at the TCP layer.
FIN-WAIT-2 keeps the restriction and gains the reasoning: it is reached
by our FIN being acknowledged, which acknowledges everything below it, so
no unacknowledged data can remain there.
PendingSegment needs the same distinction, since it decides whether to
offer send-buffer data at all; it gets an unexported State predicate
rather than a new exported one.
Two tests, the second red before the change:
- retransmission after RTO expiry, covering the Handler/LossRecovery
seam that the RTO unit tests do not reach (they exercise the state
machine in isolation, where it behaves correctly).
- retransmission after a close with unacknowledged data.
Co-authored-by: Derek den Haas <i.pestano@easyflor.nl>
757 lines
27 KiB
Go
757 lines
27 KiB
Go
package tcp
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"math"
|
|
"net"
|
|
|
|
"github.com/soypat/lneto/internal"
|
|
)
|
|
|
|
const (
|
|
// signals to create a retransmit packet after receiving this number of duplicate acks, not including the ack that set UNA.
|
|
retransmitAfterDupacks = 3
|
|
// retransmitMaxQueued sets maximum amount of retransmits to queue while receiving dupacks.
|
|
retransmitMaxQueued = 2
|
|
// maxChallengeRejects is the number of consecutive challenge ACKs sent without
|
|
// a successful Recv before aborting. Prevents infinite ACK ping-pong when both
|
|
// sides have diverged state (e.g. after packet mutation).
|
|
maxChallengeRejects = 8
|
|
)
|
|
|
|
// ControlBlock is a partial Transmission Control Block (TCB) implementation as
|
|
// per RFC 9293 in section 3.3.1. In contrast with the description in RFC9293,
|
|
// this implementation is limited to receiving only sequential segments.
|
|
// This means buffer management is left up entirely to the user of the ControlBlock.
|
|
// Use ControlBlock as the building block that solves Sequence Number calculation
|
|
// and validation in a full TCP implementation.
|
|
//
|
|
// A ControlBlock's internal state is modified by the available "System Calls" as defined in
|
|
// RFC9293, such as Close, Listen/Open, Send, and Receive.
|
|
// Sent and received data is represented with the [Segment] struct type.
|
|
//
|
|
// Note that [ControlBlock] is the lowest level implementation of TCP and as such is missing most useful functionality.
|
|
// See [Handler], which uses ControlBlock, for a higher level implementation. [Conn] is an even higher level implementation
|
|
// which makes use of a [Handler].
|
|
type ControlBlock struct {
|
|
// # Send Sequence Space
|
|
//
|
|
// 'Send' sequence numbers correspond to local data being sent.
|
|
//
|
|
// 1 2 3 4
|
|
// ----------|----------|----------|----------
|
|
// SND.UNA SND.NXT SND.UNA
|
|
// +SND.WND
|
|
// 1. old sequence numbers which have been acknowledged
|
|
// 2. sequence numbers of unacknowledged data
|
|
// 3. sequence numbers allowed for new data transmission
|
|
// 4. future sequence numbers which are not yet allowed
|
|
snd sendSpace
|
|
// # Receive Sequence Space
|
|
//
|
|
// 'Receive' sequence numbers correspond to remote data being received.
|
|
//
|
|
// 1 2 3
|
|
// ----------|----------|----------
|
|
// RCV.NXT RCV.NXT
|
|
// +RCV.WND
|
|
// 1 - old sequence numbers which have been acknowledged
|
|
// 2 - sequence numbers allowed for new reception
|
|
// 3 - future sequence numbers which are not yet allowed
|
|
rcv recvSpace
|
|
// When FlagRST is set in pending flags rstPtr will contain the sequence number of the RST segment to make it "believable" (See RFC9293)
|
|
rstPtr Value
|
|
logger
|
|
|
|
// pending is the queue of pending flags to be sent in the next 2 segments.
|
|
// On a call to Send the queue is advanced and flags set in the segment are unset.
|
|
// The second position of the queue is used for FIN segments.
|
|
pending [2]Flags
|
|
_state State // leading underscore so field not suggested on top of exported State method when developing.
|
|
|
|
// challengeAcks counts consecutive challenge acks queued on receiving out of window segment.
|
|
// challengeAcks signedness indicates whether the challengeAck is pending being sent.
|
|
// A negative value of challengeAcks means a challenge ack is pending being sent.
|
|
challengeAcks int8
|
|
|
|
// dupack counts received ACK==snd.UNA && ACK<snd.NXT received. Does not count ack that set UNA.
|
|
dupack uint8
|
|
// nRetransmit counts number of retransmits sent since last UNA update.
|
|
nRetransmit uint8
|
|
}
|
|
|
|
// State returns the current state of the TCP connection. See [State].
|
|
func (tcb *ControlBlock) State() State { return tcb._state }
|
|
|
|
// RecvNext returns the next sequence number expected to be received from remote.
|
|
// This implementation will reject segments that are not the next expected sequence.
|
|
// RecvNext returns 0 before StateSynRcvd.
|
|
func (tcb *ControlBlock) RecvNext() Value { return tcb.rcv.NXT }
|
|
|
|
// RecvWindow returns the receive window size. If connection is closed will return 0.
|
|
func (tcb *ControlBlock) RecvWindow() Size { return tcb.rcv.WND }
|
|
|
|
// ISS returns the initial sequence number of the connection that was defined on a call to Open by user.
|
|
func (tcb *ControlBlock) ISS() Value { return tcb.snd.ISS }
|
|
|
|
// MaxInFlightData returns the maximum size of a segment that can be sent by taking into account
|
|
// the send window size and the unacked data. Returns 0 before StateSynRcvd.
|
|
func (tcb *ControlBlock) MaxInFlightData() Size {
|
|
if !tcb._state.hasIRS() {
|
|
return 0 // SYN not yet received.
|
|
}
|
|
unacked := Sizeof(tcb.snd.UNA, tcb.snd.NXT)
|
|
if unacked >= tcb.snd.WND {
|
|
return 0
|
|
}
|
|
return tcb.snd.WND - unacked
|
|
}
|
|
|
|
// SetWindow sets the local receive window size. This represents the maximum amount of data
|
|
// that is permitted to be in flight.
|
|
func (tcb *ControlBlock) SetRecvWindow(wnd Size) {
|
|
tcb.rcv.WND = wnd
|
|
}
|
|
|
|
// SetLogger sets the logger to be used by the ControlBlock.
|
|
func (tcb *ControlBlock) SetLogger(log *slog.Logger) {
|
|
tcb.logger = logger{log: log}
|
|
}
|
|
|
|
// IncomingIsKeepalive checks if an incoming segment is a keepalive segment.
|
|
// Segments which are keepalives should not be passed into Recv or Send methods.
|
|
func (tcb *ControlBlock) IncomingIsKeepalive(incomingSegment Segment) bool {
|
|
return incomingSegment.SEQ == tcb.rcv.NXT-1 &&
|
|
incomingSegment.Flags == FlagACK &&
|
|
incomingSegment.ACK == tcb.snd.NXT && incomingSegment.DATALEN == 0
|
|
}
|
|
|
|
// IncomingIsDupACK returns true if the ACK value is a duplicate acknowledgement:
|
|
// the ACK equals the oldest unacknowledged sequence number (snd.UNA) meaning no
|
|
// new data is acknowledged, while snd.UNA < snd.NXT meaning data is in flight.
|
|
func (tcb *ControlBlock) IncomingIsDupACK(ack Value) bool {
|
|
return ack == tcb.snd.UNA && ack.LessThan(tcb.snd.NXT)
|
|
}
|
|
|
|
// MakeKeepalive creates a TCP keepalive segment. This segment
|
|
// should not be passed into Recv or Send methods.
|
|
func (tcb *ControlBlock) MakeKeepalive() Segment {
|
|
return Segment{
|
|
SEQ: tcb.snd.NXT - 1,
|
|
ACK: tcb.rcv.NXT,
|
|
Flags: FlagACK,
|
|
WND: tcb.rcv.WND,
|
|
DATALEN: 0,
|
|
}
|
|
}
|
|
|
|
// QueueRST queues a RST segment to be emitted on the next send, overriding any
|
|
// other pending flags. seq is the sequence number the RST will carry (per RFC
|
|
// 9293 reset generation, the acknowledged value SEG.ACK of the offending
|
|
// segment). The connection should be torn down once the RST is sent.
|
|
func (tcb *ControlBlock) QueueRST(seq Value) {
|
|
tcb.pending = [2]Flags{0: FlagRST}
|
|
tcb.rstPtr = seq
|
|
}
|
|
|
|
// MakeDupACK returns a duplicate ACK segment suitable for fast-retransmit
|
|
// recovery signaling, without advancing the sender ACK boundary. Useful for:
|
|
// - constructing an explicit duplicate ACK from local state (e.g. test harness),
|
|
// - expressing retransmit-request condition (`ACK == snd.UNA`, `SEQ == snd.UNA`)
|
|
// - advertising receive window via current `rcv.WND`.
|
|
func (tcb *ControlBlock) MakeDupACK() Segment {
|
|
return Segment{
|
|
SEQ: tcb.snd.UNA,
|
|
ACK: tcb.rcv.NXT,
|
|
Flags: FlagACK,
|
|
WND: tcb.rcv.WND,
|
|
DATALEN: 0,
|
|
}
|
|
}
|
|
|
|
// MakeChallengeAck returns a challenge ACK segment for the current ControlBlock state
|
|
// used to respond to unexpected or ambiguous segments that require the remote peer to confirm
|
|
// its connection state. A challenge ACK does not acknowledge new data,
|
|
// consume sequence space, or carry a payload.
|
|
func (tcb *ControlBlock) MakeChallengeACK() Segment {
|
|
return Segment{
|
|
SEQ: tcb.snd.NXT, // Current sequence number (no data)
|
|
ACK: tcb.rcv.NXT, // Acknowledging expected next byte
|
|
Flags: FlagACK, // Pure ACK, no SYN/FIN/RST
|
|
WND: tcb.rcv.WND, // Current receive window size
|
|
DATALEN: 0, // No payload
|
|
}
|
|
}
|
|
|
|
// recvSpace contains Receive Sequence Space data. Its sequence numbers correspond to remote data.
|
|
type recvSpace struct {
|
|
IRS Value // initial receive sequence number, defined by remote in SYN segment received.
|
|
NXT Value // receive next. seqs before this have been acked. this seq and up to NXT+WND-1 are allowed to be sent. Corresponds to remote data.
|
|
WND Size // receive window defined by local. Permitted number of remote unacked octets in flight.
|
|
}
|
|
|
|
// sendSpace contains Send Sequence Space data. Its sequence numbers correspond to local data.
|
|
type sendSpace struct {
|
|
ISS Value // initial send sequence number, defined locally on connection start
|
|
UNA Value // send unacknowledged. Seqs equal to UNA and above have NOT been acked by remote. Corresponds to local data.
|
|
NXT Value // send next. This seq and up to UNA+WND-1 are allowed to be sent. Corresponds to local data.
|
|
WND Size // send window defined by remote. Permitted number of local unacked octets in flight.
|
|
MSS Size // maximum segment size advertised by remote peer. 0 means not set.
|
|
WL1 Value // segment SEQ number of the last send-window update (RFC 9293 §3.10.7.4)
|
|
WL2 Value // segment ACK number of the last send-window update (RFC 9293 §3.10.7.4)
|
|
}
|
|
|
|
// inFlight returns amount of unacked bytes sent out.
|
|
func (snd *sendSpace) inFlight() Size {
|
|
return Sizeof(snd.UNA, snd.NXT)
|
|
}
|
|
|
|
// maxSend returns maximum segment datalength receivable by remote peer.
|
|
func (snd *sendSpace) maxSend() Size {
|
|
if inf := snd.inFlight(); inf >= snd.WND {
|
|
// Guard uint32 underflow when window shrinks below inflight.
|
|
return 0
|
|
} else {
|
|
return snd.WND - inf
|
|
}
|
|
}
|
|
|
|
// Open implements a passive opening of a connection (wait for incoming packets from an unknown remote port).
|
|
// Upon success [ControlBlock] enters LISTEN state, such as that of a server.
|
|
// To open an active connection use [ControlBlock.Send] with a segment generated with [ClientSynSegment].
|
|
func (tcb *ControlBlock) Open(iss Value, wnd Size) (err error) {
|
|
switch {
|
|
case tcb._state != StateClosed && tcb._state != StateTimeWait:
|
|
err = errNeedClosedTCBToOpen
|
|
case wnd > math.MaxUint16:
|
|
err = errWindowTooLarge
|
|
}
|
|
if err != nil {
|
|
tcb.logerr("tcb:open", slog.String("err", err.Error()))
|
|
return err
|
|
}
|
|
|
|
tcb.prepareToHandshake(iss, wnd, StateListen)
|
|
tcb.trace("tcb:open-server")
|
|
return nil
|
|
}
|
|
|
|
// prepareToHandshake initializes the TCB send/receive spaces with initial send sequence number and local window.
|
|
func (tcb *ControlBlock) prepareToHandshake(iss Value, wnd Size, newState State) {
|
|
tcb.reset()
|
|
tcb.resetRcv(wnd, 0)
|
|
tcb.resetSnd(iss, 1)
|
|
tcb._state = newState
|
|
}
|
|
|
|
// HasPending returns true if there is a pending control segment to send. Calls to Send will advance the pending queue.
|
|
func (tcb *ControlBlock) HasPending() bool {
|
|
return tcb.pending[0] != 0 || tcb.pendingChallengeAck() || tcb.HasPendingRetransmit()
|
|
}
|
|
|
|
// HasPending returns true if the control block is pending a retransmit according to simple optmist
|
|
// retransmit strategy.
|
|
func (tcb *ControlBlock) HasPendingRetransmit() bool {
|
|
// Force retransmit after 3 consecutive acks of UNA.
|
|
return tcb._state.TxDataOpen() && tcb.dupack >= retransmitAfterDupacks && tcb.nRetransmit <= tcb.dupack-retransmitAfterDupacks
|
|
}
|
|
|
|
// RetransmitAll rewinds snd.NXT back to snd.UNA so the next PendingSegment and
|
|
// Send calls retransmit all unacknowledged data from the oldest sequence number
|
|
// (go-back-N). It must be paired with ringTx.RetransmitFromUNA to rewind the
|
|
// transmit buffer. Implements RFC 9293 §3.10.8 (RETRANSMISSION TIMEOUT).
|
|
func (tcb *ControlBlock) RetransmitAll() {
|
|
tcb.snd.NXT = tcb.snd.UNA
|
|
tcb.dupack = 0
|
|
tcb.nRetransmit = 0
|
|
}
|
|
|
|
// PendingSegment calculates a suitable next segment to send from a payload length.
|
|
// It does not modify the ControlBlock state or pending segment queue.
|
|
func (tcb *ControlBlock) PendingSegment(payloadLen int) (_ Segment, ok bool) {
|
|
pending := tcb.pending[0]
|
|
if tcb.pendingChallengeAck() {
|
|
// Do not clear challengeAck here: PendingSegment is documented as read-only.
|
|
// The flag is consumed in Send when the ACK segment is actually transmitted.
|
|
return tcb.MakeChallengeACK(), true
|
|
} else if !pending.HasAny(flagctl) && tcb.HasPendingRetransmit() {
|
|
// Optimist Strategy: retransmit oldest data once.
|
|
return Segment{SEQ: tcb.snd.UNA, DATALEN: Size(payloadLen), ACK: tcb.rcv.NXT, WND: tcb.rcv.WND, Flags: FlagACK}, true
|
|
}
|
|
canSendData := tcb._state.txQueuedDataOpen()
|
|
if !canSendData {
|
|
payloadLen = 0 // No send-buffer data may go out in this state.
|
|
}
|
|
if pending == 0 && payloadLen == 0 {
|
|
return Segment{}, false // No pending segment.
|
|
}
|
|
|
|
// Limit payload to what send window allows.
|
|
inFlight := tcb.snd.inFlight()
|
|
_ = inFlight
|
|
maxPayload := tcb.snd.maxSend()
|
|
if payloadLen > int(maxPayload) {
|
|
if maxPayload == 0 && pending == 0 {
|
|
return Segment{}, false
|
|
} else if maxPayload > tcb.snd.WND {
|
|
panic("seqs: bad calculation")
|
|
}
|
|
payloadLen = int(maxPayload)
|
|
}
|
|
// Cap by remote MSS.
|
|
if tcb.snd.MSS > 0 && payloadLen > int(tcb.snd.MSS) {
|
|
payloadLen = int(tcb.snd.MSS)
|
|
}
|
|
|
|
if canSendData {
|
|
pending |= FlagACK // ACK is always set in established state. Not in RFC9293 but somehow expected?
|
|
} else {
|
|
payloadLen = 0 // Can't send data if not established.
|
|
}
|
|
|
|
var ack Value
|
|
if pending.HasAny(FlagACK) {
|
|
ack = tcb.rcv.NXT
|
|
}
|
|
|
|
var seq Value = tcb.snd.NXT
|
|
if pending.HasAny(FlagRST) {
|
|
seq = tcb.rstPtr
|
|
}
|
|
|
|
seg := Segment{
|
|
SEQ: seq,
|
|
ACK: ack,
|
|
WND: tcb.rcv.WND,
|
|
Flags: pending,
|
|
DATALEN: Size(payloadLen),
|
|
}
|
|
tcb.traceSeg("tcb:pending-out", seg)
|
|
return seg, true
|
|
}
|
|
|
|
// Recv processes a segment that is being received from the network. It updates the TCB
|
|
// if there is no error. The ControlBlock can only receive segments that are the next
|
|
// expected sequence number which means the caller must handle the out-of-order case
|
|
// and buffering that comes with it.
|
|
func (tcb *ControlBlock) Recv(seg Segment) (err error) {
|
|
err = tcb.validateIncomingSegment(seg)
|
|
if err != nil {
|
|
tcb.traceRcv("tcb:rcv.reject")
|
|
tcb.traceSeg("tcb:rcv.reject", seg)
|
|
tcb.logerr("tcb:rcv.reject", slog.String("err", err.Error()))
|
|
return err
|
|
}
|
|
|
|
// RFC 9293 §3.10.7.4: SYN on synchronized connection → challenge ACK.
|
|
if seg.Flags.HasAny(FlagSYN) && !tcb._state.IsPreestablished() {
|
|
tcb.triggerChallengeAckEmit()
|
|
tcb.pending[0] |= FlagACK
|
|
return errDropSegment
|
|
}
|
|
|
|
prevNxt := tcb.snd.NXT
|
|
var pending Flags
|
|
switch tcb._state {
|
|
case StateListen:
|
|
pending, err = tcb.rcvListen(seg)
|
|
case StateSynSent:
|
|
pending, err = tcb.rcvSynSent(seg)
|
|
case StateSynRcvd:
|
|
pending, err = tcb.rcvSynRcvd(seg)
|
|
case StateEstablished:
|
|
pending, err = tcb.rcvEstablished(seg)
|
|
case StateFinWait1:
|
|
pending, err = tcb.rcvFinWait1(seg)
|
|
case StateFinWait2:
|
|
pending, err = tcb.rcvFinWait2(seg)
|
|
case StateCloseWait:
|
|
case StateLastAck:
|
|
if seg.Flags.HasAny(FlagACK) {
|
|
tcb.Abort()
|
|
}
|
|
case StateClosing:
|
|
// Thanks to @knieriem for finding and reporting this bug.
|
|
if seg.Flags.HasAny(FlagACK) {
|
|
tcb._state = StateTimeWait
|
|
}
|
|
default:
|
|
panic("unexpected recv state:" + tcb._state.String())
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tcb.triggerChallengeAckSatisfied() // Successful Recv — reset challenge counter.
|
|
|
|
tcb.pending[0] |= pending
|
|
if prevNxt != 0 && tcb.snd.NXT != prevNxt && tcb.logenabled(slog.LevelDebug) {
|
|
tcb.debug("tcb:snd.nxt-change", slog.String("state", tcb._state.String()),
|
|
slog.Uint64("seg.ack", uint64(seg.ACK)), slog.Uint64("snd.nxt", uint64(tcb.snd.NXT)),
|
|
slog.Uint64("prevnxt", uint64(prevNxt)), slog.Uint64("seg.seq", uint64(seg.SEQ)))
|
|
}
|
|
|
|
// We accept the segment and update TCB state.
|
|
// RFC 9293 §3.10.7.4 step 5: update send window only when WL1/WL2 conditions allow it.
|
|
// WL1==WL2==0 is the uninitialized sentinel; the first update is always allowed so that
|
|
// connections with a remote ISS in the upper half of the uint32 space still work
|
|
// (modular LessThan would otherwise return false for 0.LessThan(largeISS)).
|
|
// Within that, duplicate ACKs (non-advancing) may only open the window, never shrink it.
|
|
wlUnset := tcb.snd.WL1 == 0 && tcb.snd.WL2 == 0
|
|
if wlUnset || tcb.snd.WL1.LessThan(seg.SEQ) || (tcb.snd.WL1 == seg.SEQ && tcb.snd.WL2.LessThanEq(seg.ACK)) {
|
|
if tcb.snd.UNA.LessThan(seg.ACK) || seg.WND > tcb.snd.WND {
|
|
tcb.snd.WND = seg.WND
|
|
}
|
|
tcb.snd.WL1 = seg.SEQ
|
|
tcb.snd.WL2 = seg.ACK
|
|
}
|
|
|
|
if seg.Flags.HasAny(FlagACK) && seg.ACK.LessThanEq(tcb.snd.NXT) {
|
|
if tcb.IncomingIsDupACK(seg.ACK) && tcb.State().TxDataOpen() && !seg.Flags.HasAny(flagctl) && tcb.dupack < tcb.nRetransmit+retransmitMaxQueued+retransmitMaxQueued {
|
|
// Duplicate ack. Don't advance dupack counter past scb.nRetransmit+retransmitAfterDupacks
|
|
tcb.dupack++
|
|
} else if tcb.snd.UNA.LessThan(seg.ACK) {
|
|
// Only update ACK if it advances UNA and is not in the future.
|
|
tcb.snd.UNA = seg.ACK
|
|
tcb.dupack = 0
|
|
tcb.nRetransmit = 0
|
|
}
|
|
}
|
|
|
|
seglen := seg.LEN()
|
|
tcb.rcv.NXT.UpdateForward(seglen)
|
|
|
|
if tcb.logenabled(internal.LevelTrace) {
|
|
tcb.traceRcv("tcb:rcv")
|
|
tcb.traceSeg("recv:seg", seg)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Send processes a segment that is being sent to the network. It updates the TCB
|
|
// if there is no error.
|
|
func (tcb *ControlBlock) Send(seg Segment) error {
|
|
err := tcb.validateOutgoingSegment(seg)
|
|
if err != nil {
|
|
tcb.traceSnd("tcb:snd.reject")
|
|
tcb.traceSeg("tcb:snd.reject", seg)
|
|
tcb.logerr("tcb:snd.reject", slog.String("err", err.Error()))
|
|
return err
|
|
}
|
|
|
|
hasFIN := seg.Flags.HasAny(FlagFIN)
|
|
hasACK := seg.Flags.HasAny(FlagACK)
|
|
var newPending Flags
|
|
switch tcb._state {
|
|
case StateClosed:
|
|
if seg.Flags == FlagSYN {
|
|
tcb.prepareToHandshake(seg.SEQ, seg.WND, StateSynSent)
|
|
tcb.trace("tcb:open-client")
|
|
}
|
|
case StateSynRcvd, StateEstablished:
|
|
if hasFIN {
|
|
tcb._state = StateFinWait1 // RFC 9293: 3.10.4 CLOSE call.
|
|
}
|
|
case StateClosing:
|
|
if hasACK {
|
|
tcb._state = StateTimeWait
|
|
}
|
|
case StateCloseWait:
|
|
if hasFIN {
|
|
tcb._state = StateLastAck
|
|
}
|
|
// No auto-queue of FIN on ACK: user must call Close() to initiate local FIN.
|
|
}
|
|
|
|
// Advance pending flags queue.
|
|
tcb.pending[0] &^= seg.Flags
|
|
if tcb.pending[0] == 0 {
|
|
// Ensure we don't queue a FINACK if we have already sent a FIN.
|
|
tcb.pending = [2]Flags{tcb.pending[1] &^ (seg.Flags & (FlagFIN)), 0}
|
|
}
|
|
tcb.pending[0] |= newPending
|
|
|
|
// Sending an ACK satisfies any outstanding challenge-ACK obligation.
|
|
if tcb.pendingChallengeAck() && seg.Flags.HasAny(FlagACK) {
|
|
tcb.triggerChallengeAckSent()
|
|
}
|
|
|
|
// The segment is valid, we can update TCB state.
|
|
seglen := seg.LEN()
|
|
retransmit := seg.SEQ.LessThan(tcb.snd.NXT)
|
|
if retransmit {
|
|
if tcb.nRetransmit < 255-retransmitMaxQueued-retransmitAfterDupacks {
|
|
tcb.nRetransmit++
|
|
}
|
|
} else {
|
|
tcb.snd.NXT.UpdateForward(seglen)
|
|
}
|
|
|
|
tcb.rcv.WND = seg.WND
|
|
if tcb.logenabled(internal.LevelTrace) {
|
|
tcb.traceSnd("tcb:snd")
|
|
tcb.traceSeg("tcb:snd", seg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tcb *ControlBlock) validateOutgoingSegment(seg Segment) (err error) {
|
|
hasAck := seg.Flags.HasAny(FlagACK)
|
|
isFirst := tcb._state == StateClosed && seg.isFirstSYN()
|
|
checkSeq := !isFirst && !seg.Flags.HasAny(FlagRST)
|
|
seglast := seg.Last()
|
|
// Extra check for when send Window is zero and no data is being sent.
|
|
zeroWindowOK := tcb.snd.WND == 0 && seg.DATALEN == 0 && seg.SEQ == tcb.snd.NXT
|
|
outOfWindow := checkSeq && !seg.SEQ.InWindow(tcb.snd.NXT, tcb.snd.WND) &&
|
|
!zeroWindowOK
|
|
isRetransmit := checkSeq && seg.SEQ.InRange(tcb.snd.UNA, tcb.snd.NXT)
|
|
switch {
|
|
case tcb._state == StateClosed && !isFirst:
|
|
err = io.ErrClosedPipe
|
|
case seg.WND > math.MaxUint16:
|
|
err = errWindowTooLarge
|
|
case hasAck && seg.ACK != tcb.rcv.NXT:
|
|
err = errAckNotNext
|
|
|
|
case outOfWindow && !isRetransmit:
|
|
if tcb.snd.WND == 0 {
|
|
err = errZeroWindow
|
|
} else {
|
|
err = errSeqNotInWindow
|
|
}
|
|
|
|
case seg.DATALEN > 0 && tcb._state == StateFinWait2:
|
|
// FIN-WAIT-2 means our FIN was acknowledged, so no data below it can be
|
|
// unacknowledged and data here is a caller error. FIN-WAIT-1 is excluded:
|
|
// its FIN sits above data the peer may still be missing, which must go out
|
|
// for either side to make progress (RFC 9293 §3.10.8).
|
|
err = errConnectionClosing
|
|
|
|
case checkSeq && tcb.snd.WND == 0 && seg.DATALEN > 0 && seg.SEQ == tcb.snd.NXT:
|
|
err = errZeroWindow
|
|
|
|
case checkSeq && !seglast.InWindow(tcb.snd.NXT, tcb.snd.WND) && !zeroWindowOK && !isRetransmit:
|
|
err = errLastNotInWindow
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (tcb *ControlBlock) validateIncomingSegment(seg Segment) (err error) {
|
|
flags := seg.Flags
|
|
hasAck := flags.HasAll(FlagACK)
|
|
// Short circuit SEQ checks if SYN present in pre-established states only.
|
|
// In synchronized states SYN must pass normal SEQ validation (RFC 9293 §3.10.7.4).
|
|
preestablished := tcb._state.IsPreestablished()
|
|
// LISTEN has no receive window context; RFC 9293 §3.10.7.1 step 1: "no checking in LISTEN state."
|
|
checkSEQ := (!flags.HasAny(FlagSYN) || !preestablished) && tcb._state != StateListen
|
|
established := tcb._state == StateEstablished
|
|
acksOld := hasAck && !tcb.snd.UNA.LessThan(seg.ACK)
|
|
acksUnsentData := hasAck && !seg.ACK.LessThanEq(tcb.snd.NXT)
|
|
ctlOrDataSegment := established && (seg.DATALEN > 0 || flags.HasAny(FlagFIN|FlagRST))
|
|
zeroWindowOK := tcb.rcv.WND == 0 && seg.DATALEN == 0 && seg.SEQ == tcb.rcv.NXT
|
|
// See section 3.4 of RFC 9293 for more on these checks.
|
|
switch {
|
|
case seg.WND > math.MaxUint16:
|
|
err = errWindowOverflow
|
|
case tcb._state == StateClosed:
|
|
err = io.ErrClosedPipe
|
|
|
|
case checkSEQ && tcb.rcv.WND == 0 && seg.DATALEN > 0 && seg.SEQ == tcb.rcv.NXT:
|
|
err = errZeroWindow
|
|
|
|
case checkSEQ && !seg.SEQ.InWindow(tcb.rcv.NXT, tcb.rcv.WND) && !zeroWindowOK:
|
|
err = errSeqNotInWindow
|
|
|
|
case checkSEQ && !seg.Last().InWindow(tcb.rcv.NXT, tcb.rcv.WND) && !zeroWindowOK:
|
|
err = errLastNotInWindow
|
|
|
|
case checkSEQ && !flags.HasAny(FlagRST) && seg.SEQ != tcb.rcv.NXT:
|
|
// This part diverts from TCB as described in RFC 9293. We want to support
|
|
// only sequential segments to keep implementation simple and maintainable. See SHLD-31.
|
|
err = errRequireSequential
|
|
}
|
|
if err != nil {
|
|
// RFC 9293 §3.4: If segment not acceptable, send ACK (unless RST).
|
|
switch err {
|
|
case errSeqNotInWindow, errLastNotInWindow, errRequireSequential, errZeroWindow:
|
|
if !flags.HasAny(FlagRST) {
|
|
if tcb.tooManyChallengeAcks() {
|
|
tcb.Abort()
|
|
return net.ErrClosed
|
|
}
|
|
tcb.triggerChallengeAckEmit()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
if flags.HasAny(FlagRST) {
|
|
return tcb.handleRST(seg.SEQ)
|
|
}
|
|
|
|
isDebug := tcb.logenabled(slog.LevelDebug)
|
|
// Drop-segment checks.
|
|
switch {
|
|
// Special treatment of duplicate ACKs on established connection and of ACKs of unsent data.
|
|
// https://www.rfc-editor.org/rfc/rfc9293.html#section-3.10.7.4-2.5.2.2.2.3.2.1
|
|
case established && acksOld && !ctlOrDataSegment:
|
|
// We don't drop packet.
|
|
if isDebug {
|
|
tcb.debug("rcv:ACK-old", slog.String("state", tcb._state.String()),
|
|
slog.Uint64("seg.ack", uint64(seg.ACK)), slog.Uint64("snd.una", uint64(tcb.snd.UNA)))
|
|
}
|
|
|
|
case established && acksUnsentData:
|
|
// ACK for data we haven't sent. Drop and send challenge ACK.
|
|
// Note: after Retransmit() rewinds snd.NXT, a cumulative ACK may exceed
|
|
// the rewound NXT. That case is handled by Handler.RecoveryACK, not here —
|
|
// NXT==UNA is ambiguous (also true when no data is in flight).
|
|
err = errDropSegment
|
|
tcb.pending[0] |= FlagACK // Send ACK for unsent data; |= preserves any pending FIN.
|
|
if isDebug {
|
|
tcb.debug("rcv:ACK-unsent", slog.String("state", tcb._state.String()),
|
|
slog.Uint64("seg.ack", uint64(seg.ACK)), slog.Uint64("snd.nxt", uint64(tcb.snd.NXT)))
|
|
}
|
|
|
|
case preestablished && (acksOld || acksUnsentData):
|
|
err = errDropSegment
|
|
tcb.pending[0] = FlagRST
|
|
tcb.rstPtr = seg.ACK
|
|
tcb.resetSnd(tcb.snd.ISS, seg.WND)
|
|
if isDebug {
|
|
tcb.debug("rcv:RST-old", slog.String("state", tcb._state.String()), slog.Uint64("ack", uint64(seg.ACK)))
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (tcb *ControlBlock) resetSnd(localISS Value, remoteWND Size) {
|
|
tcb.snd = sendSpace{
|
|
ISS: localISS,
|
|
UNA: localISS,
|
|
NXT: localISS,
|
|
WND: remoteWND,
|
|
// UP, WL1, WL2 defaults to zero values.
|
|
}
|
|
}
|
|
|
|
func (tcb *ControlBlock) resetRcv(localWND Size, remoteISS Value) {
|
|
tcb.rcv = recvSpace{
|
|
IRS: remoteISS,
|
|
NXT: remoteISS,
|
|
WND: localWND,
|
|
}
|
|
}
|
|
|
|
func (tcb *ControlBlock) handleRST(seq Value) error {
|
|
tcb.debug("rcv:RST", slog.String("state", tcb._state.String()))
|
|
switch tcb._state {
|
|
case StateSynSent:
|
|
// RFC 9293 §3.10.7.2: RST in SYN-SENT aborts the active open.
|
|
tcb.Abort()
|
|
return net.ErrClosed
|
|
case StateListen:
|
|
// RFC 9293 §3.5.3: RST in LISTEN state is ignored.
|
|
return errDropSegment
|
|
case StateSynRcvd:
|
|
// RFC 9293 §3.5.3: SYN-RCVD (passive open) returns to LISTEN on RST.
|
|
tcb.pending[0] = 0
|
|
tcb._state = StateListen
|
|
tcb.resetSnd(tcb.snd.ISS+tcb.rstJump(), tcb.snd.WND)
|
|
tcb.resetRcv(tcb.rcv.WND, 3_14159_2653^tcb.rcv.IRS)
|
|
return errDropSegment
|
|
}
|
|
// Synchronized states: exact SEQ match required; challenge ACK for in-window non-exact.
|
|
if seq != tcb.rcv.NXT {
|
|
tcb.triggerChallengeAckEmit()
|
|
tcb.pending[0] |= FlagACK
|
|
return errDropSegment
|
|
}
|
|
tcb.Abort()
|
|
return net.ErrClosed
|
|
}
|
|
|
|
func (tcb *ControlBlock) rstJump() Value {
|
|
return 100
|
|
}
|
|
|
|
// Retransmit resets snd.NXT back to snd.UNA, allowing the next PendingSegment
|
|
// and Send calls to retransmit unacknowledged data. Must be paired with
|
|
// ringTx.RetransmitFromUNA to rewind the transmit buffer.
|
|
// Implements RFC 9293 §3.10.8 (RETRANSMISSION TIMEOUT).
|
|
// func (tcb *ControlBlock) Retransmit() { tcb.snd.NXT = tcb.snd.UNA }
|
|
|
|
// Abort sets ControlBlock state to Closed and resets all sequence numbers and pending flag.
|
|
// No more data can be sent nor received after the connection is aborted until opened again.
|
|
// An abort call prepares the connection for opening an active connection via a
|
|
// SYN packet during Send call in state=StateClosed.
|
|
func (tcb *ControlBlock) Abort() {
|
|
tcb.reset()
|
|
tcb.debug("tcb:abort")
|
|
}
|
|
|
|
func (tcb *ControlBlock) reset() {
|
|
*tcb = ControlBlock{
|
|
logger: tcb.logger,
|
|
}
|
|
}
|
|
|
|
// Close implements a passive/active closing of a connection. It does not immediately
|
|
// delete the TCB but initiates the process so that pending outgoing segments initiate
|
|
// the closing process. After a call to Close users should not send more data.
|
|
// Close returns an error if the connection is already closed or closing.
|
|
func (tcb *ControlBlock) Close() (err error) {
|
|
// See RFC 9293: 3.10.4 CLOSE call.
|
|
switch tcb._state {
|
|
case StateClosed:
|
|
err = errConnNotExist
|
|
case StateCloseWait:
|
|
tcb._state = StateLastAck
|
|
tcb.pending = [2]Flags{FlagFIN | FlagACK, 0}
|
|
case StateListen, StateSynSent:
|
|
// In Listen State there is no established connection.
|
|
// In SynSent the remote endpoint is not yet synchronized and upon receiving an RST will abort connection.
|
|
tcb.Abort()
|
|
case StateSynRcvd, StateEstablished:
|
|
// We suppose user has no more pending data to send, so we flag FIN to be sent.
|
|
// Users of this API should call Close only when they have no more data to send.
|
|
// When FIN is sent SCB will transition to FinWait1.
|
|
tcb.pending[0] = (tcb.pending[0] & FlagACK) | FlagFIN
|
|
case StateFinWait2, StateTimeWait:
|
|
err = errConnectionClosing
|
|
default:
|
|
err = errInvalidState
|
|
}
|
|
if err == nil {
|
|
tcb.trace("tcb:close", slog.String("state", tcb._state.String()))
|
|
} else {
|
|
tcb.logerr("tcb:close", slog.String("err", err.Error()))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (tcb *ControlBlock) triggerChallengeAckSatisfied() {
|
|
tcb.challengeAcks = 0
|
|
}
|
|
func (tcb *ControlBlock) triggerChallengeAckEmit() {
|
|
if tcb.challengeAcks >= 0 {
|
|
// Only increment challenge ack counter if last challenge ack already sent.
|
|
tcb.challengeAcks = -tcb.challengeAcks - 1
|
|
}
|
|
}
|
|
func (tcb *ControlBlock) triggerChallengeAckSent() {
|
|
if tcb.challengeAcks < 0 {
|
|
tcb.challengeAcks = -tcb.challengeAcks // Make positive.
|
|
}
|
|
}
|
|
func (tcb *ControlBlock) pendingChallengeAck() bool {
|
|
return tcb.challengeAcks < 0
|
|
}
|
|
func (tcb *ControlBlock) tooManyChallengeAcks() bool {
|
|
if tcb.challengeAcks >= 0 {
|
|
return tcb.challengeAcks > maxChallengeRejects
|
|
} else {
|
|
return tcb.challengeAcks < -maxChallengeRejects
|
|
}
|
|
}
|