From fa47ab7bd218a1148c16fa5f006dbc220e4de4b9 Mon Sep 17 00:00:00 2001 From: zhanghongyu Date: Mon, 11 Aug 2025 17:23:28 +0800 Subject: [PATCH] net/tcp: add TCP_RXCLOSE to handle rx is shutdown notify the local waiting recv process; otherwise, after the peer TX is closed, the wait of the recv process will continue to wait. Signed-off-by: zhanghongyu --- net/devif/devif.h | 33 ++++++++++++++++++++++----------- net/tcp/tcp.h | 4 ++-- net/tcp/tcp_callback.c | 12 ++++++------ net/tcp/tcp_input.c | 5 +++-- net/tcp/tcp_monitor.c | 10 +++++++++- net/tcp/tcp_netpoll.c | 7 ++++--- net/tcp/tcp_recvfrom.c | 20 +++++++++++++++----- net/tcp/tcp_shutdown.c | 12 ++++++++++-- 8 files changed, 71 insertions(+), 32 deletions(-) diff --git a/net/devif/devif.h b/net/devif/devif.h index 234d26df66d..d59ddde9106 100644 --- a/net/devif/devif.h +++ b/net/devif/devif.h @@ -62,8 +62,8 @@ /* The following flags may be set in the set of flags by the lower, device- * interfacing layer before calling through the socket layer callback. The - * TCP_ACKDATA, XYZ_NEWDATA, and TCP_CLOSE flags may be set at the same time, - * whereas the others are mutually exclusive. + * TCP_ACKDATA, XYZ_NEWDATA, TCP_CLOSE, TCP_RXCLOSE and TCP_TXCLOSE flags + * may be set at the same time, whereas the others are mutually exclusive. * * Connection Specific Events: These are events that may be notified * through callback lists residing in TCP, UDP, or PKT port connection @@ -123,6 +123,19 @@ * retransmissions. (TCP only) * OUT: Not used * + * TCP_RXCLOSE IN: The remote host has closed the connection, thus the + * connection has gone away. (TCP only) + * OUT: The socket layer signals that it wants to close the + * connection. (TCP only) + * + * TCP_TXCLOSE IN: The local host has closed the connection, thus the + * connection has gone away. (TCP only) + * OUT: The socket layer signals that it wants to close the + * connection. (TCP only) + * + * NETDEV_DOWN: IN: The network device has been taken down. + * OUT: Not used + * * Device Specific Events: These are events that may be notified through * callback lists residing in the network device structure. * @@ -159,12 +172,9 @@ * event, not associated with a socket. The appdata * pointer is not used in this case. * OUT: Not used - * - * NETDEV_DOWN: IN: The network device has been taken down. - * OUT: Not used */ -/* Bits 0-11: Connection specific event bits */ +/* Bits 0-12: Connection specific event bits */ #define TCP_ACKDATA (1 << 0) #define TCP_NEWDATA (1 << 1) @@ -193,18 +203,19 @@ #define TCP_TIMEDOUT (1 << 9) #define TCP_WAITALL (1 << 10) #define TCP_TXCLOSE (1 << 11) +#define TCP_RXCLOSE (1 << 12) -/* Bit 12: Device specific event bits */ +/* Bit 13: Device specific event bits */ -#define NETDEV_DOWN (1 << 12) +#define NETDEV_DOWN (1 << 13) -/* Bits 13-15: Encoded device specific poll events. Unlike connection +/* Bits 14-16: Encoded device specific poll events. Unlike connection * oriented poll events, device related poll events must distinguish * between what is being polled for since the callbacks all reside in * the same list in the network device structure. */ -#define DEVPOLL_SHIFT (13) +#define DEVPOLL_SHIFT (14) #define DEVPOLL_MASK (7 << DEVPOLL_SHIFT) # define DEVPOLL_NONE (0 << DEVPOLL_SHIFT) # define ARP_POLL (1 << DEVPOLL_SHIFT) @@ -216,7 +227,7 @@ #define TCP_CONN_EVENTS \ (TCP_CLOSE | TCP_ABORT | TCP_CONNECTED | TCP_TIMEDOUT | NETDEV_DOWN | \ - TCP_TXCLOSE) + TCP_TXCLOSE | TCP_RXCLOSE) #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 d888fad00e0..82f6d152639 100644 --- a/net/tcp/tcp.h +++ b/net/tcp/tcp.h @@ -189,9 +189,9 @@ 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_RXCLOSE - Gracefully close the current connection (RX) * TCP_TXCLOSE - Gracefully close the current connection (TX) - * TCP_ABORT - Abort (reset) the current connection on an error that - * prevents TCP_CLOSE from working. + * TCP_ABORT - Abort (reset) the current connection * * And/Or set/clear the following: * diff --git a/net/tcp/tcp_callback.c b/net/tcp/tcp_callback.c index 360814227ab..21aee97b5b6 100644 --- a/net/tcp/tcp_callback.c +++ b/net/tcp/tcp_callback.c @@ -271,10 +271,10 @@ uint16_t tcp_callback(FAR struct net_driver_s *dev, return 0; } - /* 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. + /* Preserve the TCP_ACKDATA, TCP_CLOSE, TCP_RXCLOSE, 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. */ ninfo("flags: %04x\n", flags); @@ -283,9 +283,9 @@ 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_RXCLOSE - Gracefully close the current connection (RX) * TCP_TXCLOSE - Gracefully close the current connection (TX) - * TCP_ABORT - Abort (reset) the current connection on an error that - * prevents TCP_CLOSE from working. + * TCP_ABORT - Abort (reset) the current connection * * And/Or set/clear the following: * diff --git a/net/tcp/tcp_input.c b/net/tcp/tcp_input.c index f97c41f0907..979155d9458 100644 --- a/net/tcp/tcp_input.c +++ b/net/tcp/tcp_input.c @@ -1398,6 +1398,7 @@ found: * has been closed. */ + flags |= TCP_RXCLOSE; if (dev->d_len > 0) { flags |= TCP_NEWDATA; @@ -1577,7 +1578,7 @@ found: } net_incr32(conn->rcvseq, 1); /* ack FIN */ - tcp_callback(dev, conn, TCP_CLOSE); + tcp_callback(dev, conn, TCP_RXCLOSE); tcp_send(dev, conn, TCP_ACK, tcpiplen); return; } @@ -1606,7 +1607,7 @@ found: ninfo("TCP state: TCP_TIME_WAIT\n"); net_incr32(conn->rcvseq, 1); /* ack FIN */ - tcp_callback(dev, conn, TCP_CLOSE); + tcp_callback(dev, conn, TCP_RXCLOSE); tcp_send(dev, conn, TCP_ACK, tcpiplen); return; } diff --git a/net/tcp/tcp_monitor.c b/net/tcp/tcp_monitor.c index 7ee10b50125..63ae6b9243e 100644 --- a/net/tcp/tcp_monitor.c +++ b/net/tcp/tcp_monitor.c @@ -87,6 +87,10 @@ static void tcp_close_connection(FAR struct tcp_conn_s *conn, uint16_t flags) * _SF_CONNECTED==0 && _SF_CLOSED==0 - the socket was rudely disconnected */ + /* The loss of connection was less than graceful. This will + * (eventually) be reported as an ENOTCONN error. + */ + if ((flags & TCP_CLOSE) != 0) { /* The peer gracefully closed the connection. Marking the @@ -174,6 +178,10 @@ static uint16_t tcp_monitor_event(FAR struct net_driver_s *dev, conn->sconn.s_flags |= (_SF_BOUND | _SF_CONNECTED); conn->sconn.s_flags &= ~_SF_CLOSED; } + else if ((flags & TCP_RXCLOSE) != 0) + { + conn->shutdown |= SHUT_RD; + } } return flags; @@ -294,7 +302,7 @@ int tcp_start_monitor(FAR struct socket *psock) { cb->event = tcp_monitor_event; cb->priv = (FAR void *)conn; - cb->flags = TCP_DISCONN_EVENTS; + cb->flags = TCP_DISCONN_EVENTS | TCP_RXCLOSE; /* Monitor the connected event */ diff --git a/net/tcp/tcp_netpoll.c b/net/tcp/tcp_netpoll.c index 0c71ec8d976..2c60796af19 100644 --- a/net/tcp/tcp_netpoll.c +++ b/net/tcp/tcp_netpoll.c @@ -83,7 +83,7 @@ static uint16_t tcp_poll_eventhandler(FAR struct net_driver_s *dev, /* Check for data or connection availability events. */ - if ((flags & (TCP_NEWDATA | TCP_BACKLOG)) != 0) + if ((flags & (TCP_NEWDATA | TCP_BACKLOG | TCP_RXCLOSE)) != 0) { eventset |= POLLIN; } @@ -301,7 +301,7 @@ int tcp_pollsetup(FAR struct socket *psock, FAR struct pollfd *fds) if ((fds->events & POLLIN) != 0) { - cb->flags |= TCP_NEWDATA | TCP_BACKLOG; + cb->flags |= TCP_NEWDATA | TCP_BACKLOG | TCP_RXCLOSE; } /* Save the reference in the poll info structure as fds private as well @@ -312,7 +312,8 @@ int tcp_pollsetup(FAR struct socket *psock, FAR struct pollfd *fds) /* Check for read data or backlogged connection availability now */ - if (conn->readahead != NULL || tcp_backlogpending(conn)) + if (conn->readahead != NULL || tcp_backlogpending(conn) || + (conn->shutdown & SHUT_RD) != 0) { /* Normal data may be read without blocking. */ diff --git a/net/tcp/tcp_recvfrom.c b/net/tcp/tcp_recvfrom.c index 271bcaa83e9..4dbcc677478 100644 --- a/net/tcp/tcp_recvfrom.c +++ b/net/tcp/tcp_recvfrom.c @@ -209,10 +209,11 @@ static inline uint16_t tcp_newdata(FAR struct net_driver_s *dev, if (recvlen < dev->d_len) { - /* Clear the TCP_CLOSE because we effectively dropped the FIN as well. + /* Clear the TCP_RXCLOSE because we effectively dropped the FIN as + * well. */ - flags &= ~TCP_CLOSE; + flags &= ~TCP_RXCLOSE; } net_incr32(conn->rcvseq, recvlen); @@ -522,6 +523,12 @@ static uint16_t tcp_recvhandler(FAR struct net_driver_s *dev, /* Wake up the waiting thread */ + nxsem_post(&pstate->ir_sem); + } + else if ((flags & TCP_RXCLOSE) != 0) + { + ninfo("TCP_RXCLOSE\n"); + pstate->ir_result = 0; nxsem_post(&pstate->ir_sem); } } @@ -719,7 +726,8 @@ static ssize_t tcp_recvfrom_one(FAR struct tcp_conn_s *conn, FAR void *buf, /* Verify that the SOCK_STREAM has been and still is connected */ - if (!_SS_ISCONNECTED(conn->sconn.s_flags)) + if (!_SS_ISCONNECTED(conn->sconn.s_flags) || + (conn->shutdown & SHUT_RD) != 0) { /* Was any data transferred from the readahead buffer after we were * disconnected? If so, then return the number of bytes received. @@ -732,7 +740,8 @@ static ssize_t tcp_recvfrom_one(FAR struct tcp_conn_s *conn, FAR void *buf, * recvfrom() will get an end-of-file indication. */ - if (ret <= 0 && !_SS_ISCLOSED(conn->sconn.s_flags)) + if (ret <= 0 && !_SS_ISCLOSED(conn->sconn.s_flags) && + (conn->shutdown & SHUT_RD) == 0) { /* Nothing was previously received from the read-ahead buffers. * The SOCK_STREAM must be (re-)connected in order to receive @@ -794,7 +803,8 @@ static ssize_t tcp_recvfrom_one(FAR struct tcp_conn_s *conn, FAR void *buf, state.ir_cb = tcp_callback_alloc(conn); if (state.ir_cb) { - state.ir_cb->flags = (TCP_NEWDATA | TCP_DISCONN_EVENTS); + state.ir_cb->flags = (TCP_NEWDATA | TCP_RXCLOSE | + TCP_DISCONN_EVENTS); state.ir_cb->flags |= (flags & MSG_WAITALL) ? TCP_WAITALL : 0; state.ir_cb->priv = (FAR void *)&state; state.ir_cb->event = tcp_recvhandler; diff --git a/net/tcp/tcp_shutdown.c b/net/tcp/tcp_shutdown.c index 6871d2ba085..cf649509607 100644 --- a/net/tcp/tcp_shutdown.c +++ b/net/tcp/tcp_shutdown.c @@ -71,7 +71,15 @@ static uint16_t tcp_shutdown_eventhandler(FAR struct net_driver_s *dev, #endif dev->d_len = 0; - flags |= TCP_TXCLOSE; + if ((conn->shutdown & SHUT_WR) != 0) + { + flags |= TCP_TXCLOSE; + } + + if ((conn->shutdown & SHUT_RD) != 0) + { + flags |= TCP_RXCLOSE; + } if (conn->shdcb != NULL) { @@ -165,7 +173,7 @@ int tcp_shutdown(FAR struct socket *psock, int how) conn = psock->s_conn; DEBUGASSERT(conn != NULL); - if (!(how & SHUT_WR)) + if (!(how & SHUT_RDWR)) { return -EOPNOTSUPP; }