#include "etcp_keepalive.h" #include "etcp.h" #include "etcp_api.h" #include "stcp_link.h" #include "topo_node.h" #include "../lib/mem.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/memory_pool.h" #ifdef UTUN_HAVE_STANDBY #include "standby.h" #endif // forward declarations (static) static void keepalive_timer_cb(void* arg); static int etcp_all_links_down(struct ETCP_CONN* etcp); static void ka_stop_keepalive_timer(struct ETCP_LINK* link); static void ka_link_down(struct ETCP_LINK* link); 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_announce_burst(struct ETCP_LINK* link); static void ka_enter_sleep(struct ETCP_LINK* link); static void ka_wake(struct ETCP_LINK* link); // === перенесено из etcp_connections.c без изменений === // Вычислить keepalive по типу устройств: оба десктоп/сервер → min, иначе (есть mobile) → max uint16_t negotiate_keepalive(uint8_t my_type, uint16_t my_ka, uint8_t peer_type, uint16_t peer_ka) { int my_lp = (my_type == CLIENT_TYPE_MOBILE); int peer_lp = (peer_type == CLIENT_TYPE_MOBILE); uint16_t result = (my_lp || peer_lp) ? (my_ka > peer_ka ? my_ka : peer_ka) : (my_ka < peer_ka ? my_ka : peer_ka); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "negotiate: my=%d(type=%d) peer=%d(type=%d) → %d (rule=%s)", my_ka, my_type, peer_ka, peer_type, result, (my_lp || peer_lp) ? "max(low_power)" : "min(both_desktop)"); return result; } // Send keepalive packet: code(0x08) + flags + ka_period_ms + [active_remaining + sleep_duration] 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; struct ETCP_DGRAM* dgram = u_malloc(sizeof(struct ETCP_DGRAM) + 8); if (!dgram) { DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "malloc failed"); return; } dgram->link = link; dgram->data[0] = ETCP_KEEPALIVE; dgram->data[1] = flags; dgram->data[2] = link->ka_period_ms & 0xFF; dgram->data[3] = link->ka_period_ms >> 8; 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; dgram->data[7] = link->ka_sleep_duration >> 8; dgram->data_len = 8; } else { dgram->data_len = 4; } dgram->noencrypt_len = 0; dgram->timestamp = get_current_timestamp(); dgram->flag_up = link->is_tcp ? 1 : link->recv_keepalive; link->keepalive_sent_count++; etcp_encrypt_send(dgram); 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) { if (!etcp || !etcp->links) return 1; struct ETCP_LINK* l = etcp->links; while (l) { if (l->link_status == 1) { return 0; // At least one link is up } l = l->next; } return 1; // All links are down } void start_keepalive_timer(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance || !link->etcp->instance->ua) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "start_keepalive_timer: BAD link=%p etcp=%p inst=%p ua=%p", (void*)link, (void*)(link ? link->etcp : NULL), (void*)(link && link->etcp ? link->etcp->instance : NULL), (void*)(link && link->etcp && link->etcp->instance ? link->etcp->instance->ua : NULL)); return; } // Start keepalive timer if (link->init_timer) {// cancel init timer uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); link->init_timer = NULL; } if (link->keepalive_timer == NULL) { DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] Keepalive timer started on link %p (interval=%d ms)", link->etcp->log_name, link, link->keepalive_interval); link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->keepalive_interval * 10, link, keepalive_timer_cb, "link_keepalive"); } } static void restart_keepalive_timer_ms(struct ETCP_LINK* link, uint16_t ms) { if (!link || !link->etcp || !link->etcp->instance) return; if (link->keepalive_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer); link->keepalive_timer = NULL; } link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, (uint32_t)ms * 10, link, keepalive_timer_cb, "link_keepalive"); } // Keepalive timer callback static void keepalive_timer_cb(void* arg) { DEBUG_TRACE(DEBUG_CATEGORY_KEEPALIVE, ""); struct ETCP_LINK* link = (struct ETCP_LINK*)arg; if (!link || !link->etcp || !link->etcp->instance) { DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "KEEPALIVE NULL !!!!!!!!"); return; } link->keepalive_timer = NULL; // Обычный keepalive работает только в normal-режиме. В спячке (моей или пира) // таймер гасится (ka_stop_keepalive_timer); если долетел — переарм и выход. if (link->ka_my_sleeping || link->ka_peer_sleeping) { link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->ka_period_ms * 10, link, keepalive_timer_cb, "link_keepalive"); return; } // Check if all links are down and start recovery if needed (client only) if (link->is_server == 0 && etcp_all_links_down(link->etcp)) { if (link->is_tcp) { if (link->tcp_link) { stcp_link_close(link->tcp_link); link->tcp_link = NULL; } etcp_tcp_link_start_reconnect(link); return; } DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] All links are down, starting recovery", link->etcp->log_name); etcp_link_enter_reinit(link);// keepalive timr после reinit не нужен return; } // Skip if link is not initialized if (!link->initialized) { DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] Keepalive skipped - link not initialized", link->etcp->log_name); goto restart_timer; } // Check keepalive timeout uint64_t now = get_time_tb(); uint64_t timeout_units = (uint64_t)link->keepalive_timeout * 10; // ms -> 0.1ms units uint64_t elapsed = now - link->last_recv_local_time; if (elapsed > timeout_units) { if (link->recv_keepalive != 0) { link->recv_keepalive = 0; int old_link_status = link->link_status; link->link_status = 0; etcp_fire_link_status_cbk(link, link->link_state, old_link_status); /* Снапшот до каскада: etcp_on_link_down может синхронно закрыть conn/линки * (topo_recovery → ncd_revive_entry) — link может быть освобождён. */ struct ETCP_CONN *etcp = link->etcp; int is_tcp = link->is_tcp; int local_link_id = link->local_link_id; struct sockaddr_storage raddr = link->remote_addr; uint8_t remote_ka = link->remote_keepalive; int link_state = link->link_state, initialized = link->initialized; etcp_on_link_down(etcp, link); if (etcp->state == 2) return; /* conn+link освобождены каскадом, link трогать нельзя */ /* каскад мог закрыть линк и без state==2 — проверить, что link ещё в списке */ int alive = 0; for (struct ETCP_LINK *x = etcp->links; x; x = x->next) if (x == link) { alive = 1; break; } if (!alive) return; if (is_tcp && link->tcp_link) { stcp_link_close(link->tcp_link); link->tcp_link = NULL; } if (old_link_status) { DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %d down: addr=%s ka=%d remote_ka=%d state=%d init=%d tmo: %llu>%llu els=%llums", etcp->log_name, local_link_id, sockaddr_storage_to_str(&raddr).str, 0, remote_ka, link_state, initialized, (unsigned long long)timeout_units, (unsigned long long)elapsed, (unsigned long long)(elapsed/10)); } } } // Период: адаптивный от базового (normal-режим). if (link->pkt_sent_since_keepalive) link->ka_period_ms = (uint16_t)link->keepalive_interval; else if (link->etcp->instance && link->etcp->instance->config && link->etcp->instance->config->global.keepalive_adaptive) { uint32_t next = (uint32_t)link->ka_period_ms * 105 / 100 + 1; link->ka_period_ms = next > KA_PERIOD_MAX_MS ? KA_PERIOD_MAX_MS : (uint16_t)next; } link->pkt_sent_since_keepalive = 0; // Отправка обычного flagless-keepalive. if (!link->is_server || link->recv_keepalive) etcp_link_send_keepalive(link); restart_timer: link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->ka_period_ms * 10, link, keepalive_timer_cb, "link_keepalive"); } // === протокол спячки (sleep) на флагах keepalive-пакета === // Период keepalive в спячке (мс): из настройки standby_keepalive_ms (Android). static uint16_t ka_sleep_period_ms(struct UTUN_INSTANCE* inst) { #ifdef UTUN_HAVE_STANDBY int ms = standby_get_keepalive_ms(); if (ms < 100) ms = 100; return (uint16_t)ms; #else (void)inst; return 30000; #endif } // Сколько осталось до SLEEP-фазы (x0.1с): 0 = уже спим. static uint16_t ka_active_remaining_01s(void) { #ifdef UTUN_HAVE_STANDBY uint32_t v = (uint32_t)(standby_get_active_remaining_tb() / 1000); return v > 65535 ? 65535 : (uint16_t)v; #else return 0; #endif } // Длительность SLEEP-фазы (x0.1с): в SLEEP — оставшийся сон, в ACTIVE — полная длительность. static uint16_t ka_sleep_duration_01s(void) { #ifdef UTUN_HAVE_STANDBY int s = standby_get_sleep_tb(); uint32_t v = s > 0 ? (uint32_t)(s / 1000) : (uint32_t)(standby_get_sleep_duration_tb() / 1000); return v > 65535 ? 65535 : (uint16_t)v; #else return 0; #endif } // Отменить ретрай-таймер анонса спячки. static void ka_cancel_announce_retry(struct ETCP_LINK* link) { if (link->ka_sleep_announce_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->ka_sleep_announce_timer); link->ka_sleep_announce_timer = NULL; } } // Гасим обычный keepalive-таймер (переход в спячку / пир уснул). static void ka_stop_keepalive_timer(struct ETCP_LINK* link) { if (link->keepalive_timer) { uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer); link->keepalive_timer = NULL; } } // Максимум попыток анонса в ACTIVE-окне: сколько влезет в окно, но ≥ KA_SLEEP_MIN_ATTEMPTS. static uint8_t ka_announce_max_calc(struct ETCP_LINK* link) { uint16_t interval = link->keepalive_interval ? link->keepalive_interval : 200; uint32_t window_ms = (uint32_t)ka_active_remaining_01s() * 100; // остаток ACTIVE-окна, мс uint32_t fit = window_ms / interval + 1; // +1 за первый анонс if (fit < KA_SLEEP_MIN_ATTEMPTS) fit = KA_SLEEP_MIN_ATTEMPTS; if (fit > 255) fit = 255; return (uint8_t)fit; } // Разрыв линка (мёртвый пир в спячке). Может освободить link — после вызова link не трогать. static void ka_link_down(struct ETCP_LINK* link) { if (!link || !link->etcp) return; link->recv_keepalive = 0; int old_link_status = link->link_status; link->link_status = 0; etcp_fire_link_status_cbk(link, link->link_state, old_link_status); struct ETCP_CONN* etcp = link->etcp; int is_tcp = link->is_tcp; int local_link_id = link->local_link_id; struct sockaddr_storage raddr = link->remote_addr; uint8_t remote_ka = link->remote_keepalive; int link_state = link->link_state, initialized = link->initialized; etcp_on_link_down(etcp, link); if (etcp->state == 2) return; /* conn+link освобождены каскадом */ int alive = 0; for (struct ETCP_LINK* x = etcp->links; x; x = x->next) if (x == link) { alive = 1; break; } if (!alive) return; if (is_tcp && link->tcp_link) { stcp_link_close(link->tcp_link); link->tcp_link = NULL; } if (old_link_status) { DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %d down (sleep unresponsive): addr=%s remote_ka=%d state=%d init=%d", etcp->log_name, local_link_id, sockaddr_storage_to_str(&raddr).str, remote_ka, link_state, initialized); } } // Отправить один анонс спячки с таймингом. 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(); link->ka_announce_attempts++; etcp_link_send_keepalive_ex(link, KA_FLAG_SLEEP); } // Заармить ретрай анонса (интервал = keepalive_interval). static void ka_arm_announce_retry(struct ETCP_LINK* link) { if (link->ka_sleep_announce_timer) return; link->ka_sleep_announce_timer = uasync_set_timeout(link->etcp->instance->ua, (uint32_t)link->keepalive_interval * 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; if (!link || !link->etcp || !link->etcp->instance) return; if (!link->ka_my_sleeping || link->ka_sleep_acked) return; // анонс завершён link->ka_fail_count++; // предыдущая попытка без ACK if (link->ka_fail_count >= KA_SLEEP_MAX_FAILS) { DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] peer unresponsive in sleep (%u fails) — link down", link->etcp->log_name, link->ka_fail_count); ka_link_down(link); return; } if (link->ka_announce_attempts >= link->ka_announce_max_attempts) { DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] announce attempts exhausted (%u/%u) in window", link->etcp->log_name, link->ka_announce_attempts, link->ka_announce_max_attempts); return; } ka_send_sleep_keepalive(link); // ретрай ka_arm_announce_retry(link); } // Анонс-серия (ACTIVE-окно): анонс + ретраи до ACK, лимит попыток на окно. static void ka_announce_burst(struct ETCP_LINK* link) { link->ka_announce_attempts = 0; link->ka_sleep_acked = 0; link->ka_announce_max_attempts = ka_announce_max_calc(link); ka_send_sleep_keepalive(link); // попытка 1 ka_arm_announce_retry(link); } // Вход в спячку — моя сторона: гасим normal-keepalive, анонс-серия. 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_my_sleeping = 1; link->ka_sleep_period_ms = period; link->ka_period_ms = period; link->keepalive_timeout = (uint32_t)period * KA_TIMEOUT_MULT; ka_stop_keepalive_timer(link); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] entering sleep (period=%dms)", link->etcp->log_name, period); ka_announce_burst(link); } // Пробуждение (форграунд) — обычный keepalive, возврат к normal-режиму. static void ka_wake(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance) return; if (!link->ka_my_sleeping) return; link->ka_my_sleeping = 0; link->ka_sleep_acked = 0; link->ka_fail_count = 0; link->ka_announce_attempts = 0; ka_cancel_announce_retry(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] woke up, back to normal (interval=%dms)", link->etcp->log_name, link->keepalive_interval); etcp_link_send_keepalive(link); if (!link->ka_peer_sleeping) restart_keepalive_timer_ms(link, link->ka_period_ms); } typedef void (*ka_link_fn)(struct ETCP_LINK* link); static void ka_for_each_link(struct UTUN_INSTANCE* inst, ka_link_fn fn) { if (inst->connections) { for (struct ll_entry* e = inst->connections->head; e; e = e->next) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; if (!ce || !ce->conn) continue; for (struct ETCP_LINK* l = ce->conn->links; l; l = l->next) if (l->initialized) fn(l); } } if (inst->tcp_connections) { for (struct ll_entry* e = inst->tcp_connections->head; e; e = e->next) { struct tcp_conn_entry* te = (struct tcp_conn_entry*)e->data; if (!te || !te->etcp_conn) continue; for (struct ETCP_LINK* l = te->etcp_conn->links; l; l = l->next) if (l->initialized) fn(l); } } } #ifdef UTUN_HAVE_STANDBY // Вход в ACTIVE-окно — анонс-серия с ретраями до ACK (active_remaining>0). static void ka_active_announce(struct ETCP_LINK* link) { if (!link->ka_my_sleeping) return; ka_announce_burst(link); } // Фаза duty-cycle: SLEEP → переанонс спячки; ACTIVE → анонс активности (флаг всегда стоит). 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) { /* SLEEP-фаза не анонсируется отдельно — её тайминг уже передан в ACTIVE-окне (active_remaining→0 + sleep_duration), пир выведет переход по своему фазовому таймеру. Гасим ретраи анонса окна, чтобы не дослали лишний анонс в SLEEP. */ ka_for_each_link(inst, ka_cancel_announce_retry); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive: duty-cycle SLEEP phase (no announce, window retries cancelled, node=%016llx)", (unsigned long long)inst->node_id); } else { 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) ── */ static void ka_peer_arm_phase(struct ETCP_CONN* etcp); 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_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_peer_set_phase(etcp, etcp->peer_sleep_phase ? 0 : 1); ka_peer_arm_phase(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) { uasync_cancel_timeout(etcp->instance->ua, etcp->peer_phase_timer); etcp->peer_phase_timer = NULL; } /* гвард: нулевой тайминг (битый/legacy пакет) → не армать, иначе timer=0 даёт busy-loop */ if (dur_01s == 0) { 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_peer_phase_timer_cb, "peer_phase"); } int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, size_t pkt_len) { (void)pkt_len; if (!e_sock || !pkt || !link) return 0; uint8_t code = pkt->data[0]; if (code == ETCP_KEEPALIVE) { uint16_t peer_period = 0; if (pkt->data_len >= 4) peer_period = pkt->data[2] | ((uint16_t)pkt->data[3] << 8); else if (pkt->data_len >= 3) /* легаси-формат (без флагов) */ peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8); if (peer_period) link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT; uint8_t flags = (pkt->data_len >= 2) ? pkt->data[1] : 0; if (flags & KA_FLAG_SLEEP_ACK) { /* Ответ на мой анонс сна — команда дошла, ретраи сняты, счётчик провалов обнулён. */ link->ka_sleep_acked = 1; link->ka_fail_count = 0; link->ka_announce_attempts = 0; ka_cancel_announce_retry(link); DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] sleep ack received", link->etcp->log_name); } else if (flags & KA_FLAG_SLEEP) { /* Пир объявил сон — заткнуться (гасим свой normal-keepalive) и вести фазу троттлинга. */ 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; link->ka_peer_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; ka_stop_keepalive_timer(link); 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); } 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); } else if (link->ka_peer_sleeping) { /* Обычный keepalive (без флагов): пир проснулся — вернуться в normal-режим. */ link->ka_peer_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_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); if (!link->ka_my_sleeping) restart_keepalive_timer_ms(link, link->ka_period_ms); } link->keepalive_recv_count++; memory_pool_free(e_sock->instance->pkt_pool, pkt); return 1; } return 0; } 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_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); } // На только что (пере)инициализированном линке: если я в standby — анонсировать спячку // (возвращает 1, обычный flagless в этом случае слать НЕ надо); иначе 0. int etcp_keepalive_announce_mode(struct ETCP_LINK* link) { if (!link || !link->etcp || !link->etcp->instance) return 0; struct UTUN_INSTANCE* inst = link->etcp->instance; if (inst->client_type != CLIENT_TYPE_MOBILE) return 0; if (!inst->standby_enabled) return 0; /* не в standby — обычный режим */ ka_enter_sleep(link); return 1; } void etcp_keepalive_register(struct UTUN_INSTANCE* inst) { if (!inst) return; utun_add_activity_cbk(inst, etcp_keepalive_on_activity, NULL); #ifdef UTUN_HAVE_STANDBY standby_set_phase_callback(ka_phase_cb, inst); #endif DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "keepalive activity + phase callbacks registered"); }