Skip to content

Commit 243b86f

Browse files
florincorasDave Barach
authored andcommitted
tcp: add cubic hystart++ support RFC 9406
Use HyStart++ in cubic's initial slow start to leave it before the queue overflows. An increase of the per-round minimum rtt moves the connection to Conservative Slow Start (CSS), which resumes slow start if the increase proves spurious and otherwise ends it after five rounds. RTT samples come from ac->rtt_time, and a round needs N_RTT_SAMPLE of them before a delay increase is checked. HyStart++ is enabled by default and configured in the cubic stanza with [no-]hystart and [no-]hystart-css. Without css, a delay increase ends slow start. To avoid growing cc TCP_CC_DATA_SZ, K is now stored as 2^-20s ticks. Type: feature Change-Id: Ief0251cd6de0e39ae02967495f3f93bd8773bb5a Signed-off-by: Florin Coras <fcoras@cisco.com>
1 parent 49e52c7 commit 243b86f

3 files changed

Lines changed: 591 additions & 41 deletions

File tree

‎src/plugins/unittest/tcp_test.c‎

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,6 +2385,318 @@ tcp_test_cwnd_limited_growth (void)
23852385
return 0;
23862386
}
23872387

2388+
static void
2389+
tcp_test_cubic_hystart_init (tcp_connection_t *tc, clib_thread_index_t thread_index, u32 snd_mss)
2390+
{
2391+
clib_memset (tc, 0, sizeof (*tc));
2392+
tc->c_thread_index = thread_index;
2393+
tc->snd_mss = snd_mss;
2394+
tc->snd_una = snd_mss;
2395+
tc->snd_nxt = 11 * snd_mss;
2396+
tc->tx_fifo_size = 1 << 30;
2397+
tc->mrtt_us = 0.01;
2398+
tc->srtt = 0.01 / TCP_TICK;
2399+
tc->cc_algo = tcp_cc_algo_get (TCP_CC_CUBIC);
2400+
tc->cc_algo->init (tc);
2401+
}
2402+
2403+
static void
2404+
tcp_test_cubic_hystart_ack (tcp_connection_t *tc, u32 ack, u32 snd_nxt, u32 bytes_acked,
2405+
f64 rtt_time)
2406+
{
2407+
tcp_ack_ctx_t ac = {
2408+
.bytes_acked = bytes_acked,
2409+
.acked_and_sacked = bytes_acked,
2410+
.rtt_time = rtt_time,
2411+
};
2412+
2413+
tc->snd_una = ack;
2414+
tc->snd_nxt = snd_nxt;
2415+
tc->cwnd_limited_seq = ack;
2416+
tc->cc_algo->rcv_ack (tc, &ac);
2417+
}
2418+
2419+
/* 10 ms baseline round, then eight 20 ms samples */
2420+
static u32
2421+
tcp_test_cubic_hystart_enter_css (tcp_connection_t *tc, u32 snd_mss)
2422+
{
2423+
u32 ack = tc->snd_una;
2424+
u32 first_round_end = tc->snd_nxt;
2425+
u32 next_round_end = first_round_end + 29 * snd_mss;
2426+
u32 i;
2427+
2428+
/* Baseline samples before the round boundary */
2429+
for (i = 0; i < 9; i++)
2430+
{
2431+
ack += snd_mss;
2432+
tcp_test_cubic_hystart_ack (tc, ack, first_round_end, snd_mss, 0.010);
2433+
}
2434+
2435+
/* Boundary ack is the new round's first sample */
2436+
ack += snd_mss;
2437+
tcp_test_cubic_hystart_ack (tc, ack, next_round_end, snd_mss, 0.020);
2438+
for (i = 1; i < 8; i++)
2439+
{
2440+
ack += snd_mss;
2441+
tcp_test_cubic_hystart_ack (tc, ack, next_round_end, snd_mss, 0.020);
2442+
}
2443+
2444+
return ack;
2445+
}
2446+
2447+
/* Drain and refill counts as one CSS round */
2448+
static int
2449+
tcp_test_cubic_hystart_drained_rounds (void)
2450+
{
2451+
const clib_thread_index_t thread_index = 0;
2452+
const u32 snd_mss = 1000;
2453+
const struct
2454+
{
2455+
u32 seq_offset;
2456+
u8 ack_each_segment;
2457+
const char *name;
2458+
} cases[] = {
2459+
{ 0, 1, "per-segment ACKs" },
2460+
{ 0, 0, "one cumulative ACK" },
2461+
{ ~0U - 29999, 1, "sequence wrap" },
2462+
};
2463+
tcp_connection_t _tc, *tc = &_tc;
2464+
u32 ack, round_end, bytes_acked, i, round;
2465+
2466+
tcp_test_set_time (thread_index, 1);
2467+
for (i = 0; i < ARRAY_LEN (cases); i++)
2468+
{
2469+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2470+
if (cases[i].seq_offset)
2471+
{
2472+
tc->snd_una += cases[i].seq_offset;
2473+
tc->snd_nxt += cases[i].seq_offset;
2474+
tc->cc_algo->init (tc);
2475+
}
2476+
tc->connection.flags |= TRANSPORT_CONNECTION_F_IS_TX_PACED;
2477+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2478+
round_end = tc->snd_nxt;
2479+
2480+
for (round = 1; round <= 5; round++)
2481+
{
2482+
while (seq_lt (ack, round_end))
2483+
{
2484+
bytes_acked = cases[i].ack_each_segment ? snd_mss : round_end - ack;
2485+
ack += bytes_acked;
2486+
tcp_test_cubic_hystart_ack (tc, ack, round_end, bytes_acked, 0.020);
2487+
}
2488+
if (round < 5)
2489+
{
2490+
TCP_TEST ((tcp_in_slowstart (tc)), "CSS remains after drained round %u (%s)", round,
2491+
cases[i].name);
2492+
round_end += (tc->cwnd / snd_mss) * snd_mss;
2493+
}
2494+
else
2495+
TCP_TEST ((tc->ssthresh == tc->cwnd), "CSS exits after five drained rounds (%s)",
2496+
cases[i].name);
2497+
}
2498+
}
2499+
2500+
return 0;
2501+
}
2502+
2503+
static void
2504+
tcp_test_cubic_config (char *cfg)
2505+
{
2506+
unformat_input_t input;
2507+
2508+
unformat_init_string (&input, cfg, strlen (cfg));
2509+
tcp_cc_algo_get (TCP_CC_CUBIC)->unformat_cfg (&input);
2510+
unformat_free (&input);
2511+
}
2512+
2513+
static int
2514+
tcp_test_cubic_hystart (void)
2515+
{
2516+
const clib_thread_index_t thread_index = 0;
2517+
const u32 snd_mss = 1000;
2518+
tcp_connection_t _tc, *tc = &_tc;
2519+
u32 ack, before, initial_cwnd, initial_ssthresh, i;
2520+
u64 pacing_rate;
2521+
2522+
tcp_test_set_time (thread_index, 1);
2523+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2524+
TCP_TEST ((tcp_cc_get_pacing_rate (tc) == (u64) (2.0 * tc->cwnd / 0.010)),
2525+
"HyStart++ standard slow start paces at 2x");
2526+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2527+
TCP_TEST ((tcp_cc_get_pacing_rate (tc) == (u64) (1.25 * tc->cwnd / 0.010)),
2528+
"CSS paces at 1.25x despite the high ssthresh");
2529+
initial_ssthresh = tc->ssthresh;
2530+
tc->ssthresh = tc->cwnd;
2531+
pacing_rate = tcp_cc_get_pacing_rate (tc);
2532+
tc->ssthresh = initial_ssthresh;
2533+
TCP_TEST ((pacing_rate == (u64) ((f64) tc->cwnd / 0.010)),
2534+
"CSS uses 1x pacing once cwnd reaches ssthresh");
2535+
2536+
/* CSS grows at a quarter of the slow start rate */
2537+
before = tc->cwnd;
2538+
ack += snd_mss;
2539+
tcp_test_cubic_hystart_ack (tc, ack, 40 * snd_mss, snd_mss, 0.020);
2540+
TCP_TEST ((tc->cwnd == before + snd_mss / 4), "HyStart++ enters conservative slow start");
2541+
2542+
/* Lower rtt in the next round resumes slow start */
2543+
ack = 40 * snd_mss;
2544+
tcp_test_cubic_hystart_ack (tc, ack, 80 * snd_mss, snd_mss, 0.010);
2545+
for (i = 1; i < 8; i++)
2546+
{
2547+
ack += snd_mss;
2548+
tcp_test_cubic_hystart_ack (tc, ack, 80 * snd_mss, snd_mss, 0.010);
2549+
}
2550+
before = tc->cwnd;
2551+
ack += snd_mss;
2552+
tcp_test_cubic_hystart_ack (tc, ack, 80 * snd_mss, snd_mss, 0.010);
2553+
TCP_TEST ((tc->cwnd == before + snd_mss), "HyStart++ resumes standard slow start");
2554+
TCP_TEST ((tcp_cc_get_pacing_rate (tc) == (u64) (2.0 * tc->cwnd / 0.010)),
2555+
"HyStart++ restores 2x pacing after a transient delay increase");
2556+
2557+
/* Five inflated CSS rounds exit to avoidance, cwnd is rounded */
2558+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2559+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2560+
tcp_test_cubic_hystart_ack (tc, ack + snd_mss, 40 * snd_mss, snd_mss, 0.020);
2561+
for (i = 0; i < 5; i++)
2562+
{
2563+
ack = (40 + i * 40) * snd_mss;
2564+
tcp_test_cubic_hystart_ack (tc, ack, ack + 40 * snd_mss, snd_mss, 0.020);
2565+
}
2566+
TCP_TEST ((tc->ssthresh == tc->cwnd), "HyStart++ exits CSS after five rounds");
2567+
TCP_TEST ((tc->cwnd % snd_mss == 0), "lossless HyStart++ exit rounds cwnd to segments");
2568+
TCP_TEST ((tcp_cc_get_pacing_rate (tc) == (u64) ((f64) tc->cwnd / 0.010)),
2569+
"HyStart++ exit restores 1x avoidance pacing");
2570+
2571+
/* Unpaced growth capped at 8 mss per ack */
2572+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2573+
before = tc->cwnd;
2574+
tcp_test_cubic_hystart_ack (tc, 2 * snd_mss, 40 * snd_mss, 16 * snd_mss, 0.010);
2575+
TCP_TEST ((tc->cwnd == before + 8 * snd_mss), "HyStart++ caps unpaced ACK growth");
2576+
2577+
/* Paced growth is not capped */
2578+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2579+
tc->connection.flags |= TRANSPORT_CONNECTION_F_IS_TX_PACED;
2580+
before = tc->cwnd;
2581+
tcp_test_cubic_hystart_ack (tc, 2 * snd_mss, 40 * snd_mss, 16 * snd_mss, 0.010);
2582+
TCP_TEST ((tc->cwnd == before + 16 * snd_mss), "HyStart++ preserves paced ACK growth");
2583+
2584+
/* FIFO limited acks do not grow cwnd but are sampled */
2585+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2586+
before = tc->cwnd;
2587+
tc->tx_fifo_size = before;
2588+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2589+
TCP_TEST ((tc->cwnd == before), "HyStart++ respects FIFO ceiling in standard slow start");
2590+
tcp_test_cubic_hystart_ack (tc, ack + snd_mss, 40 * snd_mss, snd_mss, 0.020);
2591+
TCP_TEST ((tc->cwnd == before), "HyStart++ respects FIFO ceiling in conservative slow start");
2592+
tc->tx_fifo_size *= 2;
2593+
tcp_test_cubic_hystart_ack (tc, ack + 2 * snd_mss, 40 * snd_mss, snd_mss, 0.020);
2594+
TCP_TEST ((tc->cwnd == before + snd_mss / 4), "FIFO-limited RTT samples still trigger CSS");
2595+
2596+
/* Undo restores HyStart++, only it caps unpaced growth */
2597+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2598+
initial_cwnd = tc->cwnd;
2599+
initial_ssthresh = tc->ssthresh;
2600+
tc->cc_algo->congestion (tc);
2601+
tc->cwnd = initial_cwnd;
2602+
tc->ssthresh = initial_ssthresh;
2603+
tc->cc_algo->undo_recovery (tc);
2604+
tcp_test_cubic_hystart_ack (tc, 2 * snd_mss, 40 * snd_mss, 16 * snd_mss, 0.010);
2605+
TCP_TEST ((tc->cwnd == initial_cwnd + 8 * snd_mss), "spurious loss restores HyStart++");
2606+
2607+
/* Undo restores CSS, which can still resume slow start */
2608+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2609+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2610+
initial_cwnd = tc->cwnd;
2611+
initial_ssthresh = tc->ssthresh;
2612+
tc->cc_algo->congestion (tc);
2613+
tc->cwnd = initial_cwnd;
2614+
tc->ssthresh = initial_ssthresh;
2615+
tc->cc_algo->undo_recovery (tc);
2616+
ack += snd_mss;
2617+
tcp_test_cubic_hystart_ack (tc, ack, 40 * snd_mss, snd_mss, 0.010);
2618+
TCP_TEST ((tc->cwnd == initial_cwnd + snd_mss / 4), "spurious loss in CSS restores CSS");
2619+
for (i = 1; i < 8; i++)
2620+
{
2621+
ack += snd_mss;
2622+
tcp_test_cubic_hystart_ack (tc, ack, 40 * snd_mss, snd_mss, 0.010);
2623+
}
2624+
before = tc->cwnd;
2625+
ack += snd_mss;
2626+
tcp_test_cubic_hystart_ack (tc, ack, 40 * snd_mss, snd_mss, 0.010);
2627+
TCP_TEST ((tc->cwnd == before + snd_mss), "restored CSS resumes slow start on lower RTT");
2628+
2629+
/* CSS rounds before undo still count */
2630+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2631+
tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2632+
for (i = 0; i < 3; i++)
2633+
{
2634+
ack = (40 + i * 40) * snd_mss;
2635+
tcp_test_cubic_hystart_ack (tc, ack, ack + 40 * snd_mss, snd_mss, 0.020);
2636+
}
2637+
initial_cwnd = tc->cwnd;
2638+
initial_ssthresh = tc->ssthresh;
2639+
tc->cc_algo->congestion (tc);
2640+
tc->cwnd = initial_cwnd;
2641+
tc->ssthresh = initial_ssthresh;
2642+
tc->cc_algo->undo_recovery (tc);
2643+
for (i = 3; i < 5; i++)
2644+
{
2645+
ack = (40 + i * 40) * snd_mss;
2646+
tcp_test_cubic_hystart_ack (tc, ack, ack + 40 * snd_mss, snd_mss, 0.020);
2647+
}
2648+
TCP_TEST ((tc->ssthresh == tc->cwnd), "CSS rounds before a spurious loss still count");
2649+
2650+
/* Under N_RTT_SAMPLE samples per round, e.g., no timestamps, no CSS */
2651+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2652+
tcp_test_cubic_hystart_ack (tc, 2 * snd_mss, 11 * snd_mss, snd_mss, 0.010);
2653+
tcp_test_cubic_hystart_ack (tc, 11 * snd_mss, 40 * snd_mss, snd_mss, 0.020);
2654+
before = tc->cwnd;
2655+
tcp_test_cubic_hystart_ack (tc, 12 * snd_mss, 40 * snd_mss, snd_mss, 0.020);
2656+
TCP_TEST ((tc->cwnd == before + snd_mss), "HyStart++ needs N_RTT_SAMPLE samples per round");
2657+
2658+
/* Without CSS, a delay increase exits slow start */
2659+
tcp_test_cubic_config ("no-hystart-css");
2660+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2661+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2662+
/* Restore before asserts can return */
2663+
tcp_test_cubic_config ("hystart-css");
2664+
TCP_TEST ((tc->ssthresh == tc->cwnd), "HyStart++ without CSS exits slow start");
2665+
before = tc->cwnd;
2666+
ack += snd_mss;
2667+
tcp_test_cubic_hystart_ack (tc, ack, 40 * snd_mss, snd_mss, 0.020);
2668+
TCP_TEST ((tc->cwnd < before + snd_mss / 4), "HyStart++ without CSS skips CSS growth");
2669+
2670+
/* Empty acks after a drain do not end CSS rounds */
2671+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2672+
ack = tcp_test_cubic_hystart_enter_css (tc, snd_mss);
2673+
tcp_test_cubic_hystart_ack (tc, 40 * snd_mss, 40 * snd_mss, 40 * snd_mss - ack, 0.020);
2674+
for (i = 0; i < 5; i++)
2675+
tcp_test_cubic_hystart_ack (tc, 40 * snd_mss, 40 * snd_mss, 0, 0);
2676+
TCP_TEST ((tcp_in_slowstart (tc)), "HyStart++ ignores empty ACKs for CSS rounds");
2677+
2678+
/* ssthresh exit rounds cwnd, Reno-friendly growth follows */
2679+
tcp_test_cubic_hystart_init (tc, thread_index, snd_mss);
2680+
tc->ssthresh = 28 * snd_mss;
2681+
tc->cwnd = tc->ssthresh + snd_mss / 2;
2682+
before = tc->ssthresh;
2683+
ack = tc->snd_una;
2684+
for (u32 rtt = 1; rtt <= 10; rtt++)
2685+
{
2686+
tcp_test_set_time (thread_index, 1 + rtt * 0.010);
2687+
for (i = 0; i < 28; i++)
2688+
{
2689+
ack += snd_mss;
2690+
tcp_test_cubic_hystart_ack (tc, ack, ack + tc->cwnd, snd_mss, 0.010);
2691+
}
2692+
}
2693+
TCP_TEST ((tc->cwnd % snd_mss == 0), "ssthresh exit rounds cwnd to segments");
2694+
TCP_TEST ((tc->cwnd > before), "ssthresh exit grows the Reno-friendly window");
2695+
tcp_test_set_time (thread_index, 1);
2696+
2697+
return 0;
2698+
}
2699+
23882700
/* RFC 9438 excludes continuously application-limited time from the CUBIC
23892701
* epoch even when the flight never drains and START_TX is not generated. */
23902702
static int
@@ -2672,6 +2984,12 @@ tcp_test_cubic (vlib_main_t *vm, unformat_input_t *input)
26722984
if ((rv = tcp_test_cwnd_limited_growth ()))
26732985
return rv;
26742986

2987+
if ((rv = tcp_test_cubic_hystart ()))
2988+
return rv;
2989+
2990+
if ((rv = tcp_test_cubic_hystart_drained_rounds ()))
2991+
return rv;
2992+
26752993
if ((rv = tcp_test_cubic_app_limited ()))
26762994
return rv;
26772995

@@ -6480,6 +6798,7 @@ tcp_test_delivery (vlib_main_t * vm, unformat_input_t * input)
64806798
TCP_TEST (ac->delivered == 2 * burst, "delivered should be 200 is %u", ac->delivered);
64816799
TCP_TEST (ac->prior_delivered == 3 * burst + 30, "sample delivered should be %u", 3 * burst + 30);
64826800
TCP_TEST (ac->flags & TCP_BTS_IS_RXT, "is retransmitted");
6801+
TCP_TEST (ac->rtt_time == 0, "BT does not export an ambiguous retransmit RTT");
64836802
/* Sample is app limited because of the retransmits */
64846803
TCP_TEST (ac->flags & TCP_BTS_IS_APP_LIMITED, "is app limited");
64856804
TCP_TEST (tc->app_limited, "app limited should be set");

0 commit comments

Comments
 (0)