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 <zhanghongyu@xiaomi.com>
This commit is contained in:
zhanghongyu 2025-08-27 20:34:40 +08:00 committed by Xiang Xiao
parent 0588d08d75
commit 2ad01e2aab
14 changed files with 92 additions and 95 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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.
*

View file

@ -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 */

View file

@ -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.

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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 */