diff --git a/net/socket/Kconfig b/net/socket/Kconfig index 23a45cf386..3dc7002099 100644 --- a/net/socket/Kconfig +++ b/net/socket/Kconfig @@ -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 diff --git a/net/tcp/tcp.h b/net/tcp/tcp.h index 73858fea9b..f28dd9758a 100644 --- a/net/tcp/tcp.h +++ b/net/tcp/tcp.h @@ -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 * diff --git a/net/tcp/tcp_close.c b/net/tcp/tcp_close.c index 86fc22a6e9..7dc1d08110 100644 --- a/net/tcp/tcp_close.c +++ b/net/tcp/tcp_close.c @@ -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 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 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); diff --git a/net/tcp/tcp_timer.c b/net/tcp/tcp_timer.c index b17a26a189..931c2fd272 100644 --- a/net/tcp/tcp_timer.c +++ b/net/tcp/tcp_timer.c @@ -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