This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new aeaa13227e2 net/tcp: don't accept a reset connection as connected 
(fixes send hang)
aeaa13227e2 is described below

commit aeaa13227e256af57afdd2000e9a04353d5d9a15
Author: Ricard Rosson <[email protected]>
AuthorDate: Fri Jul 24 07:20:49 2026 +0100

    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 <[email protected]>
    Assisted-by: Claude (Anthropic Claude Code)
---
 net/tcp/tcp_monitor.c | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/net/tcp/tcp_monitor.c b/net/tcp/tcp_monitor.c
index 1ba7b4ce9df..11b43e95675 100644
--- a/net/tcp/tcp_monitor.c
+++ b/net/tcp/tcp_monitor.c
@@ -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;
         }

Reply via email to