diff --git a/tests/test_etcp_bbr.c b/tests/test_etcp_bbr.c index 095943d2..c7766642 100644 --- a/tests/test_etcp_bbr.c +++ b/tests/test_etcp_bbr.c @@ -253,6 +253,33 @@ static int test_probe_rtt(void) PASS(); return 0; } +/* Длинный канал осушается до половины BDP, затем выдерживает 200мс и полный раунд доставки. */ +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.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; + uint32_t cwnd = 400000, pacing = 1000000; + struct bbr_rate_sample rs = { .delivered = 20000, .interval_us = 20000, .rtt_us = 200000, + .acked_sacked = 1000, .tx_in_flight = 400000 }; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 200001, 0); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PROBE_RTT drain: BDP=200000 cwnd=%u timer=%llu", cwnd, + (unsigned long long)s.probe_rtt_done_stamp_tb); + if (s.mode != BBR_PROBE_RTT || cwnd != 100000) FAIL("probe window must be half of 200000-byte BDP"); + if (s.probe_rtt_done_stamp_tb) FAIL("probe hold started before flight drained"); + s.now_tb++; rs.prior_delivered = s.delivered; rs.is_app_limited = 1; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0); + if (!s.probe_rtt_done_stamp_tb) FAIL("probe hold did not start at half-BDP"); + s.now_tb += BBR_PROBE_RTT_MS * 10 - 1; rs.prior_delivered = s.delivered; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0); + if (s.mode != BBR_PROBE_RTT || cwnd != 100000) FAIL("probe ended before 200ms"); + s.now_tb++; rs.prior_delivered = s.delivered; + bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0); + if (s.mode != BBR_PROBE_BW || cwnd < 400000) FAIL("probe did not restore the previous window after hold and round"); + 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); @@ -270,6 +297,7 @@ int main(void) test_loss_cut(); test_fast_path(); test_probe_rtt(); + test_probe_rtt_lifecycle(); printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed); return test_failed ? 1 : 0; } diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index 846f14bf..a8524666 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -18,6 +18,7 @@ static struct ETCP_CONN* conn; static struct ETCP_LINK *a, *b; static unsigned sends, fail_alloc, fail_send, fail_tx_timer, bbr_calls, saw_cwnd_limited, saw_app_limited; static struct bbr_rate_sample last_sample; +static uint32_t last_mss; void* __real_uasync_call_soon(struct UASYNC*, void*, timeout_callback_t); void* __wrap_uasync_call_soon(struct UASYNC* loop, void* arg, timeout_callback_t callback) { @@ -49,6 +50,7 @@ void __wrap_bbr_main(struct bbr* model, const struct bbr_rate_sample* sample, ui uint32_t* pacing, uint32_t mss, uint32_t inflight, int limited) { bbr_calls++; saw_cwnd_limited |= !!limited; saw_app_limited |= !!sample->is_app_limited; last_sample = *sample; + last_mss = mss; __real_bbr_main(model, sample, cwnd, pacing, mss, inflight, limited); } @@ -344,6 +346,44 @@ static void bbr_delivery_clock(void) { teardown(); } +/* Модель и транспорт используют один DATA MSS и одно окно, в том числе ниже 8KiB. */ +static void bbr_probe_window(void) { + setup(); b->link_status = 0; conn->rtt_avg_10 = 10000; + etcp_update_mtu(conn); + uint32_t minimum = BBR_CWND_MIN_TARGET * conn->normalizer->frag_size; + a->rtt_last = 100; + a->bbr = u_calloc(1, sizeof(*a->bbr)); CHECK(a->bbr); + a->bbr->now_tb = 10000; bbr_init(a->bbr); + a->bbr->mode = BBR_PROBE_BW; a->bbr->full_bw_reached = 1; a->bbr->cycle_idx = BBR_BW_PROBE_CRUISE; + a->bbr->min_rtt_us = a->bbr->probe_rtt_min_us = 10000; a->bbr->has_seen_rtt = 1; + a->bbr->bw_hi[0] = BW_UNIT / 10; a->bbr->now_tb += BBR_PROBE_RTT_WIN_MS * 10 + 1; + fill(a, 8, 1000); test_clock_run_for(ua, 200); ack(1); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PROBE_RTT transport: DATA_MSS=%u model_MSS=%u window=%u expected=%u", + conn->normalizer->frag_size, last_mss, a->inflight_lim_bytes, minimum); + CHECK(last_mss == conn->normalizer->frag_size && a->inflight_lim_bytes == minimum && minimum < INFLIGHT_LIM_MIN); + struct INFLIGHT_PACKET* probe = enqueue(1000); CHECK(!request()); + ack(2); ack(3); ack(4); CHECK(a->bbr->probe_rtt_done_stamp_tb); + struct ETCP_DGRAM* pkt = request(); CHECK(pkt && probe->is_app_limited); + memory_pool_free(inst.pkt_pool, pkt); + test_clock_run_for(ua, 10); a->bbr->now_tb += BBR_PROBE_RTT_MS * 10; + ack(probe->seq); CHECK(a->bbr->mode == BBR_PROBE_BW); + uint32_t window = a->inflight_lim_bytes; + etcp_link_update_inflight_lim(a, 0); CHECK(a->inflight_lim_bytes == window); + etcp_link_update_inflight_lim(a, UINT32_MAX); CHECK(a->inflight_lim_bytes == conn->max_inflight); + ack_all(); teardown(); +} + +/* Размер STCP-кадра 65535 не является размером DATA-фрагмента и не задаёт начальное окно. */ +static void tcp_initial_window(void) { + setup(); + struct ETCP_LINK* link = etcp_link_new(conn, NULL, NULL, 1); CHECK(link && link->is_tcp); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "STCP initial window: frame=%u DATA_MSS=%u window=%u", link->mtu, + conn->normalizer->frag_size, link->inflight_lim_bytes); + CHECK(link->inflight_lim_bytes == BBR_CWND_MIN_TARGET * conn->normalizer->frag_size); + CHECK(link->bbr->init_cwnd == BBR_CWND_MIN_TARGET); + teardown(); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); @@ -351,7 +391,7 @@ int main(void) { size_t baseline = u_get_allocated_count(); void (*cases[])(void) = {selection, boundaries_ack_burst, late_ack_and_migration, lost_ack_and_shrink, grow_and_session, allocation_and_send_failure, removed_link_and_bbr, pacing_reset_close, initial_readiness, - config_limits, timer_failure, immediate_send, ack_without_data_pacing, bbr_delivery_clock}; + config_limits, timer_failure, immediate_send, ack_without_data_pacing, bbr_delivery_clock, bbr_probe_window, tcp_initial_window}; for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { cases[i](); CHECK(u_get_allocated_count() == baseline); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS ETCP window scenario %u", i);