net: tcp: Prevent Silly Window Syndrome
Implement a mechanism, according to RFC 813, which allows to prevent so called "Silly Window Syndrome" - a scenario where the TCP receiver keeps reporting small window sizes in the acknowledgments, effectively limiting the connection throughput. This allows to improve performance in low-buffer configurations, where the maximum window size is small, and the issue was hitting quite often. Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
This commit is contained in:
parent
e03e4d5ad1
commit
28fb1a5f39
2 changed files with 67 additions and 5 deletions
|
@ -31,6 +31,7 @@ LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL);
|
||||||
/* Allow for (tcp_retries + 1) transmissions */
|
/* Allow for (tcp_retries + 1) transmissions */
|
||||||
#define FIN_TIMEOUT_MS (tcp_rto * (tcp_retries + 1))
|
#define FIN_TIMEOUT_MS (tcp_rto * (tcp_retries + 1))
|
||||||
#define FIN_TIMEOUT K_MSEC(FIN_TIMEOUT_MS)
|
#define FIN_TIMEOUT K_MSEC(FIN_TIMEOUT_MS)
|
||||||
|
#define ACK_DELAY K_MSEC(100)
|
||||||
|
|
||||||
static int tcp_rto = CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT;
|
static int tcp_rto = CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT;
|
||||||
static int tcp_retries = CONFIG_NET_TCP_RETRY_COUNT;
|
static int tcp_retries = CONFIG_NET_TCP_RETRY_COUNT;
|
||||||
|
@ -53,6 +54,7 @@ static K_KERNEL_STACK_DEFINE(work_q_stack, CONFIG_NET_TCP_WORKQ_STACK_SIZE);
|
||||||
|
|
||||||
static void tcp_in(struct tcp *conn, struct net_pkt *pkt);
|
static void tcp_in(struct tcp *conn, struct net_pkt *pkt);
|
||||||
static bool is_destination_local(struct net_pkt *pkt);
|
static bool is_destination_local(struct net_pkt *pkt);
|
||||||
|
static void tcp_out(struct tcp *conn, uint8_t flags);
|
||||||
|
|
||||||
int (*tcp_send_cb)(struct net_pkt *pkt) = NULL;
|
int (*tcp_send_cb)(struct net_pkt *pkt) = NULL;
|
||||||
size_t (*tcp_recv_cb)(struct tcp *conn, struct net_pkt *pkt) = NULL;
|
size_t (*tcp_recv_cb)(struct tcp *conn, struct net_pkt *pkt) = NULL;
|
||||||
|
@ -431,6 +433,7 @@ static int tcp_conn_unref(struct tcp *conn, int status)
|
||||||
(void)k_work_cancel_delayable(&conn->timewait_timer);
|
(void)k_work_cancel_delayable(&conn->timewait_timer);
|
||||||
(void)k_work_cancel_delayable(&conn->fin_timer);
|
(void)k_work_cancel_delayable(&conn->fin_timer);
|
||||||
(void)k_work_cancel_delayable(&conn->persist_timer);
|
(void)k_work_cancel_delayable(&conn->persist_timer);
|
||||||
|
(void)k_work_cancel_delayable(&conn->ack_timer);
|
||||||
|
|
||||||
sys_slist_find_and_remove(&tcp_conns, &conn->next);
|
sys_slist_find_and_remove(&tcp_conns, &conn->next);
|
||||||
|
|
||||||
|
@ -692,6 +695,17 @@ end:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool tcp_short_window(struct tcp *conn)
|
||||||
|
{
|
||||||
|
int32_t threshold = MIN(conn_mss(conn), conn->recv_win_max / 2);
|
||||||
|
|
||||||
|
if (conn->recv_win > threshold) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update TCP receive window
|
* @brief Update TCP receive window
|
||||||
*
|
*
|
||||||
|
@ -704,14 +718,26 @@ end:
|
||||||
static int tcp_update_recv_wnd(struct tcp *conn, int32_t delta)
|
static int tcp_update_recv_wnd(struct tcp *conn, int32_t delta)
|
||||||
{
|
{
|
||||||
int32_t new_win;
|
int32_t new_win;
|
||||||
|
bool short_win_before;
|
||||||
|
bool short_win_after;
|
||||||
|
|
||||||
new_win = conn->recv_win + delta;
|
new_win = conn->recv_win + delta;
|
||||||
if (new_win < 0 || new_win > UINT16_MAX) {
|
if (new_win < 0 || new_win > UINT16_MAX) {
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
short_win_before = tcp_short_window(conn);
|
||||||
|
|
||||||
conn->recv_win = new_win;
|
conn->recv_win = new_win;
|
||||||
|
|
||||||
|
short_win_after = tcp_short_window(conn);
|
||||||
|
|
||||||
|
if (short_win_before && !short_win_after &&
|
||||||
|
conn->state == TCP_ESTABLISHED) {
|
||||||
|
k_work_cancel_delayable(&conn->ack_timer);
|
||||||
|
tcp_out(conn, ACK);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1294,6 +1320,18 @@ static void tcp_send_zwp(struct k_work *work)
|
||||||
k_mutex_unlock(&conn->lock);
|
k_mutex_unlock(&conn->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void tcp_send_ack(struct k_work *work)
|
||||||
|
{
|
||||||
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||||
|
struct tcp *conn = CONTAINER_OF(dwork, struct tcp, ack_timer);
|
||||||
|
|
||||||
|
k_mutex_lock(&conn->lock, K_FOREVER);
|
||||||
|
|
||||||
|
tcp_out(conn, ACK);
|
||||||
|
|
||||||
|
k_mutex_unlock(&conn->lock);
|
||||||
|
}
|
||||||
|
|
||||||
static void tcp_conn_ref(struct tcp *conn)
|
static void tcp_conn_ref(struct tcp *conn)
|
||||||
{
|
{
|
||||||
int ref_count = atomic_inc(&conn->ref_count) + 1;
|
int ref_count = atomic_inc(&conn->ref_count) + 1;
|
||||||
|
@ -1338,17 +1376,19 @@ static struct tcp *tcp_conn_alloc(struct net_context *context)
|
||||||
|
|
||||||
conn->in_connect = false;
|
conn->in_connect = false;
|
||||||
conn->state = TCP_LISTEN;
|
conn->state = TCP_LISTEN;
|
||||||
conn->recv_win = tcp_window;
|
conn->recv_win_max = tcp_window;
|
||||||
conn->tcp_nodelay = false;
|
conn->tcp_nodelay = false;
|
||||||
|
|
||||||
/* Set the recv_win with the rcvbuf configured for the socket. */
|
/* Set the recv_win with the rcvbuf configured for the socket. */
|
||||||
if (IS_ENABLED(CONFIG_NET_CONTEXT_RCVBUF) &&
|
if (IS_ENABLED(CONFIG_NET_CONTEXT_RCVBUF) &&
|
||||||
net_context_get_option(context, NET_OPT_RCVBUF, &recv_window, &len) == 0) {
|
net_context_get_option(context, NET_OPT_RCVBUF, &recv_window, &len) == 0) {
|
||||||
if (recv_window != 0) {
|
if (recv_window != 0) {
|
||||||
conn->recv_win = recv_window;
|
conn->recv_win_max = recv_window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn->recv_win = conn->recv_win_max;
|
||||||
|
|
||||||
/* The ISN value will be set when we get the connection attempt or
|
/* The ISN value will be set when we get the connection attempt or
|
||||||
* when trying to create a connection.
|
* when trying to create a connection.
|
||||||
*/
|
*/
|
||||||
|
@ -1362,6 +1402,7 @@ static struct tcp *tcp_conn_alloc(struct net_context *context)
|
||||||
k_work_init_delayable(&conn->send_data_timer, tcp_resend_data);
|
k_work_init_delayable(&conn->send_data_timer, tcp_resend_data);
|
||||||
k_work_init_delayable(&conn->recv_queue_timer, tcp_cleanup_recv_queue);
|
k_work_init_delayable(&conn->recv_queue_timer, tcp_cleanup_recv_queue);
|
||||||
k_work_init_delayable(&conn->persist_timer, tcp_send_zwp);
|
k_work_init_delayable(&conn->persist_timer, tcp_send_zwp);
|
||||||
|
k_work_init_delayable(&conn->ack_timer, tcp_send_ack);
|
||||||
|
|
||||||
tcp_conn_ref(conn);
|
tcp_conn_ref(conn);
|
||||||
|
|
||||||
|
@ -1789,7 +1830,17 @@ static bool tcp_data_received(struct tcp *conn, struct net_pkt *pkt,
|
||||||
|
|
||||||
net_stats_update_tcp_seg_recv(conn->iface);
|
net_stats_update_tcp_seg_recv(conn->iface);
|
||||||
conn_ack(conn, *len);
|
conn_ack(conn, *len);
|
||||||
tcp_out(conn, ACK);
|
|
||||||
|
/* Delay ACK response in case of small window or missing PSH,
|
||||||
|
* as described in RFC 813.
|
||||||
|
*/
|
||||||
|
if (tcp_short_window(conn)) {
|
||||||
|
k_work_schedule_for_queue(&tcp_work_q, &conn->ack_timer,
|
||||||
|
ACK_DELAY);
|
||||||
|
} else {
|
||||||
|
k_work_cancel_delayable(&conn->ack_timer);
|
||||||
|
tcp_out(conn, ACK);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2306,12 +2357,21 @@ int net_tcp_listen(struct net_context *context)
|
||||||
|
|
||||||
int net_tcp_update_recv_wnd(struct net_context *context, int32_t delta)
|
int net_tcp_update_recv_wnd(struct net_context *context, int32_t delta)
|
||||||
{
|
{
|
||||||
if (!context->tcp) {
|
struct tcp *conn = context->tcp;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!conn) {
|
||||||
NET_ERR("context->tcp == NULL");
|
NET_ERR("context->tcp == NULL");
|
||||||
return -EPROTOTYPE;
|
return -EPROTOTYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcp_update_recv_wnd((struct tcp *)context->tcp, delta);
|
k_mutex_lock(&conn->lock, K_FOREVER);
|
||||||
|
|
||||||
|
ret = tcp_update_recv_wnd((struct tcp *)context->tcp, delta);
|
||||||
|
|
||||||
|
k_mutex_unlock(&conn->lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* net_context queues the outgoing data for the TCP connection */
|
/* net_context queues the outgoing data for the TCP connection */
|
||||||
|
|
|
@ -244,6 +244,7 @@ struct tcp { /* TCP connection */
|
||||||
struct k_work_delayable send_data_timer;
|
struct k_work_delayable send_data_timer;
|
||||||
struct k_work_delayable timewait_timer;
|
struct k_work_delayable timewait_timer;
|
||||||
struct k_work_delayable persist_timer;
|
struct k_work_delayable persist_timer;
|
||||||
|
struct k_work_delayable ack_timer;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
/* Because FIN and establish timers are never happening
|
/* Because FIN and establish timers are never happening
|
||||||
|
@ -263,6 +264,7 @@ struct tcp { /* TCP connection */
|
||||||
enum tcp_data_mode data_mode;
|
enum tcp_data_mode data_mode;
|
||||||
uint32_t seq;
|
uint32_t seq;
|
||||||
uint32_t ack;
|
uint32_t ack;
|
||||||
|
uint16_t recv_win_max;
|
||||||
uint16_t recv_win;
|
uint16_t recv_win;
|
||||||
uint16_t send_win;
|
uint16_t send_win;
|
||||||
uint8_t send_data_retries;
|
uint8_t send_data_retries;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue