diff --git a/src/transport_layer/etcp_bbr.h b/src/transport_layer/etcp_bbr.h index a4687d13..50818c56 100644 --- a/src/transport_layer/etcp_bbr.h +++ b/src/transport_layer/etcp_bbr.h @@ -23,6 +23,7 @@ extern "C" { #define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2) #define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885) #define BBR_CWND_GAIN (BBR_UNIT * 2) +#define BBR_QUEUE_BUDGET_US 20000 // целевая добавочная очередь после STARTUP; минимум 4*MSS сохраняется #define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2) #define BBR_CWND_MIN_TARGET 4 #define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4) @@ -68,6 +69,8 @@ struct bbr { uint32_t inflight_hi; uint32_t bw_latest; uint32_t inflight_latest; + uint32_t queue_budget_us; // целевое время очереди в PROBE_BW, мкс; init задаёт 20мс, 0 отключает ограничение + uint32_t queue_cwnd_cap; // последний предел DATA: max(4*MSS, BW*(min_rtt+budget)); 0 — ограничение не активно uint8_t mode; uint8_t cycle_idx; uint16_t pacing_gain; diff --git a/tests/test_etcp_bbr.c b/tests/test_etcp_bbr.c index 4b2ff247..0c9c3f24 100644 --- a/tests/test_etcp_bbr.c +++ b/tests/test_etcp_bbr.c @@ -50,7 +50,7 @@ static int test_byte_rate(void) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BBR byte sample: MSS=%u delivered=10000 interval_us=10000 pace=%u cwnd=%u", sizes[i], pacing, cwnd); if (pacing != 990000) FAIL("1MB/s delivery must produce 990000B/s cruise pacing"); - if (cwnd < 40000 || cwnd > 41000) FAIL("2*BDP plus ACK aggregation must be 40000..41000 bytes"); + if (cwnd != 40000) FAIL("20ms queue budget at 20ms RTT must cap the window at 40000 bytes"); } PASS(); return 0; } @@ -97,6 +97,7 @@ static int test_probe_growth_bytes(void) { static int test_probe_packet_budget(void) { MSEG("PROBE_UP reserves two MSS; Reno rounds use packets"); struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.queue_budget_us = 0; // здесь проверяем отдельный резерв PROBE_UP без ограничения по времени очереди s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP; uint32_t cwnd = 100000, pacing = 0; struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = 10000, .rtt_us = 20000, @@ -116,6 +117,7 @@ static int test_probe_packet_budget(void) { static int test_large_ack_aggregation(void) { MSEG("ACK aggregation above 64KiB keeps byte precision"); struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.queue_budget_us = 0; // размер ACK-резерва проверяется отдельно от политики времени очереди s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE; s.cycle_mstamp_tb = s.now_tb; s.probe_wait_us = 2000000; uint32_t cwnd = 300000, pacing = 0; @@ -205,6 +207,7 @@ static int test_cwnd_bounded(void) const uint32_t caps[] = {4000, 8000}, expected[] = {5600, 8000}; for (unsigned i = 0; i < 2; i++) { struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.queue_budget_us = 0; s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP; s.inflight_hi = s.inflight_lo = caps[i]; g_cwnd = 14000; g_pacing = 0; s.now_tb += 5000; run_ack(&s, 1400, 5000, 0, 0, 14000); @@ -261,6 +264,7 @@ static int test_probe_rtt(void) static int test_probe_rtt_lifecycle(void) { MSEG("PROBE_RTT half-BDP drain, hold and window restore"); struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.queue_budget_us = 0; // восстановление исходного окна проверяем без последующего ограничения бюджета s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE; s.bw_hi[0] = BW_UNIT; s.min_rtt_us = s.probe_rtt_min_us = 200000; s.has_seen_rtt = 1; s.now_tb += BBR_PROBE_RTT_WIN_MS * 10 + 1; @@ -284,6 +288,45 @@ static int test_probe_rtt_lifecycle(void) { PASS(); return 0; } +/* Бюджет включает все резервы окна и не зависит от удлинения текущего RTT очередью. */ +static int test_queue_budget(void) { + MSEG("queue time budget includes ACK and probing reserves"); + const uint32_t rtts[] = {20000, 200000, 20000, 20000}; + const uint32_t budgets[] = {20000, 20000, 5000, 0}; + const uint32_t expected[] = {40000, 220000, 25000, 43800}; + for (unsigned i = 0; i < 4; i++) { + struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.queue_budget_us = budgets[i]; s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; + s.cycle_idx = BBR_BW_PROBE_UP; s.min_rtt_us = s.probe_rtt_min_us = rtts[i]; s.has_seen_rtt = 1; + s.bw_hi[0] = BW_UNIT; + uint32_t cwnd = 1000000, pacing = 0; + struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = 10000, .rtt_us = rtts[i] * 2, + .acked_sacked = 1000, .tx_in_flight = 1000000 }; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Queue budget: min_rtt=%u budget=%u cwnd=%u expected=%u", + rtts[i], budgets[i], cwnd, expected[i]); + if (cwnd != expected[i]) FAIL("queue budget must bound the entire window using min_rtt"); + } + PASS(); return 0; +} + +static int test_queue_budget_floor_and_startup(void) { + MSEG("queue budget preserves minimum window and STARTUP"); + struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP; + uint32_t cwnd = 1000000, pacing = 0; + struct bbr_rate_sample rs = { .delivered = 100, .interval_us = 100000, .rtt_us = 20000, + .acked_sacked = 100, .tx_in_flight = 1000000 }; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0); + if (cwnd != 5600) FAIL("minimum four-MSS window must survive a tiny estimated BDP"); + memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s); + cwnd = 5600; pacing = 0; rs.delivered = rs.acked_sacked = 5600; + rs.interval_us = rs.rtt_us = 200000; rs.tx_in_flight = 5600; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 5600, 1); + if (s.mode != BBR_STARTUP || cwnd != 11200) FAIL("queue budget must not prevent STARTUP window growth"); + PASS(); return 0; +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); @@ -302,6 +345,8 @@ int main(void) test_fast_path(); test_probe_rtt(); test_probe_rtt_lifecycle(); + test_queue_budget(); + test_queue_budget_floor_and_startup(); printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed); return test_failed ? 1 : 0; }