@@ -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. */
23902702static 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