From 54ddf435338ce035c1a92199acd5a3b217db177b Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 21 Dec 2018 13:54:09 -0800 Subject: [PATCH] tcp: improve waitclose in closing states Change-Id: I90056176194cb2a144d49a3cb283653d8d30f051 Signed-off-by: Florin Coras --- src/vnet/session/session.c | 18 ++++++++++++++ src/vnet/session/session.h | 1 + src/vnet/tcp/tcp.c | 60 +++++++++++++++++++++++++++++++++------------- src/vnet/tcp/tcp.h | 7 ++++++ src/vnet/tcp/tcp_input.c | 5 ++++ src/vnet/tcp/tcp_output.c | 9 ++++++- 6 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/vnet/session/session.c b/src/vnet/session/session.c index 4081f909482..6492ce7089e 100644 --- a/src/vnet/session/session.c +++ b/src/vnet/session/session.c @@ -829,6 +829,24 @@ stream_session_delete_notify (transport_connection_t * tc) } } +/** + * Notification from transport that session can be closed + * + * Should be called by transport only if it was closed with non-empty + * tx fifo and once it decides to begin the closing procedure prior to + * issuing a delete notify. This gives the chance to the session layer + * to cleanup any outstanding events. + */ +void +session_stream_close_notify (transport_connection_t * tc) +{ + stream_session_t *s; + + if (!(s = session_get_if_valid (tc->s_index, tc->thread_index))) + return; + s->session_state = SESSION_STATE_CLOSED; +} + /** * Notify application that connection has been reset. */ diff --git a/src/vnet/session/session.h b/src/vnet/session/session.h index 6c1bdb639df..3b729234967 100644 --- a/src/vnet/session/session.h +++ b/src/vnet/session/session.h @@ -613,6 +613,7 @@ void stream_session_init_fifos_pointers (transport_connection_t * tc, int stream_session_accept_notify (transport_connection_t * tc); void stream_session_disconnect_notify (transport_connection_t * tc); void stream_session_delete_notify (transport_connection_t * tc); +void session_stream_close_notify (transport_connection_t * tc); void stream_session_reset_notify (transport_connection_t * tc); int stream_session_accept (transport_connection_t * tc, u32 listener_index, u8 notify); diff --git a/src/vnet/tcp/tcp.c b/src/vnet/tcp/tcp.c index cbf570f2dc7..b0c44638b36 100644 --- a/src/vnet/tcp/tcp.c +++ b/src/vnet/tcp/tcp.c @@ -299,6 +299,7 @@ tcp_connection_reset (tcp_connection_t * tc) case TCP_STATE_FIN_WAIT_1: case TCP_STATE_FIN_WAIT_2: case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: tcp_connection_timers_reset (tc); tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME); tc->state = TCP_STATE_CLOSED; @@ -1249,38 +1250,65 @@ tcp_timer_waitclose_handler (u32 conn_index) tc = tcp_connection_get (conn_index, thread_index); if (!tc) return; + tc->timers[TCP_TIMER_WAITCLOSE] = TCP_TIMER_HANDLE_INVALID; - /* Session didn't come back with a close(). Send FIN either way - * and switch to LAST_ACK. */ - if (tc->state == TCP_STATE_CLOSE_WAIT && (tc->flags & TCP_CONN_FINPNDG)) + switch (tc->state) { - /* Make sure we don't try to send unsent data */ + case TCP_STATE_CLOSE_WAIT: tcp_connection_timers_reset (tc); + + if (!(tc->flags & TCP_CONN_FINPNDG)) + { + tcp_connection_set_state (tc, TCP_STATE_CLOSED); + tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); + break; + } + + /* Session didn't come back with a close. Send FIN either way + * and switch to LAST_ACK. */ tcp_cong_recovery_off (tc); + /* Make sure we don't try to send unsent data */ tc->snd_una_max = tc->snd_nxt = tc->snd_una; tcp_send_fin (tc); - tc->state = TCP_STATE_LAST_ACK; + tcp_connection_set_state (tc, TCP_STATE_LAST_ACK); /* Make sure we don't wait in LAST ACK forever */ tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); /* Don't delete the connection yet */ - return; - } - else if (tc->state == TCP_STATE_FIN_WAIT_1) - { + break; + case TCP_STATE_FIN_WAIT_1: tcp_connection_timers_reset (tc); - /* If FIN pending send it before closing */ if (tc->flags & TCP_CONN_FINPNDG) - tcp_send_fin (tc); - tc->state = TCP_STATE_CLOSED; - /* Wait for session layer to clean up tx events */ + { + /* If FIN pending send it before closing and wait as long as + * the rto timeout would wait. Notify session layer that transport + * is closed. We haven't sent everything but we did try. */ + tcp_cong_recovery_off (tc); + tcp_send_fin (tc); + tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, + clib_max (tc->rto * TCP_TO_TIMER_TICK, 1)); + session_stream_close_notify (&tc->connection); + } + else + { + /* We've sent the fin but no progress. Close the connection and + * to make sure everything is flushed, setup a cleanup timer */ + tcp_connection_set_state (tc, TCP_STATE_CLOSED); + tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); + } + break; + case TCP_STATE_LAST_ACK: + case TCP_STATE_CLOSING: + tcp_connection_timers_reset (tc); + tcp_connection_set_state (tc, TCP_STATE_CLOSED); tcp_timer_set (tc, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); - return; + break; + default: + tcp_connection_del (tc); + break; } - - tcp_connection_del (tc); } /* *INDENT-OFF* */ diff --git a/src/vnet/tcp/tcp.h b/src/vnet/tcp/tcp.h index 2327b9a6ad5..bd0fa9a7204 100644 --- a/src/vnet/tcp/tcp.h +++ b/src/vnet/tcp/tcp.h @@ -562,6 +562,13 @@ tcp_get_connection_from_transport (transport_connection_t * tconn) return (tcp_connection_t *) tconn; } +always_inline void +tcp_connection_set_state (tcp_connection_t * tc, tcp_state_t state) +{ + tc->state = state; + TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc); +} + void tcp_connection_close (tcp_connection_t * tc); void tcp_connection_cleanup (tcp_connection_t * tc); void tcp_connection_del (tcp_connection_t * tc); diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index 32de369b4cc..b5e3ce74c38 100644 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -509,6 +509,7 @@ tcp_estimate_initial_rtt (tcp_connection_t * tc) if (mrtt > 0 && mrtt < TCP_RTT_MAX) tcp_estimate_rtt (tc, mrtt); + tcp_update_rto (tc); } /** @@ -3710,6 +3711,10 @@ do { \ _(FIN_WAIT_1, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(CLOSING, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); /* FIN confirming that the peer (app) has closed */ _(FIN_WAIT_2, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(FIN_WAIT_2, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); diff --git a/src/vnet/tcp/tcp_output.c b/src/vnet/tcp/tcp_output.c index fc4bceb58cb..5f48d410126 100644 --- a/src/vnet/tcp/tcp_output.c +++ b/src/vnet/tcp/tcp_output.c @@ -392,9 +392,13 @@ tcp_make_options (tcp_connection_t * tc, tcp_options_t * opts, switch (state) { case TCP_STATE_ESTABLISHED: + case TCP_STATE_CLOSE_WAIT: case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_LAST_ACK: + case TCP_STATE_CLOSING: + case TCP_STATE_FIN_WAIT_2: + case TCP_STATE_TIME_WAIT: case TCP_STATE_CLOSED: - case TCP_STATE_CLOSE_WAIT: return tcp_make_established_options (tc, opts); case TCP_STATE_SYN_RCVD: return tcp_make_synack_options (tc, opts); @@ -1124,6 +1128,8 @@ tcp_make_state_flags (tcp_connection_t * tc, tcp_state_t next_state) { case TCP_STATE_ESTABLISHED: case TCP_STATE_CLOSE_WAIT: + case TCP_STATE_TIME_WAIT: + case TCP_STATE_FIN_WAIT_2: return TCP_FLAG_ACK; case TCP_STATE_SYN_RCVD: return TCP_FLAG_SYN | TCP_FLAG_ACK; @@ -1131,6 +1137,7 @@ tcp_make_state_flags (tcp_connection_t * tc, tcp_state_t next_state) return TCP_FLAG_SYN; case TCP_STATE_LAST_ACK: case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_CLOSING: if (tc->snd_nxt + 1 < tc->snd_una_max) return TCP_FLAG_ACK; else -- 2.16.6