@ -14,9 +14,12 @@
// 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 ) ;
@ -130,10 +133,9 @@ static void keepalive_timer_cb(void* arg) {
link - > keepalive_timer = NULL ;
// В режиме спячки (я сплю ИЛИ пир спит) обе стороны молчат: только переарм —
// никакого recovery/dead-link/отправки. Фазу сигналят фазовые переходы (ka_phase_cb),
// NAT держит сам сон (sleep ≤ NAT-таймаут).
if ( link - > ka_sleeping ) {
// Обычный 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 ;
@ -190,10 +192,8 @@ static void keepalive_timer_cb(void* arg) {
}
}
// Период: спячка — фиксированный sleep-период; normal — адаптивный от базового.
if ( link - > ka_sleeping )
link - > ka_period_ms = link - > ka_sleep_period_ms ;
else if ( link - > pkt_sent_since_keepalive )
// Период: адаптивный от базового (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 ) {
@ -202,11 +202,9 @@ static void keepalive_timer_cb(void* arg) {
}
link - > pkt_sent_since_keepalive = 0 ;
// Отправка keepalive: в спячке НЕ шлём (ни клиент, ни сервер) — фазу сигналят фазовые переходы.
if ( ! link - > ka_sleeping ) {
if ( ! link - > is_server | | link - > recv_keepalive )
etcp_link_send_keepalive ( link ) ;
}
// Отправка обычного 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 " ) ;
@ -248,15 +246,70 @@ static uint16_t ka_sleep_duration_01s(void) {
# 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 ) ;
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 " ) ;
}
}
// Заармить ретрай анонса (интервал = 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 " ) ;
}
// Ответ на анонс спячки пира: подтверждение доставки (без тайминга).
@ -268,48 +321,62 @@ 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 ) ; // ретрай
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_sleeping = 1 ;
link - > ka_sleep_acked = 0 ;
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 ;
link - > ka_sleep_announce_end_tb = get_time_tb ( ) + ( uint64_t ) KA_SLEEP_ANNOUNCE_TIMEOUT_MS * 10 ;
ka_stop_keepalive_timer ( link ) ;
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_reannounce ( struct ETCP_LINK * link ) {
if ( ! link - > ka_sleeping ) return ;
ka_send_sleep_keepalive ( link ) ;
ka_announce_burst ( link ) ;
}
// Пробуждение (форграунд) — обычный keepalive, возврат к базовому период у.
// Пробуждение (форграунд) — обычный keepalive, возврат к normal-режиму.
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 ;
if ( ! link - > ka_my_sleeping ) return ;
link - > ka_my_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_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 ) ;
restart_keepalive_timer_ms ( link , link - > ka_period_ms ) ;
if ( ! link - > ka_peer_sleeping )
restart_keepalive_timer_ms ( link , link - > ka_period_ms ) ;
}
typedef void ( * ka_link_fn ) ( struct ETCP_LINK * link ) ;
@ -334,10 +401,10 @@ static void ka_for_each_link(struct UTUN_INSTANCE* inst, ka_link_fn fn) {
}
# ifdef UTUN_HAVE_STANDBY
// Вход в ACTIVE-окно — отправить keepalive с active_remaining>0 (флаг стоит ).
// Вход в ACTIVE-окно — анонс-серия с ретраями до ACK (active_remaining>0 ).
static void ka_active_announce ( struct ETCP_LINK * link ) {
if ( ! link - > ka_sleeping ) return ;
ka_send_sleep_keepalive ( link ) ;
if ( ! link - > ka_my_ sleeping ) return ;
ka_announce_burst ( link ) ;
}
// Фаза duty-cycle: SLEEP → переанонс спячки; ACTIVE → анонс активности (флаг всегда стоит).
@ -345,8 +412,11 @@ 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_reannounce ) ;
DEBUG_INFO ( DEBUG_CATEGORY_KEEPALIVE , " keepalive: duty-cycle SLEEP phase, re-announce (node=%016llx) " ,
/* 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 ) ;
@ -409,24 +479,24 @@ 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_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 ;
}
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_sleeping = 1 ;
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 ;
@ -438,10 +508,9 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
( 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 ;
} 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 ) {
@ -453,7 +522,8 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
}
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 ) ;
if ( ! link - > ka_my_sleeping )
restart_keepalive_timer_ms ( link , link - > ka_period_ms ) ;
}
link - > keepalive_recv_count + + ;
@ -472,13 +542,15 @@ static void etcp_keepalive_on_activity(struct UTUN_INSTANCE* inst, int active, v
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 ;
// На только что (пере)инициализированном линке: если я в 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 ;
if ( ! inst - > standby_enabled ) return ; /* не в standby — обычный режим */
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 ) {