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.
752 lines
43 KiB
752 lines
43 KiB
/* conn_mgr_core.c — init/destroy, open/close/handles, DIRECT, REVERSE, invite */ |
|
#include "conn_mgr_priv.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
#ifdef _WIN32 |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#else |
|
#include <arpa/inet.h> |
|
#endif |
|
#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 "stcp_client.h" |
|
#include "etcp_router.h" |
|
#include "config_parser.h" |
|
#include "topo_node.h" |
|
#include "topo_node_sqlite.h" |
|
#include "sock_match.h" |
|
#include "topo_group.h" |
|
#include "route_ping.h" |
|
#ifdef UTUN_HAVE_STANDBY |
|
#include "standby.h" |
|
#endif |
|
|
|
/* ═══════ forward-декларации ═══════ */ |
|
|
|
void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry); |
|
static void cm_attempt_tick(void* arg); |
|
static void cm_start_attempt(struct CONN_MGR_ENTRY* entry, int recovery); |
|
|
|
static const char* cm_path_name(uint8_t type) { |
|
static const char* names[] = { "NONE", "DIRECT", "REVERSE", "INDIRECT" }; |
|
return type <= CONN_TYPE_INDIRECT ? names[type] : "INVALID"; |
|
} |
|
|
|
/* ═══════ утилиты ═══════ */ |
|
|
|
static struct CONN_MGR_HANDLE* cm_handle_new(struct CONN_MGR_ENTRY* entry, uint64_t group_id, |
|
conn_mgr_cb_t cb, void* cb_arg) { |
|
struct CONN_MGR_HANDLE* h = u_calloc(1, sizeof(*h)); |
|
if (!h) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: handle allocation failed"); return NULL; } |
|
h->node_id = entry->node_id; h->group_id = group_id; h->cb = cb; h->cb_arg = cb_arg; |
|
h->ua = entry->mgr->instance->ua; |
|
h->entry = entry; h->next = entry->handles; entry->handles = h; |
|
return h; |
|
} |
|
|
|
static void cm_deliver_up_cb(void* arg) { |
|
struct CONN_MGR_HANDLE* h = (struct CONN_MGR_HANDLE*)arg; |
|
h->deliver_up_token = NULL; |
|
if (!h->closed && h->entry && h->entry->state == CONN_MGR_STATE_CONNECTED && h->cb) |
|
h->cb(h, h->node_id, h->group_id, CONN_EVENT_UP, h->cb_arg); |
|
} |
|
|
|
/* Закрытые handles освобождаются через call_soon, поэтому next переживает callbacks. |
|
* Рассылает событие ВСЕМ handle'ам entry. Одно соединение — много слушателей: |
|
* каждый conn_mgr_open создал свой handle, все получают UP/DOWN/TIMEOUT. |
|
* Все поля handle'а читаются до вызова коллбэка — коллбэк может закрыть |
|
* handle (conn_mgr_close), после вызова к handle не обращаемся. */ |
|
void cm_deliver_event(struct CONN_MGR_ENTRY* entry, enum conn_mgr_event event) { |
|
if (!entry || !entry->mgr || !entry->mgr->group) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "cm_deliver_event: BAD entry=%p mgr=%p", entry, entry ? (void*)entry->mgr : NULL); |
|
return; |
|
} |
|
uint64_t gid = entry->mgr->group->group_id; |
|
struct CONN_MGR_HANDLE* h = entry->handles; |
|
while (h) { |
|
struct CONN_MGR_HANDLE* next = h->next; |
|
uint64_t node_id = h->node_id; |
|
conn_mgr_cb_t cb = h->cb; |
|
void* cb_arg = h->cb_arg; |
|
if (event != CONN_EVENT_UP && h->deliver_up_token) { |
|
uasync_call_soon_cancel(h->ua, h->deliver_up_token); h->deliver_up_token = NULL; |
|
} |
|
if (!h->closed && h->entry == entry && cb) cb(h, node_id, gid, event, cb_arg); |
|
h = next; |
|
} |
|
} |
|
|
|
/* ═══════ NAT / адресные утилиты ═══════ */ |
|
|
|
/* Классификация IPv6-адреса: link-local (fe80), локальный ULA (fc/fd), |
|
* глобальный/прямой, другое (loopback, multicast, ::). */ |
|
uint8_t cm_classify_v6_addr(const uint8_t addr[16]) { |
|
if (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80) return CM_V6_LL; |
|
if (addr[0] == 0xfc || addr[0] == 0xfd) return CM_V6_LOC; |
|
if (addr[0] == 0xff) return CM_V6_OTH; |
|
{ static const uint8_t z[16]; if (!memcmp(addr, z, 16)) return CM_V6_OTH; } |
|
{ static const uint8_t lb[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; if (!memcmp(addr, lb, 16)) return CM_V6_OTH; } |
|
return CM_V6_DIR; |
|
} |
|
|
|
/* Та же классификация, что при создании линков; EIM и прямой IPv6 доступны извне. */ |
|
int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) { |
|
struct TOPO_NODE* ni = nq ? topo_node_registry_find(mgr->instance->topo_groups, nq->node_id) : NULL; |
|
if (!ni) return 0; |
|
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { |
|
if (!(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) continue; |
|
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) |
|
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, m->nat_type) == SOCK_TC_PUB) return 1; |
|
} |
|
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) |
|
if ((a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP)) && cm_classify_v6_addr(a->addr) == CM_V6_DIR) return 1; |
|
return 0; |
|
} |
|
|
|
/* ═══════ entries ═══════ */ |
|
|
|
struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) { |
|
if (!mgr || !mgr->entries) return NULL; |
|
struct ll_entry* e = queue_find_data_by_index(mgr->entries, &node_id); |
|
return e ? (struct CONN_MGR_ENTRY*)e : NULL; |
|
} |
|
|
|
struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id) { |
|
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id); |
|
if (e) return e; |
|
struct ll_entry* qe = queue_entry_new(sizeof(struct CONN_MGR_ENTRY) - sizeof(struct ll_entry)); |
|
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: queue_entry_new failed"); return NULL; } |
|
struct CONN_MGR_ENTRY* new_e = (struct CONN_MGR_ENTRY*)qe; |
|
memset((uint8_t*)new_e + sizeof(struct ll_entry), 0, sizeof(*new_e) - sizeof(struct ll_entry)); |
|
new_e->node_id = node_id; new_e->mgr = mgr; |
|
if (queue_data_put_with_index(mgr->entries, qe) < 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: entry insertion failed peer=%016llx", (unsigned long long)node_id); |
|
queue_entry_free(qe); return NULL; |
|
} |
|
return new_e; |
|
} |
|
|
|
static void cm_entry_free_cb(void* arg) { queue_entry_free(arg); } |
|
static void cm_handle_free_cb(void* arg) { u_free(arg); } |
|
|
|
void cm_entry_cleanup(struct CONN_MGR_ENTRY* entry) { |
|
if (!entry) return; |
|
struct CONN_MGR* mgr = entry->mgr; |
|
if (cm_find_entry(mgr, entry->node_id) != entry) return; |
|
entry->state = CONN_MGR_STATE_DISCONNECTED; |
|
queue_remove_data(mgr->entries, &entry->ll); |
|
if (entry->timer) { uasync_cancel_timeout(mgr->instance->ua, entry->timer); entry->timer = NULL; } |
|
if (entry->refresh_token) { uasync_call_soon_cancel(mgr->instance->ua, entry->refresh_token); entry->refresh_token = NULL; } |
|
for (struct CONN_MGR_HANDLE* h = entry->handles; h; h = h->next) { |
|
if (h->deliver_up_token) { uasync_call_soon_cancel(h->ua, h->deliver_up_token); h->deliver_up_token = NULL; } |
|
h->entry = NULL; // Владелец обязан закрыть отсоединённый handle. |
|
} |
|
entry->handles = NULL; |
|
struct NODE_CONN_DIRECT* ncd = entry->ncd_handle; entry->ncd_handle = NULL; |
|
if (ncd) node_conn_direct_force_close(ncd); |
|
cm_clear_nodeinfo(mgr, entry->node_id); |
|
cm_cleanup_db_node(entry); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: cleanup group=%016llx peer=%016llx", |
|
(unsigned long long)mgr->group->group_id, (unsigned long long)entry->node_id); |
|
uasync_call_soon(mgr->instance->ua, entry, cm_entry_free_cb); |
|
} |
|
|
|
void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id) { |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, node_id); |
|
if (!nq) return; |
|
nq->conn_mgr_type = CONN_TYPE_NONE; nq->conn_mgr_intermediariy_count = 0; |
|
nq->conn_presence &= ~NCONN_INDIRECT; nq->conn_up &= ~NCONN_INDIRECT; |
|
} |
|
|
|
void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) { |
|
if (!entry->mgr->group) return; |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(entry->mgr->group, entry->node_id); |
|
if (!nq) return; |
|
nq->conn_mgr_type = entry->conn_type; |
|
memcpy(nq->conn_mgr_intermediaries, entry->intermediaries, sizeof(entry->intermediaries)); |
|
nq->conn_mgr_intermediariy_count = entry->intermediariy_count; |
|
if (entry->conn_type == CONN_TYPE_INDIRECT) { nq->conn_presence |= NCONN_INDIRECT; nq->conn_up |= NCONN_INDIRECT; } |
|
else { nq->conn_presence &= ~NCONN_INDIRECT; nq->conn_up &= ~NCONN_INDIRECT; } |
|
topo_fire_nodeinfo_cbk(entry->mgr->instance, entry->mgr->group, nq); |
|
} |
|
|
|
void cm_cleanup_db_node(struct CONN_MGR_ENTRY* entry) { |
|
if (!entry || !entry->mgr || !entry->mgr->group || !entry->db_loaded) return; |
|
struct TOPO_GROUP* group = entry->mgr->group; |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, entry->node_id); |
|
if (!nq) return; |
|
if (nq->paths && queue_entry_count(nq->paths) > 0) { |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: db_node 0x%016llx got paths, keep in group", (unsigned long long)entry->node_id); |
|
return; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: removing db_node 0x%016llx from group (no paths)", (unsigned long long)entry->node_id); |
|
topo_nodeq_remove_node(group, nq); |
|
} |
|
|
|
/* Прямой транспорт пригоден только вместе с READY-сессией этой группы. */ |
|
struct ETCP_CONN* cm_direct_conn(struct CONN_MGR* mgr, uint64_t node_id) { |
|
struct ETCP_CONN* conn = topo_group_find_conn_for_node(mgr->group, node_id); |
|
return conn && conn->peer_node_id == node_id && conn->links_up && !conn->close_requested && |
|
topo_group_peer_ready(mgr->group, node_id) ? conn : NULL; |
|
} |
|
|
|
/* READY первого плеча недостаточно: посредник должен ещё объявлять маршрут к получателю. */ |
|
static struct ETCP_CONN* cm_intermediary_conn(struct CONN_MGR_ENTRY* entry, uint64_t node_id) { |
|
struct ETCP_CONN* conn = cm_direct_conn(entry->mgr, node_id); |
|
struct TOPO_GROUP_NODE* target = topo_node_find_by_id(entry->mgr->group, entry->node_id); |
|
for (struct ll_entry* e = conn && target && target->paths ? target->paths->head : NULL; e; e = e->next) |
|
if (((struct TOPO_NODEPATH*)e)->conn == conn) return conn; |
|
return NULL; |
|
} |
|
|
|
uint16_t cm_candidate_rtt(struct CONN_MGR* mgr, uint64_t node_id) { |
|
struct ETCP_CONN* conn = cm_direct_conn(mgr, node_id); |
|
if (!conn) return UINT16_MAX; |
|
uint16_t rtt = conn->rtt_last; |
|
if (!rtt) { |
|
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, node_id); |
|
// Нулевой текущий RTT на loopback допустим, если успешный замер уже был. |
|
if (conn->last_rtt_cb_time) return 1; |
|
if (nq && cm_is_rtt_fresh(nq, get_time_tb())) { |
|
rtt = nq->connectivity.real_min_rtt; |
|
if (!rtt) rtt = nq->connectivity.nat_min_rtt; |
|
if (!rtt) rtt = nq->connectivity.interface_min_rtt; |
|
} |
|
if (!rtt) DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: READY candidate lacks RTT peer=%016llx", |
|
(unsigned long long)node_id); |
|
} |
|
return rtt && rtt != UINT16_MAX ? rtt : UINT16_MAX; |
|
} |
|
|
|
struct ETCP_CONN* conn_mgr_route_conn(struct CONN_MGR* mgr, uint64_t node_id, int allow_probe) { |
|
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); |
|
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return NULL; |
|
struct ETCP_CONN* direct = cm_direct_conn(mgr, node_id); |
|
if (direct) return direct; |
|
for (uint8_t i = 0; entry->indirect_confirmed && i < entry->intermediariy_count; i++) { |
|
struct ETCP_CONN* conn = cm_intermediary_conn(entry, entry->intermediaries[i]); |
|
if (conn) return conn; |
|
} |
|
// Пока нет ACK, через этот путь идёт проверка; сервис ещё не получил UP. |
|
for (uint8_t i = 0; allow_probe && i < entry->selection.count; i++) { |
|
struct ETCP_CONN* conn = cm_intermediary_conn(entry, entry->selection.selected[i].node_id); |
|
if (conn) return conn; |
|
} |
|
return NULL; |
|
} |
|
|
|
/* Исходящие и входящие линки могут сосуществовать; тип не зависит от порядка их создания. */ |
|
uint8_t conn_mgr_connection_type(const struct ETCP_CONN* conn, uint64_t node_id) { |
|
if (!conn || !conn->links_up || conn->close_requested || conn->state == 2) return CONN_TYPE_NONE; |
|
if (conn->peer_node_id != node_id) return CONN_TYPE_INDIRECT; |
|
int incoming = 0; |
|
for (const struct ETCP_LINK* link = conn->links; link; link = link->next) { |
|
if (!link->initialized || !link->link_status) continue; |
|
if (!link->is_server) return CONN_TYPE_DIRECT; |
|
incoming = 1; |
|
} |
|
return incoming ? CONN_TYPE_REVERSE : CONN_TYPE_NONE; |
|
} |
|
|
|
/* Смена пути не сбрасывает router-очереди и не повторяет UP при улучшении. */ |
|
void cm_refresh_path(struct CONN_MGR_ENTRY* entry) { |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
uint8_t type = CONN_TYPE_NONE; |
|
struct ETCP_CONN* direct = cm_direct_conn(entry->mgr, entry->node_id); |
|
if (direct) type = conn_mgr_connection_type(direct, entry->node_id); |
|
if (entry->indirect_confirmed) { |
|
int reserve_up = 0; |
|
for (uint8_t i = 0; i < entry->intermediariy_count; i++) |
|
if (cm_intermediary_conn(entry, entry->intermediaries[i])) { reserve_up = 1; break; } |
|
if (reserve_up && !direct) type = CONN_TYPE_INDIRECT; |
|
if (!reserve_up) { |
|
entry->indirect_confirmed = 0; |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: reserve unavailable peer=%016llx", (unsigned long long)entry->node_id); |
|
} |
|
} |
|
int was_up = entry->state == CONN_MGR_STATE_CONNECTED; |
|
if (type == entry->conn_type && was_up == (type != CONN_TYPE_NONE)) return; |
|
struct ETCP_CONN* next = conn_mgr_route_conn(entry->mgr, entry->node_id, 0); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: path group=%016llx peer=%016llx %s->%s via=%016llx elapsed=%llums", |
|
(unsigned long long)entry->mgr->group->group_id, (unsigned long long)entry->node_id, |
|
cm_path_name(entry->conn_type), cm_path_name(type), (unsigned long long)(next ? next->peer_node_id : 0), |
|
(unsigned long long)((get_time_tb() - entry->start_tb) / 10)); |
|
int lost_direct = entry->conn_type == CONN_TYPE_DIRECT || entry->conn_type == CONN_TYPE_REVERSE; |
|
lost_direct &= type != CONN_TYPE_DIRECT && type != CONN_TYPE_REVERSE; |
|
entry->conn_type = type; |
|
entry->state = type ? CONN_MGR_STATE_CONNECTED : CONN_MGR_STATE_CONNECTING; |
|
if (entry->handles && (lost_direct || (was_up && !type))) { |
|
entry->attempt_active = 0; entry->retry_tb = get_time_tb(); entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
} |
|
cm_update_nodeinfo(entry); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
if (!was_up && type) { entry->ever_up = 1; cm_deliver_event(entry, CONN_EVENT_UP); } |
|
else if (was_up && !type) cm_deliver_event(entry, CONN_EVENT_DOWN); |
|
} |
|
|
|
static void cm_refresh_cb(void* arg) { |
|
struct CONN_MGR_ENTRY* entry = arg; entry->refresh_token = NULL; |
|
struct TOPO_NODE* ni = topo_node_registry_find(entry->mgr->instance->topo_groups, entry->node_id); |
|
if (entry->handles && ni && ni->timestamp != entry->node_timestamp && !cm_direct_conn(entry->mgr, entry->node_id)) { |
|
entry->attempt_active = 0; entry->retry_tb = get_time_tb(); entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: addresses changed peer=%016llx version=%llu; retry now", |
|
(unsigned long long)entry->node_id, (unsigned long long)ni->timestamp); |
|
entry->node_timestamp = ni->timestamp; |
|
} |
|
cm_refresh_path(entry); |
|
if (entry->state != CONN_MGR_STATE_DISCONNECTED) cm_arm_timer(entry); |
|
} |
|
|
|
void conn_mgr_routes_changed(struct CONN_MGR* mgr) { |
|
if (!mgr || !mgr->initialized) return; |
|
// Не вызываем сервисы внутри обхода entries/изменения таблицы: callback может закрыть соседний entry. |
|
for (struct ll_entry* e = mgr->entries->head; e; e = e->next) { |
|
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)e; |
|
if (entry->refresh_token) continue; |
|
entry->refresh_token = uasync_call_soon(mgr->instance->ua, entry, cm_refresh_cb); |
|
if (!entry->refresh_token) DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: refresh scheduling failed peer=%016llx", |
|
(unsigned long long)entry->node_id); |
|
} |
|
} |
|
|
|
/* Изменение собственных сокетов обходит backoff, но не прерывает рабочую доставку. */ |
|
void conn_mgr_network_changed(struct CONN_MGR* mgr) { |
|
if (!mgr || !mgr->initialized) return; |
|
for (struct ll_entry* e = mgr->entries->head; e; e = e->next) { |
|
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)e; |
|
if (!entry->handles || cm_direct_conn(mgr, entry->node_id)) continue; |
|
entry->attempt_active = 0; entry->retry_tb = get_time_tb(); entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local network changed peer=%016llx; retry now", |
|
(unsigned long long)entry->node_id); |
|
} |
|
conn_mgr_routes_changed(mgr); |
|
} |
|
|
|
/* TIMEOUT первого NCD не закрывает транспорт: поздний UP всё ещё может улучшить путь. */ |
|
void cm_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ev, void* arg) { |
|
struct CONN_MGR_ENTRY* entry = arg; |
|
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: NCD event=%d peer=%016llx path=%u elapsed=%llums", |
|
ev, (unsigned long long)entry->node_id, entry->conn_type, |
|
(unsigned long long)((get_time_tb() - entry->start_tb) / 10)); |
|
if (ev == NCD_EVENT_UP) { |
|
entry->direct_up = 1; entry->main_connect_state = CM_TRY_OK; |
|
struct ETCP_CONN* conn = node_conn_direct_get_conn(ncd_h); |
|
if (conn && topo_group_new_conn(entry->mgr->group, conn) < 0) |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: group negotiation unavailable peer=%016llx", |
|
(unsigned long long)entry->node_id); |
|
} else if (ev == NCD_EVENT_TIMEOUT) { |
|
entry->main_connect_state = CM_TRY_FAILED; |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct probe timeout peer=%016llx; retaining transport path=%u", |
|
(unsigned long long)entry->node_id, entry->conn_type); |
|
} else { |
|
entry->direct_up = 0; |
|
if (ev == NCD_EVENT_CLOSED) { entry->ncd_handle = NULL; node_conn_direct_close(ncd_h); } |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct transport lost peer=%016llx event=%d", |
|
(unsigned long long)entry->node_id, ev); |
|
if (entry->handles) { |
|
entry->attempt_active = 0; entry->retry_tb = get_time_tb(); entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
} |
|
} |
|
cm_refresh_path(entry); |
|
if (entry->state != CONN_MGR_STATE_DISCONNECTED) cm_arm_timer(entry); |
|
} |
|
|
|
int cm_send_control(struct CONN_MGR* mgr, uint64_t node_id, const void* packet, size_t len) { |
|
struct ll_entry* qe = ll_alloc_lldgram(1 + len); |
|
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: control allocation failed peer=%016llx", (unsigned long long)node_id); return -1; } |
|
qe->dgram[0] = ETCP_RT_ID_CONN_MGR; memcpy(qe->dgram + 1, packet, len); qe->len = (uint16_t)(1 + len); |
|
int ret = etcp_route_send(mgr->instance, mgr->group->group_id, node_id, qe, 1, 0); |
|
if (ret < 0) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: control send failed peer=%016llx ret=%d", |
|
(unsigned long long)node_id, ret); |
|
return ret; |
|
} |
|
|
|
void cm_arm_timer(struct CONN_MGR_ENTRY* entry) { |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
if (entry->timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->timer); entry->timer = NULL; } |
|
uint64_t now = get_time_tb(), when = UINT64_MAX; |
|
int direct_ready = entry->conn_type == CONN_TYPE_DIRECT || entry->conn_type == CONN_TYPE_REVERSE; |
|
if (!entry->handles) when = entry->deadline_tb; |
|
else if (direct_ready && (!entry->indirect_started || entry->indirect_confirmed)) { |
|
entry->attempt_active = 0; entry->retry_tb = 0; entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
} else if (entry->attempt_active) when = entry->deadline_tb; |
|
else if (!direct_ready) { |
|
if (!entry->retry_tb) entry->retry_tb = now; |
|
when = entry->retry_tb; |
|
} |
|
if (entry->handles && !entry->ever_up && !entry->timeout_notified && entry->initial_deadline_tb < when) |
|
when = entry->initial_deadline_tb; |
|
if (entry->handles && entry->attempt_active && !direct_ready) { |
|
uint64_t reverse = entry->reverse_started ? entry->reverse_next_tb : entry->reverse_start_tb; |
|
if (reverse < when) when = reverse; |
|
} |
|
if (entry->handles && entry->attempt_active && !entry->indirect_confirmed && (!direct_ready || entry->indirect_started)) { |
|
uint64_t indirect = entry->indirect_started ? entry->indirect_next_tb : entry->indirect_start_tb; |
|
if (indirect < when) when = indirect; |
|
} |
|
if (when == UINT64_MAX) return; |
|
entry->timer = uasync_set_timeout(entry->mgr->instance->ua, when > now ? (int)(when - now) : 1, |
|
entry, cm_attempt_tick, "conn_mgr_attempt"); |
|
if (!entry->timer) DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: timer allocation failed peer=%016llx", |
|
(unsigned long long)entry->node_id); |
|
} |
|
|
|
static void cm_attempt_tick(void* arg) { |
|
struct CONN_MGR_ENTRY* entry = arg; entry->timer = NULL; |
|
cm_refresh_path(entry); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
uint64_t now = get_time_tb(); |
|
int direct_ready = entry->conn_type == CONN_TYPE_DIRECT || entry->conn_type == CONN_TYPE_REVERSE; |
|
if (!entry->handles) { |
|
if (now >= entry->deadline_tb) cm_entry_cleanup(entry); |
|
else cm_arm_timer(entry); |
|
return; |
|
} |
|
if (!entry->ever_up && !entry->timeout_notified && now >= entry->initial_deadline_tb) { |
|
entry->timeout_notified = 1; |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: initial connection timeout peer=%016llx; retaining demand", |
|
(unsigned long long)entry->node_id); |
|
cm_deliver_event(entry, CONN_EVENT_TIMEOUT); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
} |
|
if (!direct_ready && !entry->attempt_active && now >= entry->retry_tb) cm_start_attempt(entry, 1); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
if (entry->attempt_active && now < entry->deadline_tb) { |
|
if (!entry->ncd_handle && !direct_ready) cm_start_phase_direct(entry); |
|
if (!direct_ready && now >= entry->reverse_start_tb && now >= entry->reverse_next_tb) cm_start_phase_reverse(entry); |
|
if ((!direct_ready || entry->indirect_started) && !entry->indirect_confirmed && |
|
now >= entry->indirect_start_tb && now >= entry->indirect_next_tb) |
|
cm_start_phase_indirect(entry); |
|
} |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
if (entry->attempt_active && now >= entry->deadline_tb) { |
|
entry->attempt_active = 0; |
|
if (!entry->retry_delay_ms) entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
entry->retry_tb = now + entry->retry_delay_ms * 10; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: attempt exhausted peer=%016llx path=%s retry=%ums", |
|
(unsigned long long)entry->node_id, cm_path_name(entry->conn_type), entry->retry_delay_ms); |
|
unsigned next = entry->retry_delay_ms * 2; |
|
entry->retry_delay_ms = next > CONN_MGR_RETRY_MAX_MS ? CONN_MGR_RETRY_MAX_MS : next; |
|
} |
|
cm_arm_timer(entry); |
|
} |
|
|
|
/* Новый цикл меняет только пробы; NCD, подтверждённый резерв и очереди router остаются живы. */ |
|
static void cm_start_attempt(struct CONN_MGR_ENTRY* entry, int recovery) { |
|
struct CONN_MGR* mgr = entry->mgr; |
|
struct TOPO_GROUP_NODE* target = topo_node_find_by_id(mgr->group, entry->node_id); |
|
struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, entry->node_id); |
|
entry->start_tb = get_time_tb(); entry->attempt_active = 1; entry->retry_tb = 0; |
|
entry->node_timestamp = ni ? ni->timestamp : 0; |
|
entry->reverse_started = 0; entry->reverse_next_tb = 0; entry->indirect_started = 0; entry->indirect_next_tb = 0; |
|
entry->exchange_request_id = 0; |
|
memset(&entry->selection, 0, sizeof(entry->selection)); |
|
int local_public = cm_has_direct_ip(mgr, mgr->group->local_node), peer_public = cm_has_direct_ip(mgr, target); |
|
entry->reverse_start_tb = entry->start_tb + (local_public && !peer_public ? 0 : CONN_MGR_REVERSE_DELAY_MS * 10); |
|
entry->indirect_start_tb = entry->start_tb + (recovery || (!local_public && !peer_public) ? 0 : CONN_MGR_INDIRECT_DELAY_MS * 10); |
|
entry->deadline_tb = entry->indirect_start_tb + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10; |
|
if (!recovery) entry->initial_deadline_tb = entry->deadline_tb; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s group=%016llx peer=%016llx public=%d/%d reverse=%llums indirect=%llums reserve=%d", |
|
recovery ? "recover" : "start", (unsigned long long)mgr->group->group_id, (unsigned long long)entry->node_id, |
|
local_public, peer_public, (unsigned long long)((entry->reverse_start_tb - entry->start_tb) / 10), |
|
(unsigned long long)((entry->indirect_start_tb - entry->start_tb) / 10), entry->indirect_confirmed); |
|
cm_start_phase_direct(entry); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; |
|
if (!local_public && !peer_public) cm_start_local_scan(entry); |
|
if (entry->reverse_start_tb == entry->start_tb) cm_start_phase_reverse(entry); |
|
if (!entry->indirect_confirmed && entry->indirect_start_tb == entry->start_tb) cm_start_phase_indirect(entry); |
|
} |
|
|
|
/* ═══════ init / destroy ═══════ */ |
|
|
|
struct CONN_MGR* conn_mgr_init(struct TOPO_GROUP* group) { |
|
if (!group || !group->instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NULL group"); return NULL; } |
|
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR)); |
|
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "alloc failed"); return NULL; } |
|
mgr->instance = group->instance; mgr->group = group; |
|
mgr->entries = queue_new(mgr->instance->ua, 256, |
|
offsetof(struct CONN_MGR_ENTRY, node_id) - sizeof(struct ll_entry), |
|
8, "conn_mgr_entries"); |
|
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "entries alloc failed"); u_free(mgr); return NULL; } |
|
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"); |
|
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"); |
|
mgr->bg_ping_cycle_start_tb = get_time_tb(); mgr->initialized = 1; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: initialized for group %016llx", (unsigned long long)group->group_id); |
|
return mgr; |
|
} |
|
|
|
void conn_mgr_destroy(struct CONN_MGR* mgr) { |
|
if (!mgr) return; |
|
mgr->initialized = 0; |
|
if (mgr->bg_ping_timer) uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); |
|
if (mgr->candidate_ping_timer) uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); |
|
#ifdef UTUN_HAVE_STANDBY |
|
if (mgr->bg_ping_wait) standby_wait_cancel(mgr->bg_ping_wait); |
|
if (mgr->candidate_ping_wait) standby_wait_cancel(mgr->candidate_ping_wait); |
|
#endif |
|
for (int i = 0; i < CONN_MGR_MAX_CANDIDATES; i++) |
|
if (mgr->cand_tcp_pings[i]) stcp_client_destroy(mgr->cand_tcp_pings[i]); |
|
while (mgr->entries->head) cm_entry_cleanup((struct CONN_MGR_ENTRY*)mgr->entries->head); |
|
queue_free(mgr->entries); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed group=%016llx", (unsigned long long)mgr->group->group_id); |
|
u_free(mgr); |
|
} |
|
|
|
/* ═══════ публичное API ═══════ */ |
|
|
|
int cm_open(struct CONN_MGR* mgr, uint64_t node_id, |
|
conn_mgr_cb_t cb, void* cb_arg, struct CONN_MGR_HANDLE** out_handle) { |
|
if (out_handle) *out_handle = NULL; |
|
if (!mgr) { if (cb) cb(NULL, node_id, 0, CONN_EVENT_TIMEOUT, cb_arg); return -1; } |
|
struct TOPO_GROUP* group = mgr->group; |
|
if (!group) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "BGP not initialized"); return -1; } |
|
if (node_id == 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: cm_open node_id=0 — rejected grp=%016llx type=%d ch=%s", |
|
(unsigned long long)group->group_id, group->group_type, |
|
group->channel_id[0] ? group->channel_id : "-"); |
|
if (cb) cb(NULL, node_id, 0, CONN_EVENT_TIMEOUT, cb_arg); |
|
return -1; |
|
} |
|
uint64_t gid = group->group_id; |
|
struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, node_id); |
|
int loaded_from_db = 0; |
|
if (!target && mgr->instance->topo_sqlite_db && mgr->instance->topo_groups) { |
|
struct TOPO_NODE* ni = topo_node_sqlite_node_load(mgr->instance->topo_sqlite_db, mgr->instance->topo_groups, node_id); |
|
if (ni) { |
|
ni = topo_node_registry_store(mgr->instance->topo_groups, ni); |
|
struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry)); |
|
if (qe) { |
|
target = (struct TOPO_GROUP_NODE*)qe; |
|
memset((uint8_t*)target + sizeof(struct ll_entry), 0, sizeof(*target) - sizeof(struct ll_entry)); |
|
target->node_id = node_id; target->conn_mgr_type = CONN_TYPE_NONE; |
|
queue_data_put_with_index(group->nodes, &target->ll); loaded_from_db = 1; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx loaded from DB", (unsigned long long)node_id); |
|
} else DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: failed to alloc TOPO_GROUP_NODE for DB node 0x%016llx", (unsigned long long)node_id); |
|
} |
|
} |
|
if (!target) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "node 0x%016llx skipped — not in routing, no active connections", (unsigned long long)node_id); return -1; } |
|
|
|
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); |
|
|
|
if (entry && entry->handles) { |
|
struct CONN_MGR_HANDLE* h = cm_handle_new(entry, gid, cb, cb_arg); |
|
if (!h) return -1; if (out_handle) *out_handle = h; |
|
if (entry->state == CONN_MGR_STATE_CONNECTED) |
|
h->deliver_up_token = uasync_call_soon(mgr->instance->ua, h, cm_deliver_up_cb); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: joined demand peer=%016llx state=%u retry=%llu", |
|
(unsigned long long)node_id, entry->state, (unsigned long long)entry->retry_tb); |
|
return 0; |
|
} |
|
|
|
entry = cm_ensure_entry(mgr, node_id); |
|
if (!entry) return -1; |
|
int already_up = entry->state == CONN_MGR_STATE_CONNECTED; |
|
if (!already_up) entry->state = CONN_MGR_STATE_CONNECTING; |
|
struct CONN_MGR_HANDLE* h = cm_handle_new(entry, gid, cb, cb_arg); |
|
if (!h) { cm_entry_cleanup(entry); return -1; } if (out_handle) *out_handle = h; |
|
// Первый владелец входящего пути начинает свои пробы, сохраняя рабочую доставку. |
|
if (entry->timer) { uasync_cancel_timeout(mgr->instance->ua, entry->timer); entry->timer = NULL; } |
|
if (already_up) h->deliver_up_token = uasync_call_soon(mgr->instance->ua, h, cm_deliver_up_cb); |
|
entry->last_traffic_tb = get_time_tb(); |
|
entry->timeout_notified = 0; |
|
entry->retry_delay_ms = CONN_MGR_RETRY_MIN_MS; |
|
entry->db_loaded |= (uint8_t)loaded_from_db; |
|
cm_start_attempt(entry, 0); |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) return 0; |
|
cm_arm_timer(entry); |
|
return 0; |
|
} |
|
|
|
void conn_mgr_close(struct CONN_MGR_HANDLE* h) { |
|
if (!h || h->closed) return; |
|
h->closed = 1; |
|
if (h->deliver_up_token) { uasync_call_soon_cancel(h->ua, h->deliver_up_token); h->deliver_up_token = NULL; } |
|
struct CONN_MGR_ENTRY* entry = h->entry; h->entry = NULL; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: close handle=%p node=%016llx attached=%d", (void*)h, |
|
(unsigned long long)h->node_id, entry != NULL); |
|
if (entry) { |
|
struct CONN_MGR_HANDLE** pp = &entry->handles; |
|
while (*pp && *pp != h) pp = &(*pp)->next; |
|
if (*pp) *pp = h->next; |
|
if (!entry->handles) cm_entry_cleanup(entry); |
|
} |
|
// Не посылаем DISCONNECT: удалённые владельцы и групповой транспорт живут независимо. |
|
uasync_call_soon(h->ua, h, cm_handle_free_cb); |
|
} |
|
|
|
struct ETCP_CONN* conn_mgr_get_conn(struct CONN_MGR_HANDLE* h) { |
|
if (!h || h->closed || !h->entry || h->entry->state != CONN_MGR_STATE_CONNECTED || |
|
h->entry->conn_type == CONN_TYPE_INDIRECT) return NULL; |
|
return cm_direct_conn(h->entry->mgr, h->entry->node_id); |
|
} |
|
|
|
int conn_mgr_send(struct CONN_MGR_HANDLE* h, struct ll_entry* e) { |
|
if (!h || !e) return -1; |
|
struct CONN_MGR_ENTRY* entry = h->entry; |
|
if (!entry || entry->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(e); return -1; } |
|
entry->last_traffic_tb = get_time_tb(); |
|
struct ETCP_ROUTER_CONN* r = etcp_router_conn_get(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, ETCP_RT_ID_CONN_MGR); |
|
if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: no route to 0x%016llx, dropping %u bytes", (unsigned long long)entry->node_id, (unsigned)e->len); queue_entry_free(e); return -1; } |
|
int ret = etcp_router_conn_send(r, e->dgram, e->len, 0); queue_entry_free(e); return ret; |
|
} |
|
|
|
/* Локальные пробы уже созданы DIRECT через NCD. Проверяем все LAN-адреса, |
|
* не дублируя handshake и не создавая отдельные ping-контексты. */ |
|
void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) { |
|
struct TOPO_NODE* ni = topo_node_registry_find(entry->mgr->instance->topo_groups, entry->node_id); |
|
if (!ni || !entry->ncd_handle) { |
|
entry->local_scan_state = CM_TRY_FAILED; |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: LOCAL probe missing node/transport peer=%016llx", |
|
(unsigned long long)entry->node_id); return; |
|
} |
|
int addresses = 0; |
|
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { |
|
if (!(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) continue; |
|
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) |
|
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, m->nat_type) == SOCK_TC_PRIV) { |
|
addresses++; break; |
|
} |
|
} |
|
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) { |
|
uint8_t cls = cm_classify_v6_addr(a->addr); |
|
if ((a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP)) && (cls == CM_V6_LL || cls == CM_V6_LOC)) addresses++; |
|
} |
|
entry->local_scan_state = addresses ? CM_TRY_PENDING : CM_TRY_FAILED; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: LOCAL probes peer=%016llx addresses=%d shared_with_DIRECT=1 elapsed=%llums", |
|
(unsigned long long)entry->node_id, addresses, (unsigned long long)((get_time_tb() - entry->start_tb) / 10)); |
|
if (!addresses) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: no advertised local addresses peer=%016llx", |
|
(unsigned long long)entry->node_id); |
|
} |
|
|
|
void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) { |
|
if (!entry->ncd_handle && node_conn_direct_open(entry->mgr->instance, entry->node_id, cm_ncd_callback, entry, |
|
&entry->ncd_handle, NULL) == NCD_ERR) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: NCD open failed peer=%016llx", (unsigned long long)entry->node_id); |
|
return; |
|
} |
|
// Входящий REVERSE добавляет новые адреса, не заменяя уже идущие пробы. |
|
if (node_conn_direct_update_node(entry->mgr->instance, entry->node_id) < 0) |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: address update failed peer=%016llx", (unsigned long long)entry->node_id); |
|
if (!entry->direct_up) entry->main_connect_state = CM_TRY_PENDING; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT probes peer=%016llx elapsed=%llums transport=%p", |
|
(unsigned long long)entry->node_id, (unsigned long long)((get_time_tb() - entry->start_tb) / 10), |
|
(void*)node_conn_direct_get_conn(entry->ncd_handle)); |
|
} |
|
|
|
/* Запрос использует подписанный снимок отправителя: одинаковые правила IPv4/IPv6, UDP/TCP и Reality с DIRECT. */ |
|
void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) { |
|
uint64_t now = get_time_tb(); |
|
if (!entry->reverse_started) { |
|
entry->reverse_started = 1; entry->reverse_request_id = ++entry->mgr->next_request_id; |
|
entry->reverse_deadline_tb = now + CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: REVERSE start peer=%016llx elapsed=%llums", |
|
(unsigned long long)entry->node_id, (unsigned long long)((now - entry->start_tb) / 10)); |
|
} |
|
if (now >= entry->reverse_deadline_tb) { entry->reverse_next_tb = UINT64_MAX; return; } |
|
struct CM_DIRECT_REQ req = { ETCP_RT_ID_CONN_MGR, CM_SUBCMD_DIRECT_REQ, entry->reverse_request_id }; |
|
cm_send_control(entry->mgr, entry->node_id, &req, sizeof(req)); |
|
entry->reverse_next_tb = now + CONN_MGR_REVERSE_RETRANSMIT_MS * 10; |
|
} |
|
|
|
void cm_handle_direct_req(uint64_t src, struct CONN_MGR* mgr, const uint8_t* data, size_t len) { |
|
if (!mgr || len != sizeof(struct CM_DIRECT_REQ)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed DIRECT_REQ peer=%016llx len=%zu", (unsigned long long)src, len); |
|
return; |
|
} |
|
struct CM_DIRECT_REQ req; memcpy(&req, data, sizeof(req)); |
|
struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, src); |
|
if (!ni) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: REVERSE unknown peer=%016llx", (unsigned long long)src); return; } |
|
struct CONN_MGR_ENTRY* entry = cm_ensure_entry(mgr, src); |
|
if (!entry) return; |
|
if (entry->state == CONN_MGR_STATE_DISCONNECTED) { |
|
entry->state = CONN_MGR_STATE_CONNECTING; entry->start_tb = get_time_tb(); |
|
entry->deadline_tb = entry->start_tb + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10; |
|
} |
|
if (entry->peer_reverse_request_id == req.request_id) return; |
|
entry->peer_reverse_request_id = req.request_id; |
|
if (!entry->handles) entry->deadline_tb = get_time_tb() + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10; |
|
if (entry->direct_up && cm_direct_conn(mgr, src)) return; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: REVERSE request peer=%016llx path=%u existing_transport=%d", |
|
(unsigned long long)src, entry->conn_type, entry->ncd_handle != NULL); |
|
cm_start_phase_direct(entry); |
|
cm_arm_timer(entry); |
|
} |
|
|
|
/* ═══════ disconnect / recv ═══════ */ |
|
|
|
void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) { |
|
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); if (!entry) return; |
|
cm_clear_nodeinfo(mgr, node_id); cm_entry_cleanup(entry); |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id); |
|
} |
|
|
|
void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) { |
|
if (!entry || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 2) { |
|
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } |
|
return; |
|
} |
|
if (!conn || !conn->instance) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "conn_mgr_router_recv: conn=%p", (void*)conn); |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
return; |
|
} |
|
uint8_t* d = entry->dgram + ROUTER_SVC_PAYLOAD_OFF; size_t len = entry->len - ROUTER_SVC_PAYLOAD_OFF; |
|
if (d[0] != ETCP_RT_ID_CONN_MGR) { queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
uint8_t sub = d[1]; |
|
/* conn_mgr — per-group: резолвим группу из заголовка доставки, а не instance->conn_mgr */ |
|
uint64_t group_id; memcpy(&group_id, entry->dgram + ROUTER_SVC_GROUP_OFF, 8); |
|
uint64_t src; memcpy(&src, entry->dgram + ROUTER_SVC_SRC_OFF, 8); |
|
struct TOPO_GROUP* grp = topo_groups_find(conn->instance->topo_groups, group_id); |
|
struct CONN_MGR* mgr = grp ? grp->conn_mgr : NULL; |
|
if (!mgr) { |
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: packet for unknown group=%016llx peer=%016llx", |
|
(unsigned long long)group_id, (unsigned long long)src); |
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
|
} |
|
if (mgr) { struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, src); if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb(); } |
|
switch (sub) { |
|
case CM_SUBCMD_DIRECT_REQ: cm_handle_direct_req(src, mgr, d, len); break; |
|
case CM_SUBCMD_DIRECT_RESP: break; |
|
case CM_SUBCMD_INTERM_EXCHANGE_REQ: |
|
if (len == CM_EXCHANGE_REQ_SIZE) cm_handle_interm_exchange_req(src, mgr, (struct CM_EXCHANGE_REQ*)d); |
|
else DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed EXCHANGE_REQ peer=%016llx len=%zu", (unsigned long long)src, len); |
|
break; |
|
case CM_SUBCMD_INTERM_EXCHANGE_RESP: if (mgr) cm_handle_interm_exchange_resp(mgr, src, d, len); break; |
|
case CM_SUBCMD_INTERM_SELECTED: if (mgr) cm_handle_interm_selected(mgr, src, d, len); break; |
|
case CM_SUBCMD_INTERM_ACK: if (mgr) cm_handle_interm_ack(mgr, src, d, len); break; |
|
case CM_SUBCMD_DISCONNECT: |
|
if (len == CM_DISCONNECT_SIZE && ((struct CM_DISCONNECT*)d)->node_id == src) cm_handle_disconnect(mgr, src); |
|
else DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed DISCONNECT peer=%016llx len=%zu", (unsigned long long)src, len); |
|
break; |
|
default: DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: unknown command=%u peer=%016llx", sub, (unsigned long long)src); break; |
|
} |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
} |
|
|
|
int conn_mgr_open(struct UTUN_INSTANCE* inst, |
|
uint64_t group_id, |
|
uint64_t node_id, |
|
conn_mgr_cb_t cb, void* cb_arg, |
|
struct CONN_MGR_HANDLE** out_handle) { |
|
if (out_handle) *out_handle = NULL; |
|
if (!inst || !inst->topo_groups || !cb) return -1; |
|
|
|
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id); |
|
if (!group) { |
|
char ch[32]; snprintf(ch, sizeof(ch), "%llu", (unsigned long long)group_id); |
|
group = topo_groups_create_group(inst->topo_groups, group_id, TOPO_GROUP_TYPE_CHAT, ch); |
|
if (!group) return -1; |
|
} |
|
struct CONN_MGR* mgr = group->conn_mgr; |
|
if (!mgr) return -1; |
|
|
|
return cm_open(mgr, node_id, cb, cb_arg, out_handle); |
|
}
|
|
|