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 <s.a.hettinga@gmail.com>
This commit is contained in:
Sjors Hettinga 2022-06-30 12:02:27 +02:00 committed by Fabio Baltieri
parent 9d772efbbc
commit f2d94a7f5c
4 changed files with 73 additions and 6 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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