From 03d69cb772315cf994f5bf62da20c6effa82fd21 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 21:58:27 +0200 Subject: [PATCH] Verify TCP retry timing, recovery and cancellation end to end --- src/transport_layer/etcp_connections.h | 8 ++++- tests/test_etcp_stcp.c | 47 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h index 1fdbafb4..287011dc 100644 --- a/src/transport_layer/etcp_connections.h +++ b/src/transport_layer/etcp_connections.h @@ -364,7 +364,7 @@ struct ETCP_LINK { struct reality_client_config reality; // reality-параметры пира (для исходящего TCP) struct stcp_link *tcp_link; // STCP линк (для TCP) void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент) - uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff) + uint32_t tcp_reconnect_delay_ms; // 0 после READY; ошибки: 1000, 2000, 4000, 8000, 16000, затем 30000 мс }; // ── Внутренняя отправка пакетов ── @@ -414,6 +414,9 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn // Минимум выбирает BBR; ноль отклоняется с логом без изменения окна. Неверный link игнорируется. void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim); +/* Снимает линк из списков, отменяет таймеры/STCP и освобождает память до возврата. + * Из callback его закрытия повторный close допустим; после возврата handle недействителен. + * Callback может закрыть ETCP_CONN или соседний линк: вызывающий не должен хранить next через уведомление. */ void etcp_link_close(struct ETCP_LINK* link); //int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет @@ -445,10 +448,13 @@ void etcp_link_enter_init(struct ETCP_LINK *link); void etcp_link_enter_reinit(struct ETCP_LINK *link); +/* Публикует готовность STCP и запускает согласование ETCP-сессии. Close из callback прекращает переход. */ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link); void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage *addr, uint16_t port); +/* Только исходящий TCP: закрывает текущий адаптер и ставит один backoff-таймер; повторный вызов его не сдвигает. + * Ошибка таймера закрывает ETCP_CONN. Close линка/соединения отменяет попытку; READY сбрасывает backoff. */ void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link); /* Принять plaintext установленного линка: session/формат проверяются до соответствующей обработки RX. diff --git a/tests/test_etcp_stcp.c b/tests/test_etcp_stcp.c index 0416aa13..e53a9a15 100644 --- a/tests/test_etcp_stcp.c +++ b/tests/test_etcp_stcp.c @@ -360,6 +360,52 @@ static void callback_close_case(int reality, int side, unsigned action, unsigned reality, side, action, on_down); } +static void reconnect_case(void) { + size_t allocated = u_get_allocated_count(); + memset(endpoints, 0, sizeof(endpoints)); phase = 0; failed = 0; + ua = uasync_create(); CHECK(ua); + start_endpoint(0, 0); start_endpoint(1, 0); + struct TOPO_NODE *peer = import_peer(0); + CHECK(node_conn_direct_open_node(endpoints[0].inst, peer->node_id, NULL, NULL, + &endpoints[0].owner, peer, NULL) >= 0); + wait_pair(); exchange_messages(); + struct ETCP_LINK *path = endpoints[0].conn->links; + CHECK(path && !path->is_server && !path->next && path->tcp_reconnect_delay_ms == 0); + stop_endpoint(1); + static const unsigned delays[] = {1000, 2000, 4000, 8000, 16000, 30000, 30000}; + for (unsigned i = 0; i < sizeof(delays) / sizeof(delays[0]); i++) { + unsigned previous = i ? delays[i-1] : 0; + uint64_t started = get_time_tb(), deadline = started + (previous + 2000) * 10; + while ((!path->tcp_reconnect_timer || path->tcp_reconnect_delay_ms != delays[i] || + (i && get_time_tb() - started < previous * 10)) && get_time_tb() < deadline) + test_clock_poll(ua, 100); + CHECK(path->tcp_reconnect_timer && path->tcp_reconnect_delay_ms == delays[i] && !path->tcp_link); + uint64_t elapsed = get_time_tb() - started; + if (i) CHECK(elapsed >= previous * 10 && elapsed < (previous + 1000) * 10); + void *timer = path->tcp_reconnect_timer; + etcp_tcp_link_start_reconnect(path); + CHECK(path->tcp_reconnect_timer == timer && path->tcp_reconnect_delay_ms == delays[i]); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS TCP retry stage=%u delay_ms=%u elapsed_tb=%llu single_timer=1", + i, delays[i], (unsigned long long)elapsed); + } + start_endpoint(1, 0); + uint64_t deadline = get_time_tb() + 350000; + while (!pair_ready() && get_time_tb() < deadline) test_clock_poll(ua, 100); + CHECK(pair_ready() && path->tcp_reconnect_delay_ms == 0 && !path->tcp_reconnect_timer); + exchange_messages(); + stop_endpoint(1); + deadline = get_time_tb() + 20000; + while (!path->tcp_reconnect_timer && get_time_tb() < deadline) test_clock_poll(ua, 100); + CHECK(path->tcp_reconnect_timer && path->tcp_reconnect_delay_ms == 1000); + etcp_link_close(path); /* Отменённая попытка не должна ожить после истечения таймера. */ + test_clock_run_for(ua, 20000); + CHECK(!endpoints[0].conn->links); + stop_endpoint(0); uasync_drain_immediate(ua); uasync_poll(ua, 0); + CHECK(ua->timer_alloc_count == ua->timer_free_count && ua->socket_alloc_count == ua->socket_free_count); + uasync_destroy(ua, 0); CHECK(u_get_allocated_count() == allocated); + printf("PASS TCP retry: capped backoff, recovery, reset after READY and cancellation\n"); +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); if (getenv("UTUN_TEST_DEBUG")) { @@ -377,6 +423,7 @@ int main(void) { if (action != 3) callback_close_case(reality, side, action, 1); } } + reconnect_case(); test_clock_finish(); return 0; }