Browse Source

etcp: keepalive sleep-протокол на флагах (KA_FLAG_SLEEP + sleep_time, announce-retry, ack/echo)

proxy
evgeny 1 week ago
parent
commit
5628138d16
  1. 1
      src/chat/chat_setting.c
  2. 12
      src/transport_layer/etcp_connections.c
  3. 10
      src/transport_layer/etcp_connections.h
  4. 327
      src/transport_layer/etcp_keepalive.c
  5. 56
      src/transport_layer/etcp_keepalive.h
  6. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt
  7. 7
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt
  8. 26
      tools/chatgui-android/libutun_lite/standby.c
  9. 13
      tools/chatgui-android/libutun_lite/standby.h

1
src/chat/chat_setting.c

@ -42,6 +42,7 @@ static const struct chat_setting_def g_setting_defs[] = {
{"standby_active_sec", CHAT_SETTING_INT, 2, 1, 3600},
{"standby_sleep_sec", CHAT_SETTING_INT, 60, 1, 86400},
{"standby_min_sleep_sec", CHAT_SETTING_INT, 15, 1, 86400},
{"standby_keepalive_ms", CHAT_SETTING_INT, 30000, 1000, 600000},
{"join_policy", CHAT_SETTING_INT, 1, 0, 2}, /* 0=autojoin, 1=ask, 2=deny */
{"whisper_enabled", CHAT_SETTING_BOOL, 0, 0, 1},
{"whisper_model_path", CHAT_SETTING_STRING, 0, 0, 0},

12
src/transport_layer/etcp_connections.c

@ -1005,7 +1005,13 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->keepalive_sent_count = 0;
link->keepalive_recv_count = 0;
link->ka_period_ms = (uint16_t)link->keepalive_interval;
link->ka_pinned = 0;
link->ka_sleep_period_ms = 0;
link->ka_sleeping = 0;
link->ka_sleep_acked = 0;
link->ka_sleep_time = 0;
link->ka_sleep_deadline_tb = 0;
link->ka_sleep_announce_timer = NULL;
link->ka_sleep_announce_end_tb = 0;
/* inflight_lim_bytes set above */
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
link->burst_id = 0;
@ -1195,6 +1201,7 @@ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link) {
if (etcp->state == 2) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "enter_ready_tcp: conn DELETED after conn_ready, link=%p", (void*)link); return; }
etcp_link_send_keepalive(link);
start_keepalive_timer(link);
etcp_keepalive_announce_mode(link);
loadbalancer_link_ready(link);
etcp_fire_link_status_cbk(link, old_state, link->link_status);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d init=%d up=%d tcp_links=%d)",
@ -1993,6 +2000,7 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
start_keepalive_timer(link);
etcp_keepalive_announce_mode(link);
loadbalancer_link_ready(link);
@ -2500,7 +2508,7 @@ int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
char who[256];
snprintf(who, sizeof(who), "%s addr=%s", link->etcp->log_name,
sockaddr_storage_to_str(&link->remote_addr).str);
int is_ka = (pkt_code == ETCP_KEEPALIVE || pkt_code == ETCP_KEEPALIVE_REQ || pkt_code == ETCP_KEEPALIVE_RESP);
int is_ka = (pkt_code == ETCP_KEEPALIVE);
standby_log_rx(is_ka ? (link->is_tcp ? "tcp keepalive" : "udp keepalive")
: (link->is_tcp ? "tcp data" : "udp data"), who);
}

10
src/transport_layer/etcp_connections.h

@ -281,8 +281,14 @@ struct ETCP_LINK {
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
uint32_t keepalive_timeout; // таймаут (ms)
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
uint8_t ka_pinned; // 1 = период зафиксирован протоколом (standby-режим мобильного), адаптивный рост отключён
uint16_t ka_period_ms; // текущий период отправки keepalive (адаптивный в normal, фиксированный в спячке)
uint16_t ka_sleep_period_ms; // период keepalive в спячке (мс; настройка клиента / ka_period_ms пира)
uint8_t ka_sleeping; // 1 = линк в режиме спячки
uint8_t ka_sleep_acked; // клиент: получен ack спячки от сервера
uint16_t ka_sleep_time; // дедлайн пробуждения (x0.1с), текущий
uint64_t ka_sleep_deadline_tb; // абсолютный дедлайн пробуждения (timebase 0.1мс)
void* ka_sleep_announce_timer; // таймер ретрая анонса спячки (клиент)
uint64_t ka_sleep_announce_end_tb; // конец окна ретрая анонса (4с)
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive

327
src/transport_layer/etcp_keepalive.c

@ -7,10 +7,17 @@
#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 без изменений ===
@ -27,12 +34,12 @@ uint16_t negotiate_keepalive(uint8_t my_type, uint16_t my_ka,
return result;
}
// Send empty keepalive packet (only timestamp, no sections)
// 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) + 4);
struct ETCP_DGRAM* dgram = u_malloc(sizeof(struct ETCP_DGRAM) + 8);
if (!dgram) {
DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "malloc failed");
return;
@ -40,9 +47,16 @@ void etcp_link_send_keepalive(struct ETCP_LINK* link) {
dgram->link = link;
dgram->data[0] = ETCP_KEEPALIVE;
dgram->data[1] = link->ka_period_ms & 0xFF;
dgram->data[2] = link->ka_period_ms >> 8;
dgram->data_len = 3;
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;
@ -88,6 +102,15 @@ void start_keepalive_timer(struct ETCP_LINK* link) {
}
}
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, "");
@ -150,9 +173,19 @@ static void keepalive_timer_cb(void* arg) {
}
}
// Adaptive keepalive period (only if adaptive enabled)
if (link->ka_pinned)
link->ka_period_ms = (uint16_t)link->keepalive_interval;
// Сервер: дедлайн спячки пира вышел — пир должен был проснуться/переанонсировать,
// выходим из спячки в обычный режим (базовый период, 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 &&
@ -162,94 +195,135 @@ static void keepalive_timer_cb(void* arg) {
}
link->pkt_sent_since_keepalive = 0;
// Send keepalive (server stops if link lost, client always sends)
if (!link->is_server || link->recv_keepalive)
// Отправка 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");
}
// === протокол смены keepalive-режима (standby/normal, p2p) ===
// === протокол спячки (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
}
static uint8_t mobile_desired_ka_mode(struct UTUN_INSTANCE* inst) {
return (inst->client_activity == CLIENT_ACTIVITY_ACTIVE) ? KA_MODE_NORMAL : KA_MODE_STANDBY;
// Дедлайн пробуждения (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
}
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;
// Отправить 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");
}
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, (uint32_t)ms * 10, link, keepalive_timer_cb, "link_keepalive");
}
static void apply_link_ka_mode(struct ETCP_LINK* link, uint8_t mode) {
uint16_t ms = (mode == KA_MODE_STANDBY) ? KA_MOBILE_STANDBY_MS : KA_MOBILE_ACTIVE_MS;
link->keepalive_interval = ms;
link->ka_period_ms = ms;
link->keepalive_timeout = (uint32_t)ms * KA_TIMEOUT_MULT;
link->ka_pinned = (mode == KA_MODE_STANDBY) ? 1 : 0;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive mode applied: mode=%s interval=%dms timeout=%dms pinned=%d",
link->etcp->log_name, mode == KA_MODE_STANDBY ? "standby" : "normal",
ms, link->keepalive_timeout, link->ka_pinned);
restart_keepalive_timer_ms(link, link->ka_period_ms);
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 etcp_keepalive_send_mode_pkt(struct ETCP_LINK* link, uint8_t code, uint8_t mode) {
// Клиент: вход в спячку (анонс с ретраями).
static void ka_client_enter_sleep(struct ETCP_LINK* link) {
if (!link || !link->etcp || !link->etcp->instance) return;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive %s send: mode=%s",
link->etcp->log_name,
code == ETCP_KEEPALIVE_REQ ? "REQ" : "RESP",
mode == KA_MODE_STANDBY ? "standby" : "normal");
struct ETCP_DGRAM* dgram = u_malloc(sizeof(struct ETCP_DGRAM) + 4);
if (!dgram) {
DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "malloc failed");
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);
}
dgram->link = link;
dgram->data[0] = code;
dgram->data[1] = mode;
dgram->data_len = 2;
dgram->noencrypt_len = 0;
dgram->timestamp = get_current_timestamp();
dgram->flag_up = link->is_tcp ? 1 : link->recv_keepalive;
// Клиент: переанонс на входе в очередной цикл спячки (обновить дедлайн).
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);
}
etcp_encrypt_send(dgram);
u_free(dgram);
// Клиент: пробуждение (форграунд) — обычный 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);
}
static void handle_keepalive_req(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link) {
uint8_t req_mode = (pkt->data_len >= 2) ? pkt->data[1] : KA_MODE_NORMAL;
uint8_t agreed;
if (e_sock->instance->client_type == CLIENT_TYPE_MOBILE) {
uint8_t my_mode = mobile_desired_ka_mode(e_sock->instance);
agreed = (my_mode == KA_MODE_STANDBY || req_mode == KA_MODE_STANDBY) ? KA_MODE_STANDBY : KA_MODE_NORMAL;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive REQ recv: req=%s my=%s -> agreed=%s",
link->etcp->log_name,
req_mode == KA_MODE_STANDBY ? "standby" : "normal",
my_mode == KA_MODE_STANDBY ? "standby" : "normal",
agreed == KA_MODE_STANDBY ? "standby" : "normal");
} else {
agreed = req_mode;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive REQ recv: req=%s (non-mobile) -> agreed=%s",
link->etcp->log_name,
req_mode == KA_MODE_STANDBY ? "standby" : "normal",
agreed == KA_MODE_STANDBY ? "standby" : "normal");
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);
}
}
apply_link_ka_mode(link, agreed);
etcp_keepalive_send_mode_pkt(link, ETCP_KEEPALIVE_RESP, agreed);
}
static void handle_keepalive_resp(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link) {
uint8_t agreed = (pkt->data_len >= 2) ? pkt->data[1] : KA_MODE_NORMAL;
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive RESP recv: agreed=%s",
link->etcp->log_name, agreed == KA_MODE_STANDBY ? "standby" : "normal");
apply_link_ka_mode(link, agreed);
#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) {
@ -258,30 +332,52 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
uint8_t code = pkt->data[0];
if (code == ETCP_KEEPALIVE) {
if (pkt->data_len >= 3) {
uint16_t peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8);
link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
}
link->keepalive_recv_count++;
/* Сервер в standby синхронизируется с keepalive клиента: отвечает echo и
* пере-армит свой таймер на период+запас, чтобы собственная проба ушла
* только если клиент опаздывает. */
if (link->is_server && link->ka_pinned) {
etcp_link_send_keepalive(link);
restart_keepalive_timer_ms(link, (uint16_t)(link->ka_period_ms + KA_SYNC_MARGIN_MS));
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);
}
}
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 1;
}
if (code == ETCP_KEEPALIVE_REQ) {
handle_keepalive_req(e_sock, pkt, link);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 1;
}
if (code == ETCP_KEEPALIVE_RESP) {
handle_keepalive_resp(pkt, link);
link->keepalive_recv_count++;
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 1;
}
@ -290,36 +386,27 @@ 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)active; (void)arg;
(void)arg;
if (!inst || inst->client_type != CLIENT_TYPE_MOBILE) return;
uint8_t mode = mobile_desired_ka_mode(inst);
int sent = 0;
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) { etcp_keepalive_send_mode_pkt(l, ETCP_KEEPALIVE_REQ, mode); sent++; }
}
}
}
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) { etcp_keepalive_send_mode_pkt(l, ETCP_KEEPALIVE_REQ, mode); sent++; }
}
}
}
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);
}
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "keepalive activity change: mode=%s -> REQ sent to %d links (node=%016llx)",
mode == KA_MODE_STANDBY ? "standby" : "normal", sent, (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);
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "keepalive activity callback registered");
#ifdef UTUN_HAVE_STANDBY
standby_set_phase_callback(ka_phase_cb, inst);
#endif
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "keepalive activity + phase callbacks registered");
}

56
src/transport_layer/etcp_keepalive.h

@ -5,25 +5,45 @@
extern "C" {
#endif
// подмодуль ETCP: keepalive-пакеты, таймер, адаптивный период и протокол смены
// keepalive-режима (standby/normal) при смене active mode у мобильных узлов.
// подмодуль ETCP: keepalive-пакеты, таймер, адаптивный период и протокол
// спячки (sleep) на флагах самого keepalive-пакета.
#include "etcp_connections.h"
// Типы кодограмм keepalive
#define ETCP_KEEPALIVE_REQ 0x09 // запрос смены keepalive-режима (data[1] = режим)
#define ETCP_KEEPALIVE_RESP 0x0A // ответ: согласованный режим (data[1] = режим)
/* ─── Протокол спячки поверх keepalive-пакета (code ETCP_KEEPALIVE = 0x08) ───
*
* Режим спячки передаётся флагом в самом keepalive (отдельные кодограммы
* REQ/RESP не используются). Формат keepalive-пакета:
*
* data[0] = ETCP_KEEPALIVE (0x08)
* data[1] = flags (bit0 = KA_FLAG_SLEEP — спячка)
* data[2..3] = ka_period_ms (LE 16-bit) — период keepalive в спячке, мс
* data[4..5] = sleep_time (LE 16-bit, x0.1с) — дедлайн пробуждения (при KA_FLAG_SLEEP)
*
* Засыпание (клиент, перед входом в каждый цикл спячки duty-cycle):
* 1) шлёт keepalive с KA_FLAG_SLEEP + sleep_time. sleep_time = остаток ACTIVE +
* полная длительность SLEEP (дедлайн, после которого клиент ТОЧНО проснётся;
* может и раньше). В спячке при ретраях корректируется вниз до остатка SLEEP.
* 2) ретраит каждые KA_SLEEP_RETRY_MS, пока не получит keepalive с KA_FLAG_SLEEP
* (ack сервера); суммарно KA_SLEEP_ANNOUNCE_TIMEOUT_MS, после чего спит anyway.
* 3) сервер отвечает ответным keepalive с KA_FLAG_SLEEP (ack) и переводит период
* keepalive в спячку (ka_period_ms из пакета).
*
* В спячке (включая ACTIVE-фазу duty-cycle) клиент продолжает слать keepalive с
* KA_FLAG_SLEEP. Сервер отвечает echo на входящие keepalive, либо, если дедлайн
* sleep_time вышел — шлёт серию keepalive с обычным таймаутом, пока не получит
* keepalive со sleep_time (клиент переанонсировал) или обычный (клиент проснулся).
*
* Пробуждение: только выход в форграунд — клиент шлёт обычный keepalive (без
* KA_FLAG_SLEEP), сервер возвращается в обычный адаптивный режим с базовым периодом.
*/
// Режимы keepalive (мобильные узлы)
#define KA_MODE_NORMAL 0 // активен: интервал 3с, адаптивный
#define KA_MODE_STANDBY 1 // спит: интервал 30с, пиннится
// Флаги keepalive-пакета (data[1])
#define KA_FLAG_SLEEP 0x01 // спячка: анонс (клиент) / ack-эхо (сервер)
#define KA_MOBILE_ACTIVE_MS 3000
#define KA_MOBILE_STANDBY_MS 30000
/* Запас (мс) поверх периода keepalive: сервер в standby пере-армит свой таймер
* на период+запас после эхо-ответа, чтобы пробу успеть отправить при опоздании клиента. */
#define KA_SYNC_MARGIN_MS 2000
// Параметры анонса спячки
#define KA_SLEEP_RETRY_MS 200 // ретрай анонса спячки (клиент)
#define KA_SLEEP_ANNOUNCE_TIMEOUT_MS 4000 // суммарный таймаут анонса спячки
// --- перенесены из etcp_connections.c (имена сохранены) ---
uint16_t negotiate_keepalive(uint8_t my_type, uint16_t my_ka,
@ -32,15 +52,19 @@ void etcp_link_send_keepalive(struct ETCP_LINK* link);
void start_keepalive_timer(struct ETCP_LINK* link);
// --- новое ---
// Обработка keepalive-пакетов (ETCP_KEEPALIVE/REQ/RESP) из etcp_packet_decrypted.
// Обработка keepalive-пакетов (ETCP_KEEPALIVE) из etcp_packet_decrypted.
// Возвращает 1 если пакет обработан (и освобождён), 0 — если это не keepalive.
int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
struct ETCP_LINK* link, size_t pkt_len);
// Подписка на смену active mode (рассылка KEEPALIVE_REQ всем линкам).
// Подписка на смену active mode (foreground/background) и фазы спячки duty-cycle.
// Вызывается из init_connections.
void etcp_keepalive_register(struct UTUN_INSTANCE* inst);
// Переанонсировать спячку на только что (пере)инициализированном линке.
// Вызывается из etcp_link_enter_ready_tcp / handle_init_response_client.
void etcp_keepalive_announce_mode(struct ETCP_LINK* link);
#ifdef __cplusplus
}
#endif

