tcp: _Receive: reset connection when received another ack packet

while finish acknowledged and zero receive window.

* SendQueued: try to get window update when send window is zero.
* Golang test TestServerGracefulClose now passes.

tcp_receive_data generates a RST packet with a random sequence, etc.
with _SendReset, the actual connection will be used to generate the segment information, like Linux, ie:

18:07:17.657812 IP localhost.localdomain.40009 > localhost.localdomain.40034: Flags [R.], seq 184, ack 69632, win 0, options [nop,nop,TS val 34750 ecr 33649], length 0

instead of:

18:10:50.816193 IP localhost.localdomain.40051 > localhost.localdomain.40062: Flags [R], seq 1736711, win 0, length 0

Change-Id: Ib621605f74cb6d1cbc2add050bcddbf471402254
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10223
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Jérôme Duval
2026-01-16 17:42:33 +00:00
committed by waddlesplash
parent c30e5cf106
commit ac508fcb4a
2 changed files with 47 additions and 3 deletions
@@ -1755,6 +1755,16 @@ TCPEndpoint::_Receive(tcp_segment_header& segment, net_buffer* buffer)
return DROP;
}
if (fState == FINISH_ACKNOWLEDGED
&& segment.AcknowledgeOnly()
&& (fReceiveMaxAdvertised - fReceiveNext).Number() == 0
&& segmentLength == 0
&& segment.acknowledge == fSendUnacknowledged) {
// reset the connection - received another ack packet
// while finish acknowledged and zero receive window
return DROP | RESET;
}
if ((segment.flags & TCP_FLAG_SYNCHRONIZE) != 0
|| (fState == SYNCHRONIZE_RECEIVED
&& (fInitialReceiveSequence > segment.sequence
@@ -1979,7 +1989,7 @@ TCPEndpoint::_Receive(tcp_segment_header& segment, net_buffer* buffer)
_UpdateTimestamps(segment, segmentLength);
TRACE("Receive() Action %" B_PRId32, action);
TRACE("Receive() Action 0x%" B_PRIx32, action);
return action;
}
@@ -2031,6 +2041,13 @@ TCPEndpoint::SegmentReceived(tcp_segment_header& segment, net_buffer* buffer)
if (segmentAction & SEND_QUEUED)
_SendQueued();
// handle RESET action separately to use actual connection
// to generate the segment information
if ((segmentAction & RESET) != 0 && _SendReset(true) == B_OK) {
fState = CLOSED;
segmentAction &= ~RESET;
}
if ((fFlags & (FLAG_CLOSED | FLAG_DELETE_ON_CLOSE))
== (FLAG_CLOSED | FLAG_DELETE_ON_CLOSE)) {
@@ -2296,6 +2313,29 @@ TCPEndpoint::_SendAcknowledge(bool force)
}
/*! Sends a RST with segment information related to the connection. */
status_t
TCPEndpoint::_SendReset(bool force)
{
if (fRoute == NULL || fState == LISTEN)
return B_ERROR;
tcp_segment_header segment = _PrepareSendSegment();
// Is there actually anything to do?
if (!force && (fState != FINISH_ACKNOWLEDGED || (fFlags & FLAG_CLOSED) == 0))
return B_OK;
segment.flags |= TCP_FLAG_RESET;
net_buffer* buffer = gBufferModule->create(256);
if (buffer == NULL)
return B_NO_MEMORY;
return _PrepareAndSend(segment, buffer, false);
}
/*! Sends one or more TCP segments with the data waiting in the queue. */
status_t
TCPEndpoint::_SendQueued(bool force)
@@ -2328,12 +2368,15 @@ TCPEndpoint::_SendQueued(bool force)
if (consumedWindow > sendWindow) {
sendWindow = 0;
// TODO: enter persist state? try to get a window update.
} else
sendWindow -= consumedWindow;
uint32 length = min_c(fSendQueue.Available(fSendNext), sendWindow);
if (length == 0 && !state_needs_finish(fState)) {
if (!force && length == 0 && !state_needs_finish(fState)) {
// try to get a window update
if (sendWindow == 0 && !gStackModule->is_timer_active(&fPersistTimer))
_StartPersistTimer();
// Nothing to send.
return B_OK;
}
@@ -83,6 +83,7 @@ private:
status_t _PrepareAndSend(tcp_segment_header& segment, net_buffer* buffer,
bool isRetransmit);
status_t _SendAcknowledge(bool force = false);
status_t _SendReset(bool force = false);
status_t _SendQueued(bool force = false);
status_t _Disconnect(bool closing);