Skip to content

Commit

Permalink
net: tcp: Implement TCP new Reno collision avoidance
Browse files Browse the repository at this point in the history
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 <s.a.hettinga@gmail.com>
  • Loading branch information
ssharks committed Jul 20, 2023
1 parent 278563e commit 14f9fd6
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 3 deletions.
8 changes: 8 additions & 0 deletions subsys/net/ip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 92 additions & 3 deletions subsys/net/ip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ static int tcp_tx_window =
#define TCP_RTO_MS (tcp_rto)
#endif

/* Define the number of MSS sections the congestion window is initialized at */
#define TCP_CONGESTION_INITIAL_WIN 1
#define TCP_CONGESTION_INITIAL_SSTHRESH 3

static sys_slist_t tcp_conns = SYS_SLIST_STATIC_INIT(&tcp_conns);

static K_MUTEX_DEFINE(tcp_lock);
Expand Down Expand Up @@ -392,6 +396,47 @@ 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;
conn->ca.state = TCP_NEW_RENO_RAMPUP;
}

static void tcp_new_reno_fast_retransmit(struct tcp *conn)
{
conn->ca.congestion_win = MAX(conn_mss(conn), conn->ca.congestion_win / 2);
conn->ca.ssthresh = conn->ca.congestion_win;
conn->ca.state = TCP_NEW_RENO_LINEAR;
}

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;
conn->ca.state = TCP_NEW_RENO_RAMPUP;
}

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;
Expand Down Expand Up @@ -1140,6 +1185,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 = window_full || (conn->send_data_total >= conn->ca.congestion_win);
#endif

NET_DBG("conn: %p window_full=%hu", conn, window_full);

Expand All @@ -1162,6 +1210,13 @@ static int tcp_unsent_len(struct tcp *conn)
unsent_len = 0;
} else {
unsent_len = MIN(unsent_len, conn->send_win - conn->unacked_len);
#ifdef 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);
Expand All @@ -1175,9 +1230,11 @@ static int tcp_send_data(struct tcp *conn)
int len;
struct net_pkt *pkt;

len = MIN3(conn->send_data_total - conn->unacked_len,
conn->send_win - conn->unacked_len,
conn_mss(conn));
len = MIN(tcp_unsent_len(conn), conn_mss(conn));
if (len < 0) {
ret = len;
goto out;
}
if (len == 0) {
NET_DBG("conn: %p no data to send", conn);
ret = -ENODATA;
Expand Down Expand Up @@ -1306,6 +1363,15 @@ static void tcp_resend_data(struct k_work *work)
goto out;
}

#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE
if (conn->send_data_retries == 0) {
tcp_new_reno_timeout(conn);
if (tcp_window_full(conn)) {
(void)k_sem_take(&conn->tx_sem, K_NO_WAIT);
}
}
#endif

conn->data_mode = TCP_DATA_MODE_RESEND;
conn->unacked_len = 0;

Expand Down Expand Up @@ -1485,6 +1551,12 @@ static struct tcp *tcp_conn_alloc(void)
#ifdef CONFIG_NET_TCP_FAST_RETRANSMIT
conn->dup_ack_cnt = 0;
#endif
#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE
/* Initially set the congestion window at its max size, since only the MSS
* is available as soon as the connection is established
*/
conn->ca.congestion_win = UINT16_MAX;
#endif

/* The ISN value will be set when we get the connection attempt or
* when trying to create a connection.
Expand Down Expand Up @@ -2225,6 +2297,10 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
conn->accepted_conn = NULL;
}

#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE
tcp_new_reno_init(conn);
#endif

if (len) {
verdict = tcp_data_get(conn, pkt, &len);
if (verdict == NET_OK) {
Expand Down Expand Up @@ -2263,6 +2339,9 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
tcp_conn_ref(conn);
net_context_set_state(conn->context,
NET_CONTEXT_CONNECTED);
#ifdef CONFIG_NET_TCP_CONGESTION_AVOIDANCE
tcp_new_reno_init(conn);
#endif
tcp_out(conn, ACK);

/* The connection semaphore is released *after*
Expand Down Expand Up @@ -2340,6 +2419,13 @@ 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);
if (tcp_window_full(conn)) {
(void)k_sem_take(&conn->tx_sem, K_NO_WAIT);
}
#endif
}
}
#endif
Expand All @@ -2366,6 +2452,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) {
Expand Down
17 changes: 17 additions & 0 deletions subsys/net/ip/tcp_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,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;
Expand Down Expand Up @@ -273,6 +287,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
Expand Down

0 comments on commit 14f9fd6

Please sign in to comment.