4
tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt

@ -46,6 +46,7 @@ class ConfigProvider(private val context: Context) {
private val chatStandbyActiveSec = intPreferencesKey("chat_standby_active_sec")
private val chatStandbySleepSec = intPreferencesKey("chat_standby_sleep_sec")
private val chatStandbyMinSleepSec = intPreferencesKey("chat_standby_min_sleep_sec")
private val chatStandbyKeepaliveMs = intPreferencesKey("chat_standby_keepalive_ms")
private val chatGroupAutoconnect = intPreferencesKey("chat_group_autoconnect")
private val firstLaunchDone = booleanPreferencesKey("first_launch_done")
private val connectionModeKey = stringPreferencesKey("connection_mode")
@ -131,6 +132,7 @@ class ConfigProvider(private val context: Context) {
key == "chat.standby_active_sec" -> context.dataStore.data.first()[chatStandbyActiveSec] ?: 2
key == "chat.standby_sleep_sec" -> context.dataStore.data.first()[chatStandbySleepSec] ?: 60
key == "chat.standby_min_sleep_sec" -> context.dataStore.data.first()[chatStandbyMinSleepSec] ?: 15
key == "chat.standby_keepalive_ms" -> context.dataStore.data.first()[chatStandbyKeepaliveMs] ?: 30000
key == "chat.group_autoconnect" -> context.dataStore.data.first()[chatGroupAutoconnect] ?: 1
else -> 0
}
@ -162,6 +164,7 @@ class ConfigProvider(private val context: Context) {
"chat.standby_active_sec" -> prefs[chatStandbyActiveSec] = value.toIntOrNull() ?: 2
"chat.standby_sleep_sec" -> prefs[chatStandbySleepSec] = value.toIntOrNull() ?: 60
"chat.standby_min_sleep_sec" -> prefs[chatStandbyMinSleepSec] = value.toIntOrNull() ?: 15
"chat.standby_keepalive_ms" -> prefs[chatStandbyKeepaliveMs] = value.toIntOrNull() ?: 30000
"chat.group_autoconnect" -> prefs[chatGroupAutoconnect] = value.toIntOrNull() ?: 1
}
}
@ -442,6 +445,7 @@ class ConfigProvider(private val context: Context) {
appendLine("standby_active_sec=${getInt("chat.standby_active_sec")}")
appendLine("standby_sleep_sec=${getInt("chat.standby_sleep_sec")}")
appendLine("standby_min_sleep_sec=${getInt("chat.standby_min_sleep_sec")}")
appendLine("standby_keepalive_ms=${getInt("chat.standby_keepalive_ms")}")
appendLine("group_autoconnect=${getInt("chat.group_autoconnect")}")
appendLine()
appendLine("[debug]")

7
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt

@ -117,6 +117,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
var standbyActiveSec by remember { mutableStateOf("2") }
var standbySleepSec by remember { mutableStateOf("60") }
var standbyMinSleepSec by remember { mutableStateOf("15") }
var standbyKeepaliveMs by remember { mutableStateOf("30000") }
val pubKey by vm.pubKey.collectAsState()
val generatingKeys by vm.generatingKeys.collectAsState()
@ -147,6 +148,7 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
standbyActiveSec = provider.getInt("chat.standby_active_sec").toString()
standbySleepSec = provider.getInt("chat.standby_sleep_sec").toString()
standbyMinSleepSec = provider.getInt("chat.standby_min_sleep_sec").toString()
standbyKeepaliveMs = provider.getInt("chat.standby_keepalive_ms").toString()
val port = logUdpPort.toIntOrNull() ?: 0
if (logUdpIp.isNotEmpty() && port > 0) NativeLib.setUdpLogTarget(logUdpIp, port)
val stored = provider.getString("debug.categories") ?: ""
@ -345,6 +347,11 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
scope.launch { provider.setValue("chat.standby_min_sleep_sec", v) }; NativeLib.setChatSetting("standby_min_sleep_sec", v) } },
label = { Text("Min sleep (sec)") }, modifier = Modifier.heightIn(min = 40.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
OutlinedTextField(standbyKeepaliveMs,
{ v -> standbyKeepaliveMs = v; val nb = v.toIntOrNull(); if (nb != null && nb in 1000..600000) {
scope.launch { provider.setValue("chat.standby_keepalive_ms", v) }; NativeLib.setChatSetting("standby_keepalive_ms", v) } },
label = { Text("Keepalive in sleep (ms)") }, modifier = Modifier.heightIn(min = 40.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true)
HorizontalDivider(Modifier.padding(vertical = 8.dp))

26
tools/chatgui-android/libutun_lite/standby.c

@ -47,6 +47,8 @@ static uint64_t g_min_sleep_end_tb = 0; /* до этой точки спим б
static void* g_phase_timer = NULL;
static void (*g_switch_cb)(int enabled, void* arg) = NULL;
static void* g_switch_arg = NULL;
static void (*g_phase_cb)(int sleeping, void* arg) = NULL;
static void* g_phase_cb_arg = NULL;
static struct memory_pool* g_wait_pool = NULL;
static struct standby_wait_entry* g_wait_head = NULL;
@ -126,10 +128,12 @@ static void standby_phase_timer_cb(void* arg) {
"standby: SLEEP (active done in %d.%ds, sleep=%dms min_sleep=%dms)",
(int)(elapsed / STANDBY_TB_PER_SEC), (int)((elapsed % STANDBY_TB_PER_SEC) / 1000),
standby_sleep_tb() / 10, standby_min_sleep_tb() / 10);
if (g_phase_cb) g_phase_cb(1, g_phase_cb_arg);
} else {
g_cnt_wakes_timer++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: WAKE (timer), slept %d.%ds",
(int)(elapsed / STANDBY_TB_PER_SEC), (int)((elapsed % STANDBY_TB_PER_SEC) / 1000));
if (g_phase_cb) g_phase_cb(0, g_phase_cb_arg);
}
standby_arm_phase();
}
@ -286,6 +290,28 @@ int standby_get_active_remaining_tb(void) {
return (int)(g_phase_end_tb - now);
}
int standby_get_sleep_duration_tb(void) {
return standby_sleep_tb();
}
int standby_get_wake_deadline_tb(void) {
if (!g_enabled) return 0;
if (g_phase == PHASE_SLEEP) return standby_get_sleep_tb();
/* ACTIVE: остаток активного + полная длительность предстоящего сна */
return standby_get_active_remaining_tb() + standby_sleep_tb();
}
int standby_get_keepalive_ms(void) {
int ms = chat_setting_get_int(g_inst, "standby_keepalive_ms", 30000);
if (ms < 100) ms = 100;
return ms;
}
void standby_set_phase_callback(void (*cb)(int sleeping, void* arg), void* arg) {
g_phase_cb = cb;
g_phase_cb_arg = arg;
}
void* standby_wait(void* arg, void (*cb)(void* arg)) {
if (!cb) return NULL;
if (!g_ua) {

13
tools/chatgui-android/libutun_lite/standby.h

@ -39,6 +39,19 @@ int standby_get_sleep_tb(void);
/* Сколько TB осталось активного режима (0 = спим или standby выключен). */
int standby_get_active_remaining_tb(void);
/* Полная длительность SLEEP-фазы duty-cycle (timebase 0.1мс). */
int standby_get_sleep_duration_tb(void);
/* Время до гарантированного пробуждения (timebase 0.1мс): остаток SLEEP, либо
* остаток ACTIVE + полная длительность SLEEP. 0 = standby выключен. */
int standby_get_wake_deadline_tb(void);
/* Период keepalive в спячке (мс), из настройки standby_keepalive_ms. */
int standby_get_keepalive_ms(void);
/* Колбэк смены фазы duty-cycle: sleeping=1 — вошли в SLEEP, 0 — вышли в ACTIVE. */
void standby_set_phase_callback(void (*cb)(int sleeping, void* arg), void* arg);
/* Ждёт до начала следующего активного интервала (как uasync timeout).
* При выходе из standby таймер отменяется и cb вызывается немедленно.
* Возвращает handle для standby_wait_cancel(). */

Loading…
Cancel
Save