From a3df1720b4592fa5a8d65702a1f7fc7afe13485c Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 22 Sep 2026 02:41:15 +0300 Subject: [PATCH] =?UTF-8?q?keepalive:=20=D1=81=D0=B8=D0=BC=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D1=87=D0=BD=D1=8B=D0=B9=20sleep-=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=20(=D0=B1=D0=B5=D0=B7=20is?= =?UTF-8?q?=5Fserver)=20+=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20ACK-=D0=B1=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/transport_layer/etcp_keepalive.c | 167 ++++++++++++++------------- src/transport_layer/etcp_keepalive.h | 26 +++-- 2 files changed, 99 insertions(+), 94 deletions(-) diff --git a/src/transport_layer/etcp_keepalive.c b/src/transport_layer/etcp_keepalive.c index cb26fcc4..80f9236f 100644 --- a/src/transport_layer/etcp_keepalive.c +++ b/src/transport_layer/etcp_keepalive.c @@ -15,9 +15,10 @@ static void keepalive_timer_cb(void* arg); static int etcp_all_links_down(struct ETCP_CONN* etcp); static void ka_send_sleep_keepalive(struct ETCP_LINK* link); +static void ka_send_sleep_ack(struct ETCP_LINK* link); static void ka_announce_retry_cb(void* arg); -static void ka_client_enter_sleep(struct ETCP_LINK* link); -static void ka_client_wake(struct ETCP_LINK* link); +static void ka_enter_sleep(struct ETCP_LINK* link); +static void ka_wake(struct ETCP_LINK* link); // === перенесено из etcp_connections.c без изменений === @@ -35,7 +36,7 @@ uint16_t negotiate_keepalive(uint8_t my_type, uint16_t my_ka, } // Send keepalive packet: code(0x08) + flags + ka_period_ms + [active_remaining + sleep_duration] -void etcp_link_send_keepalive(struct ETCP_LINK* link) { +static void etcp_link_send_keepalive_ex(struct ETCP_LINK* link, uint8_t flags) { DEBUG_TRACE(DEBUG_CATEGORY_KEEPALIVE, ""); if (!link || !link->etcp || !link->etcp->instance) return; @@ -47,10 +48,10 @@ void etcp_link_send_keepalive(struct ETCP_LINK* link) { dgram->link = link; dgram->data[0] = ETCP_KEEPALIVE; - dgram->data[1] = link->ka_sleeping ? KA_FLAG_SLEEP : 0; + dgram->data[1] = flags; dgram->data[2] = link->ka_period_ms & 0xFF; dgram->data[3] = link->ka_period_ms >> 8; - if (link->ka_sleeping) { + if (flags & KA_FLAG_SLEEP) { dgram->data[4] = link->ka_active_remaining & 0xFF; dgram->data[5] = link->ka_active_remaining >> 8; dgram->data[6] = link->ka_sleep_duration & 0xFF; @@ -69,6 +70,11 @@ void etcp_link_send_keepalive(struct ETCP_LINK* link) { u_free(dgram); } +// Обычный keepalive (без sleep-флагов): «жив» / «проснулся». +void etcp_link_send_keepalive(struct ETCP_LINK* link) { + etcp_link_send_keepalive_ex(link, 0); +} + // Check if all links for an ETCP_CONN are down // Returns 1 if all links are down or no links exist, 0 otherwise static int etcp_all_links_down(struct ETCP_CONN* etcp) { @@ -124,9 +130,10 @@ static void keepalive_timer_cb(void* arg) { link->keepalive_timer = NULL; - // Клиент в standby: ничего не делаем (recovery/dead-link/отправка) — только переарм. - // Фазу сигналят фазовые переходы (ka_phase_cb), NAT держит сам сон (sleep ≤ NAT-таймаут). - if (link->ka_sleeping && !link->is_server) { + // В режиме спячки (я сплю ИЛИ пир спит) обе стороны молчат: только переарм — + // никакого recovery/dead-link/отправки. Фазу сигналят фазовые переходы (ka_phase_cb), + // NAT держит сам сон (sleep ≤ NAT-таймаут). + if (link->ka_sleeping) { link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->ka_period_ms * 10, link, keepalive_timer_cb, "link_keepalive"); return; @@ -241,17 +248,22 @@ static uint16_t ka_sleep_duration_01s(void) { #endif } -// Отправить keepalive спячки (обновить поля тайминга) + при необходимости заармить ретрай. +// Отправить анонс спячки (обновить поля тайминга) + при необходимости заармить ретрай. static void ka_send_sleep_keepalive(struct ETCP_LINK* link) { link->ka_active_remaining = ka_active_remaining_01s(); link->ka_sleep_duration = ka_sleep_duration_01s(); - etcp_link_send_keepalive(link); + etcp_link_send_keepalive_ex(link, KA_FLAG_SLEEP); if (!link->ka_sleep_acked && link->ka_sleep_announce_timer == NULL) { link->ka_sleep_announce_timer = uasync_set_timeout(link->etcp->instance->ua, KA_SLEEP_RETRY_MS * 10, link, ka_announce_retry_cb, "ka_sleep_announce"); } } +// Ответ на анонс спячки пира: подтверждение доставки (без тайминга). +static void ka_send_sleep_ack(struct ETCP_LINK* link) { + etcp_link_send_keepalive_ex(link, KA_FLAG_SLEEP_ACK); +} + static void ka_announce_retry_cb(void* arg) { struct ETCP_LINK* link = (struct ETCP_LINK*)arg; link->ka_sleep_announce_timer = NULL; @@ -261,8 +273,8 @@ static void ka_announce_retry_cb(void* arg) { ka_send_sleep_keepalive(link); // ретрай } -// Клиент: вход в спячку (анонс с ретраями). -static void ka_client_enter_sleep(struct ETCP_LINK* link) { +// Вход в спячку (анонс с ретраями) — моя сторона. +static void ka_enter_sleep(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance) return; uint16_t period = ka_sleep_period_ms(link->etcp->instance); link->ka_sleeping = 1; @@ -271,19 +283,19 @@ static void ka_client_enter_sleep(struct ETCP_LINK* link) { link->ka_period_ms = period; link->keepalive_timeout = (uint32_t)period * KA_TIMEOUT_MULT; link->ka_sleep_announce_end_tb = get_time_tb() + (uint64_t)KA_SLEEP_ANNOUNCE_TIMEOUT_MS * 10; - DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] client entering sleep (period=%dms)", link->etcp->log_name, period); + DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] entering sleep (period=%dms)", link->etcp->log_name, period); ka_send_sleep_keepalive(link); restart_keepalive_timer_ms(link, period); } -// Клиент: переанонс на входе в очередной цикл спячки (обновить фазу, без ре-ack). -static void ka_client_reannounce(struct ETCP_LINK* link) { +// Переанонс на входе в очередной цикл спячки (обновить фазу, без ре-ack). +static void ka_reannounce(struct ETCP_LINK* link) { if (!link->ka_sleeping) return; ka_send_sleep_keepalive(link); } -// Клиент: пробуждение (форграунд) — обычный keepalive, возврат к базовому периоду. -static void ka_client_wake(struct ETCP_LINK* link) { +// Пробуждение (форграунд) — обычный keepalive, возврат к базовому периоду. +static void ka_wake(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance) return; if (!link->ka_sleeping) return; link->ka_sleeping = 0; @@ -294,7 +306,7 @@ static void ka_client_wake(struct ETCP_LINK* link) { } link->ka_period_ms = (uint16_t)link->keepalive_interval; link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT; - DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] client woke up, back to normal (interval=%dms)", + DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] woke up, back to normal (interval=%dms)", link->etcp->log_name, link->keepalive_interval); etcp_link_send_keepalive(link); restart_keepalive_timer_ms(link, link->ka_period_ms); @@ -322,8 +334,8 @@ static void ka_for_each_link(struct UTUN_INSTANCE* inst, ka_link_fn fn) { } #ifdef UTUN_HAVE_STANDBY -// Клиент: вход в ACTIVE-окно — отправить keepalive с active_remaining>0 (флаг стоит). -static void ka_client_active_announce(struct ETCP_LINK* link) { +// Вход в ACTIVE-окно — отправить keepalive с active_remaining>0 (флаг стоит). +static void ka_active_announce(struct ETCP_LINK* link) { if (!link->ka_sleeping) return; ka_send_sleep_keepalive(link); } @@ -333,37 +345,37 @@ static void ka_phase_cb(int sleeping, void* arg) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg; if (!inst || inst->client_type != CLIENT_TYPE_MOBILE) return; if (sleeping) { - ka_for_each_link(inst, ka_client_reannounce); + ka_for_each_link(inst, ka_reannounce); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive: duty-cycle SLEEP phase, re-announce (node=%016llx)", (unsigned long long)inst->node_id); } else { - ka_for_each_link(inst, ka_client_active_announce); + ka_for_each_link(inst, ka_active_announce); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive: duty-cycle ACTIVE phase, announce (node=%016llx)", (unsigned long long)inst->node_id); } } #endif -/* ── Сервер: машина фаз троттлинга (по active_remaining/sleep_duration) ── */ +/* ── Пир: машина фаз троттлинга (по active_remaining/sleep_duration) ── */ -static void ka_server_arm_phase(struct ETCP_CONN* etcp); +static void ka_peer_arm_phase(struct ETCP_CONN* etcp); -static void ka_server_set_phase(struct ETCP_CONN* etcp, int sleeping) { +static void ka_peer_set_phase(struct ETCP_CONN* etcp, int sleeping) { if (!etcp) return; if (etcp->peer_sleep_phase == (uint8_t)(sleeping ? 1 : 0)) return; etcp->peer_sleep_phase = sleeping ? 1 : 0; utun_fire_peer_sleep_cbk(etcp->instance, etcp->peer_node_id, sleeping); } -static void ka_server_phase_timer_cb(void* arg) { +static void ka_peer_phase_timer_cb(void* arg) { struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; etcp->peer_phase_timer = NULL; if (!etcp || etcp->state == 2) return; - ka_server_set_phase(etcp, etcp->peer_sleep_phase ? 0 : 1); - ka_server_arm_phase(etcp); + ka_peer_set_phase(etcp, etcp->peer_sleep_phase ? 0 : 1); + ka_peer_arm_phase(etcp); } -static void ka_server_arm_phase(struct ETCP_CONN* etcp) { +static void ka_peer_arm_phase(struct ETCP_CONN* etcp) { if (!etcp || !etcp->instance) return; uint16_t dur_01s = etcp->peer_sleep_phase ? etcp->peer_sleep_duration : etcp->peer_active_remaining; if (etcp->peer_phase_timer) { @@ -372,12 +384,12 @@ static void ka_server_arm_phase(struct ETCP_CONN* etcp) { } /* гвард: нулевой тайминг (битый/legacy пакет) → не армать, иначе timer=0 даёт busy-loop */ if (dur_01s == 0) { - DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] server: zero phase duration (phase=%d), skip arming", + DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] peer: zero phase duration (phase=%d), skip arming", etcp->log_name, etcp->peer_sleep_phase); return; } etcp->peer_phase_timer = uasync_set_timeout(etcp->instance->ua, (int)((uint32_t)dur_01s * 1000), - etcp, ka_server_phase_timer_cb, "peer_phase"); + etcp, ka_peer_phase_timer_cb, "peer_phase"); } int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, @@ -396,61 +408,52 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, uint8_t flags = (pkt->data_len >= 2) ? pkt->data[1] : 0; - if (flags & KA_FLAG_SLEEP) { + if (flags & KA_FLAG_SLEEP_ACK) { + /* Ответ на мой анонс сна — команда дошла, можно спать без ретраев. */ + link->ka_sleep_acked = 1; + if (link->ka_sleep_announce_timer) { + uasync_cancel_timeout(e_sock->instance->ua, link->ka_sleep_announce_timer); + link->ka_sleep_announce_timer = NULL; + } + DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] sleep ack received", link->etcp->log_name); + } else if (flags & KA_FLAG_SLEEP) { + /* Пир объявил сон — заткнуться и вести фазу троттлинга по таймингу. */ uint16_t active_remaining = (pkt->data_len >= 6) ? (pkt->data[4] | ((uint16_t)pkt->data[5] << 8)) : 0; uint16_t sleep_duration = (pkt->data_len >= 8) ? (pkt->data[6] | ((uint16_t)pkt->data[7] << 8)) : 0; - if (link->is_server) { - /* Сервер: пир в спячке — зеркалим флаг/поля и ведём фазу троттлинга. - ack-эхо шлём только на анонсе (переход 0→1), дальше в спячке молчим. */ - int was_sleeping = link->ka_sleeping; - link->ka_sleeping = 1; - link->ka_sleep_period_ms = peer_period; - link->ka_active_remaining = active_remaining; - link->ka_sleep_duration = sleep_duration; - link->ka_period_ms = peer_period; - if (link->etcp) { - link->etcp->peer_active_remaining = active_remaining; - link->etcp->peer_sleep_duration = sleep_duration; - ka_server_set_phase(link->etcp, active_remaining == 0); - ka_server_arm_phase(link->etcp); - } - DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] server: peer sleeping (period=%dms active_rem=%d.%ds sleep_dur=%d.%ds), echo=%s", - link->etcp->log_name, peer_period, - (int)(active_remaining / 10), (int)(active_remaining % 10), - (int)(sleep_duration / 10), (int)(sleep_duration % 10), - was_sleeping ? "no" : "ack"); - if (!was_sleeping) - etcp_link_send_keepalive(link); - restart_keepalive_timer_ms(link, peer_period); - } else { - /* Клиент: ack/эхо сервера — анонс завершён. */ - link->ka_sleep_acked = 1; - if (link->ka_sleep_announce_timer) { - uasync_cancel_timeout(e_sock->instance->ua, link->ka_sleep_announce_timer); - link->ka_sleep_announce_timer = NULL; - } - DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] client: sleep ack received", link->etcp->log_name); + link->ka_sleeping = 1; + link->ka_sleep_period_ms = peer_period; + link->ka_active_remaining = active_remaining; + link->ka_sleep_duration = sleep_duration; + link->ka_period_ms = peer_period; + if (link->etcp) { + link->etcp->peer_active_remaining = active_remaining; + link->etcp->peer_sleep_duration = sleep_duration; + ka_peer_set_phase(link->etcp, active_remaining == 0); + ka_peer_arm_phase(link->etcp); } - } else { - /* Обычный keepalive (без флага). */ - if (link->is_server && link->ka_sleeping) { - /* Пир проснулся (форграунд) — вернуться в обычный режим. */ - link->ka_sleeping = 0; - link->ka_period_ms = (uint16_t)link->keepalive_interval; - link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT; - if (link->etcp) { - if (link->etcp->peer_phase_timer) { - uasync_cancel_timeout(link->etcp->instance->ua, link->etcp->peer_phase_timer); - link->etcp->peer_phase_timer = NULL; - } - ka_server_set_phase(link->etcp, 0); + DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] peer sleeping (period=%dms active_rem=%d.%ds sleep_dur=%d.%ds)", + link->etcp->log_name, peer_period, + (int)(active_remaining / 10), (int)(active_remaining % 10), + (int)(sleep_duration / 10), (int)(sleep_duration % 10)); + ka_send_sleep_ack(link); + restart_keepalive_timer_ms(link, peer_period); + } else if (link->ka_sleeping) { + /* Обычный keepalive (без флагов): пир проснулся — вернуться в обычный режим. */ + link->ka_sleeping = 0; + link->ka_period_ms = (uint16_t)link->keepalive_interval; + link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT; + if (link->etcp) { + if (link->etcp->peer_phase_timer) { + uasync_cancel_timeout(link->etcp->instance->ua, link->etcp->peer_phase_timer); + link->etcp->peer_phase_timer = NULL; } - DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] server: peer woke up, back to normal (interval=%dms)", - link->etcp->log_name, link->keepalive_interval); - restart_keepalive_timer_ms(link, link->ka_period_ms); + ka_peer_set_phase(link->etcp, 0); } + DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] peer woke up, back to normal (interval=%dms)", + link->etcp->log_name, link->keepalive_interval); + restart_keepalive_timer_ms(link, link->ka_period_ms); } link->keepalive_recv_count++; @@ -464,18 +467,18 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, static void etcp_keepalive_on_activity(struct UTUN_INSTANCE* inst, int active, void* arg) { (void)arg; if (!inst || inst->client_type != CLIENT_TYPE_MOBILE) return; - ka_for_each_link(inst, active ? ka_client_wake : ka_client_enter_sleep); + ka_for_each_link(inst, active ? ka_wake : ka_enter_sleep); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive activity change: %s -> applied to links (node=%016llx)", active ? "ACTIVE(wake)" : "STANDBY(sleep)", (unsigned long long)inst->node_id); } -// Переанонс спячки на только что (пере)инициализированном линке (рестарт сервера). +// Переанонс спячки на только что (пере)инициализированном линке (рестарт пира). void etcp_keepalive_announce_mode(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance) return; struct UTUN_INSTANCE* inst = link->etcp->instance; if (inst->client_type != CLIENT_TYPE_MOBILE) return; if (!inst->standby_enabled) return; /* не в standby — обычный режим */ - ka_client_enter_sleep(link); + ka_enter_sleep(link); } void etcp_keepalive_register(struct UTUN_INSTANCE* inst) { diff --git a/src/transport_layer/etcp_keepalive.h b/src/transport_layer/etcp_keepalive.h index 945b8a84..b08d37aa 100644 --- a/src/transport_layer/etcp_keepalive.h +++ b/src/transport_layer/etcp_keepalive.h @@ -16,27 +16,29 @@ extern "C" { * стоит весь фон). Тайминг duty-cycle (SLEEP 120с / ACTIVE 2с) — двумя полями: * * data[0] = ETCP_KEEPALIVE (0x08) - * data[1] = flags (bit0 = KA_FLAG_SLEEP — standby) + * data[1] = flags (bit0 = KA_FLAG_SLEEP — «я сплю», bit1 = KA_FLAG_SLEEP_ACK — ответ) * data[2..3] = ka_period_ms (LE 16-bit) — база dead-link таймаута, мс * data[4..5] = active_remaining (LE 16-bit, x0.1с) — до SLEEP-фазы (0 = уже спим) * data[6..7] = sleep_duration (LE 16-bit, x0.1с) — длительность SLEEP-фазы * - * В standby обе стороны НЕ шлют периодические keepalive: standby-время само - * удерживает NAT (sleep ≤ NAT-таймаута). Keepalive шлёт только клиент, и только - * на фазовых переходах duty-cycle: вход в SLEEP (active_remaining=0, - * sleep_duration=полная) и вход в ACTIVE (active_remaining=остаток окна). Сервер - * по этим полям ведёт фазу троттлинга (SLEEP-фаза → тормозим протоколы, - * ACTIVE-окно → обмен) и ack-эхом отвечает один раз на анонсе (переход флага 0→1). + * Протокол полностью симметричный (без привязки к STCP-роли is_server): + * спящий узел шлёт анонс KA_FLAG_SLEEP (с таймингом), пир на это «затыкается» — + * перестаёт слать периодические keepalive — и отвечает одним ACK (KA_FLAG_SLEEP_ACK). + * Спящий, получив ACK, снимает ретраи анонса и спит молча (NAT держит сам сон, + * sleep ≤ NAT-таймаута). В standby обе стороны НЕ шлют периодические keepalive. * - * Засыпание (клиент): анонс + ретраи KA_SLEEP_RETRY_MS до ack (эхо с флагом), - * окно KA_SLEEP_ANNOUNCE_TIMEOUT_MS, после чего спит anyway. + * По таймингу анонса пир ведёт фазу троттлинга duty-cycle (SLEEP-фаза → тормозим + * протоколы, ACTIVE-окно → обмен). Анонс повторяется на фазовых переходах: вход + * в SLEEP (active_remaining=0, sleep_duration=полная) и вход в ACTIVE + * (active_remaining=остаток окна). * - * Пробуждение (форграунд): клиент шлёт обычный keepalive (без флага) — сервер - * снимает standby и фазу троттлинга, возвращается в обычный режим. + * Пробуждение (форграунд): спящий шлёт обычный keepalive (без флагов) — пир снимает + * standby и фазу троттлинга, возвращается в обычный режим. */ // Флаги keepalive-пакета (data[1]) -#define KA_FLAG_SLEEP 0x01 // спячка: анонс (клиент) / ack-эхо (сервер) +#define KA_FLAG_SLEEP 0x01 // «я сплю» (анонс, с таймингом) +#define KA_FLAG_SLEEP_ACK 0x02 // «подтверждаю твой сон» (ответ на анонс) // Параметры анонса спячки #define KA_SLEEP_RETRY_MS 200 // ретрай анонса спячки (клиент)