From c3ac337ef05eecf3b3f18b97ffb69ea916c0baf3 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 02:20:10 +0200 Subject: [PATCH] Clamp ETCP retransmission timeout to 10 ms through 1 second --- src/transport_layer/etcp.c | 14 +++++++++----- src/transport_layer/etcp.h | 6 +++--- tests/test_etcp_windows.c | 30 ++++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/transport_layer/etcp.c b/src/transport_layer/etcp.c index 9d80fc62..b558047b 100644 --- a/src/transport_layer/etcp.c +++ b/src/transport_layer/etcp.c @@ -31,6 +31,8 @@ #define MAX_INFLIGHT_BYTES 65536 // Initial window #define RETRANS_K1 32 // RTT multiplier for retrans timeout #define RETRANS_K2 32 // Jitter multiplier +#define RETRANS_MIN_TB 100 // Минимальный RTO 10мс, в единицах 0.1мс +#define RETRANS_MAX_TB 10000 // Максимальный RTO 1с, в единицах 0.1мс #define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) #define BURST_DELAY_FACTOR 4 // Delay before burst #define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) @@ -1091,12 +1093,14 @@ static void input_queue_cb(struct ll_queue* q, void* arg) { static void ack_timeout_cb(void* arg); static void ack_timeout_check(struct ETCP_CONN* etcp) { uint64_t now = get_time_tb(); - int32_t timeout = (etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2) / 16; - if (timeout<50) timeout=50; - if (timeout>10000) timeout=10000; + int32_t raw_timeout = (etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2) / 16; + int32_t timeout = raw_timeout; + if (timeout < RETRANS_MIN_TB) timeout = RETRANS_MIN_TB; + if (timeout > RETRANS_MAX_TB) timeout = RETRANS_MAX_TB; - DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "starting check, now=%llu, timeout=%llu, rtt_avg_10=%u, jitter=%u", - (unsigned long long)now, (unsigned long long)timeout, etcp->rtt_avg_10, etcp->jitter); + DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] RTO: raw_tb=%d timeout_tb=%d bounds_tb=%d..%d RTT_tb=%u jitter_tb=%u now_tb=%llu", + etcp->log_name, raw_timeout, timeout, RETRANS_MIN_TB, RETRANS_MAX_TB, + etcp->rtt_avg_10, etcp->jitter, (unsigned long long)now); struct ll_entry* current; diff --git a/src/transport_layer/etcp.h b/src/transport_layer/etcp.h index 40047851..69045222 100644 --- a/src/transport_layer/etcp.h +++ b/src/transport_layer/etcp.h @@ -173,8 +173,8 @@ struct ETCP_CONN { uint16_t rtt_last;// round-trip time uint16_t tt_last;// transmission time (время пакета до меня) // uint16_t rxt_last;// recv relative time (бустро регаирует на изменение задержки rx и медленно на изменение задержки tx) - uint16_t rtt_avg_10; - uint16_t jitter; + uint16_t rtt_avg_10; // Максимальный RTT среди links, в 0.1мс; имя историческое + uint16_t jitter; // Среднее по links от link->jitter/32768, в 0.1мс (удвоенный EWMA |ΔRTT|) uint64_t last_rtt_cb_time; // время последнего вызова ping_update_rtt (0.1ms) uint32_t bytes_sent_total; // uint32_t bytes_received_total; // Not used @@ -190,7 +190,7 @@ struct ETCP_CONN { uint32_t optimal_inflight; // Sum over links // Timers - void* retrans_timer; // Retrans check timer + void* retrans_timer; // Общий RTO: 2*(rtt_avg_10+jitter), ограничен 10мс..1с; callback проверяет FIFO void* ack_resp_timer; // ACK send timer void* down_timer; // удаление после непрерывного DOWN, включая первоначальное ожидание uint64_t down_deadline_tb; // сохраняется при наличии NCD handles после истечения таймера diff --git a/tests/test_etcp_windows.c b/tests/test_etcp_windows.c index c6c4b033..53f3c771 100644 --- a/tests/test_etcp_windows.c +++ b/tests/test_etcp_windows.c @@ -183,7 +183,7 @@ static void boundaries_ack_burst(void) { static void late_ack_and_migration(void) { setup(); fill(a, 8, 1024); enqueue(600); a->link_status = b->link_status = 0; - test_clock_run_for(ua, 70); + test_clock_run_for(ua, 120); check_accounting(); CHECK(!conn->input_wait_ack->count && !a->inflight_bytes); ack(1); ack(1); CHECK(!a->inflight_bytes); b->link_status = 1; etcp_link_update_inflight_lim(b, 8192); @@ -255,6 +255,31 @@ static void shaper_ready(void* arg) { loadbalancer_link_ready(link); } +/* Реальный RTO: нижняя/верхняя границы и влияние RTT/jitter, без подмены обработчика таймера. */ +static void retransmission_timeout(void) { + const struct { uint16_t rtt, jitter; uint32_t expected_tb; } cases[] = { + {0, 0, 100}, {20, 10, 100}, {200, 0, 400}, {200, 75, 550}, + {2000, 100, 4200}, {6000, 1000, 10000}, {UINT16_MAX, UINT16_MAX, 10000} + }; + for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + setup(); b->link_status = 0; + conn->rtt_avg_10 = cases[i].rtt; conn->jitter = cases[i].jitter; + fill(a, 1, 100); + struct INFLIGHT_PACKET* packet = (struct INFLIGHT_PACKET*)conn->input_wait_ack->head; + uint64_t sent = packet->last_timestamp, deadline = sent + cases[i].expected_tb + 1000; + // Блокируем только новую передачу: статус UP и настоящий retrans-таймер сохраняются. + a->shaper_timer = uasync_set_timeout(ua, cases[i].expected_tb + 10000, a, shaper_ready, "test_rto_shaper"); + CHECK(a->shaper_timer); + while (packet->state == INFLIGHT_STATE_WAIT_ACK && get_time_tb() < deadline) test_clock_poll(ua, 10); + uint64_t elapsed = get_time_tb() - sent; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RTO: RTT_tb=%u jitter_tb=%u expected_tb=%u observed_tb=%llu state=%u", + cases[i].rtt, cases[i].jitter, cases[i].expected_tb, (unsigned long long)elapsed, packet->state); + CHECK(packet->state == INFLIGHT_STATE_WAIT_SEND && elapsed >= cases[i].expected_tb && elapsed <= cases[i].expected_tb + 1000); + CHECK(!sends && !a->inflight_bytes && conn->input_send_q->count == 1 && conn->unacked_bytes == 100); + check_accounting(); ack(packet->seq); teardown(); + } +} + static void pacing_reset_close(void) { setup(); b->link_status = 0; a->shaper_timer = uasync_set_timeout(ua, 20, a, shaper_ready, "test_shaper"); CHECK(a->shaper_timer); @@ -392,7 +417,8 @@ 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, bbr_probe_window, tcp_initial_window}; + config_limits, timer_failure, immediate_send, ack_without_data_pacing, bbr_delivery_clock, bbr_probe_window, tcp_initial_window, + retransmission_timeout}; 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);