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:
parent
58b5a0412e
commit
1f75d02bb5
4 changed files with 98 additions and 37 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue