net/tcp: correct behavior of SO_LINGER

1. Remove tcp_txdrain() from close() to avoid indefinitely block
2. Send TCP_RST immediately if linger timeout

Signed-off-by: chao an <anchao@xiaomi.com>
This commit is contained in:
chao an 2023-01-16 11:57:03 +08:00 committed by Xiang Xiao
parent 58b5a0412e
commit 1f75d02bb5
4 changed files with 98 additions and 37 deletions

View file

@ -43,7 +43,6 @@ config NET_SOLINGER
bool "SO_LINGER socket option"
default n
depends on NET_TCP_WRITE_BUFFERS || NET_UDP_WRITE_BUFFERS
select NET_TCP_NOTIFIER if NET_TCP
select NET_UDP_NOTIFIER if NET_UDP
---help---
Enable or disable support for the SO_LINGER socket option. Requires

View file

@ -233,7 +233,9 @@ struct tcp_conn_s
uint16_t tx_unacked; /* Number bytes sent but not yet ACKed */
#endif
uint16_t flags; /* Flags of TCP-specific options */
#ifdef CONFIG_NET_SOLINGER
sclock_t ltimeout; /* Linger timeout expiration */
#endif
/* If the TCP socket is bound to a local address, then this is
* a reference to the device that routes traffic on the corresponding
* network.
@ -852,6 +854,27 @@ void tcp_poll(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn);
void tcp_timer(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn);
/****************************************************************************
* Name: tcp_update_timer
*
* Description:
* Update the TCP timer for the provided TCP connection,
* The timeout is accurate
*
* Input Parameters:
* conn - The TCP "connection" to poll for TX data
*
* Returned Value:
* None
*
* Assumptions:
* conn is not NULL.
* The connection (conn) is bound to the polling device (dev).
*
****************************************************************************/
void tcp_update_timer(FAR struct tcp_conn_s *conn);
/****************************************************************************
* Name: tcp_update_retrantimer
*

View file

@ -267,35 +267,6 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
conn = (FAR struct tcp_conn_s *)psock->s_conn;
DEBUGASSERT(conn != NULL);
#ifdef CONFIG_NET_SOLINGER
/* SO_LINGER
* Lingers on a close() if data is present. This option controls the
* action taken when unsent messages queue on a socket and close() is
* performed. If SO_LINGER is set, the system shall block the calling
* thread during close() until it can transmit the data or until the
* time expires. If SO_LINGER is not specified, and close() is issued,
* the system handles the call in a way that allows the calling thread
* to continue as quickly as possible. This option takes a linger
* structure, as defined in the <sys/socket.h> header, to specify the
* state of the option and linger interval.
*/
if (_SO_GETOPT(conn->sconn.s_options, SO_LINGER))
{
/* Wait until for the buffered TX data to be sent. */
ret = tcp_txdrain(psock, _SO_TIMEOUT(conn->sconn.s_linger));
if (ret < 0)
{
/* tcp_txdrain may fail, but that won't stop us from closing
* the socket.
*/
nerr("ERROR: tcp_txdrain() failed: %d\n", ret);
}
}
#endif
/* Discard our reference to the connection */
conn->crefs = 0;
@ -318,6 +289,30 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
conn->clscb->event = tcp_close_eventhandler;
conn->clscb->priv = conn; /* reference for event handler to free cb */
#ifdef CONFIG_NET_SOLINGER
/* SO_LINGER
* Lingers on a close() if data is present. This option controls the
* action taken when unsent messages queue on a socket and close() is
* performed. If SO_LINGER is set, the system shall block the calling
* thread during close() until it can transmit the data or until the
* time expires. If SO_LINGER is not specified, and close() is
* issued, the system handles the call in a way that allows the
* calling thread to continue as quickly as possible. This option
* takes a linger structure, as defined in the <sys/socket.h> header,
* to specify the state of the option and linger interval.
*/
if (_SO_GETOPT(conn->sconn.s_options, SO_LINGER))
{
conn->ltimeout = clock_systime_ticks() +
DSEC2TICK(conn->sconn.s_linger);
/* Update RTO timeout if the work exceeds expire */
tcp_update_timer(conn);
}
#endif
/* Notify the device driver of the availability of TX data */
tcp_close_txnotify(psock, conn);

View file

@ -155,6 +155,10 @@ static void tcp_timer_expiry(FAR void *arg)
net_unlock();
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_update_timer
*
@ -174,13 +178,32 @@ static void tcp_timer_expiry(FAR void *arg)
*
****************************************************************************/
static void tcp_update_timer(FAR struct tcp_conn_s *conn)
void tcp_update_timer(FAR struct tcp_conn_s *conn)
{
int timeout = tcp_get_timeout(conn);
if (timeout > 0)
{
if (TICK2HSEC(work_timeleft(&conn->work)) != timeout)
#ifdef CONFIG_NET_SOLINGER
/* Re-update tcp timeout */
if (conn->ltimeout != 0)
{
sclock_t ticks = conn->ltimeout - clock_systime_ticks();
if (ticks <= 0)
{
timeout = 0;
}
else if (timeout > TICK2HSEC(ticks))
{
timeout = TICK2HSEC(ticks);
}
}
#endif
if (work_available(&conn->work) ||
TICK2HSEC(work_timeleft(&conn->work)) != timeout)
{
work_queue(LPWORK, &conn->work, tcp_timer_expiry,
conn, HSEC2TICK(timeout));
@ -192,10 +215,6 @@ static void tcp_update_timer(FAR struct tcp_conn_s *conn)
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_update_retrantimer
*
@ -352,6 +371,31 @@ void tcp_timer(FAR struct net_driver_s *dev, FAR struct tcp_conn_s *conn)
return;
}
#ifdef CONFIG_NET_SOLINGER
/* Send reset immediately if linger timeout */
if (conn->ltimeout != 0 &&
((sclock_t)(conn->ltimeout - clock_systime_ticks()) <= 0))
{
conn->tcpstateflags = TCP_CLOSED;
ninfo("TCP state: TCP_CLOSED\n");
/* We call tcp_callback() with TCP_TIMEDOUT to
* inform the application that the connection has
* timed out.
*/
tcp_callback(dev, conn, TCP_TIMEDOUT);
/* We also send a reset packet to the remote host. */
tcp_send(dev, conn, TCP_RST | TCP_ACK, hdrlen);
goto done;
}
else
#endif
/* Check if the connection is in a state in which we simply wait
* for the connection to time out. If so, we increase the
* connection's timer and remove the connection if it times