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= 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, } } // 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 } // 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 } established := tcb._state == StateEstablished canSendData := established || tcb._state == StateCloseWait if !canSendData { payloadLen = 0 // Can't send data if not established or close-wait. } 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 == StateFinWait1 || tcb._state == StateFinWait2): err = errConnectionClosing // Case 1: No further SENDs from the user will be accepted by the TCP implementation. 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 } }