mirror of
https://github.com/soypat/lneto.git
synced 2026-09-07 23:39:04 +00:00
fix(tcp): retransmit unacknowledged data after a local close (#182)
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>
This commit is contained in:
@@ -329,6 +329,14 @@ func (s State) TxDataOpen() bool {
|
||||
return s == StateEstablished || s == StateCloseWait
|
||||
}
|
||||
|
||||
// txQueuedDataOpen returns true if already-queued send-buffer data may still be
|
||||
// put on the wire. It stays true after a local close, where the FIN occupies a
|
||||
// sequence above data the peer has not acknowledged: until that data is
|
||||
// (re)transmitted the peer cannot reach the FIN. RFC 9293 §3.10.8.
|
||||
func (s State) txQueuedDataOpen() bool {
|
||||
return s.TxDataOpen() || s == StateFinWait1 || s == StateClosing || s == StateLastAck
|
||||
}
|
||||
|
||||
// RxDataOpen returns true if the state allows the receiving of incoming data segments.
|
||||
// Combine with [State.IsPreestablished] to know whether there is no more data to be received over the network.
|
||||
func (s State) RxDataOpen() bool {
|
||||
|
||||
Reference in New Issue
Block a user