From 647302c405633e87dcb0e9c77cee25650fce76a3 Mon Sep 17 00:00:00 2001 From: Sjors Hettinga Date: Thu, 6 Jul 2023 22:28:26 +0200 Subject: [PATCH] net: tcp: Implement TCP new Reno collision avoidance To avoid a TCP connection from collapsing a link, implement a collision avoidance algorithm. Initially TCP new Reno is implemented for its simplicity. Signed-off-by: Sjors Hettinga --- subsys/net/ip/Kconfig | 8 +++++ subsys/net/ip/tcp.c | 68 +++++++++++++++++++++++++++++++++++++ subsys/net/ip/tcp_private.h | 24 +++++++++++++ 3 files changed, 100 insertions(+) diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index e5f096e594f5680..67081af528dca7a 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -456,6 +456,14 @@ config NET_TCP_FAST_RETRANSMIT In that case a retransmission is triggerd to avoid having to wait for the retransmit timer to elapse. +config NET_TCP_CONGESTION_AVOIDANCE + bool "Implement a collision avoidance algorithm in TCP" + depends on NET_TCP + default y + help + To avoid overstressing a link reduce the transmission rate as soon as + packets are starting to drop. + 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 ae9d59e7e16d5d2..0ef3010e2fe60a9 100644 --- a/subsys/net/ip/tcp.c +++ b/subsys/net/ip/tcp.c @@ -392,6 +392,44 @@ static void tcp_derive_rto(struct tcp *conn) #endif } +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE +static void tcp_new_reno_init(struct tcp *conn) +{ + conn->ca.congestion_win = conn_mss(conn) * TCP_CONGESTION_INITIAL_WIN; + conn->ca.ssthresh = conn_mss(conn) * TCP_CONGESTION_INITIAL_SSTHRESH; +} + +static void tcp_new_reno_fast_retransmit(struct tcp *conn) +{ + conn->ca.congestion_win = conn->ca.congestion_win / 2; + conn->ca.ssthresh = conn->ca.congestion_win; +} + +static void tcp_new_reno_timeout(struct tcp *conn) +{ + conn->ca.congestion_win = conn_mss(conn) * TCP_CONGESTION_INITIAL_WIN; + conn->ca.ssthresh = conn->ca.ssthresh / 2; +} + +static void tcp_new_reno_pkts_acked(struct tcp *conn) +{ + int32_t new_win = conn->ca.congestion_win; + + if (conn->ca.state == TCP_NEW_RENO_RAMPUP) { + new_win += conn_mss(conn); + conn->ca.congestion_win += conn_mss(conn); + } else { + int32_t mss = conn_mss(conn); + + new_win += (mss * mss) / conn->ca.congestion_win; + } + conn->ca.congestion_win = MIN(new_win, UINT16_MAX); + if (conn->ca.congestion_win > conn->ca.ssthresh) { + conn->ca.state = TCP_NEW_RENO_LINEAR; + } +} +#endif + static void tcp_send_queue_flush(struct tcp *conn) { struct net_pkt *pkt; @@ -1140,6 +1178,9 @@ static int tcp_pkt_peek(struct net_pkt *to, struct net_pkt *from, size_t pos, static bool tcp_window_full(struct tcp *conn) { bool window_full = (conn->send_data_total >= conn->send_win); +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + window_full |= (conn->send_data_total >= conn->ca.congestion_win); +#endif NET_DBG("conn: %p window_full=%hu", conn, window_full); @@ -1162,6 +1203,13 @@ static int tcp_unsent_len(struct tcp *conn) unsent_len = 0; } else { unsent_len = MIN(unsent_len, conn->send_win - conn->unacked_len); +#if CONFIG_NET_TCP_CONGESTION_AVOIDANCE + if (conn->unacked_len >= conn->ca.congestion_win) { + unsent_len = 0; + } else { + unsent_len = MIN(unsent_len, conn->ca.congestion_win - conn->unacked_len); + } +#endif } out: NET_DBG("unsent_len=%d", unsent_len); @@ -1306,6 +1354,10 @@ static void tcp_resend_data(struct k_work *work) goto out; } +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + tcp_new_reno_timeout(conn); +#endif + conn->data_mode = TCP_DATA_MODE_RESEND; conn->unacked_len = 0; @@ -1486,6 +1538,11 @@ static struct tcp *tcp_conn_alloc(void) conn->dup_ack_cnt = 0; #endif +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + tcp_new_reno_init(conn); +#endif + + /* The ISN value will be set when we get the connection attempt or * when trying to create a connection. */ @@ -2211,6 +2268,10 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt) net_context_set_state(conn->context, NET_CONTEXT_CONNECTED); +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + tcp_new_reno_init(conn); +#endif + if (conn->accepted_conn) { if (conn->accepted_conn->accept_cb) { conn->accepted_conn->accept_cb( @@ -2340,6 +2401,10 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt) /* Restore the current transmission */ conn->unacked_len = temp_unacked_len; + +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + tcp_new_reno_fast_retransmit(conn); +#endif } } #endif @@ -2366,6 +2431,9 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt) /* New segment, reset duplicate ack counter */ conn->dup_ack_cnt = 0; #endif +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + tcp_new_reno_pkts_acked(conn); +#endif conn->send_data_total -= len_acked; if (conn->unacked_len < len_acked) { diff --git a/subsys/net/ip/tcp_private.h b/subsys/net/ip/tcp_private.h index 74562d3848c4040..c8119dc5394899d 100644 --- a/subsys/net/ip/tcp_private.h +++ b/subsys/net/ip/tcp_private.h @@ -7,6 +7,13 @@ #include "tp.h" +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE +/* Define the number of MSS sections the congestion window is initialized at */ +#define TCP_CONGESTION_INITIAL_WIN 1 +#define TCP_CONGESTION_INITIAL_SSTHRESH 3 + +#endif + #define is(_a, _b) (strcmp((_a), (_b)) == 0) #ifndef MIN3 @@ -223,6 +230,20 @@ struct tcp_options { bool wnd_found : 1; }; +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + +enum tcp_new_reno_state { + TCP_NEW_RENO_RAMPUP = 1, + TCP_NEW_RENO_LINEAR +}; + +struct tcp_collision_avoidance_reno { + uint16_t congestion_win; + uint16_t ssthresh; + enum tcp_new_reno_state state; +}; +#endif + struct tcp { /* TCP connection */ sys_snode_t next; struct net_context *context; @@ -273,6 +294,9 @@ struct tcp { /* TCP connection */ uint16_t send_win; #ifdef CONFIG_NET_TCP_RANDOMIZED_RTO uint16_t rto; +#endif +#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE + struct tcp_collision_avoidance_reno ca; #endif uint8_t send_data_retries; #ifdef CONFIG_NET_TCP_FAST_RETRANSMIT