mirror of
https://github.com/apache/nuttx.git
synced 2026-08-17 17:43:22 +08:00
net/tcp: don't accept a reset connection as connected (fixes send hang)
tcp_start_monitor() is called from accept() (net/inet/inet_sockif.c) for
each newly accepted connection. When the peer had already closed the
connection before accept() ran, the monitor takes an early-return path so
that any read-ahead data buffered on the connection can still be drained;
it returns OK in that case. accept() (net/socket/accept.c) then marks the
new socket _SF_CONNECTED unconditionally.
If the peer aborts the connection with an RST immediately after the
three-way handshake completes (for example any close with SO_LINGER
{1, 0}), the connection is moved to TCP_CLOSED with no buffered data, yet
accept() still hands back a socket that reports _SS_ISCONNECTED. A
subsequent blocking send() on that socket passes the connected check,
registers a send callback and waits on its semaphore forever: the only
TCP_ABORT event was delivered before the callback existed, and no further
ACK, POLL or disconnect event is generated for a closed connection, so the
waiter is never woken.
Any server that writes before reading can hit this; the telnet daemon
(netutils/telnetd) is one example, where the accepted session task blocks
in send() and never completes.
Only return OK from the already-closed path when there is actually
read-ahead data to drain. Otherwise the connection is dead, so fall
through to the -ENOTCONN return: accept() then fails cleanly instead of
handing back a socket wedged on a connection that will never make progress.
The graceful-close-with-pending-data case (the reason the OK path exists)
is preserved by the conn->readahead check.
Signed-off-by: Ricard Rosson <ricard@groundbits.com>
Assisted-by: Claude (Anthropic Claude Code)
This commit is contained in:
committed by
Alan C. Assis
parent
1f166a970e
commit
aeaa13227e
+17
-5
@@ -272,13 +272,25 @@ int tcp_start_monitor(FAR struct socket *psock)
|
||||
|
||||
tcp_shutdown_monitor(conn, TCP_ABORT);
|
||||
|
||||
/* If the peer close the connection before we call accept,
|
||||
* in order to allow user to read the readahead data,
|
||||
* return OK.
|
||||
/* If the peer closed the connection before we called accept, and
|
||||
* there is buffered read-ahead data, return OK so that the caller
|
||||
* still gets a socket from which the pending data can be drained
|
||||
* (followed by EOF).
|
||||
*
|
||||
* If there is no buffered data, however, the connection is dead:
|
||||
* presenting it to the caller as a successfully-accepted socket
|
||||
* makes accept() mark it _SF_CONNECTED (see net/socket/accept.c),
|
||||
* and a subsequent blocking send() would then wait forever on a
|
||||
* connection that will never post another event. This happens when
|
||||
* a peer resets the connection immediately after the handshake (for
|
||||
* example a close with SO_LINGER {1, 0}). Report it as not-connected
|
||||
* instead so accept() fails cleanly rather than handing back a wedged
|
||||
* socket.
|
||||
*/
|
||||
|
||||
if (conn->tcpstateflags == TCP_CLOSED ||
|
||||
conn->tcpstateflags == TCP_LAST_ACK)
|
||||
if ((conn->tcpstateflags == TCP_CLOSED ||
|
||||
conn->tcpstateflags == TCP_LAST_ACK) &&
|
||||
conn->readahead != NULL)
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user