net: tcp: Implement Nagle's algorithm

To improve the performance with small chunks send, implement Nagle's
algorithm. Provide the option TCP_NODELAY to disable the algorithm.

Signed-off-by: Sjors Hettinga <s.a.hettinga@gmail.com>
This commit is contained in:
Sjors Hettinga 2022-06-06 22:00:01 +02:00 committed by Carles Cufí
commit e097d95c66
4 changed files with 160 additions and 4 deletions

View file

@ -848,6 +848,37 @@ static int ip_header_add(struct tcp *conn, struct net_pkt *pkt)
return -EINVAL; return -EINVAL;
} }
static int set_tcp_nodelay(struct tcp *conn, const void *value, size_t len)
{
int no_delay_int;
if (len != sizeof(int)) {
return -EINVAL;
}
no_delay_int = *(int *)value;
if ((no_delay_int < 0) || (no_delay_int > 1)) {
return -EINVAL;
}
conn->tcp_nodelay = (bool)no_delay_int;
return 0;
}
static int get_tcp_nodelay(struct tcp *conn, void *value, size_t *len)
{
int no_delay_int = (int)conn->tcp_nodelay;
*((int *)value) = no_delay_int;
if (len) {
*len = sizeof(int);
}
return 0;
}
static int net_tcp_set_mss_opt(struct tcp *conn, struct net_pkt *pkt) static int net_tcp_set_mss_opt(struct tcp *conn, struct net_pkt *pkt)
{ {
NET_PKT_DATA_ACCESS_DEFINE(mss_opt_access, struct tcp_mss_option); NET_PKT_DATA_ACCESS_DEFINE(mss_opt_access, struct tcp_mss_option);
@ -1097,6 +1128,19 @@ static int tcp_send_queued_data(struct tcp *conn)
} }
while (tcp_unsent_len(conn) > 0) { while (tcp_unsent_len(conn) > 0) {
/* Implement Nagle's algorithm */
if ((conn->tcp_nodelay == false) && (conn->unacked_len > 0)) {
/* If there is already pending data */
if (tcp_unsent_len(conn) < conn_mss(conn)) {
/* The number of bytes to be transmitted is less than an MSS,
* skip transmission for now.
* Wait for more data to be transmitted or all pending data
* being acknowledged.
*/
break;
}
}
ret = tcp_send_data(conn); ret = tcp_send_data(conn);
if (ret < 0) { if (ret < 0) {
break; break;
@ -1301,6 +1345,7 @@ 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 = tcp_window;
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) &&
@ -2994,6 +3039,56 @@ uint16_t net_tcp_get_supported_mss(const struct tcp *conn)
return 0; return 0;
} }
int net_tcp_set_option(struct net_context *context,
enum tcp_conn_option option,
const void *value, size_t len)
{
int ret = 0;
NET_ASSERT(context);
struct tcp *conn = context->tcp;
NET_ASSERT(conn);
k_mutex_lock(&conn->lock, K_FOREVER);
switch (option) {
case TCP_OPT_NODELAY:
ret = set_tcp_nodelay(conn, value, len);
break;
}
k_mutex_unlock(&conn->lock);
return ret;
}
int net_tcp_get_option(struct net_context *context,
enum tcp_conn_option option,
void *value, size_t *len)
{
int ret = 0;
NET_ASSERT(context);
struct tcp *conn = context->tcp;
NET_ASSERT(conn);
k_mutex_lock(&conn->lock, K_FOREVER);
switch (option) {
case TCP_OPT_NODELAY:
ret = get_tcp_nodelay(conn, value, len);
break;
}
k_mutex_unlock(&conn->lock);
return ret;
}
const char *net_tcp_state_str(enum tcp_state state) const char *net_tcp_state_str(enum tcp_state state)
{ {
return tcp_state_to_str(state, false); return tcp_state_to_str(state, false);

View file

@ -30,6 +30,10 @@ extern "C" {
#include "tcp_private.h" #include "tcp_private.h"
enum tcp_conn_option {
TCP_OPT_NODELAY = 1,
};
/** /**
* @brief Calculates and returns the MSS for a given TCP context * @brief Calculates and returns the MSS for a given TCP context
* *
@ -367,6 +371,57 @@ void net_tcp_init(void);
#define net_tcp_init(...) #define net_tcp_init(...)
#endif #endif
/**
* @brief Set tcp specific options of a socket
*
* @param context Network context
*
* @return 0 on success, -EINVAL if the value is not allowed
*/
#if defined(CONFIG_NET_NATIVE_TCP)
int net_tcp_set_option(struct net_context *context,
enum tcp_conn_option option,
const void *value, size_t len);
#else
static inline int net_tcp_set_option(struct net_context *context,
enum tcp_conn_option option,
const void *value, size_t len)
{
ARG_UNUSED(context);
ARG_UNUSED(option);
ARG_UNUSED(value);
ARG_UNUSED(len);
return -EPROTONOSUPPORT;
}
#endif
/**
* @brief Obtain tcp specific options of a socket
*
* @param context Network context
*
* @return 0 on success
*/
#if defined(CONFIG_NET_NATIVE_TCP)
int net_tcp_get_option(struct net_context *context,
enum tcp_conn_option option,
void *value, size_t *len);
#else
static inline int net_tcp_get_option(struct net_context *context,
enum tcp_conn_option option,
void *value, size_t *len)
{
ARG_UNUSED(context);
ARG_UNUSED(option);
ARG_UNUSED(value);
ARG_UNUSED(len);
return -EPROTONOSUPPORT;
}
#endif
/** /**
* @brief Obtain a semaphore indicating if transfers are blocked (either due to * @brief Obtain a semaphore indicating if transfers are blocked (either due to
* filling TX window or entering retransmission mode). * filling TX window or entering retransmission mode).

View file

@ -269,6 +269,7 @@ struct tcp { /* TCP connection */
bool in_retransmission : 1; bool in_retransmission : 1;
bool in_connect : 1; bool in_connect : 1;
bool in_close : 1; bool in_close : 1;
bool tcp_nodelay : 1;
}; };
#define _flags(_fl, _op, _mask, _cond) \ #define _flags(_fl, _op, _mask, _cond) \

View file

@ -1852,6 +1852,12 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname,
} }
break; break;
} }
case IPPROTO_TCP:
switch (optname) {
case TCP_NODELAY:
ret = net_tcp_get_option(ctx, TCP_OPT_NODELAY, optval, optlen);
return ret;
}
} }
errno = ENOPROTOOPT; errno = ENOPROTOOPT;
@ -2099,10 +2105,9 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname,
case IPPROTO_TCP: case IPPROTO_TCP:
switch (optname) { switch (optname) {
case TCP_NODELAY: case TCP_NODELAY:
/* Ignore for now. Provided to let port ret = net_tcp_set_option(ctx,
* existing apps. TCP_OPT_NODELAY, optval, optlen);
*/ return ret;
return 0;
} }
break; break;