From 2ad01e2aaba6bb11ad783415816bee58a237cd29 Mon Sep 17 00:00:00 2001 From: zhanghongyu Date: Wed, 27 Aug 2025 20:34:40 +0800 Subject: [PATCH] net/tcp: support recv packet in the TCP_FIN_WAIT_1/2 state shutdown should send TCP_FIN packet. close should send TCP_RST packet when the data in readahead has not been read and NEW_DATA has arrived. send TCP_FIN packet when in other cases. Signed-off-by: zhanghongyu --- .../components/net/tcp_state_machine.rst | 11 ++-- net/devif/devif.h | 8 +-- net/tcp/tcp.h | 4 +- net/tcp/tcp_appsend.c | 4 +- net/tcp/tcp_callback.c | 4 +- net/tcp/tcp_close.c | 62 +++++++++++-------- net/tcp/tcp_conn.c | 2 +- net/tcp/tcp_devpoll.c | 4 +- net/tcp/tcp_input.c | 56 +++-------------- net/tcp/tcp_monitor.c | 2 +- net/tcp/tcp_send_buffered.c | 9 ++- net/tcp/tcp_send_unbuffered.c | 9 ++- net/tcp/tcp_shutdown.c | 9 ++- net/tcp/tcp_timer.c | 3 +- 14 files changed, 92 insertions(+), 95 deletions(-) diff --git a/Documentation/components/net/tcp_state_machine.rst b/Documentation/components/net/tcp_state_machine.rst index da4b6156136..287c603536e 100644 --- a/Documentation/components/net/tcp_state_machine.rst +++ b/Documentation/components/net/tcp_state_machine.rst @@ -78,11 +78,11 @@ NuttX today. * - FIN-WAIT-1 - ``TCP_FIN_WAIT_1`` - Yes - - Entered on active close (local FIN sent). However, it is currently unable to continue receiving data in this state + - Entered on active close (local FIN sent). * - FIN-WAIT-2 - ``TCP_FIN_WAIT_2`` - Yes - - Entered after ACK for local FIN (when peer hasn't closed yet). However, it is currently unable to continue receiving data in this state + - Entered after ACK for local FIN (when peer hasn't closed yet). * - CLOSE-WAIT - Not implemented - Yes @@ -243,8 +243,7 @@ TCP_FIN_WAIT_1 * Data received in FIN_WAIT_1: - * Current behavior is to send a RST and force ``TCP_CLOSED``. - * The implementation notes this as a TODO to improve shutdown behavior. + * Can continue receiving data until close or peer FIN. TCP_FIN_WAIT_2 -------------- @@ -257,7 +256,7 @@ TCP_FIN_WAIT_2 * Data received in FIN_WAIT_2: - * Current behavior is to send a RST and force ``TCP_CLOSED``. + * Can continue receiving data until close or peer FIN. TCP_CLOSING ----------- @@ -311,8 +310,6 @@ Deviations and Notable Simplifications ====================================== * LISTEN is not an explicit TCP state; it is represented by listener table entries. -* FIN_WAIT_* data handling is currently strict: received payload data in - FIN_WAIT_1/2 results in sending RST and closing the connection. * RST processing is intentionally simple (accept RST and close). Where to Look in the Code diff --git a/net/devif/devif.h b/net/devif/devif.h index e63abcf9b8f..234d26df66d 100644 --- a/net/devif/devif.h +++ b/net/devif/devif.h @@ -164,7 +164,7 @@ * OUT: Not used */ -/* Bits 0-9: Connection specific event bits */ +/* Bits 0-11: Connection specific event bits */ #define TCP_ACKDATA (1 << 0) #define TCP_NEWDATA (1 << 1) @@ -192,8 +192,7 @@ #define TCP_CONNECTED (1 << 8) #define TCP_TIMEDOUT (1 << 9) #define TCP_WAITALL (1 << 10) - -/* Bits 10-11: Unused, available */ +#define TCP_TXCLOSE (1 << 11) /* Bit 12: Device specific event bits */ @@ -216,7 +215,8 @@ /* The set of events that and implications to the TCP connection state */ #define TCP_CONN_EVENTS \ - (TCP_CLOSE | TCP_ABORT | TCP_CONNECTED | TCP_TIMEDOUT | NETDEV_DOWN) + (TCP_CLOSE | TCP_ABORT | TCP_CONNECTED | TCP_TIMEDOUT | NETDEV_DOWN | \ + TCP_TXCLOSE) #define TCP_DISCONN_EVENTS \ (TCP_CLOSE | TCP_ABORT | TCP_TIMEDOUT | NETDEV_DOWN) diff --git a/net/tcp/tcp.h b/net/tcp/tcp.h index 4c937c6d4bc..d888fad00e0 100644 --- a/net/tcp/tcp.h +++ b/net/tcp/tcp.h @@ -189,7 +189,7 @@ struct tcp_conn_s * When an callback is executed from 'list', the input flags are normally * returned, however, the implementation may set one of the following: * - * TCP_CLOSE - Gracefully close the current connection + * TCP_TXCLOSE - Gracefully close the current connection (TX) * TCP_ABORT - Abort (reset) the current connection on an error that * prevents TCP_CLOSE from working. * @@ -220,6 +220,8 @@ struct tcp_conn_s #if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6) uint8_t domain; /* IP domain: PF_INET or PF_INET6 */ #endif + uint8_t shutdown; /* Whether the connection is shutdown, SHUT_RD and + * SHUT_WR */ uint8_t sa; /* Retransmission time-out calculation state * variable */ uint8_t sv; /* Retransmission time-out calculation state diff --git a/net/tcp/tcp_appsend.c b/net/tcp/tcp_appsend.c index bf1f2e6d6a8..35b5f4734d8 100644 --- a/net/tcp/tcp_appsend.c +++ b/net/tcp/tcp_appsend.c @@ -190,9 +190,9 @@ void tcp_appsend(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn, tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen); } - /* Check for connection closed */ + /* Check for connection tx closed */ - else if ((result & TCP_CLOSE) != 0) + else if ((result & TCP_TXCLOSE) != 0) { conn->tcpstateflags = conn->tcpstateflags == TCP_CLOSE_WAIT ? TCP_LAST_ACK : TCP_FIN_WAIT_1; diff --git a/net/tcp/tcp_callback.c b/net/tcp/tcp_callback.c index 9910151cfca..360814227ab 100644 --- a/net/tcp/tcp_callback.c +++ b/net/tcp/tcp_callback.c @@ -271,7 +271,7 @@ uint16_t tcp_callback(FAR struct net_driver_s *dev, return 0; } - /* Preserve the TCP_ACKDATA, TCP_CLOSE, and TCP_ABORT in the response. + /* Preserve the TCP_ACKDATA, TCP_TXCLOSE, and TCP_ABORT in the response. * These is needed by the network to handle responses and buffer state. * The TCP_NEWDATA indication will trigger the ACK response, but must be * explicitly set in the callback. @@ -283,7 +283,7 @@ uint16_t tcp_callback(FAR struct net_driver_s *dev, * 'list', the input flags are normally returned, however, the * implementation may set one of the following: * - * TCP_CLOSE - Gracefully close the current connection + * TCP_TXCLOSE - Gracefully close the current connection (TX) * TCP_ABORT - Abort (reset) the current connection on an error that * prevents TCP_CLOSE from working. * diff --git a/net/tcp/tcp_close.c b/net/tcp/tcp_close.c index 8df0cb76198..1a3a4e41045 100644 --- a/net/tcp/tcp_close.c +++ b/net/tcp/tcp_close.c @@ -60,7 +60,7 @@ static void tcp_close_work(FAR void *param) /* Stop the network monitor for all sockets */ conn_dev_lock(&conn->sconn, conn->dev); - tcp_stop_monitor(conn, TCP_CLOSE); + tcp_stop_monitor(conn, TCP_TXCLOSE); conn_dev_unlock(&conn->sconn, conn->dev); tcp_free(conn); } @@ -78,22 +78,16 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev, ninfo("flags: %04x\n", flags); /* TCP_DISCONN_EVENTS: - * TCP_CLOSE: The remote host has closed the connection * TCP_ABORT: The remote host has aborted the connection * TCP_TIMEDOUT: The remote did not respond, the connection timed out * NETDEV_DOWN: The network device went down */ - if ((flags & TCP_DISCONN_EVENTS) != 0) + if ((flags & (TCP_TXCLOSE | TCP_DISCONN_EVENTS)) != 0) { /* The disconnection is complete. Wake up the waiting thread with an * appropriate result. Success is returned in these cases: * - * * TCP_CLOSE indicates normal successful closure. The TCP_CLOSE - * event is sent when the remote ACKs the outgoing FIN in the - * FIN_WAIT_1 state. That is the appropriate time for the - * application to close the socket. - * * NOTE: The underlying connection, however, will persist, waiting * for the FIN to be returned by the remote in the TIME_WAIT state. * @@ -128,7 +122,7 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev, ) { /* No... we are still waiting for ACKs. Drop any received data, but - * do not yet report TCP_CLOSE in the response. + * do not yet report TCP_TXCLOSE in the response. */ dev->d_len = 0; @@ -139,23 +133,20 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev, { /* Note: the following state shouldn't reach here because * - * FIN_WAIT_1, CLOSING, LAST_ACK + * CLOSING, LAST_ACK * should have tx_unacked != 0, already handled above * * CLOSED, TIME_WAIT - * a TCP_CLOSE callback should have already cleared this callback + * a TCP_TXCLOSE callback should have already cleared this callback * when transitioning to these states. - * - * FIN_WAIT_2 - * new data is dropped by tcp_input without invoking tcp_callback. - * timer is handled by tcp_timer without invoking tcp_callback. - * TCP_CLOSE is handled above. */ DEBUGASSERT(conn->tcpstateflags == TCP_ESTABLISHED || - conn->tcpstateflags == TCP_CLOSE_WAIT); + conn->tcpstateflags == TCP_CLOSE_WAIT || + conn->tcpstateflags == TCP_FIN_WAIT_1 || + conn->tcpstateflags == TCP_FIN_WAIT_2); - /* Drop data received in this state and make sure that TCP_CLOSE + /* Drop data received in this state and make sure that TCP_TXCLOSE * is set in the response */ @@ -174,7 +165,30 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev, #endif dev->d_len = 0; - flags = (flags & ~TCP_NEWDATA) | TCP_CLOSE; + if (conn->readahead != NULL || (flags & TCP_NEWDATA) != 0) + { + /* We need to send RST when read-ahead buffer data is not consumed + * or new data is coming. + * Set TCP_ABORT flag to trigger sending RST. + */ + + flags = flags & ~TCP_NEWDATA; + flags |= TCP_ABORT; + + /* Free rx buffers of the connection immediately */ + + tcp_free_rx_buffers(conn); + + goto end_wait; + } + else if ((conn->shutdown & SHUT_WR) == 0) + { + flags |= TCP_TXCLOSE; + + /* Avoid sending multiple FIN */ + + conn->shutdown |= SHUT_WR; + } } UNUSED(conn); /* May not be used */ @@ -236,18 +250,16 @@ static inline int tcp_close_disconnect(FAR struct socket *psock) */ if ((conn->tcpstateflags == TCP_ESTABLISHED || + conn->tcpstateflags == TCP_FIN_WAIT_1 || + conn->tcpstateflags == TCP_FIN_WAIT_2 || conn->tcpstateflags == TCP_LAST_ACK || conn->tcpstateflags == TCP_CLOSE_WAIT) && (conn->clscb = tcp_callback_alloc(conn)) != NULL) { - /* Free rx buffers of the connection immediately */ - - tcp_free_rx_buffers(conn); - /* Set up to receive TCP data event callbacks */ conn->clscb->flags = TCP_NEWDATA | TCP_ACKDATA | - TCP_POLL | TCP_DISCONN_EVENTS; + TCP_POLL | TCP_TXCLOSE | TCP_DISCONN_EVENTS; conn->clscb->event = tcp_close_eventhandler; conn->clscb->priv = conn; /* reference for event handler to free cb */ @@ -284,7 +296,7 @@ static inline int tcp_close_disconnect(FAR struct socket *psock) { /* Stop the network monitor for all sockets */ - tcp_stop_monitor(conn, TCP_CLOSE); + tcp_stop_monitor(conn, TCP_TXCLOSE); conn_dev_unlock(&conn->sconn, conn->dev); /* Free network resources */ diff --git a/net/tcp/tcp_conn.c b/net/tcp/tcp_conn.c index 9b60a8cb47f..0fadc202d02 100644 --- a/net/tcp/tcp_conn.c +++ b/net/tcp/tcp_conn.c @@ -791,7 +791,7 @@ void tcp_free(FAR struct tcp_conn_s *conn) /* Make sure monitor is stopped. */ conn_dev_lock(&conn->sconn, conn->dev); - tcp_stop_monitor(conn, TCP_CLOSE); + tcp_stop_monitor(conn, TCP_TXCLOSE); /* Free remaining callbacks, actually there should be only the send * callback for CONFIG_NET_TCP_WRITE_BUFFERS is left. diff --git a/net/tcp/tcp_devpoll.c b/net/tcp/tcp_devpoll.c index b5e3531a1bf..582a8ea9c34 100644 --- a/net/tcp/tcp_devpoll.c +++ b/net/tcp/tcp_devpoll.c @@ -111,7 +111,9 @@ void tcp_poll(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn) /* Verify that the connection is established. */ if ((conn->tcpstateflags & TCP_STATE_MASK) == TCP_ESTABLISHED || - (conn->tcpstateflags & TCP_STATE_MASK) == TCP_CLOSE_WAIT) + (conn->tcpstateflags & TCP_STATE_MASK) == TCP_CLOSE_WAIT || + (conn->tcpstateflags & TCP_STATE_MASK) == TCP_FIN_WAIT_1 || + (conn->tcpstateflags & TCP_STATE_MASK) == TCP_FIN_WAIT_2) { /* Set up for the callback. We can't know in advance if the * application is going to send a IPv4 or an IPv6 packet, so this diff --git a/net/tcp/tcp_input.c b/net/tcp/tcp_input.c index df74dcb3d26..f97c41f0907 100644 --- a/net/tcp/tcp_input.c +++ b/net/tcp/tcp_input.c @@ -1112,7 +1112,8 @@ found: /* Check if no packet need to retransmission, clear timer. */ if (conn->tx_unacked == 0 && (conn->tcpstateflags == TCP_ESTABLISHED || - conn->tcpstateflags == TCP_CLOSE_WAIT)) + conn->tcpstateflags == TCP_CLOSE_WAIT || + conn->tcpstateflags == TCP_FIN_WAIT_1)) { timeout = 0; } @@ -1183,18 +1184,15 @@ found: return; } } - else if ((conn->tcpstateflags & TCP_STATE_MASK) <= TCP_ESTABLISHED) + else if ((conn->tcpstateflags & TCP_STATE_MASK) <= TCP_FIN_WAIT_2) { #ifdef CONFIG_NET_TCP_OUT_OF_ORDER /* Queue out-of-order segments. */ tcp_input_ofosegs(dev, conn, iplen); #endif - if ((conn->tcpstateflags & TCP_STATE_MASK) <= TCP_ESTABLISHED) - { - tcp_send(dev, conn, TCP_ACK, tcpiplen); - return; - } + tcp_send(dev, conn, TCP_ACK, tcpiplen); + return; } } } @@ -1553,7 +1551,7 @@ found: conn->tcpstateflags = TCP_CLOSED; ninfo("TCP_LAST_ACK TCP state: TCP_CLOSED\n"); - tcp_callback(dev, conn, TCP_CLOSE); + tcp_callback(dev, conn, TCP_TXCLOSE); } break; @@ -1563,11 +1561,6 @@ found: * until we receive a FIN from the remote. */ - if (dev->d_len > 0) - { - net_incr32(conn->rcvseq, dev->d_len); - } - if ((tcp->flags & TCP_FIN) != 0) { if ((flags & TCP_ACKDATA) != 0 && conn->tx_unacked == 0) @@ -1592,36 +1585,18 @@ found: { conn->tcpstateflags = TCP_FIN_WAIT_2; ninfo("TCP state: TCP_FIN_WAIT_2\n"); - goto drop; } if (dev->d_len > 0) { - /* Due to RFC 2525, Section 2.17, we SHOULD send RST if we can no - * longer read any received data. Also set state into TCP_CLOSED - * because the peer will not send FIN after RST received. - * - * TODO: Modify shutdown behavior to allow read in FIN_WAIT. - */ - - conn->tcpstateflags = TCP_CLOSED; - - /* In the TCP_FIN_WAIT_1, we need call tcp_close_eventhandler to - * release nofosegs, that we received in this state. - */ - - tcp_callback(dev, conn, TCP_CLOSE); - tcp_reset(dev, conn); + result = tcp_callback(dev, conn, TCP_NEWDATA); + tcp_appsend(dev, conn, result); return; } goto drop; case TCP_FIN_WAIT_2: - if (dev->d_len > 0) - { - net_incr32(conn->rcvseq, dev->d_len); - } if ((tcp->flags & TCP_FIN) != 0) { @@ -1638,19 +1613,8 @@ found: if (dev->d_len > 0) { - /* Due to RFC 2525, Section 2.17, we SHOULD send RST if we can no - * longer read any received data. Also set state into TCP_CLOSED - * because the peer will not send FIN after RST received. - */ - - conn->tcpstateflags = TCP_CLOSED; - - /* In the TCP_FIN_WAIT_2, we need call tcp_close_eventhandler to - * release nofosegs, that we received in this state. - */ - - tcp_callback(dev, conn, TCP_CLOSE); - tcp_reset(dev, conn); + result = tcp_callback(dev, conn, TCP_NEWDATA); + tcp_appsend(dev, conn, result); return; } diff --git a/net/tcp/tcp_monitor.c b/net/tcp/tcp_monitor.c index 540607aa3ca..7ee10b50125 100644 --- a/net/tcp/tcp_monitor.c +++ b/net/tcp/tcp_monitor.c @@ -187,7 +187,7 @@ static uint16_t tcp_monitor_event(FAR struct net_driver_s *dev, * * Input Parameters: * conn - The TCP connection of interest - * flags - Indicates the type of shutdown. TCP_CLOSE or TCP_ABORT + * flags - Indicates the type of shutdown. TCP_TXCLOSE or TCP_ABORT * * Returned Value: * None diff --git a/net/tcp/tcp_send_buffered.c b/net/tcp/tcp_send_buffered.c index 8060698ee9d..0c87dc287fe 100644 --- a/net/tcp/tcp_send_buffered.c +++ b/net/tcp/tcp_send_buffered.c @@ -1361,6 +1361,13 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf, goto errout; } + if ((conn->shutdown & SHUT_WR) != 0) + { + nerr("ERROR: Connection is shutdown\n"); + ret = -EPIPE; + goto errout; + } + /* Make sure that we have the IP address mapping */ #if defined(CONFIG_NET_ARP_SEND) || defined(CONFIG_NET_ICMPv6_NEIGHBOR) @@ -1443,7 +1450,7 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf, /* Set up the callback in the connection */ conn->sndcb->flags = (TCP_ACKDATA | TCP_REXMIT | TCP_POLL | - TCP_DISCONN_EVENTS); + TCP_DISCONN_EVENTS | TCP_TXCLOSE); conn->sndcb->priv = (FAR void *)conn; conn->sndcb->event = psock_send_eventhandler; diff --git a/net/tcp/tcp_send_unbuffered.c b/net/tcp/tcp_send_unbuffered.c index 3fcd5b92d69..04477dc793d 100644 --- a/net/tcp/tcp_send_unbuffered.c +++ b/net/tcp/tcp_send_unbuffered.c @@ -511,6 +511,13 @@ ssize_t psock_tcp_send(FAR struct socket *psock, goto errout; } + if ((conn->shutdown & SHUT_WR) != 0) + { + nerr("ERROR: Connection is shutdown\n"); + ret = -EPIPE; + goto errout; + } + /* Make sure that we have the IP address mapping */ #if defined(CONFIG_NET_ARP_SEND) || defined(CONFIG_NET_ICMPv6_NEIGHBOR) @@ -591,7 +598,7 @@ ssize_t psock_tcp_send(FAR struct socket *psock, /* Set up the callback in the connection */ state.snd_cb->flags = (TCP_ACKDATA | TCP_REXMIT | TCP_POLL | - TCP_DISCONN_EVENTS); + TCP_DISCONN_EVENTS | TCP_TXCLOSE); state.snd_cb->priv = (FAR void *)&state; state.snd_cb->event = tcpsend_eventhandler; diff --git a/net/tcp/tcp_shutdown.c b/net/tcp/tcp_shutdown.c index 90cf11b5ab9..6871d2ba085 100644 --- a/net/tcp/tcp_shutdown.c +++ b/net/tcp/tcp_shutdown.c @@ -71,7 +71,7 @@ static uint16_t tcp_shutdown_eventhandler(FAR struct net_driver_s *dev, #endif dev->d_len = 0; - flags = (flags & ~TCP_NEWDATA) | TCP_CLOSE; + flags |= TCP_TXCLOSE; if (conn->shdcb != NULL) { @@ -160,11 +160,18 @@ out: int tcp_shutdown(FAR struct socket *psock, int how) { + FAR struct tcp_conn_s *conn; + + conn = psock->s_conn; + DEBUGASSERT(conn != NULL); + if (!(how & SHUT_WR)) { return -EOPNOTSUPP; } + conn->shutdown |= how; + tcp_unlisten(psock->s_conn); /* No longer accepting connections */ return tcp_send_fin(psock); diff --git a/net/tcp/tcp_timer.c b/net/tcp/tcp_timer.c index 3d175cff33e..be78056f191 100644 --- a/net/tcp/tcp_timer.c +++ b/net/tcp/tcp_timer.c @@ -473,8 +473,7 @@ void tcp_timer(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn) * out. */ - if (conn->tcpstateflags == TCP_TIME_WAIT || - conn->tcpstateflags == TCP_FIN_WAIT_2) + if (conn->tcpstateflags == TCP_TIME_WAIT) { /* Check if the timer exceeds the timeout value */