From 68a225baec31f384a01cef8c437a92b1ab88ea1a Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 16:09:25 +0200 Subject: [PATCH] Exercise RTT changes through real ETCP and document RTO inputs --- doc/etcp_protocol.txt | 7 ++++-- tests/test_etcp_bbr.c | 1 + tests/test_etcp_multipath.c | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index 99a93ef1..ebd20ff2 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -51,10 +51,13 @@ HELLO/CHALLENGE/CONFIRM и keepalive передаются своим незав после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть. Таймер попыток каждого линка (etcp_tx.c): - - RTO = clamp(ceil(1.2*data_rtt_us + 1.5*data_jitter_us + 3000), 10000, 1000000) мкс. + - RTO = clamp(ceil(1.2*RTT_for_RTO + 1.5*data_jitter_us + 3000), 10000, 1000000) мкс, + RTT_for_RTO=max(data_rtt_us,100*rtt_last): DATA EWMA и последний timestamp/keepalive RTT своего линка. Целочисленный расчёт использует uint64 и округляет вверх до целой микросекунды. RTT использует собственный DATA ACK минус receiver_delay; EWMA RTT=1/8, jitter=1/4 |RTT-EWMA|. - Первый RTT задаёт jitter=RTT/2. До DATA ACK используется RTT keepalive этого же пути, без него RTO=1с. + Первый DATA RTT задаёт jitter=RTT/2. Timestamp-RTT задаёт нижнюю границу срока, чтобы после роста задержки + попытки доживали до ACK и DATA-измерения продолжались; BBR RTT/min_rtt по этому сигналу не обновляются. + Без обоих измерений RTO=1с. - FIFO содержит физические попытки в порядке принятия транспортом. Таймер назначается по старейшей; при изменении RTO проверяется новый срок. Допускается оставить уже запланированную более раннюю проверку. uasync округляет задержку вверх до миллисекунды; callback может выполниться позже расчётного срока. diff --git a/tests/test_etcp_bbr.c b/tests/test_etcp_bbr.c index 030098ba..52685cc5 100644 --- a/tests/test_etcp_bbr.c +++ b/tests/test_etcp_bbr.c @@ -521,6 +521,7 @@ static int test_startup_delay_exit(void) { int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); + if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); test_byte_rate(); test_large_rate(); test_probe_growth_bytes(); diff --git a/tests/test_etcp_multipath.c b/tests/test_etcp_multipath.c index 85d9987a..d4dafa57 100644 --- a/tests/test_etcp_multipath.c +++ b/tests/test_etcp_multipath.c @@ -818,6 +818,46 @@ static void stalled(const struct profile* settings, unsigned count, unsigned bro teardown(); } +/* Смена базового RTT при небольшой нагрузке: DATA-модель оживает, min_rtt стареет и принимает снижение без ожидания. */ +static void changing_rtt(void) { + struct profile setting = {20,8000}; + low_load = 1; setup(&setting,1,1); + run_for(3000,1,"rtt-low"); begin_measure(); + struct ETCP_LINK* link = ep[0].conn->links; + CHECK(link && !link->next && link->bbr); + expect(link->bbr->min_rtt_us < 50000,"initial RTT minimum below 50ms",link->bbr->min_rtt_us,50000); + const unsigned rtts[] = {100,20}; + const char* phases[] = {"rtt-high","rtt-low-again"}; + for (unsigned n = 0; n < 2; n++) { + uint64_t old_acks = link->acked_packets; + for (int dir = 0; dir < 2; dir++) CHECK(dummynet_set_direction(channels[0],dir,rtts[n]/2,0,8000,512,0, + "127.0.0.1",ss_get_port(&ep[1-dir].sockets[0]->local_addr)) == 0); + profiles[0].rtt_ms = rtts[n]; + run_for(1000,1,phases[n]); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTT change target_ms=%u DATA_us=%u KA_us=%u jitter_us=%u RTO_us=%u min_us=%u " + "ACKs=%llu->%llu stalled=%u retries=%u", + rtts[n],link->data_rtt_us,link->rtt_last*100,link->data_jitter_us,etcp_tx_rto_us(link),link->bbr->min_rtt_us, + (unsigned long long)old_acks,(unsigned long long)link->acked_packets,link->tx_stalled,ep[0].conn->retransmissions_count); + expect(link->acked_packets > old_acks+5,"DATA ACK model progresses after RTT change",link->acked_packets,old_acks+6); + expect(!link->tx_stalled,"live path recovered after RTT change",link->tx_stalled,0); + if (!n) { + expect(link->data_rtt_us > 50000,"DATA RTT learns the longer path",link->data_rtt_us,50001); + run_for(11000,1,"rtt-high-aged"); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTT high aged: DATA_us=%u min_us=%u stamp_age_tb=%llu probe_us=%u ACKs=%llu", + link->data_rtt_us,link->bbr->min_rtt_us,(unsigned long long)(get_time_tb()-link->bbr->min_rtt_stamp_tb), + link->bbr->probe_rtt_min_us,(unsigned long long)link->acked_packets); + expect(link->bbr->min_rtt_us >= 100000,"old short RTT minimum expires on the longer path",link->bbr->min_rtt_us,100000); + expect(link->bbr->min_rtt_us < 130000,"new RTT minimum stays close to the 100ms path",link->bbr->min_rtt_us,130000); + } else { + expect(link->bbr->min_rtt_us < 50000,"lower RTT minimum learned within 1s",link->bbr->min_rtt_us,50000); + } + } + measuring = 0; run_for(10000,0,"drain"); + expect(ep[0].sent == ep[1].received,"complete ordered delivery after RTT changes",ep[1].received,ep[0].sent); + expect(!ep[0].conn->unacked_bytes,"no unacked DATA after RTT change drain",ep[0].conn->unacked_bytes,0); + teardown(); +} + int main(int argc, char** argv) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); test_clock_init(10); log_bbr_samples = getenv("UTUN_BBR_SAMPLES") != NULL; @@ -898,6 +938,10 @@ int main(int argc, char** argv) { stalled(settings, faults[n].count, faults[n].broken, faults[n].directions, faults[n].mode, faults[n].sparse, faults[n].ms); CHECK(u_get_allocated_count() == baseline); } + if (argc == 1 || !strcmp(argv[1],"rtt-change")) { + selected++; case_name = "rtt-change"; bidirectional = 0; + changing_rtt(); CHECK(u_get_allocated_count() == baseline); + } CHECK(selected && argc <= 2); if (csv) CHECK(fclose(csv) == 0); if (diagnostic_file) {