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.
 
 
 
 
 
 

209 lines
12 KiB

/* conn_mgr_monitor.c — bg_ping, candidate_ping, idle, best_candidates */
#include "conn_mgr_priv.h"
#include <string.h>
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "etcp_router.h"
#include "topo_node.h"
#include "topo_group.h"
#include "route_connectivity.h"
#include "route_ping.h"
#ifdef UTUN_HAVE_STANDBY
#include "standby.h"
#endif
/* ═══════ probe time утилиты ═══════ */
uint64_t cm_get_node_max_probe_time(struct TOPO_GROUP_NODE* nq) {
uint64_t t = nq->connectivity.interface_probe_time;
if (nq->connectivity.nat_probe_time > t) t = nq->connectivity.nat_probe_time;
if (nq->connectivity.real_probe_time > t) t = nq->connectivity.real_probe_time;
return t;
}
int cm_is_rtt_fresh(struct TOPO_GROUP_NODE* nq, uint64_t now_tb) {
return cm_get_node_max_probe_time(nq) > 0 && (now_tb - cm_get_node_max_probe_time(nq)) < (uint64_t)(CACHE_MS * 10);
}
/* ═══════ best_candidates ═══════ */
/* Обновить RTT кандидата-посредника в отсортированном списке (≤3). Вызывается
* из route_connectivity после успешного probe. INDIRECT-фаза использует этот
* список (best_candidates) для выбора посредника с минимальным суммарным RTT. */
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt) {
if (!mgr || node_id == mgr->instance->node_id || !rtt) return;
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
if (mgr->best_candidates[i].node_id != node_id) continue;
mgr->best_candidates[i].rtt = rtt;
for (uint8_t j = i; j > 0 && mgr->best_candidates[j].rtt < mgr->best_candidates[j-1].rtt; j--)
{ struct CONN_MGR_CANDIDATE t = mgr->best_candidates[j]; mgr->best_candidates[j]=mgr->best_candidates[j-1]; mgr->best_candidates[j-1]=t; }
for (uint8_t j = i; j + 1 < mgr->best_candidate_count && mgr->best_candidates[j].rtt > mgr->best_candidates[j+1].rtt; j++)
{ struct CONN_MGR_CANDIDATE t = mgr->best_candidates[j]; mgr->best_candidates[j]=mgr->best_candidates[j+1]; mgr->best_candidates[j+1]=t; }
return;
}
if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES)
{ mgr->best_candidates[mgr->best_candidate_count++]=(struct CONN_MGR_CANDIDATE){node_id,rtt}; }
else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES-1].rtt)
{ mgr->best_candidates[CONN_MGR_MAX_CANDIDATES-1]=(struct CONN_MGR_CANDIDATE){node_id,rtt}; }
else return;
for (uint8_t i = mgr->best_candidate_count - 1; i > 0 && mgr->best_candidates[i].rtt < mgr->best_candidates[i-1].rtt; i--)
{ struct CONN_MGR_CANDIDATE t = mgr->best_candidates[i]; mgr->best_candidates[i]=mgr->best_candidates[i-1]; mgr->best_candidates[i-1]=t; }
}
/* Маршрут может вести через посредника. Для прямой пробы нужны transport peer и его живой линк. */
static struct ETCP_LINK* cm_active_direct_link(struct TOPO_GROUP* group, uint64_t node_id) {
struct ETCP_CONN* conn = instance_find_conn(group->instance,node_id);
if (!conn || conn->state != 1 || conn->close_requested) return NULL;
for (struct ETCP_LINK* link = conn->links; link; link = link->next)
if (link->initialized && link->link_status) return link;
return NULL;
}
/* ═══════ bg_ping ═══════ */
static void cm_bg_ping_noop_cb(int s, uint16_t r, void* a, uint64_t n, const uint8_t* d, size_t l) { (void)s;(void)r;(void)a;(void)n;(void)d;(void)l; }
/* Candidate TCP-пинг: по завершении (успех/ошибка/таймаут) зануляет свой слот в
* mgr->cand_tcp_pings, чтобы conn_mgr_destroy не трогал уже освобождённый handle. */
static void cm_cand_tcp_ping_cb(int s, uint16_t r, void* arg, uint64_t n, const uint8_t* d, size_t l) {
(void)s;(void)r;(void)n;(void)d;(void)l;
struct stcp_client** slot = (struct stcp_client**)arg;
if (slot) *slot = NULL;
}
static void cm_bg_ping_to_node(struct CONN_MGR* mgr, struct TOPO_NODE* ni) {
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
if (a->type != TOPO_ADDR_NAT && a->type != TOPO_ADDR_INTERFACE) continue;
struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); sin.sin_family=AF_INET;
memcpy(&sin.sin_addr.s_addr,a->addr,4); sin.sin_port=htons(a->port);
struct sockaddr_storage sa; memcpy(&sa,&sin,sizeof(sin));
struct ETCP_SOCKET* s = mgr->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET) break; s = s->next; }
if (!s) break;
etcp_send_ping_to_socket(mgr->instance, s, ni->public_key, &sa, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0, 0);
return;
}
for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) {
if (a6->type != TOPO_ADDR_NAT && a6->type != TOPO_ADDR_INTERFACE) continue;
struct sockaddr_in6 sin6; memset(&sin6,0,sizeof(sin6)); sin6.sin6_family=AF_INET6;
memcpy(&sin6.sin6_addr,a6->addr,16); sin6.sin6_port=htons(a6->port);
struct ETCP_SOCKET* s = mgr->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET6) break; s = s->next; }
if (!s) break;
if (cm_classify_v6_addr(a6->addr) == CM_V6_LL) sin6.sin6_scope_id = s->netif_index;
struct sockaddr_storage sa; memcpy(&sa,&sin6,sizeof(sin6));
etcp_send_ping_to_socket(mgr->instance, s, ni->public_key, &sa, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0, 0);
return;
}
}
/* Фоновый ping: один узел группы за тик (~100ms), полный цикл ≥ 10с.
* Пингует только если нет активного ETCP-соединения с узлом. Цель: поддерживать
* свежие RTT-замеры для ВСЕХ узлов группы — без этого best_candidates протухают
* и INDIRECT-фаза не может выбрать посредника. */
void cm_bg_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
#ifdef UTUN_HAVE_STANDBY
if (standby_get_sleep_tb() > 0) {
mgr->bg_ping_wait = standby_wait(mgr, cm_bg_ping_timer_cb);
return;
}
mgr->bg_ping_wait = NULL;
#endif
struct TOPO_GROUP* group = mgr->group;
if (!group || !group->nodes) goto rearm;
size_t total = queue_entry_count(group->nodes);
if (!total) goto rearm;
uint64_t now = get_time_tb();
if (mgr->bg_ping_cursor >= total) {
mgr->bg_ping_cursor = 0;
if ((now - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB)
{ mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)(CONN_MGR_BG_PING_CYCLE_MIN_TB - (now - mgr->bg_ping_cycle_start_tb)), mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping"); return; }
mgr->bg_ping_cycle_start_tb = now;
}
struct ll_entry* e = group->nodes->head;
for (size_t c = 0; e && c < mgr->bg_ping_cursor; c++) e = e->next;
if (e) {
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
if (nq->node_id != mgr->instance->node_id) {
int has_active = cm_active_direct_link(group,nq->node_id) != NULL;
if (!has_active) {
struct TOPO_NODE* bg_ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id);
if (bg_ni) cm_bg_ping_to_node(mgr, bg_ni);
}
}
mgr->bg_ping_cursor++;
}
rearm:
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
}
/* ═══════ candidate_ping ═══════ */
static int cm_has_active_conn(struct TOPO_GROUP* g, uint64_t nid) {
return cm_active_direct_link(g,nid) != NULL;
}
/* Обновление кандидатов-посредников: удаляет протухших (>30с без свежих RTT),
* активных пингует напрямую, неактивных probe'ит через route_connectivity.
* Вызывается раз в ~2с, самоперезапускается. */
void cm_candidate_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg; uint64_t now = get_time_tb();
#ifdef UTUN_HAVE_STANDBY
if (standby_get_sleep_tb() > 0) {
mgr->candidate_ping_wait = standby_wait(mgr, cm_candidate_ping_timer_cb);
return;
}
mgr->candidate_ping_wait = NULL;
#endif
/* Кандидаты-посредники выводятся из topo_node-списка группы: добавляем все
* узлы с активным соединением (посредник должен быть напрямую связан с нами). */
{
struct ll_entry* e = mgr->group->nodes ? mgr->group->nodes->head : NULL;
while (e) {
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
if (nq->node_id != mgr->instance->node_id && cm_has_active_conn(mgr->group, nq->node_id)) {
uint16_t rtt = topo_get_chain_rtt(nq);
if (rtt == 0 || rtt == 0xFFFF) rtt = 1;
conn_mgr_update_best_candidates(mgr, nq->node_id, rtt);
}
e = e->next;
}
}
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
uint64_t nid = mgr->best_candidates[i].node_id;
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, nid);
if (!nq) { memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (--mgr->best_candidate_count - i) * sizeof(mgr->best_candidates[0])); i--; continue; }
if (!cm_has_active_conn(mgr->group, nid)
&& cm_get_node_max_probe_time(nq) > 0
&& (now - cm_get_node_max_probe_time(nq)) >= CONN_MGR_CANDIDATE_STALE_TB)
{ DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)nid);
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (--mgr->best_candidate_count - i) * sizeof(mgr->best_candidates[0])); i--; continue; }
struct ETCP_LINK* link = cm_active_direct_link(mgr->group,nid);
if (link) {
struct ETCP_CONN* dc = link->etcp;
struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id);
/* пир в SLEEP-фазе — прямое соединение живо, пинг будит его: пропускаем */
if (ni && !dc->peer_sleep_phase) {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "candidate probe: group=%016llx peer=%016llx link=%u transport=%s",
(unsigned long long)mgr->group->group_id, (unsigned long long)nid, link->local_link_id,
link->is_tcp ? "TCP" : "UDP");
if (link->is_tcp) {
struct stcp_client** slot = NULL;
for (uint8_t i = 0; i < CONN_MGR_MAX_CANDIDATES; i++)
if (!mgr->cand_tcp_pings[i]) { slot = &mgr->cand_tcp_pings[i]; break; }
if (slot) etcp_send_tcp_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_cand_tcp_ping_cb, slot, slot, link->reality_set ? &link->reality : NULL);
} else
etcp_send_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0);
}
} else route_connectivity_probe_node(mgr->instance, mgr->group, nq);
}
mgr->candidate_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
}