Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion docs/xlio_config_reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# XLIO Configuration Reference

This file documents all 125 XLIO runtime configuration parameters with their types, defaults, environment variables, and constraints.
This file documents all 126 XLIO runtime configuration parameters with their types, defaults, environment variables, and constraints.

> **Auto-generated** from the JSON schema by `generate_docs.py`. Do not edit manually.

Expand Down Expand Up @@ -66,6 +66,7 @@ This file documents all 125 XLIO runtime configuration parameters with their typ
- [`network.neighbor.update_interval_msec`](#networkneighborupdate_interval_msec) — Neighbor update interval (msec)
- [`network.protocols.ip.mtu`](#networkprotocolsipmtu) — MTU size
- [`network.protocols.tcp.congestion_control`](#networkprotocolstcpcongestion_control) — TCP congestion control algorithm
- [`network.protocols.tcp.congestion_control_tso_aware`](#networkprotocolstcpcongestion_control_tso_aware) — TSO-aware congestion control
- [`network.protocols.tcp.linger_0`](#networkprotocolstcplinger_0) — Abort TCP connections on close
- [`network.protocols.tcp.mss`](#networkprotocolstcpmss) — Maximum Segment Size
- [`network.protocols.tcp.nodelay.byte_threshold`](#networkprotocolstcpnodelaybyte_threshold) — Data threshold for flush
Expand Down Expand Up @@ -1504,6 +1505,28 @@ Use:

**Default:** `"lwip" (0)`

### `network.protocols.tcp.congestion_control_tso_aware`

> **Type:** boolean
>
> **Maps to:** `XLIO_TCP_CC_TSO_AWARE`

Enables TSO-aware congestion-control sizing for TCP connections that use TCP Segmentation Offload (TSO).

**Behavior:**

- *true* (default): Uses larger startup and recovery congestion-control thresholds sized for TSO payloads, allowing high-bandwidth flows to ramp up faster.
- *false*: Uses conservative congestion-control thresholds.

**Tradeoffs:**

- *true*: Better throughput ramp-up for TSO-enabled flows; may send larger bursts during startup or recovery.
- *false*: More conservative behavior; useful when lower burstiness or stricter legacy behavior is preferred.

**Scope:** Only affects connections with TSO enabled.

**Default:** `true`

### `network.protocols.tcp.linger_0`

> **Type:** boolean
Expand Down
1 change: 1 addition & 0 deletions src/core/config/config_var_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ extern const config_var_info_t<bool> CONFIG_VAR_DISTRIBUTE_CQ;
#endif
extern const config_var_info_t<uint32_t, int64_t> CONFIG_VAR_MSS;
extern const config_var_info_t<uint32_t, int64_t> CONFIG_VAR_TCP_CC_ALGO;
extern const config_var_info_t<bool> CONFIG_VAR_TCP_CC_TSO_AWARE;
extern const config_var_info_t<uint32_t, int64_t> CONFIG_VAR_SPEC;

extern const config_var_info_t<option_3::mode_t, int64_t> CONFIG_VAR_TSO;
Expand Down
6 changes: 6 additions & 0 deletions src/core/config/descriptor_providers/xlio_config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,12 @@
"title": "TCP congestion control algorithm",
"description": "Maps to XLIO_TCP_CC_ALGO environment variable.\nTCP congestion control algorithm.\nThe default algorithm coming with LwIP is a variation of Reno.\nThe new Cubic algorithm was adapted from FreeBSD implementation.\n\nUse:\n - \"lwip\" or 0 for LwIP algorithm.\n - \"cubic\" or 1 for Cubic algorithm.\n - \"disable\" or 2 to disable the congestion algorithm."
},
"congestion_control_tso_aware": {
"type": "boolean",
"default": true,
"title": "TSO-aware congestion control",
"description": "Maps to XLIO_TCP_CC_TSO_AWARE environment variable.\nEnables TSO-aware congestion-control sizing for TCP connections that use TCP Segmentation Offload (TSO).\n\n**Behavior:**\n\n- *true* (default): Uses larger startup and recovery congestion-control thresholds sized for TSO payloads, allowing high-bandwidth flows to ramp up faster.\n- *false*: Uses conservative congestion-control thresholds.\n\n**Tradeoffs:**\n\n- *true*: Better throughput ramp-up for TSO-enabled flows; may send larger bursts during startup or recovery.\n- *false*: More conservative behavior; useful when lower burstiness or stricter legacy behavior is preferred.\n\n**Scope:** Only affects connections with TSO enabled."
},
"timestamps": {
"oneOf": [
{
Expand Down
1 change: 1 addition & 0 deletions src/core/config/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"network.neighbor.update_interval_msec": "XLIO_NETLINK_TIMER",
"network.protocols.ip.mtu": "XLIO_MTU",
"network.protocols.tcp.congestion_control": "XLIO_TCP_CC_ALGO",
"network.protocols.tcp.congestion_control_tso_aware": "XLIO_TCP_CC_TSO_AWARE",
"network.protocols.tcp.linger_0": "XLIO_TCP_ABORT_ON_CLOSE",
"network.protocols.tcp.mss": "XLIO_MSS",
"network.protocols.tcp.nodelay.byte_threshold": "XLIO_TCP_NODELAY_TRESHOLD",
Expand Down
45 changes: 19 additions & 26 deletions src/core/lwip/cc_cubic.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,19 @@ static void cubic_ack_received(struct tcp_pcb *pcb, uint16_t type)
/* Use the logic in NewReno ack_received() for slow start. */
if (pcb->cwnd <= pcb->ssthresh /*||
cubic_data->min_rtt_ticks == 0*/) {
pcb->cwnd += pcb->mss;
/* Slow start: Increment cwnd by the number of bytes acknowledged.
* RFC 5681 / RFC 9438: Standard TCP slow start with exponential growth.
* Modern CUBIC (Linux 2024, RFC 9438) uses the same slow start as standard TCP.
*
* Note: This XLIO implementation is based on FreeBSD CUBIC (2007-2010), which
* originally had a bug (cwnd += mss). Fixed to match modern behavior (cwnd += acked).
*
* Critical for TSO where one ACK can acknowledge many segments (e.g., 64KB = 44
* segments).
*/
if ((u32_t)(pcb->cwnd + pcb->acked) > pcb->cwnd) {
pcb->cwnd += pcb->acked;
}
} else if (cubic_data->min_rtt_ticks > 0) {
ticks_since_cong = ticks - cubic_data->t_last_cong;

Expand Down Expand Up @@ -212,24 +224,8 @@ static void cubic_cong_signal(struct tcp_pcb *pcb, uint32_t type)
break;

case CC_RTO:
/* Set ssthresh to half of the minimum of the current
* cwnd and the advertised window */
if (pcb->cwnd > pcb->snd_wnd) {
pcb->ssthresh = pcb->snd_wnd / 2;
} else {
pcb->ssthresh = pcb->cwnd / 2;
}

/* The minimum value for ssthresh should be 2 MSS */
if ((u32_t)pcb->ssthresh < (u32_t)2 * pcb->mss) {
LWIP_DEBUGF(TCP_FR_DEBUG,
("tcp_receive: The minimum value for ssthresh %" U16_F
" should be min 2 mss %" U16_F "...\n",
pcb->ssthresh, 2 * pcb->mss));
pcb->ssthresh = 2 * pcb->mss;
}

pcb->cwnd = pcb->mss;
/* Use centralized TSO-aware congestion recovery logic */
tcp_reset_cwnd_on_congestion(pcb, true);

/*
* Grab the current time and record it so we know when the
Expand All @@ -251,13 +247,10 @@ static void cubic_conn_init(struct tcp_pcb *pcb)
{
struct cubic *cubic_data = pcb->cc_data;

pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
pcb->ssthresh = pcb->mss * 3;
/*
* Ensure we have a sane initial value for max_cwnd recorded. Without
* this here bad things happen when entries from the TCP hostcache
* get used.
*/
if (pcb->cwnd == 1) {
tcp_set_initial_cwnd_ssthresh(pcb);
}

cubic_data->max_cwnd = pcb->cwnd;
}

Expand Down
47 changes: 22 additions & 25 deletions src/core/lwip/cc_lwip.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,19 @@ static void lwip_ack_received(struct tcp_pcb *pcb, uint16_t type)
}
} else if (type == CC_ACK) {
if (pcb->cwnd < pcb->ssthresh) {
if ((u32_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
/* Slow start: Increment cwnd by the number of bytes acknowledged.
* RFC 5681: "During slow start, a TCP increments cwnd by at most SMSS
* bytes for each ACK received that cumulatively acknowledges new data."
* This means cwnd grows by N*MSS when N segments are ACKed, giving
* exponential growth (e.g., cwnd doubles per RTT if all segments are ACKed).
*
* Fixed from incorrect linear growth (cwnd += mss) to proper exponential
* growth (cwnd += acked). This matches modern TCP implementations including
* Linux CUBIC and is critical for TSO where one ACK can acknowledge many
* segments (e.g., 64KB = 44 segments at 1460 MSS).
*/
if ((u32_t)(pcb->cwnd + pcb->acked) > pcb->cwnd) {
pcb->cwnd += pcb->acked;
}
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %" U32_F "\n", pcb->cwnd));
} else {
Expand All @@ -99,28 +110,9 @@ static void lwip_ack_received(struct tcp_pcb *pcb, uint16_t type)

static void lwip_cong_signal(struct tcp_pcb *pcb, uint32_t type)
{
/* Set ssthresh to half of the minimum of the current
* cwnd and the advertised window */
if (pcb->cwnd > pcb->snd_wnd) {
pcb->ssthresh = pcb->snd_wnd / 2;
} else {
pcb->ssthresh = pcb->cwnd / 2;
}

/* The minimum value for ssthresh should be 2 MSS */
if ((u32_t)pcb->ssthresh < (u32_t)2 * pcb->mss) {
LWIP_DEBUGF(TCP_FR_DEBUG,
("tcp_receive: The minimum value for ssthresh %" U16_F
" should be min 2 mss %" U16_F "...\n",
pcb->ssthresh, 2 * pcb->mss));
pcb->ssthresh = 2 * pcb->mss;
}

if (type == CC_NDUPACK) {
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
} else if (type == CC_RTO) {
pcb->cwnd = pcb->mss;
}
/* Use centralized TSO-aware congestion recovery logic */
bool is_rto = (type == CC_RTO);
tcp_reset_cwnd_on_congestion(pcb, is_rto);
}

static void lwip_post_recovery(struct tcp_pcb *pcb)
Expand All @@ -130,7 +122,12 @@ static void lwip_post_recovery(struct tcp_pcb *pcb)

static void lwip_conn_init(struct tcp_pcb *pcb)
{
pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
/* Only set cwnd if it's still uninitialized (placeholder value of 1).
* Otherwise, preserve the value set by tcp_set_initial_cwnd_ssthresh().
*/
if (pcb->cwnd == 1) {
tcp_set_initial_cwnd_ssthresh(pcb);
}
}

#endif // TCP_CC_ALGO_MOD
97 changes: 93 additions & 4 deletions src/core/lwip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ enum cc_algo_mod lwip_cc_algo_module = CC_MOD_LWIP;
u16_t lwip_tcp_mss = CONST_TCP_MSS;
u8_t enable_push_flag = 1;
u8_t enable_ts_option = 0;
u8_t tcp_cc_tso_aware = 1;
u32_t lwip_tcp_nodelay_treshold = 0;

/* slow timer value */
Expand Down Expand Up @@ -510,6 +511,86 @@ bool tcp_recved(struct tcp_pcb *pcb, u32_t len, bool do_output)
return false;
}

/**
* Set initial congestion window and slow start threshold.
*
* With TSO and tcp_cc_tso_aware enabled:
* cwnd = 0.25 * TSO_max (64KB), ssthresh = 2GB
* Otherwise:
* cwnd = RFC 3390, ssthresh = 10*MSS
*
* @param pcb the tcp_pcb for which to set initial window parameters
*/
void tcp_set_initial_cwnd_ssthresh(struct tcp_pcb *pcb)
{
if (tcp_tso(pcb) && tcp_cc_tso_aware) {
/* TSO-aware: cwnd = 25% of TSO max (default 64KB) */
pcb->cwnd = pcb->tso.max_payload_sz / 4;
Comment thread
tomerdbz marked this conversation as resolved.

/* High ssthresh allows slow start to discover optimal window */
pcb->ssthresh = 0x7FFFFFFF; /* 2GB - effectively unlimited */
Comment thread
tomerdbz marked this conversation as resolved.
} else {
/* RFC 3390: IW = min(4*MSS, max(2*MSS, 4380 bytes)) */
if (pcb->mss * 4 <= 4380) {
pcb->cwnd = pcb->mss * 4;
} else {
pcb->cwnd = (pcb->mss * 2 > 4380) ? pcb->mss * 2 : 4380;
}

/* Set ssthresh higher than IW to allow slow start growth */
pcb->ssthresh = pcb->mss * 10;
}
}
Comment on lines +524 to +543

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Server-side (SYN_RCVD) connections do not receive TSO-aware cwnd/ssthresh

In tcp_listen_input() (tcp_in.c:367), immediately after the new PCB is cloned, the code unconditionally sets:

npcb->ssthresh = npcb->snd_wnd;

This overwrites the ssthresh value previously set by tcp_pcb_init → tcp_set_initial_cwnd_ssthresh. Additionally, at clone time npcb->tso.max_payload_sz is 0 (TSO not yet bound to a ring), so tcp_set_initial_cwnd_ssthresh takes the non-TSO branch and sets cwnd based on the default MSS.

When the SYN_RCVD → ESTABLISHED transition fires (ACK received), there is no subsequent call to tcp_set_initial_cwnd_ssthresh — contrast this with the client SYN_SENT path which gets the call at tcp_in.c:586. Server-side connections acting as senders (e.g., data servers, bidirectional flows) will therefore retain conservative, non-TSO cwnd/ssthresh values throughout the connection even when TSO is later enabled via prepare_dst_to_send, reproducing the same slow-ramp-up issue the PR targets — but now only on the passive-open side.


/**
* Reset cwnd and ssthresh after a congestion event (RTO or fast retransmit).
*
* With TSO and tcp_cc_tso_aware enabled (deviates from RFC 5681):
* cwnd = 10% of TSO_max (26KB), ssthresh = max(FlightSize/2, 64KB)
* Otherwise:
* cwnd = 1*MSS, ssthresh = max(FlightSize/2, 2*MSS) per RFC 5681
*
* @param pcb the tcp_pcb experiencing congestion
* @param is_rto true if this is an RTO event, false for fast retransmit
*/
void tcp_reset_cwnd_on_congestion(struct tcp_pcb *pcb, bool is_rto)
{
if (tcp_tso(pcb) && tcp_cc_tso_aware) {
/* TSO-aware recovery: more aggressive than RFC 5681 (1 MSS)
* to better handle large TSO segments (256KB super-packets).
*/
u32_t tso_recovery_cwnd = pcb->tso.max_payload_sz / 10; // 26KB
u32_t tso_recovery_ssthresh = pcb->tso.max_payload_sz / 4; // 64KB
Comment thread
tomerdbz marked this conversation as resolved.

u32_t eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
u32_t halved_wnd = eff_wnd >> 1;

/* ssthresh = max(halved window, TSO recovery threshold) */
pcb->ssthresh = LWIP_MAX(halved_wnd, tso_recovery_ssthresh);

if (is_rto) {
pcb->cwnd = tso_recovery_cwnd;
} else {
/* Fast retransmit: add 3*MSS for quick recovery */
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
}
} else {
/* RFC 5681 standard behavior */
u32_t eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
pcb->ssthresh = eff_wnd >> 1;

if (pcb->ssthresh < (u32_t)(2 * pcb->mss)) {
pcb->ssthresh = 2 * pcb->mss;
}

if (is_rto) {
pcb->cwnd = pcb->mss;
} else {
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
}
}
}

/**
* Connects to another host. The function given as the "connected"
* argument will be called when the connection has been established.
Expand Down Expand Up @@ -559,8 +640,10 @@ err_t tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, bool

pcb->advtsd_mss = tcp_send_mss(pcb);
pcb->mss = pcb->advtsd_mss;
pcb->cwnd = 1;
pcb->ssthresh = pcb->mss * 10;

/* Set initial congestion window and slow start threshold */
tcp_set_initial_cwnd_ssthresh(pcb);

pcb->connected = connected;

/* Send a SYN together with the MSS option. */
Expand Down Expand Up @@ -952,7 +1035,10 @@ void tcp_pcb_init(struct tcp_pcb *pcb, u8_t prio, void *container)
}
cc_init(pcb);
#endif
pcb->cwnd = 1;

/* Set initial congestion window and slow start threshold */
tcp_set_initial_cwnd_ssthresh(pcb);

iss = tcp_next_iss();
pcb->snd_wl2 = iss;
pcb->snd_nxt = iss;
Expand Down Expand Up @@ -1000,7 +1086,10 @@ void tcp_pcb_recycle(struct tcp_pcb *pcb)
#if TCP_CC_ALGO_MOD
cc_init(pcb);
#endif
pcb->cwnd = 1;

/* Set initial congestion window and slow start threshold */
tcp_set_initial_cwnd_ssthresh(pcb);

iss = tcp_next_iss();
pcb->acked = 0;
pcb->snd_wl2 = iss;
Expand Down
6 changes: 6 additions & 0 deletions src/core/lwip/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ void register_ip_route_mtu(ip_route_mtu_fn fn);
/*Initialization of tcp_pcb structure*/
void tcp_pcb_init(struct tcp_pcb *pcb, u8_t prio, void *container);
void tcp_pcb_recycle(struct tcp_pcb *pcb);
void tcp_set_initial_cwnd_ssthresh(struct tcp_pcb *pcb);
void tcp_reset_cwnd_on_congestion(struct tcp_pcb *pcb, bool is_rto);

void tcp_arg(struct tcp_pcb *pcb, void *arg);
void tcp_ip_output(struct tcp_pcb *pcb, ip_output_fn ip_output);
Expand All @@ -421,6 +423,10 @@ void tcp_err(struct tcp_pcb *pcb, tcp_err_fn err);
#define tcp_nagle_enable(pcb) ((pcb)->flags &= ~TF_NODELAY)
#define tcp_nagle_disabled(pcb) (((pcb)->flags & TF_NODELAY) != 0)

/* Returns pcb->tso.max_payload_sz. Acts as both:
* - Boolean check: 0 (false) when TSO not configured
* - Value provider: max payload size when TSO configured
* Safe to use in conditionals followed by division. */
#define tcp_tso(pcb) ((pcb)->tso.max_payload_sz)

bool tcp_recved(struct tcp_pcb *pcb, u32_t len, bool do_output);
Expand Down
1 change: 1 addition & 0 deletions src/core/lwip/tcp_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ extern int32_t enable_wnd_scale;
extern u32_t rcv_wnd_scale;
extern u8_t enable_push_flag;
extern u8_t enable_ts_option;
extern u8_t tcp_cc_tso_aware;
extern u32_t tcp_ticks;
extern ip_route_mtu_fn external_ip_route_mtu;

Expand Down
12 changes: 5 additions & 7 deletions src/core/lwip/tcp_in.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,14 +579,12 @@ static err_t tcp_process(struct tcp_pcb *pcb, tcp_in_data *in_data)
pcb->mss = LWIP_MIN(pcb->mss, tcp_send_mss(pcb));
#endif /* TCP_CALCULATE_EFF_SEND_MSS */

/* Set ssthresh again after changing pcb->mss (already set in tcp_connect
* but for the default value of pcb->mss) */
pcb->ssthresh = pcb->mss * 10;
#if TCP_CC_ALGO_MOD
/* Re-initialize cwnd/ssthresh after MSS negotiation.
* For TSO: maintain aggressive values independent of negotiated MSS.
* For non-TSO: scale with negotiated MSS per RFC 3390.
*/
tcp_set_initial_cwnd_ssthresh(pcb);
cc_conn_init(pcb);
#else
pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
#endif
rseg = pcb->unacked;
pcb->unacked = rseg->next;

Expand Down
3 changes: 3 additions & 0 deletions src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,9 @@ void print_env_vars_xlio_global_settings()
VLOG_PARAM_NUMSTR("TCP CC Algorithm", safe_mce_sys().lwip_cc_algo_mod,
MCE_DEFAULT_LWIP_CC_ALGO_MOD, SYS_VAR_TCP_CC_ALGO,
lwip_cc_algo_str(safe_mce_sys().lwip_cc_algo_mod));
VLOG_PARAM_STRING("TCP CC TSO aware", safe_mce_sys().tcp_cc_tso_aware,
MCE_DEFAULT_TCP_CC_TSO_AWARE, SYS_VAR_TCP_CC_TSO_AWARE,
safe_mce_sys().tcp_cc_tso_aware ? "true" : "false");
VLOG_PARAM_STRING("Deferred close", safe_mce_sys().deferred_close, MCE_DEFAULT_DEFERRED_CLOSE,
SYS_VAR_DEFERRED_CLOSE,
safe_mce_sys().deferred_close ? "Enabled " : "Disabled");
Expand Down
Loading