tcp_close: Fix a race with passive close

tcp_close disposes the connection immediately if it's called in
TCP_LAST_ACK. If it happens, we will end up with responding the
last ACK with a RST.

This commit fixes it by making tcp_close wait for the completion
of the passive close.
This commit is contained in:
YAMAMOTO Takashi 2021-07-01 12:14:21 +09:00 committed by Masayuki Ishikawa
parent 50eee2f081
commit 669619a06a

View file

@ -118,10 +118,16 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev,
goto end_wait;
}
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
/* Check if all outstanding bytes have been ACKed */
/* Check if all outstanding bytes have been ACKed.
*
* Note: in case of passive close, this ensures our FIN is acked.
*/
else if (conn->tx_unacked != 0 || !sq_empty(&conn->write_q))
else if (conn->tx_unacked != 0
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
|| !sq_empty(&conn->write_q)
#endif /* CONFIG_NET_TCP_WRITE_BUFFERS */
)
{
/* No... we are still waiting for ACKs. Drop any received data, but
* do not yet report TCP_CLOSE in the response.
@ -129,12 +135,27 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev,
dev->d_len = 0;
flags &= ~TCP_NEWDATA;
ninfo("waiting for ack\n");
}
#endif /* CONFIG_NET_TCP_WRITE_BUFFERS */
else
{
/* Note: the following state shouldn't reach here because
*
* FIN_WAIT_1, CLOSING, LAST_ACK
* should have tx_unacked != 0, already handled above
*
* CLOSED, TIME_WAIT
* a TCP_CLOSE 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);
/* Drop data received in this state and make sure that TCP_CLOSE
* is set in the response
*/
@ -278,9 +299,16 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
}
#endif
/* Check for the case where the host beat us and disconnected first */
/* TCP_ESTABLISHED
* We need to initiate an active close and wait for its completion.
*
* TCP_LAST_ACK
* We still need to wait for the ACK for our FIN, possibly
* retransmitting the FIN, before disposing the connection.
*/
if (conn->tcpstateflags == TCP_ESTABLISHED &&
if ((conn->tcpstateflags == TCP_ESTABLISHED ||
conn->tcpstateflags == TCP_LAST_ACK) &&
(state.cl_cb = tcp_callback_alloc(conn)) != NULL)
{
/* Set up to receive TCP data event callbacks */