You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

412 lines
19 KiB

#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_send_sleep_keepalive(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);
// === перенесено из 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 + [sleep_time]
void etcp_link_send_keepalive(struct ETCP_LINK* link) {
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] = link->ka_sleeping ? KA_FLAG_SLEEP : 0;
dgram->data[2] = link->ka_period_ms & 0xFF;
dgram->data[3] = link->ka_period_ms >> 8;
if (link->ka_sleeping) {
dgram->data[4] = link->ka_sleep_time & 0xFF;
dgram->data[5] = link->ka_sleep_time >> 8;
dgram->data_len = 6;
} 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);
}
// 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;
// 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));
}
}
}
// Сервер: дедлайн спячки пира вышел — пир должен был проснуться/переанонсировать,
// выходим из спячки в обычный режим (базовый период, probe штатным keepalive).
if (link->is_server && link->ka_sleeping && now >= link->ka_sleep_deadline_tb) {
link->ka_sleeping = 0;
link->ka_period_ms = link->keepalive_interval;
link->keepalive_timeout = (uint32_t)link->keepalive_interval * KA_TIMEOUT_MULT;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] server: sleep deadline expired, back to normal (interval=%dms)",
link->etcp->log_name, link->keepalive_interval);
}
// Период: спячка — фиксированный sleep-период; normal — адаптивный от базового.
if (link->ka_sleeping)
link->ka_period_ms = link->ka_sleep_period_ms;
else 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;
// Отправка keepalive (в спячке — с флагом и свежим дедлайном)
if (link->ka_sleeping)
ka_send_sleep_keepalive(link);
else 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
}
// Дедлайн пробуждения (x0.1с): остаток SLEEP либо остаток ACTIVE + полная SLEEP.
static uint16_t ka_wake_deadline_01s(void) {
#ifdef UTUN_HAVE_STANDBY
int deadline_tb = standby_get_wake_deadline_tb();
uint32_t v = deadline_tb > 0 ? (uint32_t)(deadline_tb / 1000) : 0;
return v > 65535 ? 65535 : (uint16_t)v;
#else
return 0;
#endif
}
// Отправить keepalive спячки (обновить дедлайн) + при необходимости заармить ретрай.
static void ka_send_sleep_keepalive(struct ETCP_LINK* link) {
uint16_t sleep_time = ka_wake_deadline_01s();
if (sleep_time == 0) sleep_time = link->ka_sleep_period_ms / 100;
link->ka_sleep_time = sleep_time;
etcp_link_send_keepalive(link);
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_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_sleeping || link->ka_sleep_acked) return; // анонс завершён
if (get_time_tb() >= link->ka_sleep_announce_end_tb) return; // окно 4с вышло
ka_send_sleep_keepalive(link); // ретрай
}
// Клиент: вход в спячку (анонс с ретраями).
static void ka_client_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;
link->ka_sleep_acked = 0;
link->ka_sleep_period_ms = period;
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);
ka_send_sleep_keepalive(link);
restart_keepalive_timer_ms(link, period);
}
// Клиент: переанонс на входе в очередной цикл спячки (обновить дедлайн).
static void ka_client_reannounce(struct ETCP_LINK* link) {
if (!link->ka_sleeping) return;
link->ka_sleep_acked = 0;
link->ka_sleep_announce_end_tb = get_time_tb() + (uint64_t)KA_SLEEP_ANNOUNCE_TIMEOUT_MS * 10;
ka_send_sleep_keepalive(link);
}
// Клиент: пробуждение (форграунд) — обычный keepalive, возврат к базовому периоду.
static void ka_client_wake(struct ETCP_LINK* link) {
if (!link || !link->etcp || !link->etcp->instance) return;
if (!link->ka_sleeping) return;
link->ka_sleeping = 0;
link->ka_sleep_acked = 0;
if (link->ka_sleep_announce_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->ka_sleep_announce_timer);
link->ka_sleep_announce_timer = NULL;
}
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)",
link->etcp->log_name, link->keepalive_interval);
etcp_link_send_keepalive(link);
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
// Фаза duty-cycle: вошли в SLEEP → переанонсировать спячку (каждый цикл).
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) return;
ka_for_each_link(inst, ka_client_reannounce);
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive: duty-cycle SLEEP phase, re-announce (node=%016llx)",
(unsigned long long)inst->node_id);
}
#endif
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) {
uint16_t sleep_time = (pkt->data_len >= 6)
? (pkt->data[4] | ((uint16_t)pkt->data[5] << 8)) : 0;
if (link->is_server) {
/* Сервер: пир заснул — перейти в спячку и ответить ack-эхо. */
link->ka_sleeping = 1;
link->ka_sleep_period_ms = peer_period;
link->ka_sleep_time = sleep_time;
link->ka_sleep_deadline_tb = get_time_tb() + (uint64_t)sleep_time * 1000;
link->ka_period_ms = peer_period;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] server: peer sleeping (period=%dms deadline=%us), echo ack",
link->etcp->log_name, peer_period, (unsigned)(sleep_time / 10));
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);
}
} 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;
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);
}
}
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_client_wake : ka_client_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);
}
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");
}