From f2d94a7f5cb63d2d7e9bbe87608bb541b1531490 Mon Sep 17 00:00:00 2001 From: Sjors Hettinga Date: Thu, 30 Jun 2022 12:02:27 +0200 Subject: [PATCH] net: tcp: Implement a fast retransmit algorithm Instead of waiting for the retransmit timeout, retransmit as soon as missing data is deduced based on a triple-duplicate ACK. Increase the number of buffers in the testcase, to allow for at least 4 packets in flight to trigger the triple-duplicate ACK. Signed-off-by: Sjors Hettinga --- subsys/net/ip/Kconfig | 12 ++++++++ subsys/net/ip/tcp.c | 58 ++++++++++++++++++++++++++++++++--- subsys/net/ip/tcp_private.h | 3 ++ tests/net/socket/tcp/prj.conf | 6 ++-- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index c0bc2a8a4b..d4a59fe6a5 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -424,6 +424,18 @@ config NET_TCP_RANDOMIZED_RTO a second collision is reduced and it reduces furter the more retransmissions occur. +config NET_TCP_FAST_RETRANSMIT + bool "Fast-retry algorithm based on the number of duplicated ACKs" + depends on NET_TCP + default y + help + When a packet is lost, the receiver will keep acknowledging the + sequence number of the last correctly received byte. Upon reception + of a sequence of acknowledgements for the same sequence number, + this can be deduced as that the packet afterwards must have been lost. + In that case a retransmission is triggerd to avoid having to wait for + the retransmit timer to elapse. + config NET_TCP_MAX_SEND_WINDOW_SIZE int "Maximum sending window size to use" depends on NET_TCP diff --git a/subsys/net/ip/tcp.c b/subsys/net/ip/tcp.c index 9e432a32ca..44054bf3c1 100644 --- a/subsys/net/ip/tcp.c +++ b/subsys/net/ip/tcp.c @@ -31,6 +31,7 @@ LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL); #define FIN_TIMEOUT K_MSEC(tcp_fin_timeout_ms) #define ACK_DELAY K_MSEC(100) #define ZWP_MAX_DELAY_MS 120000 +#define DUPLICATE_ACK_RETRANSMIT_TRHESHOLD 3 static int tcp_rto = CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT; static int tcp_retries = CONFIG_NET_TCP_RETRY_COUNT; @@ -1424,6 +1425,9 @@ static struct tcp *tcp_conn_alloc(struct net_context *context) conn->state = TCP_LISTEN; conn->recv_win_max = tcp_window; conn->tcp_nodelay = false; +#ifdef CONFIG_NET_TCP_FAST_RETRANSMIT + conn->dup_ack_cnt = 0; +#endif /* Set the recv_win with the rcvbuf configured for the socket. */ if (IS_ENABLED(CONFIG_NET_CONTEXT_RCVBUF) && @@ -2154,7 +2158,43 @@ next_state: break; } - if (th && net_tcp_seq_cmp(th_ack(th), conn->seq) > 0) { +#ifdef CONFIG_NET_TCP_FAST_RETRANSMIT + if (th && (net_tcp_seq_cmp(th_ack(th), conn->seq) == 0)) { + /* Only if there is pending data, increment the duplicate ack count */ + if (conn->send_data_total > 0) { + /* There could be also payload, only without payload account them */ + if (len == 0) { + /* Increment the duplicate acc counter, + * but maximize the value + */ + conn->dup_ack_cnt = MIN(conn->dup_ack_cnt + 1, + DUPLICATE_ACK_RETRANSMIT_TRHESHOLD + 1); + } + } else { + conn->dup_ack_cnt = 0; + } + + /* Only do fast retransmit when not already in a resend state */ + if ((conn->data_mode == TCP_DATA_MODE_SEND) && + (conn->dup_ack_cnt == DUPLICATE_ACK_RETRANSMIT_TRHESHOLD)) { + /* Apply a fast retransmit */ + int temp_unacked_len = conn->unacked_len; + + conn->unacked_len = 0; + + ret = tcp_send_data(conn); + if (ret < 0) { + /* Retry at the next duplicate ack */ + conn->dup_ack_cnt--; + } + + /* Restore the current transmission */ + conn->unacked_len = temp_unacked_len; + } + } +#endif + + if (th && (net_tcp_seq_cmp(th_ack(th), conn->seq) > 0)) { uint32_t len_acked = th_ack(th) - conn->seq; NET_DBG("conn: %p len_acked=%u", conn, len_acked); @@ -2172,6 +2212,11 @@ next_state: break; } +#ifdef CONFIG_NET_TCP_FAST_RETRANSMIT + /* New segment, reset duplicate ack counter */ + conn->dup_ack_cnt = 0; +#endif + conn->send_data_total -= len_acked; if (conn->unacked_len < len_acked) { conn->unacked_len = 0; @@ -2229,9 +2274,14 @@ next_state: if (th_seq(th) == conn->ack) { verdict = tcp_data_received(conn, pkt, &len); } else if (net_tcp_seq_greater(conn->ack, th_seq(th))) { - if ((len > 0) || FL(&fl, &, SYN)) { - tcp_out(conn, ACK); /* peer has resent */ - } + /* This should handle the acknowledgements of keep alive + * packets and retransmitted data. + * RISK: + * There is a tiny risk of creating a ACK loop this way when + * both ends of the connection are out of order due to packet + * loss is a simulatanious bidirectional data flow. + */ + tcp_out(conn, ACK); /* peer has resent */ net_stats_update_tcp_seg_ackerr(conn->iface); } else if (CONFIG_NET_TCP_RECV_QUEUE_TIMEOUT) { diff --git a/subsys/net/ip/tcp_private.h b/subsys/net/ip/tcp_private.h index 9768a37b9e..78d4e12eca 100644 --- a/subsys/net/ip/tcp_private.h +++ b/subsys/net/ip/tcp_private.h @@ -273,6 +273,9 @@ struct tcp { /* TCP connection */ uint16_t rto; #endif uint8_t send_data_retries; +#ifdef CONFIG_NET_TCP_FAST_RETRANSMIT + uint8_t dup_ack_cnt; +#endif uint8_t zwp_retries; bool in_retransmission : 1; bool in_connect : 1; diff --git a/tests/net/socket/tcp/prj.conf b/tests/net/socket/tcp/prj.conf index 4a7595b197..d3dba4cc0c 100644 --- a/tests/net/socket/tcp/prj.conf +++ b/tests/net/socket/tcp/prj.conf @@ -35,8 +35,10 @@ CONFIG_NET_STATISTICS=y CONFIG_NET_STATISTICS_IPV4=y CONFIG_NET_STATISTICS_USER_API=y -CONFIG_NET_PKT_RX_COUNT=8 -CONFIG_NET_PKT_TX_COUNT=8 +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 # Reduce the retry count, so the close always finishes within a second CONFIG_NET_TCP_RETRY_COUNT=3