From b39a861a571976b52c24f4ffc335a912eb079f94 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 02:24:10 +0200 Subject: [PATCH] Initialize STARTUP pacing from the actual initial DATA window --- src/transport_layer/etcp_bbr.c | 23 ++++++++--------------- src/transport_layer/etcp_bbr.h | 1 + tests/test_etcp_bbr.c | 26 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/transport_layer/etcp_bbr.c b/src/transport_layer/etcp_bbr.c index 554bc761..0ef9d4ba 100644 --- a/src/transport_layer/etcp_bbr.c +++ b/src/transport_layer/etcp_bbr.c @@ -582,7 +582,6 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs (rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) { bbr->probe_rtt_min_us = rs->rtt_us; bbr->probe_rtt_min_stamp_tb = bbr_now(bbr); - bbr->has_seen_rtt = 1; } if (bbr->probe_rtt_min_us == 0) bbr->probe_rtt_min_us = ~0U; @@ -715,27 +714,17 @@ static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sa bbr->extra_acked[bbr->extra_acked_win_idx] = extra_acked; } -static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd, uint32_t* pacing_rate_out) -{ - uint64_t bw; - uint32_t rtt_us; - - if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) { - rtt_us = bbr->min_rtt_us; - if (rtt_us < 1) rtt_us = 1; - } else { - rtt_us = 1000; - } - bw = (uint64_t)cwnd * BW_UNIT / rtt_us; +static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t mss, uint32_t rtt_us, uint32_t* pacing_rate_out) { + uint64_t bw = (uint64_t)bbr->init_cwnd * mss * BW_UNIT / rtt_us; *pacing_rate_out = bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[L%u] initial pacing: window=%llu DATA_MSS=%u RTT_us=%u pacing_Bps=%u", + bbr->link_id, (unsigned long long)bbr->init_cwnd * mss, mss, rtt_us, *pacing_rate_out); } static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain, uint32_t* pacing_rate_out) { uint32_t rate = bbr_bw_to_pacing_rate(bw, gain); - if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) - bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, pacing_rate_out); if (bbr->full_bw_reached || rate > *pacing_rate_out) *pacing_rate_out = rate; } @@ -913,6 +902,10 @@ void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs, uint32_t sample_bw, bw; int update_model = 1; + if (!bbr->has_seen_rtt && rs->rtt_us) { + if (bbr->mode == BBR_STARTUP) bbr_init_pacing_rate_from_rtt(bbr, mss, rs->rtt_us, pacing_rate_out); + bbr->has_seen_rtt = 1; + } bbr->delivered += rs->acked_sacked; bbr_update_round_start(bbr, rs); diff --git a/src/transport_layer/etcp_bbr.h b/src/transport_layer/etcp_bbr.h index 20d66c54..15ada9f0 100644 --- a/src/transport_layer/etcp_bbr.h +++ b/src/transport_layer/etcp_bbr.h @@ -3,6 +3,7 @@ * Модуль не отправляет пакеты: возвращённые лимиты применяет линк/loadbalancer. * cwnd/inflight/delivered — байты DATA, interval/rtt — микросекунды, pacing_rate — байты в секунду. * MSS — максимальный DATA-фрагмент normalizer, без заголовков; не wire-MTU и не размер кадра STCP. + * Первый достоверный RTT в STARTUP инициализирует pacing от начальных 4*MSS, включая короткие служебные DATA. * Минимальное окно — 4*MSS; PROBE_RTT осушает до max(4*MSS, 0.5*BDP), затем ждёт >=200мс и раунд ACK. * Транспорт применяет окно с верхним ограничением конфига и не повышает его до собственного минимума. * Целевое окно PROBE_BW включает 2*BDP и ACK-резерв, в PROBE_UP дополнительно 2*MSS. diff --git a/tests/test_etcp_bbr.c b/tests/test_etcp_bbr.c index 9adf4fc3..9355447d 100644 --- a/tests/test_etcp_bbr.c +++ b/tests/test_etcp_bbr.c @@ -352,6 +352,31 @@ static int test_queue_budget_fast_path(void) { PASS(); return 0; } +/* Короткий служебный DATA не должен задавать стартовую скорость всего bulk-потока. */ +static int test_initial_pacing(void) { + MSEG("initial pacing uses four DATA MSS and the first valid RTT"); + const uint32_t sizes[] = {576, 1400}, rtts[] = {20000, 200000}; + for (unsigned i = 0; i < 2; i++) for (unsigned j = 0; j < 2; j++) { + struct bbr s = { .now_tb = 10000 }; bbr_init(&s); + uint32_t cwnd = 4 * sizes[i], pacing = 123; + struct bbr_rate_sample rs = { .acked_sacked = 16, .delivered = 16, .is_app_limited = 1 }; + bbr_main(&s, &rs, &cwnd, &pacing, sizes[i], 0, 0); + if (s.has_seen_rtt || pacing != 123) FAIL("unknown RTT must not seed pacing with a guessed duration"); + rs.rtt_us = rs.interval_us = rtts[j]; s.now_tb += rtts[j] / 100; + bbr_main(&s, &rs, &cwnd, &pacing, sizes[i], 0, 0); + uint32_t window_rate = (uint64_t)4 * sizes[i] * 1000000 / rtts[j]; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Initial pacing: DATA_MSS=%u RTT_us=%u initial_window_rate=%u pacing=%u", + sizes[i], rtts[j], window_rate, pacing); + if (!s.has_seen_rtt || pacing < 2 * window_rate || pacing > 3 * window_rate) + FAIL("first RTT must seed STARTUP from the initial window, not the 16-byte message"); + uint32_t first = pacing; + rs.rtt_us *= 2; s.now_tb += rs.rtt_us / 100; + bbr_main(&s, &rs, &cwnd, &pacing, sizes[i], 0, 0); + if (pacing != first) FAIL("later RTT samples must not restart pacing initialization"); + } + 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); @@ -373,6 +398,7 @@ int main(void) test_queue_budget(); test_queue_budget_floor_and_startup(); test_queue_budget_fast_path(); + test_initial_pacing(); printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed); return test_failed ? 1 : 0; }