diff --git a/AGENTS.md b/AGENTS.md index 8936a48a..2b60d22a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -282,7 +282,7 @@ KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24 - `topo_group.c/h` - BGP-подобный обмен маршрутами между узлами - `topo_node.c/h` - Управление узлами (peer management) - `topo_node_lmdb.c/h` - LMDB-персистентность узлов -- `conn_mgr.c/h` - Менеджер соединений +- `conn_mgr.c/h/priv.h` + `conn_mgr_core|indirect|monitor.c` — Менеджер соединений (handle-based API, 3 фазы DIR/REV/IND, Invite) **Crypto (src/)** - `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation diff --git a/src/Makefile.am b/src/Makefile.am index cff65a72..2c4ae4fd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,9 @@ utun_CORE_SOURCES = \ routing_layer/topo_node.c \ routing_layer/topo_node_sqlite.c \ routing_layer/route_connectivity.c \ - routing_layer/conn_mgr.c \ + routing_layer/conn_mgr_core.c \ + routing_layer/conn_mgr_indirect.c \ + routing_layer/conn_mgr_monitor.c \ routing_layer/topo_recovery.c \ routing_layer/topo_group_connect.c \ chat/db_sync.c \ @@ -87,7 +89,9 @@ libutun_a_SOURCES = \ routing_layer/topo_node.c \ routing_layer/topo_node_sqlite.c \ routing_layer/route_connectivity.c \ - routing_layer/conn_mgr.c \ + routing_layer/conn_mgr_core.c \ + routing_layer/conn_mgr_indirect.c \ + routing_layer/conn_mgr_monitor.c \ routing_layer/topo_recovery.c \ routing_layer/topo_group_connect.c \ chat/db_sync.c \ diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index 9cadec04..cdb23925 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -172,7 +172,7 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { case CS_MSG_ERROR: { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: RECV ERROR from=%016llx ch=%s", CS_ID, (unsigned long long)peer, ch_id); if (g_cs->info_req_timer) { uasync_cancel_timeout(g_cs->inst->ua, g_cs->info_req_timer); g_cs->info_req_timer = NULL; } - uint8_t err[20]; memcpy(err, &peer, 8); int r = CONN_MGR_ERR_NOT_FOUND; memcpy(err + 8, &r, 4); + uint8_t err[20]; memcpy(err, &peer, 8); int r = -1; memcpy(err + 8, &r, 4); memcpy(err + 12, &g_cs->pending_invite_ch_id, 8); chat_event_post(CHAT_EVT_CONNECT_RESULT, err, 20); g_cs->pending_invite_node_id = 0; g_cs->pending_invite_ch_id = 0; break; @@ -193,7 +193,7 @@ static void cs_info_req_timeout_cb(void* arg) { CS_ID, (unsigned long long)cs->pending_invite_node_id, (unsigned long long)cs->pending_invite_ch_id); uint8_t err[20]; memcpy(err, &cs->pending_invite_node_id, 8); - int r = CONN_MGR_ERR_TIMEOUT; memcpy(err + 8, &r, 4); + int r = CONN_EVENT_TIMEOUT; memcpy(err + 8, &r, 4); memcpy(err + 12, &cs->pending_invite_ch_id, 8); chat_event_post(CHAT_EVT_CONNECT_RESULT, err, 20); cs->pending_invite_node_id = 0; @@ -208,7 +208,7 @@ static void cs_join_timeout_cb(void* arg) { DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "%s: CHANNEL_JOIN timeout peer=%016llx ch=%llu", CS_ID, (unsigned long long)peer, (unsigned long long)cs->pending_invite_ch_id); uint8_t err[20]; memcpy(err, &peer, 8); - int r = CONN_MGR_ERR_TIMEOUT; memcpy(err + 8, &r, 4); + int r = CONN_EVENT_TIMEOUT; memcpy(err + 8, &r, 4); memcpy(err + 12, &cs->pending_invite_ch_id, 8); chat_event_post(CHAT_EVT_CONNECT_RESULT, err, 20); cs->pending_invite_node_id = 0; @@ -688,7 +688,7 @@ static void cm_invite_trampoline(void* arg) { } else if (inst->topo_groups && ni->v4_addrs) { struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, channel_id); if (g && g->conn_mgr) { - conn_mgr_connect_from_invite(g->conn_mgr, ni, channel_id, 0, NULL, NULL); + conn_mgr_open_invite(inst, channel_id, ni, node_id, NULL, NULL, NULL); DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "%s: invite connect posted node=0x%016llx", CS_ID, (unsigned long long)node_id); } } else { diff --git a/src/media_delivery/media_delivery.c b/src/media_delivery/media_delivery.c index 09a07eb5..2379a68d 100644 --- a/src/media_delivery/media_delivery.c +++ b/src/media_delivery/media_delivery.c @@ -481,18 +481,17 @@ static void md_handle_super_hello(struct media_delivery_ctx* md, uint64_t from_n /* ── supernode connection ── */ -static void md_super_conn_cb(int result, uint64_t node_id, void* arg) { - struct media_delivery_ctx* md = (struct media_delivery_ctx*)arg; +static void md_super_conn_cb(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg) { + struct media_delivery_ctx* md = (struct media_delivery_ctx*)arg; (void)h; (void)group_id; struct media_super_peer* peer = md_super_peer_find(md, node_id); if (!peer) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: conn_cb for unknown peer 0x%016llx", MD_ID, (unsigned long long)node_id); return; } - if (result != CONN_MGR_OK) { + if (event != CONN_EVENT_UP) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: connect to supernode 0x%016llx failed rc=%d", - MD_ID, (unsigned long long)node_id, result); - if (result != CONN_MGR_ERR_ALREADY_CONNECTED) { - peer->connect_timer = uasync_set_timeout(md->inst->ua, - MEDIA_RECONNECT_COOLDOWN_TB, md, md_reconnect_cb, "md_reconnect"); - } + MD_ID, (unsigned long long)node_id, event); + peer->connect_timer = uasync_set_timeout(md->inst->ua, + MEDIA_RECONNECT_COOLDOWN_TB, md, md_reconnect_cb, "md_reconnect"); return; } @@ -625,7 +624,7 @@ static void md_handle_block_req(struct media_delivery_ctx* md, uint64_t from_nod if (grp && grp->conn_mgr) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: no direct conn to 0x%016llx — connecting via conn_mgr", MD_ID, (unsigned long long)from_node); - conn_mgr_connect_node(grp->conn_mgr, from_node, 0, NULL, NULL); + conn_mgr_open_invite(grp->instance, grp->group_id, NULL, from_node, NULL, NULL, NULL); } } @@ -723,7 +722,7 @@ static void md_super_connect(struct media_delivery_ctx* md, uint64_t peer_node_i DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: connecting to supernode 0x%016llx", MD_ID, (unsigned long long)peer_node_id); - conn_mgr_connect_node(grp->conn_mgr, peer_node_id, 0, md_super_conn_cb, md); + conn_mgr_open_invite(grp->instance, grp->group_id, NULL, peer_node_id, md_super_conn_cb, md, NULL); } /* ── supernode start/stop ── */ diff --git a/src/media_delivery/media_download.c b/src/media_delivery/media_download.c index f088ccdd..4e13135b 100644 --- a/src/media_delivery/media_download.c +++ b/src/media_delivery/media_download.c @@ -101,11 +101,12 @@ static void md_dl_send_have_block(struct UTUN_INSTANCE* inst, struct media_downl /* ── conn_mgr callback ── */ -static void md_dl_conn_cb(int result, uint64_t node_id, void* arg) { - struct media_download* dl = (struct media_download*)arg; - if (result != CONN_MGR_OK) { +static void md_dl_conn_cb(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg) { + struct media_download* dl = (struct media_download*)arg; (void)h; (void)group_id; + if (event != CONN_EVENT_UP) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: connect to 0x%016llx failed rc=%d", - MDL_ID, (unsigned long long)node_id, result); + MDL_ID, (unsigned long long)node_id, event); return; } /* send block requests to this peer */ @@ -259,7 +260,7 @@ void media_download_handle_query_resp(struct UTUN_INSTANCE* inst, for (int pi = 0; pi < dl->num_peers; pi++) { dl->peers[pi].connected = 1; if (grp && grp->conn_mgr) - conn_mgr_connect_node(grp->conn_mgr, dl->peers[pi].node_id, 0, md_dl_conn_cb, dl); + conn_mgr_open_invite(inst, grp->group_id, NULL, dl->peers[pi].node_id, md_dl_conn_cb, dl, NULL); else for (int bi = 0; bi < dl->peers[pi].num_blocks; bi++) md_dl_send_block_req(inst, dl->peers[pi].node_id, dl, bi); diff --git a/src/routing_layer/conn_mgr.c b/src/routing_layer/conn_mgr.c deleted file mode 100644 index 57b4e080..00000000 --- a/src/routing_layer/conn_mgr.c +++ /dev/null @@ -1,1621 +0,0 @@ -#include -#include -#ifdef _WIN32 -#include -#include -#else -#include -#endif -#include "../lib/platform_compat.h" -#include "../lib/debug_config.h" -#include "../lib/mem.h" -#include "../lib/u_async.h" -#include "../lib/ll_queue.h" -#include "utun_instance.h" -#include "etcp.h" -#include "etcp_connections.h" -#include "etcp_router.h" -#include "config_parser.h" -#include "secure_channel.h" -#include "topo_node.h" -#include "topo_node_sqlite.h" -#include "topo_group.h" -#include "../transport_layer/stcp_link.h" -#include "route_ping.h" -#include "route_connectivity.h" -#include "conn_mgr.h" - -static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id); -static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry); -static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry); -static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry); -static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry); -static void cm_idle_timer_cb(void* arg); -static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result); -static void cm_bg_ping_timer_cb(void* arg); -static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_config_type, uint8_t target_nat_type); -static uint16_t cm_get_node_min_rtt(struct TOPO_GROUP_NODE* nq); -static int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq); -static int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq); -static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry); -static void cm_exchange_probe_retry_cb(void* arg); -static uint64_t cm_get_node_max_probe_time(struct TOPO_GROUP_NODE* nq); -static int cm_is_rtt_fresh(struct TOPO_GROUP_NODE* nq, uint64_t now_tb); -static void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id); - -static struct CONN_MGR_HANDLE* cm_handle_new(struct CONN_MGR_ENTRY* entry, - conn_mgr_connect_callback_t cb, void* cb_arg) { - struct CONN_MGR_HANDLE* h = u_calloc(1, sizeof(*h)); - if (!h) return NULL; - h->target_node_id = entry->node_id; - h->conn_type = CONN_TYPE_NONE; - h->result_cb = cb; h->result_arg = cb_arg; - h->entry = entry; - h->next = entry->handles; entry->handles = h; - return h; -} - -static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry); -static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len); -static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CONN_MGR_INTERM_EXCHANGE_REQ* req); -static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len); -static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len); -static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id); -static void cm_direct_init_cb(struct ETCP_CONN* conn, int event, void* arg); -static void cm_reverse_init_cb(struct ETCP_CONN* conn, int event, void* arg); -static void cm_reverse_timeout_cb(void* arg); -static void cm_exchange_timeout_cb(void* arg); -static void cm_candidate_ping_timer_cb(void* arg); - -static void cm_invite_init_cb(struct ETCP_CONN* conn, int event, void* arg); -static void cm_invite_overall_timeout(void* arg); -static void cm_invite_fail(struct cm_invite_pending* inv, int result); -static void cm_invite_cleanup(struct cm_invite_pending* inv); -static void cm_handle_invite_info_req(struct CONN_MGR* mgr, struct ETCP_CONN* conn, - const uint8_t* data, size_t len); -static void cm_handle_invite_info_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len); - -struct cm_reverse_pending { - struct cm_reverse_pending* next; - uint32_t request_id; - struct CONN_MGR_ENTRY* entry; - struct ETCP_CONN** conns; - uint8_t conn_count; -}; - -struct cm_exchange_pending { - struct cm_exchange_pending* next; - uint32_t request_id; - struct CONN_MGR_ENTRY* entry; - void* timeout_timer; - struct CONN_MGR_INTERM_EXCHANGE_RESP cached_resp; - uint8_t resp_received; - uint8_t probes_done; -}; - -#define CM_INVITE_DEFAULT_TIMEOUT_MS 2000 - -enum CM_INVITE_STATE { - CM_INVITE_CONNECTING = 0, - CM_INVITE_WAIT_INFO = 1, -}; - -struct cm_invite_pending { - struct cm_invite_pending* next; - struct CONN_MGR* mgr; - uint64_t node_id; - uint64_t group_id; - uint32_t timeout_ms; - uint8_t state; - struct ETCP_CONN* conn; - struct stcp_link* tcp_link; - uint8_t tcp_ready : 1; - void* overall_timer; - conn_mgr_connect_callback_t cb; - void* cb_arg; - struct TOPO_GROUP_NODE* temp_nq; -}; - -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->entry_capacity = 8; - mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY)); - if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "entries alloc failed"); u_free(mgr); return NULL; } - mgr->direct_timeout_ms = CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS; - mgr->invite_list = NULL; - etcp_router_bind(mgr->instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); - etcp_bind(mgr->instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); - 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, bg_ping started", (unsigned long long)group->group_id); - return mgr; -} - -void conn_mgr_destroy(struct CONN_MGR* mgr) { - if (!mgr) return; - if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; } - if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; } - etcp_router_unbind(mgr->instance, ETCP_RT_ID_CONN_MGR); - etcp_unbind(mgr->instance, ETCP_RT_ID_CONN_MGR); - for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]); - while (mgr->invite_list) cm_invite_fail(mgr->invite_list, CONN_MGR_ERR_INTERNAL); - struct cm_exchange_pending* ep = mgr->exchange_pending; - while (ep) { - struct cm_exchange_pending* next = ep->next; - if (ep->timeout_timer) uasync_cancel_timeout(mgr->instance->ua, ep->timeout_timer); - u_free(ep); - ep = next; - } - mgr->exchange_pending = NULL; - u_free(mgr->entries); - mgr->initialized = 0; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count); - u_free(mgr); -} - -static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) { - if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return; - if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; } - if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } - struct cm_exchange_pending** pp = &entry->mgr->exchange_pending; - while (*pp) { - if ((*pp)->entry == entry) { - if ((*pp)->timeout_timer && (*pp)->timeout_timer != entry->main.timer) { - uasync_cancel_timeout(entry->mgr->instance->ua, (*pp)->timeout_timer); - (*pp)->timeout_timer = NULL; - } - struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); - } else pp = &(*pp)->next; - } - entry->state = CONN_MGR_STATE_DISCONNECTED; - entry->conn_type = CONN_TYPE_NONE; -} - -static 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; - if (mgr->entry_count >= mgr->entry_capacity) { - size_t new_cap = mgr->entry_capacity * 2; - struct CONN_MGR_ENTRY* new_e = u_realloc(mgr->entries, new_cap * sizeof(struct CONN_MGR_ENTRY)); - if (!new_e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; } - memset(new_e + mgr->entry_capacity, 0, (new_cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY)); - mgr->entries = new_e; - mgr->entry_capacity = new_cap; - } - struct CONN_MGR_ENTRY* new_entry = &mgr->entries[mgr->entry_count++]; - memset(new_entry, 0, sizeof(*new_entry)); - new_entry->node_id = node_id; - new_entry->mgr = mgr; - return new_entry; -} - -static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) { - for (size_t i = 0; i < mgr->entry_count; i++) - if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED) - return &mgr->entries[i]; - return NULL; -} - -static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result) { - struct CONN_MGR_HANDLE* h = entry->handles; - entry->handles = NULL; - while (h) { - struct CONN_MGR_HANDLE* next = h->next; - if (h->result_cb) h->result_cb(result, entry->node_id, h->result_arg); - h->entry = NULL; - u_free(h); - h = next; - } - if (entry->state == CONN_MGR_STATE_CONNECTING) - cm_clear_nodeinfo(entry->mgr, entry->node_id); -} - -static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) { - struct TOPO_GROUP* group = entry->mgr->group; - if (!group) return; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(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; -} - -static 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; -} - -static void cm_cleanup_db_node(struct CONN_MGR_ENTRY* entry) { - if (!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); - queue_remove_data(group->nodes, &nq->ll); - topo_nodeq_free_group_fields(entry->mgr->instance->topo_groups, nq); - queue_entry_free(&nq->ll); -} - -struct CONN_MGR_HANDLE* conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms, - conn_mgr_connect_callback_t cb, void* cb_arg) { - if (!mgr) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return NULL; } - struct TOPO_GROUP* group = mgr->group; - if (!group) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "BGP not initialized"); if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return NULL; } - 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)); - 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_ERROR(DEBUG_CATEGORY_GENERAL, "node 0x%016llx not found", (unsigned long long)node_id); return NULL; } - struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); - struct CONN_MGR_HANDLE* h = NULL; - if (entry && entry->state == CONN_MGR_STATE_CONNECTED) { - h = cm_handle_new(entry, cb, cb_arg); - if (cb) cb(CONN_MGR_OK, node_id, cb_arg); - return h; - } - if (entry && entry->state == CONN_MGR_STATE_CONNECTING) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "node 0x%016llx already connecting, adding handle", (unsigned long long)node_id); - return cm_handle_new(entry, cb, cb_arg); - } - struct ETCP_CONN* existing = topo_group_find_conn_for_node(group, node_id); - if (!existing) existing = instance_find_conn(mgr->instance, node_id); - if (existing && existing->peer_node_id == node_id && existing->links) { - struct ETCP_LINK* l = existing->links; - while (l) { if (l->link_state == 3 && l->initialized) break; l = l->next; } - if (l) { - entry = cm_ensure_entry(mgr, node_id); - if (!entry) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return NULL; } - entry->state = CONN_MGR_STATE_CONNECTED; - entry->conn_type = CONN_TYPE_DIRECT; - entry->idle_timeout_ms = idle_timeout_ms; - entry->last_traffic_tb = get_time_tb(); - h = cm_handle_new(entry, cb, cb_arg); - cm_update_nodeinfo(entry); - cm_deliver_result(entry, CONN_MGR_OK); - return h; - } - } - entry = cm_ensure_entry(mgr, node_id); - if (!entry) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return NULL; } - entry->state = CONN_MGR_STATE_CONNECTING; - h = cm_handle_new(entry, cb, cb_arg); - entry->idle_timeout_ms = idle_timeout_ms; - entry->last_traffic_tb = get_time_tb(); - entry->local_scan_state = CM_TRY_NONE; - entry->main_connect_state = CM_TRY_NONE; - entry->main.phase = 0; - entry->main.timer = NULL; - entry->main.request_id = 0; - entry->db_loaded = (uint8_t)loaded_from_db; - int has_our_local = cm_has_local_addr(mgr, group->local_node); - int has_target_local = cm_has_local_addr(mgr, target); - if (has_our_local && has_target_local) { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); } - entry->main_connect_state = CM_TRY_PENDING; - cm_start_phase_direct(entry); - return h; -} - -int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) { - if (!mgr) return CONN_MGR_ERR_INTERNAL; - struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); - if (!entry) return CONN_MGR_ERR_NOT_FOUND; - struct CONN_MGR_DISCONNECT pkt; - pkt.cmd = ETCP_RT_ID_CONN_MGR; - pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT; - pkt.node_id = node_id; - struct ll_entry* qe = queue_entry_new(0); - if (qe) { qe->dgram = u_malloc(sizeof(pkt)); memcpy(qe->dgram, &pkt, sizeof(pkt)); qe->len = sizeof(pkt); etcp_route_send(mgr->instance, mgr->group->group_id, node_id, qe, 1); } - cm_clear_nodeinfo(mgr, node_id); - cm_entry_destroy(entry); - entry->state = CONN_MGR_STATE_DISCONNECTED; - entry->conn_type = CONN_TYPE_NONE; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: disconnected node 0x%016llx", (unsigned long long)node_id); - return CONN_MGR_OK; -} - -int conn_mgr_cancel_callback(struct CONN_MGR* mgr, uint64_t node_id, - struct CONN_MGR_HANDLE* h) { - if (!mgr) return CONN_MGR_ERR_INTERNAL; - struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); - if (!entry) return CONN_MGR_ERR_NOT_FOUND; - - int cb_count = 0; - for (struct CONN_MGR_HANDLE* n = entry->handles; n; n = n->next) cb_count++; - if (cb_count == 0) return CONN_MGR_ERR_NOT_FOUND; - - if (h) { - struct CONN_MGR_HANDLE** pp = &entry->handles; - while (*pp) { if (*pp == h) { *pp = h->next; break; } pp = &(*pp)->next; } - h->entry = NULL; u_free(h); - cb_count--; - } - - if (cb_count <= 1 || !h) { - cm_entry_destroy(entry); - cm_clear_nodeinfo(mgr, node_id); - struct CONN_MGR_HANDLE* rem = h ? entry->handles : NULL; - while (rem) { struct CONN_MGR_HANDLE* nx = rem->next; u_free(rem); rem = nx; } - entry->handles = NULL; - } - return CONN_MGR_OK; -} - -void conn_mgr_release_handle(struct CONN_MGR_HANDLE* h) { - if (!h) return; - struct CONN_MGR_ENTRY* entry = h->entry; - if (!entry) { u_free(h); return; } - - struct CONN_MGR_HANDLE** pp = &entry->handles; - while (*pp) { if (*pp == h) { *pp = h->next; break; } pp = &(*pp)->next; } - - if (!entry->handles) { cm_entry_destroy(entry); cm_clear_nodeinfo(entry->mgr, entry->node_id); } - h->entry = NULL; u_free(h); -} - -int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms) { - if (!mgr) return CONN_MGR_ERR_INTERNAL; - struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); - if (!entry) return CONN_MGR_ERR_NOT_FOUND; - entry->idle_timeout_ms = timeout_ms; - if (entry->state == CONN_MGR_STATE_CONNECTED && timeout_ms > 0 && !entry->idle_timer) - entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle"); - if (timeout_ms == 0 && entry->idle_timer) { uasync_cancel_timeout(mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; } - return CONN_MGR_OK; -} - -void conn_mgr_set_direct_timeout_ms(struct CONN_MGR* mgr, uint32_t timeout_ms) { - if (mgr) mgr->direct_timeout_ms = timeout_ms; -} - -int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_state, uint8_t* out_conn_type) { - if (!mgr || !out_state || !out_conn_type) return CONN_MGR_ERR_INTERNAL; - struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); - if (!entry) { *out_state = CONN_MGR_STATE_DISCONNECTED; *out_conn_type = CONN_TYPE_NONE; return CONN_MGR_OK; } - *out_state = entry->state; - *out_conn_type = entry->conn_type; - return CONN_MGR_OK; -} - -int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) { - if (!mgr || !entry) return -1; - struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id); - if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; } - e->last_traffic_tb = get_time_tb(); - struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, mgr->group->group_id, node_id, ETCP_RT_ID_CONN_MGR); - if (!rconn) { queue_entry_free(entry); return -1; } - int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len); - queue_entry_free(entry); - return ret; -} - -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 == 0) return; - for (uint8_t i = 0; i < mgr->best_candidate_count; i++) { - if (mgr->best_candidates[i].node_id == node_id) { - 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 tmp = mgr->best_candidates[j]; - mgr->best_candidates[j] = mgr->best_candidates[j-1]; - mgr->best_candidates[j-1] = tmp; - } - 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 tmp = mgr->best_candidates[j]; - mgr->best_candidates[j] = mgr->best_candidates[j+1]; - mgr->best_candidates[j+1] = tmp; - } - return; - } - } - if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES) { - mgr->best_candidates[mgr->best_candidate_count].node_id = node_id; - mgr->best_candidates[mgr->best_candidate_count].rtt = rtt; - mgr->best_candidate_count++; - } else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt) { - mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].node_id = node_id; - mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt = rtt; - } else return; - for (uint8_t i = mgr->best_candidate_count - 1; i > 0; i--) { - if (mgr->best_candidates[i].rtt >= mgr->best_candidates[i-1].rtt) break; - struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[i]; - mgr->best_candidates[i] = mgr->best_candidates[i-1]; - mgr->best_candidates[i-1] = tmp; - } -} - -static void cm_idle_timer_cb(void* arg) { - struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; - struct CONN_MGR* mgr = entry->mgr; - uint64_t now_tb = get_time_tb(); - if (entry->state != CONN_MGR_STATE_CONNECTED || entry->idle_timeout_ms == 0) return; - uint64_t idle = now_tb - entry->last_traffic_tb; - if (idle > (uint64_t)entry->idle_timeout_ms * 10) { - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting", - (unsigned long long)entry->node_id, (unsigned long long)(idle / 10)); - conn_mgr_disconnect_node(mgr, entry->node_id); - return; - } - entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle"); -} - -static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_config_type, uint8_t target_nat_type) { - uint8_t tt = target_nat_type >= NAT_VERIFIED_UNKNOWN ? CFG_SERVER_TYPE_PUBLIC : target_config_type; - uint8_t ot = our->type; - if (ot == CFG_SERVER_TYPE_PUBLIC || ot == CFG_SERVER_TYPE_UNKNOWN) return 1; - if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1; - if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_NAT) return 1; - if ((ot == CFG_SERVER_TYPE_PRIVATE || ot == CFG_SERVER_TYPE_LOCAL) && tt == CFG_SERVER_TYPE_PRIVATE) return 2; - return 0; -} - -static uint16_t cm_get_node_min_rtt(struct TOPO_GROUP_NODE* nq) { - uint16_t rtt = 0xFFFF; - if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < rtt) - rtt = nq->connectivity.interface_min_rtt; - if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < rtt) - rtt = nq->connectivity.nat_min_rtt; - if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < rtt) - rtt = nq->connectivity.real_min_rtt; - return rtt; -} - -static 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; -} - -static int cm_is_rtt_fresh(struct TOPO_GROUP_NODE* nq, uint64_t now_tb) { - uint64_t last = cm_get_node_max_probe_time(nq); - return (last > 0 && (now_tb - last) < (uint64_t)(CONN_MGR_CANDIDATE_CACHE_MS * 10)); -} - -#define CM_V6_LINK_LOCAL 0 -#define CM_V6_LOCAL 1 -#define CM_V6_DIRECT 2 -#define CM_V6_OTHER 3 - -static uint8_t cm_classify_v6_addr(const uint8_t addr[16]) { - if (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80) return CM_V6_LINK_LOCAL; - if (addr[0] == 0xfc || addr[0] == 0xfd) return CM_V6_LOCAL; - if (addr[0] == 0xff) return CM_V6_OTHER; - { uint8_t zero[16] = {0}; if (memcmp(addr, zero, 16) == 0) return CM_V6_OTHER; } - { 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) == 0) return CM_V6_OTHER; } - return CM_V6_DIRECT; -} - -static uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s) { - if (s->local_addr.ss_family != AF_INET6) return CM_V6_OTHER; - return cm_classify_v6_addr(((const struct sockaddr_in6*)&s->local_addr)->sin6_addr.s6_addr); -} - -static int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) { - if (!nq) return 0; - struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); - if (!ni) return 0; - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - if (a->type == TOPO_ADDR_NAT || a->type == TOPO_ADDR_INTERFACE) { - for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) { - if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PUBLIC || m->config_type == CFG_SERVER_TYPE_UNKNOWN - || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) - return 1; - } - } - } - for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { - if ((a6->type != TOPO_ADDR_NAT && a6->type != TOPO_ADDR_INTERFACE) || cm_classify_v6_addr(a6->addr) != CM_V6_DIRECT) continue; - for (const struct TOPO_SOCKMETA6* m = ni->v6_sock_meta; m; m = m->next) - if (m->id == a6->socket_id && (m->config_type == CFG_SERVER_TYPE_PUBLIC || m->config_type == CFG_SERVER_TYPE_UNKNOWN - || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) - return 1; - } - return 0; -} - -static int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) { - if (!nq) return 0; - struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); - if (!ni) return 0; - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - if (a->type == TOPO_ADDR_INTERFACE) { - for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) { - if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PRIVATE || m->config_type == CFG_SERVER_TYPE_LOCAL)) - return 1; - } - } - } - for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { - if (a6->type != TOPO_ADDR_INTERFACE) continue; - uint8_t cls = cm_classify_v6_addr(a6->addr); - if (cls != CM_V6_LINK_LOCAL && cls != CM_V6_LOCAL) continue; - for (const struct TOPO_SOCKMETA6* m = ni->v6_sock_meta; m; m = m->next) - if (m->id == a6->socket_id && (m->config_type == CFG_SERVER_TYPE_PRIVATE || m->config_type == CFG_SERVER_TYPE_LOCAL)) - return 1; - } - return 0; -} - -static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, - const uint8_t* resp_data, size_t resp_data_len); -static void cm_direct_timeout_cb(void* arg); - -struct cm_ping_ctx { - struct CONN_MGR_ENTRY* entry; - struct sockaddr_storage addr; - struct ETCP_SOCKET* sock; - uint8_t phase; /* 0=local_scan, 1=direct */ - uint8_t attempt; -}; - -static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) { - struct TOPO_GROUP* group = entry->mgr->group; - struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, entry->node_id); - struct TOPO_NODE* ni = target ? topo_node_registry_find(entry->mgr->instance->topo_groups, target->node_id) : NULL; - if (!target || !ni) { entry->local_scan_state = CM_TRY_FAILED; return; } - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - if (a->type != TOPO_ADDR_INTERFACE) continue; - for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) { - if (m->id != a->socket_id || (m->config_type != CFG_SERVER_TYPE_PRIVATE && m->config_type != CFG_SERVER_TYPE_LOCAL)) continue; - struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; - while (s) { - if ((s->type == CFG_SERVER_TYPE_PRIVATE || s->type == CFG_SERVER_TYPE_LOCAL) && s->local_addr.ss_family == AF_INET) { - 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; memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin)); - struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx)); if (!ctx) continue; - ctx->entry = entry; ctx->addr = sa; ctx->sock = s; ctx->phase = 0; ctx->attempt = 0; - etcp_send_ping_to_socket(entry->mgr->instance, s, ni->public_key, &sa, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0, 0); - return; - } - s = s->next; - } - } - } - entry->local_scan_state = CM_TRY_FAILED; -} - -static struct ETCP_CONN* cm_get_or_create_conn(struct CONN_MGR_ENTRY* entry, const struct TOPO_GROUP_NODE* target) { - struct ETCP_CONN* conn = topo_group_find_conn_for_node(entry->mgr->group, entry->node_id); - if (!conn) conn = instance_find_conn(entry->mgr->instance, entry->node_id); - if (conn) { - if (conn->peer_node_id == entry->node_id) { etcp_conn_remove_cbk(conn, cm_direct_init_cb, entry); etcp_conn_add_cbk(conn, cm_direct_init_cb, entry, ETCP_CBK_EVENT_INIT); return conn; } - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: existing conn peer_id mismatch 0x%016llx != 0x%016llx, ignoring", - (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id); - } - conn = etcp_connection_create(entry->mgr->instance, NULL); - if (!conn) return NULL; - sc_init_ctx(&conn->crypto_ctx, &entry->mgr->instance->my_keys); - { struct TOPO_NODE* tni = topo_node_registry_find(entry->mgr->instance->topo_groups, target->node_id); - sc_set_peer_public_key(&conn->crypto_ctx, tni ? tni->public_key : NULL, 0); } - conn->peer_node_id = entry->node_id; - etcp_update_log_name(conn); - etcp_conn_add_cbk(conn, cm_direct_init_cb, entry, ETCP_CBK_EVENT_INIT); - return conn; -} - -static void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s) { - struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; - memcpy(&sin6.sin6_addr, addr, 16); sin6.sin6_port = htons(port); - if (cm_classify_v6_addr(addr) == CM_V6_LINK_LOCAL) { struct sockaddr_in6* our = (struct sockaddr_in6*)&s->local_addr; sin6.sin6_scope_id = our->sin6_scope_id; } - struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); - etcp_link_new(conn, s, &sa, 0); -} - -static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) { - if (entry->main_connect_state == CM_TRY_OK || entry->local_scan_state == CM_TRY_OK) return; - struct TOPO_GROUP* group = entry->mgr->group; - struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, entry->node_id); - struct TOPO_NODE* ni = target ? topo_node_registry_find(entry->mgr->instance->topo_groups, target->node_id) : NULL; - if (!target || !ni) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; } - - struct ETCP_CONN* conn = cm_get_or_create_conn(entry, target); - if (!conn) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; } - - int any_link = 0; - const char* timer_name = entry->db_loaded ? "conn_mgr_db_direct" : "conn_mgr_direct"; - - if (entry->db_loaded) { - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - if (!(a->protocol & TOPO_PROTO_UDP)) continue; - struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; - while (s) { - if (s->local_addr.ss_family == AF_INET) { - 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)); - if (etcp_link_new(conn, s, &sa, 0)) any_link = 1; - } - s = s->next; - } - } - for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { - if (!(a6->protocol & TOPO_PROTO_UDP)) continue; - uint8_t tclass = cm_classify_v6_addr(a6->addr); if (tclass == CM_V6_OTHER) continue; - struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; - while (s) { if (cm_sock_v6_classify(s) == tclass) { cm_add_v6_link(conn, a6->addr, a6->port, s); any_link = 1; } s = s->next; } - } - if (!any_link) { - etcp_connection_close(conn); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: db_node 0x%016llx direct failed (no compatible addr/socket)", (unsigned long long)entry->node_id); - cm_cleanup_db_node(entry); cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE); - } else { - entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, (int)(entry->mgr->direct_timeout_ms * 10), entry, cm_direct_timeout_cb, timer_name); - } - return; - } - - uint8_t priority_order[] = { TOPO_ADDR_NAT, TOPO_ADDR_INTERFACE }; - for (int pri = 0; pri < 2; pri++) { - for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { - if (a->type != priority_order[pri]) continue; - for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) { - if (m->id != a->socket_id) continue; - struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; - while (s) { - if (cm_nat_compatible(s, m->config_type, m->nat_type) && s->local_addr.ss_family == AF_INET) { - 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)); - if (etcp_link_new(conn, s, &sa, 0)) any_link = 1; - } - s = s->next; - } - } - } - for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { - if (a6->type != priority_order[pri]) continue; - uint8_t tclass = cm_classify_v6_addr(a6->addr); if (tclass == CM_V6_OTHER) continue; - struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; - while (s) { if (cm_sock_v6_classify(s) == tclass) { cm_add_v6_link(conn, a6->addr, a6->port, s); any_link = 1; } s = s->next; } - } - } - - if (any_link) { - entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, (int)(entry->mgr->direct_timeout_ms * 10), entry, cm_direct_timeout_cb, timer_name); - return; - } - etcp_connection_close(conn); - int has_our = cm_has_direct_ip(entry->mgr, group->local_node); - int has_target = cm_has_direct_ip(entry->mgr, target); - if (has_our && !has_target) { cm_start_phase_reverse(entry); return; } - cm_start_phase_indirect(entry); -} - -static void cm_direct_timeout_cb(void* arg) { - struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; - entry->main.timer = NULL; - if (entry->db_loaded) { - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: db_node 0x%016llx direct timeout", (unsigned long long)entry->node_id); - cm_cleanup_db_node(entry); - cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE); - return; - } - struct TOPO_GROUP* group = entry->mgr->group; - struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, entry->node_id); - int has_our = cm_has_direct_ip(entry->mgr, group->local_node); - int has_target = target ? cm_has_direct_ip(entry->mgr, target) : 0; - if (entry->local_scan_state != CM_TRY_OK) { - if (has_our && !has_target) { cm_start_phase_reverse(entry); return; } - cm_start_phase_indirect(entry); - return; - } - if (entry->local_scan_state == CM_TRY_FAILED && entry->main_connect_state == CM_TRY_FAILED) - cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE); -} - -static void cm_direct_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event; - struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; - if (!conn || conn->peer_node_id != entry->node_id) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready cb peer mismatch conn=%p peer=0x%llx entry=0x%llx", - (void*)conn, (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id); - return; - } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready for 0x%016llx", (unsigned long long)entry->node_id); - if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } - entry->main_connect_state = CM_TRY_OK; - entry->conn_type = CONN_TYPE_DIRECT; - entry->state = CONN_MGR_STATE_CONNECTED; - cm_update_nodeinfo(entry); - cm_deliver_result(entry, CONN_MGR_OK); -} - -static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, - const uint8_t* resp_data, size_t resp_data_len) { - struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg; - struct CONN_MGR_ENTRY* entry = ctx->entry; - (void)nonce; (void)resp_data; (void)resp_data_len; - if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; } - if (success && ctx->phase == 0) { - entry->local_scan_state = CM_TRY_OK; - entry->conn_type = CONN_TYPE_DIRECT; - entry->state = CONN_MGR_STATE_CONNECTED; - if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt); - cm_update_nodeinfo(entry); - cm_deliver_result(entry, CONN_MGR_OK); - u_free(ctx); - return; - } - ctx->attempt++; - if (ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) { - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(entry->mgr->group, entry->node_id); - struct TOPO_NODE* pub_ni = nq ? topo_node_registry_find(entry->mgr->instance->topo_groups, nq->node_id) : NULL; - etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock, - pub_ni ? pub_ni->public_key : NULL, - &ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0, 0); - return; - } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s failed for 0x%016llx after %d attempts", - ctx->phase == 0 ? "local scan" : "direct", (unsigned long long)entry->node_id, ctx->attempt); - if (ctx->phase == 0) entry->local_scan_state = CM_TRY_FAILED; - u_free(ctx); -} - -static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) { - struct TOPO_GROUP* group = entry->mgr->group; - struct TOPO_GROUP_NODE* local = group->local_node; - struct TOPO_NODE* lni = local ? topo_node_registry_find(entry->mgr->instance->topo_groups, local->node_id) : NULL; - if (!local || !lni) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; } - uint32_t req_id = ++entry->mgr->next_request_id; - entry->main.request_id = req_id; entry->main.phase = 2; - uint8_t direct_count = 0; - struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8]; - for (const struct TOPO_ADDR4* a = lni->v4_addrs; a && direct_count < 8; a = a->next) { - if (a->type != TOPO_ADDR_NAT && a->type != TOPO_ADDR_INTERFACE) continue; - for (const struct TOPO_SOCKMETA4* m = lni->v4_sock_meta; m; m = m->next) { - if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PUBLIC || m->config_type == CFG_SERVER_TYPE_UNKNOWN - || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) { - out_addrs[direct_count].type = a->type; memcpy(out_addrs[direct_count].ip, a->addr, 4); - out_addrs[direct_count].port = htons(a->port); out_addrs[direct_count].socket_id = a->socket_id; - direct_count++; break; - } - } - } - if (direct_count == 0) { cm_start_phase_indirect(entry); return; } - size_t pkt_size = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)direct_count * 8; - uint8_t* pkt = u_malloc(pkt_size); - if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; } - struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt; - req->cmd = ETCP_RT_ID_CONN_MGR; - req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ; - req->request_id = req_id; - req->addr_count = direct_count; - uint8_t* p = pkt + CONN_MGR_DIRECT_REQ_HDR_SIZE; - for (uint8_t i = 0; i < direct_count; i++) { - *p++ = out_addrs[i].type; - memcpy(p, out_addrs[i].ip, 4); p += 4; - memcpy(p, &out_addrs[i].port, 2); p += 2; - *p++ = out_addrs[i].socket_id; - } - struct ll_entry* qe = queue_entry_new(0); - if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; } - qe->dgram = pkt; - qe->len = (uint16_t)pkt_size; - etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1); - - struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending)); - if (rp) { rp->request_id = req_id; rp->entry = entry; rp->next = entry->mgr->reverse_pending; - entry->mgr->reverse_pending = rp; } - entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10, - entry, cm_reverse_timeout_cb, "conn_mgr_reverse"); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs", - (unsigned long long)entry->node_id, direct_count); -} - -static void cm_reverse_timeout_cb(void* arg) { - struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; - entry->main.timer = NULL; - struct cm_reverse_pending** pp = &entry->mgr->reverse_pending; - while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; } - cm_start_phase_indirect(entry); -} - -static void cm_reverse_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event; - struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg; - struct CONN_MGR_ENTRY* entry = rp->entry; - if (!conn || conn->peer_node_id != entry->node_id) { u_free(rp); return; } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id); - if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } - struct cm_reverse_pending** pp = &entry->mgr->reverse_pending; - while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; } - entry->main_connect_state = CM_TRY_OK; - entry->conn_type = CONN_TYPE_REVERSE; - entry->state = CONN_MGR_STATE_CONNECTED; - cm_update_nodeinfo(entry); - cm_deliver_result(entry, CONN_MGR_OK); - u_free(rp); -} - -static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) { - struct CONN_MGR* mgr = entry->mgr; - if (mgr->best_candidate_count == 0) { - DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx (direct+reverse failed)", - (unsigned long long)entry->node_id); - cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE); - return CONN_MGR_ERR_UNREACHABLE; - } - uint32_t req_id = ++mgr->next_request_id; - entry->main.request_id = req_id; - entry->main.phase = 3; - struct CONN_MGR_INTERM_EXCHANGE_REQ req; - memset(&req, 0, sizeof(req)); - req.cmd = ETCP_RT_ID_CONN_MGR; - req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ; - req.request_id = req_id; - req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count; - for (uint8_t i = 0; i < req.candidate_count; i++) - req.candidates[i] = mgr->best_candidates[i]; - struct ll_entry* qe = queue_entry_new(0); - if (!qe) return CONN_MGR_ERR_INTERNAL; - qe->dgram = u_malloc(sizeof(req)); - memcpy(qe->dgram, &req, sizeof(req)); - qe->len = (uint16_t)sizeof(req); - etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1); - entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10, - entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch"); - { - struct cm_exchange_pending* ep = u_calloc(1, sizeof(struct cm_exchange_pending)); - if (ep) { ep->request_id = req_id; ep->entry = entry; ep->timeout_timer = entry->main.timer; - ep->next = mgr->exchange_pending; mgr->exchange_pending = ep; } - } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) sent EXCHANGE_REQ to 0x%016llx with %u candidates", - (unsigned long long)entry->node_id, req.candidate_count); - return CONN_MGR_OK; -} - -static void cm_exchange_timeout_cb(void* arg) { - struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; - entry->main.timer = NULL; - struct cm_exchange_pending** pp = &entry->mgr->exchange_pending; - while (*pp) { if ((*pp)->entry == entry) { struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); break; } pp = &(*pp)->next; } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id); - cm_deliver_result(entry, CONN_MGR_ERR_TIMEOUT); -} - -static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_MGR_INTERM_EXCHANGE_RESP* resp) { - struct { uint64_t node_id; uint64_t total_rtt; } all[8]; - uint8_t count = 0; - - for (uint8_t i = 0; i < resp->your_count && count < 8; i++) { - uint64_t id = resp->your_candidates[i].node_id; - uint16_t our_rtt = 0; - for (uint8_t j = 0; j < CONN_MGR_MAX_CANDIDATES; j++) { - if (entry->mgr->best_candidates[j].node_id == id) { our_rtt = entry->mgr->best_candidates[j].rtt; break; } - } - all[count].node_id = id; - all[count].total_rtt = (uint64_t)our_rtt + resp->your_candidates[i].rtt; - count++; - } - for (uint8_t i = 0; i < resp->my_count && count < 8; i++) { - uint64_t id = resp->my_candidates[i].node_id; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(entry->mgr->group, id); - uint16_t our_rtt = nq ? cm_get_node_min_rtt(nq) : 0; - if (our_rtt == 0xFFFF) our_rtt = 0; - all[count].node_id = id; - all[count].total_rtt = (uint64_t)our_rtt + resp->my_candidates[i].rtt; - count++; - } - - for (uint8_t i = 0; i < count; i++) - for (uint8_t j = i + 1; j < count; j++) - if (all[j].total_rtt < all[i].total_rtt) { typeof(all[0]) t = all[i]; all[i] = all[j]; all[j] = t; } - - uint8_t sel = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count; - for (uint8_t i = 0; i < sel; i++) entry->intermediaries[i] = all[i].node_id; - entry->intermediariy_count = sel; - entry->rr_idx = 0; - - struct CONN_MGR_INTERM_SELECTED pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.cmd = ETCP_RT_ID_CONN_MGR; - pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED; - pkt.request_id = 0; - pkt.count = sel; - for (uint8_t i = 0; i < sel; i++) { - pkt.selected[i].node_id = all[i].node_id; - pkt.selected[i].rtt = 0; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(entry->mgr->group, all[i].node_id); - if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq); - } - struct ll_entry* qe = queue_entry_new(0); - if (qe) { qe->dgram = u_malloc(sizeof(pkt)); memcpy(qe->dgram, &pkt, sizeof(pkt)); qe->len = sizeof(pkt); etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1); } - - entry->conn_type = CONN_TYPE_INDIRECT; - entry->state = CONN_MGR_STATE_CONNECTED; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect OK for 0x%016llx via %u intermediaries", - (unsigned long long)entry->node_id, sel); - cm_update_nodeinfo(entry); - cm_deliver_result(entry, CONN_MGR_OK); -} - -static void cm_exchange_probe_retry_cb(void* arg) { - struct cm_exchange_pending* ep = (struct cm_exchange_pending*)arg; - struct CONN_MGR* mgr = ep->entry->mgr; - ep->timeout_timer = NULL; - uint8_t need_probe = 0; - uint64_t now_tb = get_time_tb(); - for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) { - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, ep->cached_resp.my_candidates[i].node_id); - if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; break; } - } - if (need_probe == 0) { - cm_compute_intermediaries(ep->entry, &ep->cached_resp); - struct cm_exchange_pending** pp = &mgr->exchange_pending; - while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; } - u_free(ep); - } else { - ep->timeout_timer = uasync_set_timeout(mgr->instance->ua, 2000, ep, cm_exchange_probe_retry_cb, "cm_exch_probe"); - } -} - -static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { - const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data; - size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates); - if (len < min_size) return; - struct cm_exchange_pending* ep = mgr->exchange_pending; - while (ep) { if (ep->request_id == resp->request_id) break; ep = ep->next; } - if (!ep) return; - - if (ep->entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, ep->entry->main.timer); ep->entry->main.timer = NULL; } - - memcpy(&ep->cached_resp, resp, len < sizeof(ep->cached_resp) ? len : sizeof(ep->cached_resp)); - ep->resp_received = 1; - ep->probes_done = 0; - - uint8_t need_probe = 0; - uint64_t now_tb = get_time_tb(); - for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) { - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, ep->cached_resp.my_candidates[i].node_id); - if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; route_connectivity_probe_node(mgr->instance, nq); } - } - if (need_probe == 0) { ep->probes_done = 1; cm_compute_intermediaries(ep->entry, &ep->cached_resp); - struct cm_exchange_pending** pp = &mgr->exchange_pending; - while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; } - u_free(ep); return; } - ep->timeout_timer = uasync_set_timeout(mgr->instance->ua, 2000, ep, cm_exchange_probe_retry_cb, "cm_exch_probe"); -} - -static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { - const struct CONN_MGR_INTERM_SELECTED* sel = (const struct CONN_MGR_INTERM_SELECTED*)data; - if (len < offsetof(struct CONN_MGR_INTERM_SELECTED, selected)) return; - struct CONN_MGR_ENTRY* entry = NULL; - for (size_t i = 0; i < mgr->entry_count; i++) - if (mgr->entries[i].state == CONN_MGR_STATE_CONNECTING) { entry = &mgr->entries[i]; break; } - if (!entry) return; - uint8_t cnt = sel->count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : sel->count; - for (uint8_t i = 0; i < cnt; i++) entry->intermediaries[i] = sel->selected[i].node_id; - entry->intermediariy_count = cnt; - entry->rr_idx = 0; - entry->conn_type = CONN_TYPE_INDIRECT; - entry->state = CONN_MGR_STATE_CONNECTED; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got INTERM_SELECTED count=%u", cnt); -} - -static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) { - if (!entry || !entry->dgram || entry->len < 3) return; - uint8_t* dgram = entry->dgram + 1; // skip svc_id byte from router - size_t len = entry->len - 1; - uint8_t subcmd = dgram[1]; - struct CONN_MGR* mgr = conn->instance->conn_mgr; - - if (mgr) { - struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id); - if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb(); - } - - switch (subcmd) { - case CONN_MGR_SUBCMD_DIRECT_REQ: - cm_handle_direct_req(conn, dgram, len); - break; - case CONN_MGR_SUBCMD_DIRECT_RESP: - break; - case CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ: - if (len >= CONN_MGR_INTERM_EXCHANGE_REQ_SIZE) - cm_handle_interm_exchange_req(conn, (struct CONN_MGR_INTERM_EXCHANGE_REQ*)dgram); - break; - case CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP: - if (mgr) cm_handle_interm_exchange_resp(mgr, dgram, len); - break; - case CONN_MGR_SUBCMD_INTERM_SELECTED: - if (mgr) cm_handle_interm_selected(mgr, dgram, len); - break; - case CONN_MGR_SUBCMD_DISCONNECT: - if (len >= CONN_MGR_DISCONNECT_SIZE && mgr) { - struct CONN_MGR_DISCONNECT* disc = (struct CONN_MGR_DISCONNECT*)dgram; - cm_handle_disconnect(mgr, disc->node_id); - } - break; - case CONN_MGR_SUBCMD_INVITE_INFO_REQ: - if (mgr && len >= sizeof(struct CONN_MGR_INVITE_INFO_REQ)) - cm_handle_invite_info_req(mgr, conn, dgram, len); - break; - case CONN_MGR_SUBCMD_INVITE_INFO_RESP: - if (mgr) cm_handle_invite_info_resp(mgr, dgram, len); - break; - } -} - -static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) { - struct CONN_MGR* mgr = conn->instance->conn_mgr; - if (!mgr) return; - struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)data; - size_t expected = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)req->addr_count * 8; - if (len < expected) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs", - (unsigned long long)conn->peer_node_id, req->addr_count); - uint8_t* p = (uint8_t*)data + CONN_MGR_DIRECT_REQ_HDR_SIZE; - uint64_t src_node_id = conn->peer_node_id; - for (uint8_t i = 0; i < req->addr_count; i++) { - uint8_t type = *p++; - uint8_t ip[4]; memcpy(ip, p, 4); p += 4; - uint16_t port; memcpy(&port, p, 2); p += 2; - uint8_t sock_id = *p++; - (void)sock_id; (void)type; - struct ETCP_SOCKET* s = conn->instance->etcp_sockets; - while (s) { - if ((s->type == CFG_SERVER_TYPE_PUBLIC || s->type == CFG_SERVER_TYPE_UNKNOWN) && s->local_addr.ss_family == AF_INET) { - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; - memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port; - struct sockaddr_storage sa; - memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin)); - struct ETCP_CONN* new_conn = etcp_connection_create(conn->instance, NULL); - if (new_conn) { - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, src_node_id); - if (nq) { sc_init_ctx(&new_conn->crypto_ctx, &conn->instance->my_keys); - { struct TOPO_NODE* dni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); - sc_set_peer_public_key(&new_conn->crypto_ctx, dni ? dni->public_key : NULL, 0); } } - struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending)); - if (rp) { rp->request_id = req->request_id; rp->entry = NULL; rp->next = mgr->reverse_pending; - etcp_conn_add_cbk(new_conn, cm_reverse_init_cb, rp, ETCP_CBK_EVENT_INIT); mgr->reverse_pending = rp; } - etcp_link_new(new_conn, s, &sa, 0); - } - break; - } - s = s->next; - } - } -} - -static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, - struct CONN_MGR_INTERM_EXCHANGE_REQ* req) { - struct CONN_MGR* mgr = conn->instance->conn_mgr; - if (!mgr) return; - uint64_t now_tb = get_time_tb(); - for (uint8_t i = 0; i < req->candidate_count && i < 4; i++) { - uint64_t cand_id = req->candidates[i].node_id; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, cand_id); - if (nq && cm_is_rtt_fresh(nq, now_tb)) continue; - route_connectivity_probe_node(mgr->instance, nq); - } - struct CONN_MGR_INTERM_EXCHANGE_RESP resp; - memset(&resp, 0, sizeof(resp)); - resp.cmd = ETCP_RT_ID_CONN_MGR; - resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP; - resp.request_id = req->request_id; - resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count; - for (uint8_t i = 0; i < resp.my_count; i++) - resp.my_candidates[i] = mgr->best_candidates[i]; - resp.your_count = req->candidate_count > 4 ? 4 : req->candidate_count; - for (uint8_t i = 0; i < resp.your_count; i++) { - resp.your_candidates[i].node_id = req->candidates[i].node_id; - struct TOPO_GROUP_NODE* nq2 = topo_node_find_by_id(mgr->group, req->candidates[i].node_id); - uint16_t rtt = nq2 ? cm_get_node_min_rtt(nq2) : 0; - resp.your_candidates[i].rtt = rtt; - } - size_t resp_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates) + - (size_t)resp.your_count * sizeof(struct CONN_MGR_CANDIDATE); - struct ll_entry* qe = queue_entry_new(0); - if (qe) { - qe->dgram = u_malloc(resp_size); - memcpy(qe->dgram, &resp, resp_size); - qe->len = (uint16_t)resp_size; - etcp_route_send(mgr->instance, mgr->group->group_id, conn->peer_node_id, qe, 1); - } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u", - (unsigned long long)conn->peer_node_id, resp.my_count, resp.your_count); -} - -static 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_destroy(entry); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id); -} - -static void cm_bg_ping_noop_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, - const uint8_t* resp_data, size_t resp_data_len) { - (void)success; (void)rtt; (void)arg; (void)nonce; (void)resp_data; (void)resp_data_len; -} - -static void cm_bg_ping_timer_cb(void* arg) { - struct CONN_MGR* mgr = (struct CONN_MGR*)arg; - struct TOPO_GROUP* group = mgr->group; - if (!group || !group->nodes) { - 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"); - return; - } - uint64_t now_tb = get_time_tb(); - struct ll_entry* e = group->nodes->head; - size_t total = 0; - while (e) { total++; e = e->next; } - if (total == 0) { - 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"); - return; - } - if (mgr->bg_ping_cursor >= total) { - mgr->bg_ping_cursor = 0; - if ((now_tb - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB) { - uint64_t delay = CONN_MGR_BG_PING_CYCLE_MIN_TB - (now_tb - mgr->bg_ping_cycle_start_tb); - mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)delay, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping"); - return; - } - mgr->bg_ping_cycle_start_tb = now_tb; - } - size_t cursor = 0; - e = group->nodes->head; - while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; } - if (e) { - struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e; - struct TOPO_NODE* bg_ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); - uint64_t node_id = nq->node_id; - if (node_id != mgr->instance->node_id) { - struct ETCP_CONN* dconn = topo_group_find_conn_for_node(group, node_id); - int has_active = 0; - if (dconn && dconn->links) { - struct ETCP_LINK* l = dconn->links; - while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; } - } - if (!has_active && bg_ni) { - int sent_v4 = 0; - for (const struct TOPO_ADDR4* a = bg_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, bg_ni->public_key, &sa, - CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0, 0); - sent_v4 = 1; break; - } - if (!sent_v4) { - for (const struct TOPO_ADDR6* a6 = bg_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 sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); - 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; - etcp_send_ping_to_socket(mgr->instance, s, bg_ni->public_key, &sa, - CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0, 0); - break; - } - } - } - } - mgr->bg_ping_cursor++; - } - 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"); -} - -static void cm_candidate_ping_timer_cb(void* arg) { - struct CONN_MGR* mgr = (struct CONN_MGR*)arg; - uint64_t now_tb = get_time_tb(); - for (uint8_t i = 0; i < mgr->best_candidate_count; i++) { - uint64_t cand_id = mgr->best_candidates[i].node_id; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, cand_id); - if (!nq) { - memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0])); - mgr->best_candidate_count--; i--; continue; - } - uint64_t last = cm_get_node_max_probe_time(nq); - if (last > 0 && (now_tb - last) >= CONN_MGR_CANDIDATE_STALE_TB) { - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)cand_id); - memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0])); - mgr->best_candidate_count--; i--; continue; - } - struct ETCP_CONN* dconn = topo_group_find_conn_for_node(mgr->group, cand_id); - int has_active = 0; - if (dconn && dconn->links) { - struct ETCP_LINK* l = dconn->links; - while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; } - } - if (has_active) { - struct TOPO_NODE* cp_ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); - if (cp_ni) etcp_send_ping(mgr->instance, cp_ni->public_key, &dconn->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0); - } - else - route_connectivity_probe_node(mgr->instance, 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"); -} - -/* ═══════════════════════════════════════════════════════════════════════ - * Invite-подключение - * ══════════════════════════════════════════════════════════════════════ */ - -static void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz) { - size_t j = 0; - for (size_t i = 0; ch_id[i] && j < out_sz - 1; i++) - if ((ch_id[i] >= '0' && ch_id[i] <= '9') || (ch_id[i] >= 'a' && ch_id[i] <= 'z') || (ch_id[i] >= 'A' && ch_id[i] <= 'Z') || ch_id[i] == '_') - out[j++] = ch_id[i]; - out[j] = '\0'; -} - -static int cm_check_group_member(struct CONN_MGR* mgr, uint64_t group_id, uint64_t peer_node_id) { - struct TOPO_GROUP* group = topo_groups_find(mgr->instance->topo_groups, group_id); - if (!group) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite_info group 0x%016llx not found", (unsigned long long)group_id); return 0; } - if (group->group_type == TOPO_GROUP_TYPE_CHAT) { - sqlite3* db = mgr->instance->topo_sqlite_db; - if (!db || !group->channel_id[0]) return 0; - char san[64]; sanitize_ch_id(group->channel_id, san, sizeof(san)); - char tbl[80]; snprintf(tbl, sizeof(tbl), "peers_%s", san); - char sql[128]; snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=?", tbl); - sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) == SQLITE_OK) { - sqlite3_bind_int64(st, 1, (sqlite3_int64)peer_node_id); - int is_member = (sqlite3_step(st) == SQLITE_ROW); - sqlite3_finalize(st); - return is_member; - } - return 0; - } - return topo_node_find_by_id(group, peer_node_id) != NULL; -} - -static void cm_handle_invite_info_req(struct CONN_MGR* mgr, struct ETCP_CONN* conn, - const uint8_t* data, size_t len) { - if (len < sizeof(struct CONN_MGR_INVITE_INFO_REQ)) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_REQ too short (%zu)", len); - return; - } - struct CONN_MGR_INVITE_INFO_REQ* req = (struct CONN_MGR_INVITE_INFO_REQ*)data; - uint64_t peer_node_id = conn->peer_node_id; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_REQ from 0x%016llx group=0x%016llx", - (unsigned long long)peer_node_id, (unsigned long long)req->group_id); - - uint8_t is_member = 1; /* invite link itself is authorization — no group membership check needed */ - - struct TOPO_GROUP* group = topo_groups_find(mgr->instance->topo_groups, req->group_id); - struct TOPO_GROUP* src_group = group ? group : mgr->group; - const uint8_t* ed_pubkey = src_group->ed25519_public_key; - struct TOPO_NODE* ln = (src_group->local_node) ? topo_node_registry_find(src_group->instance->topo_groups, src_group->local_node->node_id) : NULL; - const char* node_name = ln ? ln->node_name : NULL; - size_t name_len = node_name ? strlen(node_name) : 0; - if (name_len > 255) name_len = 255; - - size_t resp_size = sizeof(struct CONN_MGR_INVITE_INFO_RESP) + name_len; - struct CONN_MGR_INVITE_INFO_RESP* resp = u_calloc(1, resp_size); - if (!resp) return; - resp->cmd = ETCP_RT_ID_CONN_MGR; - resp->subcmd = CONN_MGR_SUBCMD_INVITE_INFO_RESP; - resp->group_id = req->group_id; - resp->is_member = is_member; - memcpy(resp->ed25519_pubkey, ed_pubkey, SC_PUBKEY_SIZE); - resp->node_name_len = (uint8_t)name_len; - if (name_len) memcpy(resp->node_name, node_name, name_len); - - struct ll_entry* qe = queue_entry_new(0); - if (qe) { qe->dgram = (uint8_t*)resp; qe->len = (uint16_t)resp_size; etcp_send(conn, qe); } - else u_free(resp); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent INVITE_INFO_RESP to 0x%016llx member=%d name_len=%zu", - (unsigned long long)peer_node_id, is_member, name_len); -} - -static void cm_handle_invite_info_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { - if (len < sizeof(struct CONN_MGR_INVITE_INFO_RESP)) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP too short (%zu)", len); - return; - } - struct CONN_MGR_INVITE_INFO_RESP* resp = (struct CONN_MGR_INVITE_INFO_RESP*)data; - size_t expected = sizeof(struct CONN_MGR_INVITE_INFO_RESP) + (size_t)resp->node_name_len; - if (len < expected) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP truncated (%zu < %zu)", len, expected); - return; - } - - struct cm_invite_pending* prev = NULL; - struct cm_invite_pending* inv = mgr->invite_list; - while (inv) { - if (inv->node_id == resp->group_id ? 0 : 0) { inv = inv->next; continue; } - /* find by state == CM_INVITE_WAIT_INFO and node_id match via conn */ - if (inv->state == CM_INVITE_WAIT_INFO && inv->conn) { - break; - } - prev = inv; inv = inv->next; - } - /* need to find the right invite by peer_node_id from the connection that delivered this */ - struct cm_invite_pending* found = NULL; - for (struct cm_invite_pending* p = mgr->invite_list; p; p = p->next) - if (p->state == CM_INVITE_WAIT_INFO) { found = p; break; } - if (!found) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP — no pending invite in WAIT_INFO state"); return; } - inv = found; - - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP node=0x%016llx group=0x%016llx member=%d name_len=%u", - (unsigned long long)inv->node_id, (unsigned long long)resp->group_id, resp->is_member, resp->node_name_len); - - if (inv->group_id != 0 && !resp->is_member) { cm_invite_fail(inv, CONN_MGR_ERR_NOT_MEMBER); return; } - - struct TOPO_GROUP_NODE* nq = inv->temp_nq; - if (nq) { - struct TOPO_NODE* inv_ni = topo_node_registry_find(inv->mgr->instance->topo_groups, nq->node_id); - if (inv_ni) { - memcpy(inv_ni->ed25519_public_key, resp->ed25519_pubkey, SC_PUBKEY_SIZE); - if (inv_ni->node_name) { u_free(inv_ni->node_name); inv_ni->node_name = NULL; } - if (resp->node_name_len) { inv_ni->node_name = u_strdup((const char*)resp->node_name); } - } - sqlite3* db = inv->mgr->instance->topo_sqlite_db; - if (db) topo_node_sqlite_node_put(db, inv->mgr->instance->topo_groups, nq, (time_t)(get_time_tb() / 10000)); - } - - if (inv->overall_timer) { uasync_cancel_timeout(inv->mgr->instance->ua, inv->overall_timer); inv->overall_timer = NULL; } - if (inv->cb) inv->cb(CONN_MGR_OK, inv->node_id, inv->cb_arg); - cm_invite_cleanup(inv); -} - -static void cm_invite_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event; - struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; - if (inv->state != CM_INVITE_CONNECTING) return; - if (!conn || conn->peer_node_id != inv->node_id) { - DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite init cb peer mismatch conn=0x%016llx inv=0x%016llx", - (unsigned long long)(conn ? conn->peer_node_id : 0), (unsigned long long)inv->node_id); - if (conn) cm_invite_fail(inv, CONN_MGR_ERR_INTERNAL); - return; - } - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite INIT OK node=0x%016llx", (unsigned long long)inv->node_id); - inv->state = CM_INVITE_WAIT_INFO; - - struct CONN_MGR_INVITE_INFO_REQ req; - req.cmd = ETCP_RT_ID_CONN_MGR; req.subcmd = CONN_MGR_SUBCMD_INVITE_INFO_REQ; - req.group_id = inv->group_id; - struct ll_entry* qe = queue_entry_new(0); - if (!qe) { cm_invite_fail(inv, CONN_MGR_ERR_INTERNAL); return; } - qe->dgram = u_malloc(sizeof(req)); - if (!qe->dgram) { queue_entry_free(qe); cm_invite_fail(inv, CONN_MGR_ERR_INTERNAL); return; } - memcpy(qe->dgram, &req, sizeof(req)); qe->len = sizeof(req); - etcp_send(conn, qe); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent INVITE_INFO_REQ to 0x%016llx group=0x%016llx", - (unsigned long long)inv->node_id, (unsigned long long)inv->group_id); -} - -static void cm_invite_overall_timeout(void* arg) { - struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; - inv->overall_timer = NULL; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite overall timeout node=0x%016llx", (unsigned long long)inv->node_id); - cm_invite_fail(inv, CONN_MGR_ERR_TIMEOUT); -} - -/* ── TCP invite callbacks ── */ - -static void cm_tcp_ready_cb(struct stcp_link* link, void* arg) { - struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; - if (!inv || inv->tcp_ready) return; - inv->tcp_ready = 1; - - struct ETCP_CONN* etcp = stcp_link_get_etcp_conn(link); - etcp->peer_node_id = inv->node_id; - - struct ll_entry* qe = queue_entry_new(sizeof(struct tcp_conn_entry)); - if (!qe) { cm_invite_fail(inv, CONN_MGR_ERR_INTERNAL); return; } - struct tcp_conn_entry* te = (struct tcp_conn_entry*)qe->data; - te->node_id = inv->node_id; te->link = link; te->etcp_conn = etcp; - queue_data_put_with_index(inv->mgr->instance->tcp_connections, qe); - - inv->conn = etcp; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: TCP invite ready node=0x%016llx", (unsigned long long)inv->node_id); - cm_invite_init_cb(etcp, ETCP_CBK_EVENT_INIT, inv); -} - -static void cm_tcp_close_cb(struct stcp_link* link, int err, void* arg) { - struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; - if (!inv) return; - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: TCP invite closed node=0x%016llx err=%d", - (unsigned long long)inv->node_id, err); - inv->tcp_link = NULL; -} - -static void cm_invite_fail(struct cm_invite_pending* inv, int result) { - if (!inv) return; - struct CONN_MGR* mgr = inv->mgr; - if (inv->overall_timer) { uasync_cancel_timeout(mgr->instance->ua, inv->overall_timer); inv->overall_timer = NULL; } - if (inv->tcp_link) { - struct ll_entry* e = queue_find_data_by_index(mgr->instance->tcp_connections, (const uint8_t*)&inv->node_id); - if (e) queue_remove_data(mgr->instance->tcp_connections, e); - stcp_link_close(inv->tcp_link); inv->tcp_link = NULL; inv->conn = NULL; - } else if (inv->conn) { - etcp_connection_close(inv->conn); inv->conn = NULL; - } - if (inv->temp_nq) { - struct TOPO_GROUP* group = mgr->group; - queue_remove_data(group->nodes, &inv->temp_nq->ll); - topo_nodeq_free_group_fields(mgr->instance->topo_groups, inv->temp_nq); - queue_entry_free(&inv->temp_nq->ll); inv->temp_nq = NULL; - } - struct cm_invite_pending** pp = &mgr->invite_list; - while (*pp) { if (*pp == inv) { *pp = inv->next; break; } pp = &(*pp)->next; } - if (inv->cb) inv->cb(result, inv->node_id, inv->cb_arg); - cm_invite_cleanup(inv); -} - -static void cm_invite_cleanup(struct cm_invite_pending* inv) { - if (!inv) return; - u_free(inv); -} - -int conn_mgr_connect_from_invite(struct CONN_MGR* mgr, struct TOPO_NODE* ni, - uint64_t group_id, uint32_t timeout_ms, - conn_mgr_connect_callback_t cb, void* cb_arg) { - if (!mgr || !ni || (!ni->v4_addrs && !ni->v6_addrs)) { if (cb) cb(CONN_MGR_ERR_INTERNAL, ni ? ni->node_id : 0, cb_arg); return CONN_MGR_ERR_INTERNAL; } - uint64_t node_id = ni->node_id; - if (node_id == 0 || node_id == mgr->instance->node_id) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - struct TOPO_GROUP* group = mgr->group; - struct TOPO_GROUPS* groups = mgr->instance->topo_groups; - if (!groups) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - - if (topo_node_find_by_id(group, node_id)) { if (cb) cb(CONN_MGR_ERR_ALREADY_CONNECTED, node_id, cb_arg); return CONN_MGR_ERR_ALREADY_CONNECTED; } - - if (!timeout_ms) timeout_ms = CM_INVITE_DEFAULT_TIMEOUT_MS; - - struct TOPO_NODE* group_ni = u_calloc(1, sizeof(struct TOPO_NODE)); - if (!group_ni) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - group_ni->node_id = node_id; group_ni->ver = 1; group_ni->group_id = mgr->group->group_id; - memcpy(group_ni->public_key, ni->public_key, SC_PUBKEY_SIZE); - for (const struct TOPO_ADDR4* src = ni->v4_addrs; src; src = src->next) { - struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); - if (!a4) continue; - memset(a4, 0, sizeof(*a4)); memcpy(a4->addr, src->addr, 4); a4->port = src->port; - a4->type = TOPO_ADDR_NAT; a4->protocol = src->protocol; a4->socket_id = 0; - a4->next = group_ni->v4_addrs; group_ni->v4_addrs = a4; - } - for (const struct TOPO_ADDR6* src6 = ni->v6_addrs; src6; src6 = src6->next) { - struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); - if (!a6) continue; - memset(a6, 0, sizeof(*a6)); memcpy(a6->addr, src6->addr, 16); a6->port = src6->port; - a6->type = TOPO_ADDR_NAT; a6->protocol = src6->protocol; a6->socket_id = 0; - a6->next = group_ni->v6_addrs; group_ni->v6_addrs = a6; - } - if (!group_ni->v4_addrs && !group_ni->v6_addrs) { u_free(group_ni); if (cb) cb(CONN_MGR_ERR_NO_ADDRESSES, node_id, cb_arg); return CONN_MGR_ERR_NO_ADDRESSES; } - - group_ni = topo_node_registry_store(groups, group_ni); /* group_ni now owned by registry (ref=1) */ - if (!group_ni) { if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - - struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); - if (!qe) { topo_node_registry_unref(groups, group_ni->node_id); if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)qe; - memset((uint8_t*)nq + sizeof(struct ll_entry), 0, sizeof(*nq) - sizeof(struct ll_entry)); - nq->node_id = node_id; - - struct cm_invite_pending* inv = u_calloc(1, sizeof(struct cm_invite_pending)); - if (!inv) { queue_entry_free(qe); topo_node_registry_unref(groups, group_ni->node_id); if (cb) cb(CONN_MGR_ERR_INTERNAL, node_id, cb_arg); return CONN_MGR_ERR_INTERNAL; } - inv->mgr = mgr; inv->node_id = node_id; inv->group_id = group_id; - inv->timeout_ms = timeout_ms; inv->state = CM_INVITE_CONNECTING; - inv->temp_nq = nq; inv->cb = cb; inv->cb_arg = cb_arg; - inv->next = mgr->invite_list; mgr->invite_list = inv; - - queue_data_put_with_index(group->nodes, &nq->ll); - - int udp_connected = 0, tcp_connected = 0; - struct ETCP_CONN* udp_conn = NULL; - - /* ── UDP: all addresses × all local sockets (single ETCP_CONN, no break) ── */ - for (const struct TOPO_ADDR4* a = group_ni->v4_addrs; a; a = a->next) { - if (!(a->protocol & TOPO_PROTO_UDP)) continue; - struct ETCP_SOCKET* s = mgr->instance->etcp_sockets; - while (s) { - if (s->local_addr.ss_family == AF_INET) { - 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; memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin)); - if (!udp_conn) { - udp_conn = etcp_connection_create(mgr->instance, NULL); - if (udp_conn) { - etcp_conn_add_cbk(udp_conn, cm_invite_init_cb, inv, ETCP_CBK_EVENT_INIT); - sc_init_ctx(&udp_conn->crypto_ctx, &mgr->instance->my_keys); - sc_set_peer_public_key(&udp_conn->crypto_ctx, group_ni->public_key, 0); - } - } - if (udp_conn && etcp_link_new(udp_conn, s, &sa, 0)) { - inv->conn = udp_conn; udp_connected = 1; - } - } - s = s->next; - } - } - for (const struct TOPO_ADDR6* a6 = group_ni->v6_addrs; a6; a6 = a6->next) { - if (!(a6->protocol & TOPO_PROTO_UDP)) continue; - uint8_t tclass = cm_classify_v6_addr(a6->addr); if (tclass == CM_V6_OTHER) continue; - struct ETCP_SOCKET* s = mgr->instance->etcp_sockets; - while (s) { - if (cm_sock_v6_classify(s) == tclass) { - if (!udp_conn) { - udp_conn = etcp_connection_create(mgr->instance, NULL); - if (udp_conn) { - etcp_conn_add_cbk(udp_conn, cm_invite_init_cb, inv, ETCP_CBK_EVENT_INIT); - sc_init_ctx(&udp_conn->crypto_ctx, &mgr->instance->my_keys); - sc_set_peer_public_key(&udp_conn->crypto_ctx, group_ni->public_key, 0); - } - } - if (udp_conn) { cm_add_v6_link(udp_conn, a6->addr, a6->port, s); inv->conn = udp_conn; udp_connected = 1; } - } - s = s->next; - } - } - - /* ── TCP: first address ── */ - for (const struct TOPO_ADDR4* a = group_ni->v4_addrs; a; a = a->next) { - if (!(a->protocol & TOPO_PROTO_TCP)) 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; memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin)); - struct stcp_link_config tcp_cfg = { - .ua = mgr->instance->ua, .my_keys = &mgr->instance->my_keys, .inst = mgr->instance, - .peer_pubkey = group_ni->public_key, .peer_pubkey_mode = 0, - .remote_addr = &sa, .remote_port = a->port, - }; - inv->tcp_link = stcp_link_connect(&tcp_cfg); - if (inv->tcp_link) { - stcp_link_set_on_ready(inv->tcp_link, cm_tcp_ready_cb, inv); - stcp_link_set_on_close(inv->tcp_link, cm_tcp_close_cb, inv); - tcp_connected = 1; - } - break; - } - for (const struct TOPO_ADDR6* a6 = group_ni->v6_addrs; a6; a6 = a6->next) { - if (!(a6->protocol & TOPO_PROTO_TCP)) 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 sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); - struct stcp_link_config tcp_cfg6 = { - .ua = mgr->instance->ua, .my_keys = &mgr->instance->my_keys, .inst = mgr->instance, - .peer_pubkey = group_ni->public_key, .peer_pubkey_mode = 0, - .remote_addr = &sa, .remote_port = a6->port, - }; - inv->tcp_link = stcp_link_connect(&tcp_cfg6); - if (inv->tcp_link) { - stcp_link_set_on_ready(inv->tcp_link, cm_tcp_ready_cb, inv); - stcp_link_set_on_close(inv->tcp_link, cm_tcp_close_cb, inv); - tcp_connected = 1; - } - break; - } - - if (!udp_connected && !tcp_connected) { - cm_invite_fail(inv, CONN_MGR_ERR_UNREACHABLE); return CONN_MGR_ERR_UNREACHABLE; - } - - inv->overall_timer = uasync_set_timeout(mgr->instance->ua, (int)inv->timeout_ms * 10, inv, cm_invite_overall_timeout, "conn_mgr_invite"); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite started node=0x%016llx group=0x%016llx timeout=%ums", - (unsigned long long)node_id, (unsigned long long)group_id, timeout_ms); - return CONN_MGR_OK; -} diff --git a/src/routing_layer/conn_mgr.h b/src/routing_layer/conn_mgr.h index f9688660..f63bcd39 100644 --- a/src/routing_layer/conn_mgr.h +++ b/src/routing_layer/conn_mgr.h @@ -5,544 +5,113 @@ extern "C" { #endif - #include -#include #include "../lib/ll_queue.h" -#include "etcp_api.h" -#include "etcp_router.h" #include "topo_node.h" +struct CONN_MGR; /* opaque */ +struct CONN_MGR_HANDLE; /* opaque */ struct UTUN_INSTANCE; struct ETCP_CONN; -struct TOPO_GROUP_NODE; struct TOPO_GROUP; /** * @file conn_mgr.h - * @brief Менеджер установки соединений. Трёхфазная машина подключения к нодам - * с выбором оптимального пути: прямой → обратный → через посредников. + * @brief Подключение к узлам P2P-сети. Handle-based API. * - * Основные возможности: - * - Автоматический выбор лучшего способа подключения к целевой ноде - * - Фаза 1 (DIRECT): попытка прямого INIT-рукопожатия на все известные IPv4-адреса цели. - * Итерация по парам (наш сокет, целевой адрес) с проверкой NAT-совместимости. - * Таймаут: CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS - * - Фаза 2 (REVERSE): если у нас есть прямой IP, а у цели нет — отправляем DIRECT_REQ - * с нашими адресами через BGP-маршрут. Цель сама инициирует INIT в нашу сторону. - * Таймаут: CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS - * - Фаза 3 (INDIRECT): если DIRECT и REVERSE не сработали — обмениваемся через BGP - * списками лучших промежуточных узлов-кандидатов с RTT, выбираем общих с минимальным - * суммарным RTT. Трафик идёт через etcp_router (транзитная ретрансляция). - * Таймаут: CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS - * - Фоновое сканирование локальной сети (broadcast/multicast UDP-ping) - * - Фоновое поддержание списка лучших кандидатов-посредников (периодический ping, - * удаление stale-кандидатов) - * - Фоновый обход всех известных BGP-нод для поддержания RTT-замеров - * - Управление idle-таймаутом с автоматическим разрывом неактивных соединений + * === Архитектура === * - * @note Правила использования: - * 1. conn_mgr_init() вызывается один раз при старте экземпляра utun - * 2. conn_mgr_connect_node() — асинхронный вызов, результат приходит в callback - * 3. Повторный вызов conn_mgr_connect_node для уже подключённой ноды вернёт - * CONN_MGR_ERR_ALREADY_CONNECTED; для ноды в состоянии CONNECTING добавит - * дополнительный callback в список - * 4. conn_mgr_disconnect_node() отправляет DISCONNECT-пакет целевой ноде и - * сбрасывает conn_mgr_type в NODEINFO_Q - * 5. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity) - * для обновления RTT кандидатов + * Один CONN_MGR = одна TOPO_GROUP. Создаётся conn_mgr_init(group), живёт пока + * существует группа. Внутри: кеш entries (по одной на целевой узел), список + * handle'ов (по одному на каждый вызов open), фоновые ping/idle/мониторинг. * - * @note Внутренние таймеры (всё в timebase-единицах, 1 tb = 0.1 мс): - * - bg_ping_timer: фоновый ping нод (CONN_MGR_BG_PING_INTERVAL_TB) - * - candidate_ping_timer: обновление RTT кандидатов (CONN_MGR_CANDIDATE_PING_TB) - * - idle_timer: проверка неактивности подключённых нод (CONN_MGR_IDLE_CHECK_INTERVAL_TB) - */ - -/* ==================== Константы и лимиты ==================== */ - -#define CONN_MGR_MAX_CANDIDATES 3 ///< Макс. число лучших кандидатов-посредников -#define CONN_MGR_CANDIDATE_CACHE_MS 20000 ///< Время жизни кандидата в кеше (мс) — не используется напрямую в .h -#define CONN_MGR_CANDIDATE_STALE_TB 300000 ///< Порог устаревания кандидата (tb), после которого удаляется из списка -#define CONN_MGR_CANDIDATE_PING_TB 20000 ///< Интервал фонового ping кандидатов (tb, ~2 сек) -#define CONN_MGR_BG_PING_INTERVAL_TB 1000 ///< Интервал фонового ping BGP-нод (tb, ~100 мс) -#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 ///< Минимальная длительность полного цикла обхода нод (tb, ~10 сек) -#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 ///< Интервал проверки idle-таймаута (tb, ~1 сек) -#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 ///< Число попыток локального сканирования -#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 ///< Таймаут локального сканирования (мс) -#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 2000 ///< Таймаут фазы DIRECT (мс) -#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 ///< Таймаут фазы REVERSE (мс) -#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 ///< Таймаут фазы INDIRECT (мс) - -/* ==================== Коды возврата ==================== */ - -#define CONN_MGR_OK 0 ///< Успех -#define CONN_MGR_ERR_NOT_FOUND -1 ///< Нода не найдена в BGP -#define CONN_MGR_ERR_NO_ADDRESSES -2 ///< Нет известных адресов для подключения -#define CONN_MGR_ERR_TIMEOUT -3 ///< Таймаут соединения -#define CONN_MGR_ERR_UNREACHABLE -4 ///< Нода недостижима (все фазы провалились) -#define CONN_MGR_ERR_REFUSED -5 ///< Соединение отклонено (зарезервировано) -#define CONN_MGR_ERR_ALREADY_CONNECTED -6 ///< Уже подключены -#define CONN_MGR_ERR_INTERNAL -7 ///< Внутренняя ошибка -#define CONN_MGR_ERR_NOT_MEMBER -8 ///< Узел не является членом группы - -/* ==================== Состояния ==================== */ - -/** - * @enum CONN_MGR_STATE - * @brief Состояние подключения к ноде. - */ -enum CONN_MGR_STATE { - CONN_MGR_STATE_DISCONNECTED = 0, ///< Не подключены - CONN_MGR_STATE_CONNECTING = 1, ///< В процессе установки соединения - CONN_MGR_STATE_CONNECTED = 2, ///< Подключены -}; - -/** - * @enum CM_TRY_STATE - * @brief Состояние отдельной попытки (локальное сканирование / основное подключение). - */ -enum CM_TRY_STATE { - CM_TRY_NONE = 0, ///< Попытка не начиналась - CM_TRY_PENDING = 1, ///< Попытка в процессе - CM_TRY_OK = 2, ///< Попытка успешна - CM_TRY_FAILED = 3, ///< Попытка провалилась -}; - -/* ==================== Протокол CONN_MGR (ETCP_RT_ID_CONN_MGR = 0x11) ==================== */ - -/** - * @defgroup conn_mgr_subcmd Подкоманды протокола CONN_MGR - * @{ - */ -#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01 ///< "Я за NAT, подключись ко мне" — передача наших адресов цели -#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02 ///< Ответ на DIRECT_REQ (зарезервирован, не реализован) -#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03 ///< Запрос обмена кандидатами-посредниками -#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04 ///< Ответ с нашими кандидатами + замеры RTT до кандидатов инициатора -#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05 ///< Выбранные посредники (финальный шаг INDIRECT-фазы) -#define CONN_MGR_SUBCMD_DISCONNECT 0x06 ///< Уведомление о разрыве соединения -#define CONN_MGR_SUBCMD_BRIDGE_REQUEST 0x10 ///< A→X: запрос моста к B (payload: target_node_id) -#define CONN_MGR_SUBCMD_BRIDGE_RESPONSE 0x11 ///< X→A: ответ с session_id -#define CONN_MGR_SUBCMD_BRIDGE_CLOSE 0x12 ///< A→X: закрыть мост (payload: session_id + target_node_id) -#define CONN_MGR_SUBCMD_BRIDGE_CLOSED 0x13 ///< X→A: мост закрыт (payload: session_id) -#define CONN_MGR_SUBCMD_INVITE_INFO_REQ 0x0A ///< Запрос проверки членства в группе при invite-подключении -#define CONN_MGR_SUBCMD_INVITE_INFO_RESP 0x0B ///< Ответ с членством, ed25519_pubkey, именем узла -/** @} */ - -/** - * @struct CONN_MGR_CANDIDATE - * @brief Запись о кандидате-посреднике: идентификатор ноды + RTT до неё. + * === Единая точка входа: conn_mgr_open_invite === * - * Используется в списке best_candidates и во всех пакетах обмена кандидатами. - */ -struct CONN_MGR_CANDIDATE { - uint64_t node_id; ///< Идентификатор ноды-кандидата - uint16_t rtt; ///< RTT до кандидата (мс) -} __attribute__((packed)); - -#pragma pack(push, 1) - -/** - * @struct CONN_MGR_DIRECT_REQ - * @brief Пакет фазы REVERSE: "подключись ко мне". + * ni != NULL — invite-режим: DIRECT + INVITE_INFO_REQ/RESP, группа создаётся. + * Коллбэк: CONN_EVENT_JOIN (успех) или CONN_EVENT_TIMEOUT (ошибка). + * ni == NULL — обычное 3-фазное подключение (DIRECT→REVERSE→INDIRECT). + * Коллбэк: CONN_EVENT_UP (успех) или CONN_EVENT_TIMEOUT (провал). * - * Отправляется когда мы имеем прямой IP, но цель за NAT. - * Содержит наши адреса, по которым цель должна инициировать INIT. - */ -struct CONN_MGR_DIRECT_REQ { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_REQ - uint32_t request_id; ///< Идентификатор запроса для сопоставления ответа - uint8_t addr_count; ///< Количество адресов, следующих за заголовком - /* далее: addr_count записей по 8 байт каждая (type:1, ip:4, port:2, sock_id:1) */ -}; - -/** - * @struct CONN_MGR_DIRECT_RESP - * @brief Ответ на DIRECT_REQ (зарезервирован, не отправляется). - */ -struct CONN_MGR_DIRECT_RESP { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_RESP - uint32_t request_id; ///< Идентификатор запроса - uint8_t accepted; ///< 1 — принято, 0 — отклонено -}; - -/** - * @struct CONN_MGR_INTERM_EXCHANGE_REQ - * @brief Запрос обмена списками кандидатов-посредников (фаза INDIRECT, инициатор). + * === 3 фазы (когда ni == NULL) === * - * Инициатор отправляет свой топ-4 кандидатов. Цель должна ответить - * CONN_MGR_INTERM_EXCHANGE_RESP со своими кандидатами и замерами RTT до наших. - */ -struct CONN_MGR_INTERM_EXCHANGE_REQ { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ - uint32_t request_id; ///< Идентификатор запроса - uint8_t candidate_count; ///< Количество кандидатов в массиве (≤4) - struct CONN_MGR_CANDIDATE candidates[4]; ///< Наши лучшие кандидаты с RTT -}; - -/** - * @struct CONN_MGR_INTERM_EXCHANGE_RESP - * @brief Ответ на INTERM_EXCHANGE_REQ (фаза INDIRECT, цель). + * 1. DIRECT: node_conn_direct на все адреса, NAT-фильтрация, LAN broadcast ping. + * 2. REVERSE: у нас прямой IP — шлём DIRECT_REQ цели через BGP. + * 3. INDIRECT: через посредника с минимальной суммой RTT (etcp_router). * - * Содержит два списка: - * - my_candidates: лучшие кандидаты цели с RTT до них (измеренным целью) - * - your_candidates: кандидаты инициатора, но с RTT, измеренным со стороны цели - */ -struct CONN_MGR_INTERM_EXCHANGE_RESP { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP - uint32_t request_id; ///< Идентификатор запроса - uint8_t my_count; ///< Количество наших кандидатов (≤4) - uint8_t your_count; ///< Количество кандидатов инициатора (≤4) - struct CONN_MGR_CANDIDATE my_candidates[4]; ///< Наши кандидаты + наш RTT до них - struct CONN_MGR_CANDIDATE your_candidates[4]; ///< Кандидаты инициатора + наш RTT до них -}; - -/** - * @struct CONN_MGR_INTERM_SELECTED - * @brief Финальный выбор посредников (фаза INDIRECT). + * === Фоновые процессы === * - * Инициатор вычисляет лучших общих посредников по сумме RTT - * и отправляет выбранный список цели. - */ -struct CONN_MGR_INTERM_SELECTED { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_SELECTED - uint32_t request_id; ///< Идентификатор запроса (всегда 0 для извещения) - uint8_t count; ///< Количество выбранных посредников (≤3) - struct CONN_MGR_CANDIDATE selected[3]; ///< Выбранные посредники (node_id + RTT от нас до них) -}; - -/** - * @struct CONN_MGR_DISCONNECT - * @brief Уведомление о разрыве соединения. - */ -struct CONN_MGR_DISCONNECT { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_DISCONNECT - uint64_t node_id; ///< Идентификатор ноды, с которой разрываем соединение -}; - -struct CONN_MGR_BRIDGE_REQUEST { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_BRIDGE_REQUEST - uint64_t target_node_id; ///< К какой ноде нужен мост -}; - -struct CONN_MGR_BRIDGE_RESPONSE { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_BRIDGE_RESPONSE - uint64_t session_id; ///< ID bridge-сессии - uint64_t target_node_id; ///< Эхо target_node_id -}; - -struct CONN_MGR_BRIDGE_CLOSE { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_BRIDGE_CLOSE - uint64_t session_id; ///< ID bridge-сессии - uint64_t target_node_id; ///< ID цели -}; - -struct CONN_MGR_BRIDGE_CLOSED { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_BRIDGE_CLOSED - uint64_t session_id; ///< ID bridge-сессии -}; - -#define CONN_MGR_BRIDGE_REQUEST_SIZE sizeof(struct CONN_MGR_BRIDGE_REQUEST) -#define CONN_MGR_BRIDGE_RESPONSE_SIZE sizeof(struct CONN_MGR_BRIDGE_RESPONSE) -#define CONN_MGR_BRIDGE_CLOSE_SIZE sizeof(struct CONN_MGR_BRIDGE_CLOSE) -#define CONN_MGR_BRIDGE_CLOSED_SIZE sizeof(struct CONN_MGR_BRIDGE_CLOSED) - -#pragma pack(pop) - -/** - * @struct CONN_MGR_INVITE_INFO_REQ - * @brief Запрос проверки членства в группе (invite-подключение). + * bg_ping: раз в ~100ms один узел группы за тик. Поддерживает свежие RTT. + * candidate_ping: раз в ~2с, удаляет протухших кандидатов. + * idle: раз в ~1с, рвёт соединение при неактивности. * - * Отправляется напрямую по ETCP после INIT-рукопожатия. - * Запрашивает: ed25519_pubkey, имя узла, членство в группе. - */ -#pragma pack(push, 1) -struct CONN_MGR_INVITE_INFO_REQ { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_INVITE_INFO_REQ - uint64_t group_id; ///< целевая группа для проверки членства -}; -#pragma pack(pop) - -/** - * @struct CONN_MGR_INVITE_INFO_RESP - * @brief Ответ на INVITE_INFO_REQ. - */ -#pragma pack(push, 1) -struct CONN_MGR_INVITE_INFO_RESP { - uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR - uint8_t subcmd; ///< CONN_MGR_SUBCMD_INVITE_INFO_RESP - uint64_t group_id; ///< эхо group_id из запроса - uint8_t is_member; ///< 1 = член группы, 0 = нет - uint8_t reserved[3]; - uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; ///< Ed25519 публичный ключ - uint8_t node_name_len; ///< длина имени (0..255) - uint8_t node_name[0]; ///< UTF-8 имя (variable-length) -}; -#pragma pack(pop) - -/* ==================== Размеры пакетов протокола ==================== */ - -#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ) ///< Размер заголовка DIRECT_REQ (без addr_count адресов) -#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP) ///< Размер DIRECT_RESP -#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ) ///< Размер INTERM_EXCHANGE_REQ -#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP) ///< Размер INTERM_EXCHANGE_RESP (макс.) -#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED) ///< Размер INTERM_SELECTED -#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT) ///< Размер DISCONNECT -#define CONN_MGR_INVITE_INFO_REQ_SIZE sizeof(struct CONN_MGR_INVITE_INFO_REQ) ///< Размер INVITE_INFO_REQ - -/* ==================== Callback-типы и внутренние структуры ==================== */ - -/** - * @typedef conn_mgr_connect_callback_t - * @brief Callback результата подключения. - * @param result код возврата (CONN_MGR_OK или CONN_MGR_ERR_*) - * @param node_id идентификатор ноды - * @param arg пользовательский аргумент - */ -typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg); - -/** - * @struct CONN_MGR_HANDLE - * @brief Универсальный handle подключения (один на каждый conn_mgr_connect_node). - */ -struct CONN_MGR_HANDLE { - uint64_t target_node_id; - uint8_t conn_type; - uint64_t intermediary_node_id; - uint64_t bridge_session_id; - conn_mgr_connect_callback_t result_cb; - void* result_arg; - struct CONN_MGR_HANDLE* next; - struct CONN_MGR_ENTRY* entry; -}; - -/** - * @struct cm_bridge_session - * @brief Bridge-сессия на посреднике X (INDIRECT-прокси). - */ -struct cm_bridge_session { - uint64_t session_id; - uint64_t initiator_id; - uint64_t target_id; - struct CONN_MGR_HANDLE* handle_to_target; - struct CONN_MGR_HANDLE* handle_to_initiator; - struct cm_bridge_session* next; -}; - -/** - * @struct CONN_MGR_ENTRY - * @brief Запись о подключении к одной ноде. + * === Refcounting и закрытие === * - * Хранит состояние трёхфазной машины, список выбранных посредников, - * idle-таймер и список ожидающих callback'ов. + * Несколько handle на один ETCP_CONN. conn_mgr_close удаляет ОДИН handle. + * Последний handle: DIRECT/REVERSE — DISCONNECT + NCD CLOSE. + * Invite — только NCD CLOSE. */ -struct CONN_MGR_ENTRY { - uint64_t node_id; ///< Идентификатор целевой ноды - uint8_t state; ///< Текущее состояние (enum CONN_MGR_STATE) - uint8_t conn_type; ///< Тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT) - uint32_t idle_timeout_ms; ///< Таймаут неактивности (мс), 0 — без таймаута - uint64_t last_traffic_tb; ///< Время последнего трафика (tb) для idle-проверки - void* idle_timer; ///< Хендл таймера idle-проверки - - struct CONN_MGR_HANDLE* handles; ///< Связный список handle'ов (один на conn_mgr_connect_node) - uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; ///< Выбранные посредники (node_id) - uint8_t intermediariy_count; ///< Количество выбранных посредников - uint8_t rr_idx; ///< Round-robin индекс для выбора посредника при отправке - - uint8_t local_scan_state; ///< Состояние локального сканирования (enum CM_TRY_STATE) - uint8_t main_connect_state; ///< Состояние основного подключения (enum CM_TRY_STATE) - uint8_t db_loaded; ///< 1 = узел загружен из БД, нет BGP-роутинга, только DIRECT - struct { - uint8_t phase; ///< Текущая фаза: 1=DIRECT, 2=REVERSE, 3=INDIRECT - void* timer; ///< Хендл таймера текущей фазы - uint32_t request_id; ///< request_id для INDIRECT-обмена - void* conn_ctx; ///< Контекст ETCP-соединения (для REVERSE) - } main; - - struct CONN_MGR* mgr; ///< Обратная ссылка на менеджер +enum conn_mgr_event { + CONN_EVENT_UP = 0, /* обычное соединение установлено */ + CONN_EVENT_DOWN = 1, /* соединение потеряно */ + CONN_EVENT_TIMEOUT = 2, /* таймаут / ошибка */ + CONN_EVENT_JOIN = 3, /* invite успешен (группа создана, соединение готово) */ }; /** - * @struct CONN_MGR - * @brief Корневая структура менеджера соединений. - * - * Создаётся один раз в conn_mgr_init(), хранится в UTUN_INSTANCE. - * Содержит динамический массив entries, список лучших кандидатов, - * таймеры фонового ping и список ожидающих обменов (reverse/exchange pending). + * Единый коллбэк для всех операций. + * @param h handle (NULL при ошибках до создания handle) + * @param node_id целевой узел (inviter при invite, target при обычном open) + * @param group_id группа + * @param event UP / DOWN / TIMEOUT / JOIN */ -struct CONN_MGR { - struct UTUN_INSTANCE* instance; ///< Родительский экземпляр utun - struct TOPO_GROUP* group; ///< Группа, в которой работает этот менеджер - - struct CONN_MGR_ENTRY* entries; ///< Динамический массив записей о подключениях - size_t entry_count; ///< Текущее количество записей - size_t entry_capacity; ///< Выделенная ёмкость массива entries - - void* bg_ping_timer; ///< Хендл таймера фонового ping BGP-нод - size_t bg_ping_cursor; ///< Курсор текущей ноды в цикле обхода - uint64_t bg_ping_cycle_start_tb; ///< Время старта текущего цикла обхода (tb) - - void* candidate_ping_timer; ///< Хендл таймера обновления RTT кандидатов - - struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; ///< Топ кандидатов-посредников, отсортированных по RTT - uint8_t best_candidate_count; ///< Количество актуальных кандидатов (≤CONN_MGR_MAX_CANDIDATES) - - uint32_t next_request_id; ///< Счётчик request_id для INTERM_EXCHANGE - uint32_t direct_timeout_ms; ///< Таймаут фазы DIRECT (мс), по умолчанию CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS - uint8_t initialized; ///< Флаг инициализации +typedef void (*conn_mgr_cb_t)(struct CONN_MGR_HANDLE* h, + uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg); - struct cm_reverse_pending* reverse_pending; ///< Связный список ожидающих REVERSE-подключений - struct cm_exchange_pending* exchange_pending; ///< Связный список ожидающих INDIRECT-обменов - struct cm_invite_pending* invite_list; ///< Связный список ожидающих invite-подключений - struct cm_bridge_session* bridge_sessions; ///< Связный список активных bridge-сессий (на посреднике X) - uint64_t next_bridge_session_id; ///< Счётчик session_id для новых bridge-сессий -}; - -/* ==================== Жизненный цикл ==================== */ +/* ═══════ lifecycle ═══════ */ -/** - * @brief Инициализирует менеджер соединений для заданной группы. - * @param group группа топологии (conn_mgr будет работать в её контексте) - * @return указатель на CONN_MGR или NULL при ошибке - * - * Регистрирует обработчик в etcp_router для ETCP_RT_ID_CONN_MGR, - * запускает фоновые таймеры (bg_ping, candidate_ping). - */ struct CONN_MGR* conn_mgr_init(struct TOPO_GROUP* group); - -/** - * @brief Уничтожает менеджер соединений. - * @param mgr менеджер - * - * Освобождает все записи, таймеры, pending-структуры. - * Вызывает cm_clear_nodeinfo для каждой записи. - */ void conn_mgr_destroy(struct CONN_MGR* mgr); -/* ==================== Подключение / отключение ==================== */ - -/** - * @brief Запускает асинхронное подключение к ноде. - * @return CONN_MGR_HANDLE* при успехе, NULL при ошибке (cb вызван с CONN_MGR_ERR_*) - */ -struct CONN_MGR_HANDLE* conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, - uint32_t idle_timeout_ms, - conn_mgr_connect_callback_t cb, void* cb_arg); - -/** - * @brief Освобождает ОДИН handle (не разрывает соединение если есть другие handles). - */ -void conn_mgr_release_handle(struct CONN_MGR_HANDLE* h); - -/** - * @brief Разрывает подключение к ноде. - * Отправляет DISCONNECT-пакет целевой ноде (через etcp_router). - * Для INDIRECT — также отправляет BRIDGE_CLOSE каждому посреднику. - * Закрывает все handles, чистит entry. - */ -int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id); - -/** - * @brief Отменяет ожидающий handle подключения к ноде. - * Если handle единственный — полностью отменяет подключение. - * Если h == NULL — отменяет все handles для node_id. - */ -int conn_mgr_cancel_callback(struct CONN_MGR* mgr, uint64_t node_id, - struct CONN_MGR_HANDLE* h); - -/* ==================== Управление idle-таймаутом ==================== */ +/* ═══════ единая точка входа ═══════ */ /** - * @brief Устанавливает idle-таймаут для подключённой ноды. - * @param mgr менеджер - * @param node_id идентификатор ноды - * @param timeout_ms таймаут неактивности (мс), 0 = отключить проверку - * @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND / CONN_MGR_ERR_INTERNAL + * Единственная публичная функция подключения. * - * Если таймаут > 0 и нода в состоянии CONNECTED — запускает периодический таймер. - * При отсутствии трафика в течение timeout_ms соединение автоматически разрывается. - */ -int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms); - -/** - * @brief Устанавливает таймаут фазы DIRECT для новых подключений. - * @param mgr менеджер - * @param timeout_ms таймаут (мс), по умолчанию CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS (5000) + * ni != NULL → invite: DIRECT + INVITE_INFO_REQ/RESP, группа создаётся, + * коллбэк CONN_EVENT_JOIN при успехе. + * ni == NULL → обычное 3-фазное подключение, коллбэк CONN_EVENT_UP при успехе. * - * Устанавливается глобально для всех новых вызовов conn_mgr_connect_node(). - * Действует на оба пути: обычный (BGP) и загруженный из БД (db_loaded). - */ -void conn_mgr_set_direct_timeout_ms(struct CONN_MGR* mgr, uint32_t timeout_ms); - -/** - * @brief Подключается к новому узлу по invite-данным и проверяет членство в группе. - * @param mgr менеджер группы - * @param ni данные узла (caller-owned, только для чтения): node_id, public_key, v4_addrs - * @param group_id группа для проверки членства (0 = без проверки) - * @param timeout_ms общий таймаут (0 = 2000ms по умолчанию) - * @param cb callback результата - * @param cb_arg аргумент для callback - * @return CONN_MGR_OK если процесс запущен, CONN_MGR_ERR_* при ошибке + * Ошибка/таймаут в обоих случаях: CONN_EVENT_TIMEOUT. + * node_id и group_id ВСЕГДА валидны в коллбэке (даже при TIMEOUT). * - * Поток: DIRECT-подключение → INIT → INVITE_INFO_REQ → INVITE_INFO_RESP → успех/ошибка. - * При успехе узел добавляется в group->nodes и сохраняется в SQLite. - * Callback гарантированно вызывается (успех, таймаут, ошибка членства). + * @param group_id группа (channel_id из invite-ссылки) + * @param ni inviter (caller-owned, копируется). NULL — поиск по node_id. + * @param node_id inviter'а (нужен когда ni == NULL) */ -int conn_mgr_connect_from_invite(struct CONN_MGR* mgr, struct TOPO_NODE* ni, - uint64_t group_id, uint32_t timeout_ms, - conn_mgr_connect_callback_t cb, void* cb_arg); +int conn_mgr_open_invite(struct UTUN_INSTANCE* inst, + uint64_t group_id, + struct TOPO_NODE* ni, + uint64_t node_id, + conn_mgr_cb_t cb, void* cb_arg, + struct CONN_MGR_HANDLE** out_handle); -/** - * @brief Получает текущий статус подключения к ноде. - * @param mgr менеджер - * @param node_id идентификатор ноды - * @param out_state [out] состояние (enum CONN_MGR_STATE) - * @param out_conn_type [out] тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT) - * @return CONN_MGR_OK - */ -int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, - uint8_t* out_state, uint8_t* out_conn_type); +/** Закрыть handle. Последний handle рвёт соединение. */ +void conn_mgr_close(struct CONN_MGR_HANDLE* h); -/** - * @brief Отправляет данные подключённой ноде через etcp_router. - * @param mgr менеджер - * @param node_id идентификатор целевой ноды - * @param entry ll_entry с данными для отправки (освобождается после отправки) - * @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND - * - * Для INDIRECT-соединений пакет автоматически маршрутизируется через - * выбранного посредника (round-robin по intermediaries[]). - * Обновляет last_traffic_tb для idle-проверки. - */ -int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry); +/* ═══════ доступ к соединению ═══════ */ -/* ==================== Управление кандидатами ==================== */ +/** ETCP_CONN для DIRECT/REVERSE, NULL для INDIRECT. */ +struct ETCP_CONN* conn_mgr_get_conn(struct CONN_MGR_HANDLE* h); -/** - * @brief Обновляет RTT кандидата в списке лучших посредников. - * @param mgr менеджер - * @param node_id идентификатор ноды-кандидата - * @param rtt RTT до кандидата (мс) - * - * Если кандидат уже в списке — обновляет RTT. Если нет — вставляет с сортировкой - * по возрастанию RTT. Если список заполнен и новый кандидат имеет худший RTT — - * игнорируется. Список всегда отсортирован: best_candidates[0] — наименьший RTT. - * - * @note Обычно вызывается из route_connectivity после успешного probe. - */ -void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt); +/** Отправить данные (для INDIRECT — через посредников). Забирает владение e. */ +int conn_mgr_send(struct CONN_MGR_HANDLE* h, struct ll_entry* e); +/* ═══════ мониторинг (вызывается извне) ═══════ */ + +/** Обновить RTT кандидата-посредника (≤3, сортировка по возрастанию). */ +void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt); #ifdef __cplusplus } diff --git a/src/routing_layer/conn_mgr_core.c b/src/routing_layer/conn_mgr_core.c new file mode 100644 index 00000000..26977aba --- /dev/null +++ b/src/routing_layer/conn_mgr_core.c @@ -0,0 +1,909 @@ +/* conn_mgr_core.c — init/destroy, open/close/handles, DIRECT, REVERSE, invite */ +#include "conn_mgr_priv.h" + +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#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 "etcp_router.h" +#include "config_parser.h" +#include "topo_node.h" +#include "topo_node_sqlite.h" +#include "topo_group.h" +#include "../transport_layer/stcp_link.h" +#include "route_ping.h" + +/* ═══════ forward-декларации ═══════ */ + +void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry); +static void cm_handle_invite_info_req(struct CONN_MGR* mgr, struct ETCP_CONN* conn, const uint8_t* data, size_t len); +static void cm_handle_invite_info_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len); + +/* ═══════ утилиты ═══════ */ + +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) return NULL; + h->node_id = entry->node_id; h->group_id = group_id; h->cb = cb; h->cb_arg = cb_arg; + h->entry = entry; h->next = entry->handles; entry->handles = h; + return h; +} + +static struct CONN_MGR_HANDLE* cm_invite_handle_new(conn_mgr_cb_t cb, void* cb_arg, uint64_t node_id, uint64_t group_id) { + struct CONN_MGR_HANDLE* h = u_calloc(1, sizeof(*h)); + if (!h) return NULL; + h->node_id = node_id; h->group_id = group_id; h->cb = cb; h->cb_arg = cb_arg; + return h; +} + +static void cm_deliver_up_cb(void* arg) { + struct CONN_MGR_HANDLE* h = (struct CONN_MGR_HANDLE*)arg; + if (h->cb) h->cb(h, h->node_id, h->entry->mgr->group->group_id, CONN_EVENT_UP, h->cb_arg); +} + +/* Рассылает событие ВСЕМ handle'ам entry. Одно соединение — много слушателей: + * каждый conn_mgr_open создал свой handle, все получают UP/DOWN/TIMEOUT. */ +void cm_deliver_event(struct CONN_MGR_ENTRY* entry, enum conn_mgr_event event) { + uint64_t gid = entry->mgr->group->group_id; + struct CONN_MGR_HANDLE* h = entry->handles; + while (h) { struct CONN_MGR_HANDLE* next = h->next; if (h->cb) h->cb(h, h->node_id, gid, event, h->cb_arg); h = next; } +} + +/* ═══════ NAT / адресные утилиты ═══════ */ + +/* Проверяет NAT-совместимость нашего сокета с целевым: возвращает 1 (публичный/EIM — + * можно пробить), 2 (локальная сеть — оба PRIVATE), 0 (несовместим). Используется + * в DIRECT фазе чтобы не создавать заведомо непробиваемые линки. */ +int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t tcfg, uint8_t tnat) { + uint8_t tt = tnat >= NAT_VERIFIED_UNKNOWN ? CFG_SERVER_TYPE_PUBLIC : tcfg; + uint8_t ot = our->type; + if (ot == CFG_SERVER_TYPE_PUBLIC || ot == CFG_SERVER_TYPE_UNKNOWN) return 1; + if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1; + if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_NAT) return 1; + if ((ot == CFG_SERVER_TYPE_PRIVATE || ot == CFG_SERVER_TYPE_LOCAL) && tt == CFG_SERVER_TYPE_PRIVATE) return 2; + return 0; +} + +#define CM_V6_LL 0 +#define CM_V6_LOC 1 +#define CM_V6_DIR 2 +#define CM_V6_OTH 3 + +/* Классификация 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; +} + +uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s) { + if (s->local_addr.ss_family != AF_INET6) return CM_V6_OTH; + return cm_classify_v6_addr(((const struct sockaddr_in6*)&s->local_addr)->sin6_addr.s6_addr); +} + +void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s) { + struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; + memcpy(&sin.sin_addr.s_addr, addr, 4); sin.sin_port = port; + struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin)); + etcp_link_new(conn, s, &sa, 0); +} + +void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s) { + struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, addr, 16); sin6.sin6_port = port; + if (cm_classify_v6_addr(addr) == CM_V6_LL) { struct sockaddr_in6* our = (struct sockaddr_in6*)&s->local_addr; sin6.sin6_scope_id = our->sin6_scope_id; } + struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6)); + etcp_link_new(conn, s, &sa, 0); +} + +/* Проверяет есть ли у узла прямые IP (public/EIM/DIRECT на NAT/INTERFACE адресах) — + * можем ли мы достучаться до узла напрямую. Определяет, пойдём в REVERSE или INDIRECT. */ +int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) { + if (!nq) return 0; + struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); + if (!ni) return 0; + const uint8_t pub_conf[2] = {CFG_SERVER_TYPE_PUBLIC, CFG_SERVER_TYPE_UNKNOWN}; + 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; + for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) + if (m->id == a->socket_id && (m->config_type == pub_conf[0] || m->config_type == pub_conf[1] + || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) return 1; + } + for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { + if ((a6->type != TOPO_ADDR_NAT && a6->type != TOPO_ADDR_INTERFACE) || cm_classify_v6_addr(a6->addr) != CM_V6_DIR) continue; + for (const struct TOPO_SOCKMETA6* m = ni->v6_sock_meta; m; m = m->next) + if (m->id == a6->socket_id && (m->config_type == pub_conf[0] || m->config_type == pub_conf[1] + || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) return 1; + } + return 0; +} + +int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) { + if (!nq) return 0; + struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); + if (!ni) return 0; + for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { + if (a->type != TOPO_ADDR_INTERFACE) continue; + for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) + if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PRIVATE || m->config_type == CFG_SERVER_TYPE_LOCAL)) return 1; + } + for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { + if (a6->type != TOPO_ADDR_INTERFACE) continue; + uint8_t cls = cm_classify_v6_addr(a6->addr); + if (cls != CM_V6_LL && cls != CM_V6_LOC) continue; + for (const struct TOPO_SOCKMETA6* m = ni->v6_sock_meta; m; m = m->next) + if (m->id == a6->socket_id && (m->config_type == CFG_SERVER_TYPE_PRIVATE || m->config_type == CFG_SERVER_TYPE_LOCAL)) return 1; + } + return 0; +} + +/* ═══════ entries ═══════ */ + +struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) { + for (size_t i = 0; i < mgr->entry_count; i++) + if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED) + return &mgr->entries[i]; + return 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; + if (mgr->entry_count >= mgr->entry_capacity) { + size_t cap = mgr->entry_capacity * 2; + struct CONN_MGR_ENTRY* ne = u_realloc(mgr->entries, cap * sizeof(struct CONN_MGR_ENTRY)); + if (!ne) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; } + memset(ne + mgr->entry_capacity, 0, (cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY)); + mgr->entries = ne; mgr->entry_capacity = cap; + } + struct CONN_MGR_ENTRY* new_e = &mgr->entries[mgr->entry_count++]; + memset(new_e, 0, sizeof(*new_e)); new_e->node_id = node_id; new_e->mgr = mgr; + return new_e; +} + +void cm_entry_cleanup(struct CONN_MGR_ENTRY* entry) { + if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return; + if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; } + if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } + { struct cm_exchange_pending** pp = &entry->mgr->exchange_pending; + while (*pp) { + if ((*pp)->entry == entry) { + if ((*pp)->timer && (*pp)->timer != entry->main.timer) + { uasync_cancel_timeout(entry->mgr->instance->ua, (*pp)->timer); (*pp)->timer = NULL; } + struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); + } else pp = &(*pp)->next; + } + } + if (entry->ncd_handle) { node_conn_direct_close(entry->ncd_handle); entry->ncd_handle = NULL; } + entry->handles = NULL; + cm_clear_nodeinfo(entry->mgr, entry->node_id); + entry->state = CONN_MGR_STATE_DISCONNECTED; entry->conn_type = CONN_TYPE_NONE; +} + +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; +} + +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; +} + +void cm_cleanup_db_node(struct CONN_MGR_ENTRY* entry) { + if (!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); + queue_remove_data(group->nodes, &nq->ll); + topo_nodeq_free_group_fields(entry->mgr->instance->topo_groups, nq); + queue_entry_free(&nq->ll); +} + +/* ═══════ NCD коллбэки ═══════ */ + +/* NCD события → conn_mgr для DIRECT/REVERSE фаз: + * UP — соединение поднялось, помечаем CONNECTED, доставляем UP всем handle'ам. + * TIMEOUT — NCD таймаут истёк, переходим к REVERSE или INDIRECT, либо фейлим db_node. + * DOWN — соединение упало, доставляем DOWN. */ +void cm_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void* arg) { + struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; (void)ncd_h; + if (entry->state == CONN_MGR_STATE_DISCONNECTED) return; + switch (ncd_ev) { + case NCD_EVENT_UP: + if (entry->state == CONN_MGR_STATE_CONNECTED) return; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: NCD UP for 0x%016llx", (unsigned long long)entry->node_id); + if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } + entry->main_connect_state = CM_TRY_OK; entry->conn_type = CONN_TYPE_DIRECT; + entry->state = CONN_MGR_STATE_CONNECTED; + cm_update_nodeinfo(entry); cm_deliver_event(entry, CONN_EVENT_UP); + break; + case NCD_EVENT_TIMEOUT: + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: NCD TIMEOUT for 0x%016llx", (unsigned long long)entry->node_id); + entry->ncd_handle = NULL; + if (entry->db_loaded) { + entry->main_connect_state = CM_TRY_FAILED; + cm_cleanup_db_node(entry); cm_deliver_event(entry, CONN_EVENT_TIMEOUT); + return; + } + entry->main_connect_state = CM_TRY_FAILED; + { struct TOPO_GROUP* g = entry->mgr->group; + struct TOPO_GROUP_NODE* t = topo_node_find_by_id(g, entry->node_id); + if (entry->local_scan_state != CM_TRY_OK) { + if (cm_has_direct_ip(entry->mgr, g->local_node) && !(t && cm_has_direct_ip(entry->mgr, t))) + { cm_start_phase_reverse(entry); return; } + cm_start_phase_indirect(entry); return; + } + } + if (entry->local_scan_state == CM_TRY_FAILED) cm_deliver_event(entry, CONN_EVENT_TIMEOUT); + break; + case NCD_EVENT_DOWN: + DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: NCD DOWN for 0x%016llx", (unsigned long long)entry->node_id); + entry->ncd_handle = NULL; + cm_deliver_event(entry, CONN_EVENT_DOWN); + break; + } +} + +/* NCD → invite: UP (отправляем INVITE_INFO_REQ для проверки членства), + * TIMEOUT/DOWN (фейлим invite с CONN_EVENT_TIMEOUT). */ +void cm_invite_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void* arg) { + struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; + switch (ncd_ev) { + case NCD_EVENT_UP: + if (inv->state != CM_INVITE_CONNECTING) return; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite INIT OK node=0x%016llx", (unsigned long long)inv->node_id); + inv->state = CM_INVITE_WAIT_INFO; + { struct CM_INVITE_REQ req; memset(&req, 0, sizeof(req)); + req.cmd = ETCP_RT_ID_CONN_MGR; req.subcmd = CM_SUBCMD_INVITE_INFO_REQ; req.group_id = inv->mgr->group->group_id; + struct ll_entry* qe = queue_entry_new(0); + if (!qe) { cm_invite_fail(inv); return; } + qe->dgram = u_malloc(sizeof(req)); if (!qe->dgram) { queue_entry_free(qe); cm_invite_fail(inv); return; } + memcpy(qe->dgram, &req, sizeof(req)); qe->len = sizeof(req); + etcp_send(node_conn_direct_get_conn(ncd_h), qe); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent INVITE_INFO_REQ to 0x%016llx group=0x%016llx", + (unsigned long long)inv->node_id, (unsigned long long)inv->mgr->group->group_id); + } + break; + case NCD_EVENT_TIMEOUT: + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite connect timeout node=0x%016llx", (unsigned long long)inv->node_id); + cm_invite_fail(inv); break; + case NCD_EVENT_DOWN: + DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite conn down node=0x%016llx", (unsigned long long)inv->node_id); + if (inv->state == CM_INVITE_CONNECTING) cm_invite_fail(inv); + break; + } +} + +/* ═══════ 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->entry_capacity = 8; mgr->direct_timeout_ms = CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS; + mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY)); + 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; + /* Первый conn_mgr биндит etcp_router (остальные перезаписывают тот же коллбэк — безвредно) */ + etcp_router_bind(mgr->instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); + etcp_bind(mgr->instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); + 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; + if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; } + if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; } + /* Отвязываемся от etcp_router (если ещё не отвязаны) */ + etcp_router_unbind(mgr->instance, ETCP_RT_ID_CONN_MGR); + etcp_unbind(mgr->instance, ETCP_RT_ID_CONN_MGR); + for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_cleanup(&mgr->entries[i]); + while (mgr->invite_list) cm_invite_fail(mgr->invite_list); + while (mgr->exchange_pending) { + struct cm_exchange_pending* ep = mgr->exchange_pending; mgr->exchange_pending = ep->next; + if (ep->timer) uasync_cancel_timeout(mgr->instance->ua, ep->timer); + u_free(ep); + } + while (mgr->reverse_pending) { struct cm_reverse_pending* rp = mgr->reverse_pending; mgr->reverse_pending = rp->next; u_free(rp); } + u_free(mgr->entries); mgr->initialized = 0; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count); + u_free(mgr); +} + +/* ═══════ публичное API ═══════ */ + +int conn_mgr_open(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms, + 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; } + 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)); + 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_ERROR(DEBUG_CATEGORY_GENERAL, "node 0x%016llx not found", (unsigned long long)node_id); return -1; } + + struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id); + + if (entry && entry->state == CONN_MGR_STATE_CONNECTED) { + struct CONN_MGR_HANDLE* h = cm_handle_new(entry, gid, cb, cb_arg); + if (!h) return -1; if (out_handle) *out_handle = h; + uasync_call_soon(mgr->instance->ua, h, cm_deliver_up_cb); return 0; + } + if (entry && entry->state == CONN_MGR_STATE_CONNECTING) { + DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "node 0x%016llx already connecting, adding handle", (unsigned long long)node_id); + struct CONN_MGR_HANDLE* h = cm_handle_new(entry, gid, cb, cb_arg); + if (!h) return -1; if (out_handle) *out_handle = h; return 0; + } + + struct ETCP_CONN* existing = topo_group_find_conn_for_node(group, node_id); + if (!existing) existing = instance_find_conn(mgr->instance, node_id); + if (existing && existing->peer_node_id == node_id && existing->links) { + struct ETCP_LINK* l = existing->links; + while (l) { if (l->link_state == 3 && l->initialized) break; l = l->next; } + if (l) { + entry = cm_ensure_entry(mgr, node_id); + if (!entry) return -1; + entry->state = CONN_MGR_STATE_CONNECTED; entry->conn_type = CONN_TYPE_DIRECT; + entry->idle_timeout_ms = idle_timeout_ms; entry->last_traffic_tb = get_time_tb(); + { struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, node_id); + struct TOPO_NODE ni_e; memset(&ni_e, 0, sizeof(ni_e)); ni_e.node_id = node_id; + if (ni) memcpy(ni_e.public_key, ni->public_key, SC_PUBKEY_SIZE); + node_conn_direct_open_node(mgr->instance, node_id, cm_ncd_callback, entry, &entry->ncd_handle, &ni_e, NULL); } + 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; + cm_update_nodeinfo(entry); uasync_call_soon(mgr->instance->ua, h, cm_deliver_up_cb); return 0; + } + } + + entry = cm_ensure_entry(mgr, node_id); + if (!entry) return -1; + 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; + entry->idle_timeout_ms = idle_timeout_ms; entry->last_traffic_tb = get_time_tb(); + entry->local_scan_state = CM_TRY_NONE; entry->main_connect_state = CM_TRY_NONE; + entry->db_loaded = (uint8_t)loaded_from_db; + if (cm_has_local_addr(mgr, group->local_node) && cm_has_local_addr(mgr, target)) + { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); } + entry->main_connect_state = CM_TRY_PENDING; cm_start_phase_direct(entry); + return 0; +} + +void conn_mgr_close(struct CONN_MGR_HANDLE* h) { + if (!h) return; + struct CONN_MGR_ENTRY* entry = h->entry; + if (!entry) { u_free(h); return; } + { struct CONN_MGR_HANDLE** pp = &entry->handles; + while (*pp) { if (*pp == h) { *pp = h->next; break; } pp = &(*pp)->next; } + } + if (entry->handles) { u_free(h); return; } + if (entry->state == CONN_MGR_STATE_CONNECTED && (entry->conn_type == CONN_TYPE_DIRECT || entry->conn_type == CONN_TYPE_REVERSE)) { + /* DISCONNECT через etcp_router — conn_mgr уровень */ + struct CM_DISCONNECT pkt; memset(&pkt, 0, sizeof(pkt)); + pkt.cmd = ETCP_RT_ID_CONN_MGR; pkt.subcmd = CM_SUBCMD_DISCONNECT; pkt.node_id = entry->node_id; + struct ll_entry* qe = queue_entry_new(0); + if (qe) { qe->dgram = u_malloc(sizeof(pkt)); memcpy(qe->dgram, &pkt, sizeof(pkt)); qe->len = sizeof(pkt); + etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1); } + /* NCD CLOSE — транспортный уровень (отправит CLOSE/KEEP_ALIVE через node_conn_direct) */ + } + cm_entry_cleanup(entry); u_free(h); +} + +struct ETCP_CONN* conn_mgr_get_conn(struct CONN_MGR_HANDLE* h) { + if (!h || !h->entry || !h->entry->ncd_handle) return NULL; + return node_conn_direct_get_conn(h->entry->ncd_handle); +} + +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) { queue_entry_free(e); return -1; } + int ret = etcp_router_conn_send(r, e->dgram, e->len); queue_entry_free(e); return ret; +} + +/* ═══════ локальное сканирование ═══════ */ + +static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len); +static void cm_ping_cb_impl(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len); + +struct cm_ping_ctx { struct CONN_MGR_ENTRY* entry; struct sockaddr_storage addr; struct ETCP_SOCKET* sock; uint8_t phase, attempt; }; + +/* LAN broadcast ping для обнаружения узла в локальной сети. Если ответит — + * соединение готово быстрее чем через интернет, без NAT. Запускается + * параллельно с DIRECT фазой. */ +void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) { + struct TOPO_GROUP* group = entry->mgr->group; + struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, entry->node_id); + struct TOPO_NODE* ni = target ? topo_node_registry_find(entry->mgr->instance->topo_groups, target->node_id) : NULL; + if (!target || !ni) { entry->local_scan_state = CM_TRY_FAILED; return; } + for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { + if (a->type != TOPO_ADDR_INTERFACE) continue; + for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) { + if (m->id != a->socket_id) continue; + if (m->config_type != CFG_SERVER_TYPE_PRIVATE && m->config_type != CFG_SERVER_TYPE_LOCAL) continue; + struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; + while (s) { + if ((s->type == CFG_SERVER_TYPE_PRIVATE || s->type == CFG_SERVER_TYPE_LOCAL) && s->local_addr.ss_family == AF_INET) { + struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx)); + if (!ctx) continue; + ctx->entry = entry; ctx->sock = s; ctx->phase = 0; ctx->attempt = 0; + 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); + memcpy(&ctx->addr, &sin, sizeof(sin)); + etcp_send_ping_to_socket(entry->mgr->instance, s, ni->public_key, &ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0, 0); + return; + } + s = s->next; + } + } + } + entry->local_scan_state = CM_TRY_FAILED; +} + +static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len) +{ cm_ping_cb_impl(success, rtt, arg, nonce, resp_data, resp_data_len); } + +static void cm_ping_cb_impl(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len) { + struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg; + struct CONN_MGR_ENTRY* entry = ctx->entry; (void)nonce; (void)resp_data; (void)resp_data_len; + if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; } + if (success && ctx->phase == 0) { + entry->local_scan_state = CM_TRY_OK; entry->conn_type = CONN_TYPE_DIRECT; entry->state = CONN_MGR_STATE_CONNECTED; + if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt); + cm_update_nodeinfo(entry); cm_deliver_event(entry, CONN_EVENT_UP); u_free(ctx); return; + } + if (++ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) { + struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(entry->mgr->group, entry->node_id); + struct TOPO_NODE* pub_ni = nq ? topo_node_registry_find(entry->mgr->instance->topo_groups, nq->node_id) : NULL; + etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock, pub_ni ? pub_ni->public_key : NULL, + &ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0, 0); + return; + } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan failed for 0x%016llx after %d attempts", + (unsigned long long)entry->node_id, ctx->attempt); + entry->local_scan_state = CM_TRY_FAILED; u_free(ctx); +} + +/* ═══════ DIRECT фаза (NCD + ручная NAT-фильтрация линков) ═══════ */ + +/* Добавляет IPv4/IPv6 линки к conn. db_loaded: все сокеты без фильтра. + * Обычный путь: NAT-фильтрация (cm_nat_compatible) + приоритет NAT > INTERFACE. + * Если ни одного линка не создалось — закрывает NCD и переходит к REVERSE/INDIRECT. */ +static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* entry, + struct TOPO_NODE* ni, int db_loaded) { + int any = 0; + /* v4 */ + if (db_loaded) { + for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { + if (!(a->protocol & TOPO_PROTO_UDP)) continue; + struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets; + while (s) { if (s->local_addr.ss_family == AF_INET) { + 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)); + if (etcp_link_new(conn,s,&sa,0)) any=1; } s=s->next; } + } + } else { + uint8_t prio[]={TOPO_ADDR_NAT,TOPO_ADDR_INTERFACE}; + for (int pr=0;pr<2;pr++) for (const struct TOPO_ADDR4* a=ni->v4_addrs;a;a=a->next) { + if (a->type!=prio[pr]) continue; + for (const struct TOPO_SOCKMETA4* m=ni->v4_sock_meta;m;m=m->next) { + if (m->id!=a->socket_id) continue; + struct ETCP_SOCKET* s=entry->mgr->instance->etcp_sockets; + while(s){if(cm_nat_compatible(s,m->config_type,m->nat_type)&&s->local_addr.ss_family==AF_INET){ + 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)); + if(etcp_link_new(conn,s,&sa,0)) any=1;} s=s->next;} + } + } + } + /* v6 */ + for (const struct TOPO_ADDR6* a6=ni->v6_addrs;a6;a6=a6->next) { + if (!(a6->protocol&TOPO_PROTO_UDP)) continue; + uint8_t tc=cm_classify_v6_addr(a6->addr); if(tc==CM_V6_OTH) continue; + struct ETCP_SOCKET* s=entry->mgr->instance->etcp_sockets; + while(s){if(cm_sock_v6_classify(s)==tc){cm_add_v6_link(conn,a6->addr,a6->port,s);any=1;} s=s->next;} + } + + if (any) return; + node_conn_direct_close(entry->ncd_handle); entry->ncd_handle=NULL; + if (db_loaded) { + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: db_node 0x%016llx direct failed (no compatible addr/socket)", (unsigned long long)entry->node_id); + cm_cleanup_db_node(entry); cm_deliver_event(entry, CONN_EVENT_TIMEOUT); + } else { + struct TOPO_GROUP* g = entry->mgr->group; + struct TOPO_GROUP_NODE* t = topo_node_find_by_id(g, entry->node_id); + if (cm_has_direct_ip(entry->mgr, g->local_node) && !(t && cm_has_direct_ip(entry->mgr, t))) + { cm_start_phase_reverse(entry); return; } + cm_start_phase_indirect(entry); + } +} + +/* DIRECT фаза: открывает NCD-соединение с пустым ni (0 линков из NCD), затем + * вручную добавляет NAT-фильтрованные линки через cm_direct_add_links. + * NCD управляет таймаутом (2с). При успехе — cm_ncd_callback доставит UP, + * при таймауте — переключится на REVERSE или INDIRECT. */ +void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) { + if (entry->main_connect_state == CM_TRY_OK || entry->local_scan_state == CM_TRY_OK) return; + struct TOPO_GROUP* group = entry->mgr->group; + struct TOPO_GROUP_NODE* target = topo_node_find_by_id(group, entry->node_id); + struct TOPO_NODE* ni = target ? topo_node_registry_find(entry->mgr->instance->topo_groups, target->node_id) : NULL; + if (!target || !ni) { cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; } + + struct TOPO_NODE ni_e; memset(&ni_e, 0, sizeof(ni_e)); ni_e.node_id = target->node_id; + memcpy(ni_e.public_key, ni->public_key, SC_PUBKEY_SIZE); + int r = node_conn_direct_open_node(entry->mgr->instance, entry->node_id, cm_ncd_callback, entry, + &entry->ncd_handle, &ni_e, NULL); + if (r == NCD_ERR) { cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; } + + cm_direct_add_links(node_conn_direct_get_conn(entry->ncd_handle), entry, ni, entry->db_loaded); +} + +/* ═══════ REVERSE фаза ═══════ */ + +void cm_reverse_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event; + struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg; + if (!rp) return; if (!conn) { u_free(rp); return; } + struct CONN_MGR_ENTRY* entry = rp->entry; + if (!entry) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx (no entry, freeing)", (unsigned long long)conn->peer_node_id); u_free(rp); return; } + if (conn->peer_node_id != entry->node_id) { u_free(rp); return; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id); + if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; } + { struct cm_reverse_pending** pp = &entry->mgr->reverse_pending; + while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; } + } + entry->main_connect_state = CM_TRY_OK; entry->conn_type = CONN_TYPE_REVERSE; entry->state = CONN_MGR_STATE_CONNECTED; + cm_update_nodeinfo(entry); cm_deliver_event(entry, CONN_EVENT_UP); u_free(rp); +} + +void cm_reverse_timeout_cb(void* arg) { + struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; entry->main.timer = NULL; + { struct cm_reverse_pending** pp = &entry->mgr->reverse_pending; + while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; } + } + cm_start_phase_indirect(entry); +} + +/* REVERSE фаза: у нас прямой IP, у цели нет. Отправляем DIRECT_REQ с НАШИМИ + * адресами цели через BGP-маршрут ("подключись ко мне"). Ждём обратного INIT. + * Таймаут 15с — при провале переходим к INDIRECT. */ +void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) { + struct TOPO_GROUP* group = entry->mgr->group; + struct TOPO_GROUP_NODE* local = group->local_node; + struct TOPO_NODE* lni = local ? topo_node_registry_find(entry->mgr->instance->topo_groups, local->node_id) : NULL; + if (!local || !lni) { cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; } + uint32_t req_id = ++entry->mgr->next_request_id; entry->main.request_id = req_id; entry->main.phase = 2; + uint8_t dc = 0; struct { uint8_t t; uint8_t ip[4]; uint16_t port; uint8_t sid; } addrs[8]; + for (const struct TOPO_ADDR4* a = lni->v4_addrs; a && dc < 8; a = a->next) { + if (a->type != TOPO_ADDR_NAT && a->type != TOPO_ADDR_INTERFACE) continue; + for (const struct TOPO_SOCKMETA4* m = lni->v4_sock_meta; m; m = m->next) { + if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PUBLIC || m->config_type == CFG_SERVER_TYPE_UNKNOWN + || m->nat_type == NAT_VERIFIED_EIM || m->nat_type == NAT_VERIFIED_DIRECT)) + { addrs[dc]=(typeof(addrs[0])){a->type}; memcpy(addrs[dc].ip,a->addr,4); addrs[dc].port=htons(a->port); addrs[dc].sid=a->socket_id; dc++; break; } + } + } + if (!dc) { cm_start_phase_indirect(entry); return; } + size_t sz = CM_DIRECT_REQ_H_SIZE + (size_t)dc * 8; uint8_t* pkt = u_malloc(sz); + if (!pkt) { cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; } + struct CM_DIRECT_REQ* req = (struct CM_DIRECT_REQ*)pkt; memset(req,0,sizeof(*req)); + req->cmd=ETCP_RT_ID_CONN_MGR; req->subcmd=CM_SUBCMD_DIRECT_REQ; req->request_id=req_id; req->addr_count=dc; + uint8_t* p = pkt + CM_DIRECT_REQ_H_SIZE; + for (uint8_t i=0;idgram=pkt; qe->len=(uint16_t)sz; etcp_route_send(entry->mgr->instance, group->group_id, entry->node_id, qe, 1); + struct cm_reverse_pending* rp = u_calloc(1, sizeof(*rp)); + if (rp) { rp->request_id=req_id; rp->entry=entry; rp->next=entry->mgr->reverse_pending; entry->mgr->reverse_pending=rp; } + entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS*10, + entry, cm_reverse_timeout_cb, "conn_mgr_reverse"); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs", + (unsigned long long)entry->node_id, dc); +} + +/* ═══════ обработка DIRECT_REQ (REVERSE входящие) ═══════ */ + +/* Принимающая сторона REVERSE: получили DIRECT_REQ от инициатора — открываем + * NCD-соединение к нему, добавляем линки по адресам из запроса. При INIT + * вызывается cm_reverse_init_cb. */ +void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) { + struct CONN_MGR* mgr = conn->instance->conn_mgr; if (!mgr) return; + struct CM_DIRECT_REQ* req = (struct CM_DIRECT_REQ*)data; + if (len < CM_DIRECT_REQ_H_SIZE + (size_t)req->addr_count * 8) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs", + (unsigned long long)conn->peer_node_id, req->addr_count); + uint64_t src = conn->peer_node_id; + struct TOPO_NODE* src_ni = topo_node_registry_find(mgr->instance->topo_groups, src); if (!src_ni) return; + struct TOPO_NODE nbuf; memset(&nbuf,0,sizeof(nbuf)); nbuf.node_id=src; memcpy(nbuf.public_key,src_ni->public_key,SC_PUBKEY_SIZE); + struct NODE_CONN_DIRECT* ncd_h = NULL; + if (node_conn_direct_open_node(mgr->instance, src, NULL, NULL, &ncd_h, &nbuf, NULL) == NCD_ERR) return; + struct ETCP_CONN* newc = node_conn_direct_get_conn(ncd_h); if (!newc) { node_conn_direct_close(ncd_h); return; } + struct cm_reverse_pending* rp = u_calloc(1,sizeof(*rp)); + if (!rp) { node_conn_direct_close(ncd_h); return; } + rp->request_id=req->request_id; rp->next=mgr->reverse_pending; mgr->reverse_pending=rp; + + uint8_t* p = (uint8_t*)data + CM_DIRECT_REQ_H_SIZE; + for (uint8_t i=0;iaddr_count;i++) { + uint8_t type=*p++; (void)type; uint8_t ip[4]; memcpy(ip,p,4);p+=4; uint16_t port; memcpy(&port,p,2);p+=2; uint8_t sid=*p++; (void)sid; + struct ETCP_SOCKET* s = conn->instance->etcp_sockets; + while (s) { if ((s->type==CFG_SERVER_TYPE_PUBLIC||s->type==CFG_SERVER_TYPE_UNKNOWN)&&s->local_addr.ss_family==AF_INET) + { cm_add_v4_link(newc, ip, port, s); break; } s=s->next; } + } + etcp_conn_add_cbk(newc, cm_reverse_init_cb, rp, ETCP_CBK_EVENT_INIT); +} + +/* ═══════ 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 < 3) return; + uint8_t* d = entry->dgram + 1; size_t len = entry->len - 1; uint8_t sub = d[1]; + struct CONN_MGR* mgr = conn->instance->conn_mgr; + if (mgr) { struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id); 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(conn, 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(conn, (struct CM_EXCHANGE_REQ*)d); break; + case CM_SUBCMD_INTERM_EXCHANGE_RESP: if (mgr) cm_handle_interm_exchange_resp(mgr, d, len); break; + case CM_SUBCMD_INTERM_SELECTED: if (mgr) cm_handle_interm_selected(mgr, d, len); break; + case CM_SUBCMD_DISCONNECT: if (len >= CM_DISCONNECT_SIZE && mgr) cm_handle_disconnect(mgr, ((struct CM_DISCONNECT*)d)->node_id); break; + case CM_SUBCMD_INVITE_INFO_REQ: if (mgr && len >= sizeof(struct CM_INVITE_REQ)) cm_handle_invite_info_req(mgr, conn, d, len); break; + case CM_SUBCMD_INVITE_INFO_RESP: if (mgr) cm_handle_invite_info_resp(mgr, d, len); break; + } +} + +/* ═══════ invite обработчики ═══════ */ + +void cm_handle_invite_info_req(struct CONN_MGR* mgr, struct ETCP_CONN* conn, const uint8_t* data, size_t len) { + if (len < sizeof(struct CM_INVITE_REQ)) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_REQ too short (%zu)", len); return; } + struct CM_INVITE_REQ* req = (struct CM_INVITE_REQ*)data; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_REQ from 0x%016llx group=0x%016llx", (unsigned long long)conn->peer_node_id, (unsigned long long)req->group_id); + struct TOPO_GROUP* grp = topo_groups_find(mgr->instance->topo_groups, req->group_id); + struct TOPO_GROUP* sg = grp ? grp : mgr->group; + struct TOPO_NODE* ln = sg->local_node ? topo_node_registry_find(sg->instance->topo_groups, sg->local_node->node_id) : NULL; + const char* nm = ln ? ln->node_name : NULL; size_t nl = nm ? strlen(nm) : 0; if (nl > 255) nl = 255; + size_t rs = sizeof(struct CM_INVITE_RESP) + nl; + struct CM_INVITE_RESP* resp = u_calloc(1, rs); if (!resp) return; + resp->cmd=ETCP_RT_ID_CONN_MGR; resp->subcmd=CM_SUBCMD_INVITE_INFO_RESP; resp->group_id=req->group_id; resp->is_member=1; + memcpy(resp->ed25519_pubkey, sg->ed25519_public_key, SC_PUBKEY_SIZE); resp->node_name_len=(uint8_t)nl; + if (nl) memcpy(resp->node_name, nm, nl); + struct ll_entry* qe = queue_entry_new(0); + if (qe) { qe->dgram=(uint8_t*)resp; qe->len=(uint16_t)rs; etcp_send(conn, qe); } else u_free(resp); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent INVITE_INFO_RESP to 0x%016llx name_len=%zu", (unsigned long long)conn->peer_node_id, nl); +} + +void cm_handle_invite_info_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { + if (len < sizeof(struct CM_INVITE_RESP)) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP too short (%zu)", len); return; } + struct CM_INVITE_RESP* resp = (struct CM_INVITE_RESP*)data; + if (len < sizeof(struct CM_INVITE_RESP) + (size_t)resp->node_name_len) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP truncated"); return; } + struct cm_invite_pending* inv = NULL; + for (struct cm_invite_pending* p = mgr->invite_list; p; p = p->next) if (p->state == CM_INVITE_WAIT_INFO) { inv = p; break; } + if (!inv) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP — no pending invite in WAIT_INFO"); return; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INVITE_INFO_RESP node=0x%016llx group=0x%016llx member=%d name_len=%u", + (unsigned long long)inv->node_id, (unsigned long long)resp->group_id, resp->is_member, resp->node_name_len); + if (!resp->is_member) { cm_invite_fail(inv); return; } + if (inv->temp_nq) { + struct TOPO_NODE* ni2 = topo_node_registry_find(inv->mgr->instance->topo_groups, inv->temp_nq->node_id); + if (ni2) { memcpy(ni2->ed25519_public_key, resp->ed25519_pubkey, SC_PUBKEY_SIZE); + if (ni2->node_name) { u_free(ni2->node_name); ni2->node_name = NULL; } + if (resp->node_name_len) ni2->node_name = u_strdup((const char*)resp->node_name); } + if (inv->mgr->instance->topo_sqlite_db) topo_node_sqlite_node_put(inv->mgr->instance->topo_sqlite_db, inv->mgr->instance->topo_groups, inv->temp_nq, (time_t)(get_time_tb()/10000)); + } + if (inv->overall_timer) { uasync_cancel_timeout(inv->mgr->instance->ua, inv->overall_timer); inv->overall_timer = NULL; } + if (inv->cb) inv->cb(inv->handle, inv->node_id, inv->mgr->group->group_id, CONN_EVENT_JOIN, inv->cb_arg); + cm_invite_cleanup(inv); +} + +void cm_invite_overall_timeout(void* arg) { + struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; inv->overall_timer = NULL; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite overall timeout node=0x%016llx", (unsigned long long)inv->node_id); + cm_invite_fail(inv); +} + +/* Очистка invite при ошибке/таймауте: отменяет overall_timer, закрывает TCP-link + * и NCD-handle, удаляет временный TOPO_GROUP_NODE, доставляет CONN_EVENT_TIMEOUT + * через handle и освобождает его. Вызывается также из conn_mgr_destroy. */ +void cm_invite_fail(struct cm_invite_pending* inv) { + if (!inv) return; + if (inv->overall_timer) { uasync_cancel_timeout(inv->mgr->instance->ua, inv->overall_timer); inv->overall_timer = NULL; } + if (inv->tcp_link) { + struct ll_entry* e = queue_find_data_by_index(inv->mgr->instance->tcp_connections, (const uint8_t*)&inv->node_id); + if (e) queue_remove_data(inv->mgr->instance->tcp_connections, e); + stcp_link_close(inv->tcp_link); inv->tcp_link = NULL; + } + if (inv->ncd_handle) { node_conn_direct_close(inv->ncd_handle); inv->ncd_handle = NULL; } + if (inv->temp_nq) { + queue_remove_data(inv->mgr->group->nodes, &inv->temp_nq->ll); + topo_nodeq_free_group_fields(inv->mgr->instance->topo_groups, inv->temp_nq); + queue_entry_free(&inv->temp_nq->ll); inv->temp_nq = NULL; + } + { struct cm_invite_pending** pp = &inv->mgr->invite_list; + while (*pp) { if (*pp == inv) { *pp = inv->next; break; } pp = &(*pp)->next; } + } + if (inv->cb) inv->cb(inv->handle, inv->node_id, inv->mgr->group->group_id, CONN_EVENT_TIMEOUT, inv->cb_arg); + if (inv->handle) u_free(inv->handle); + cm_invite_cleanup(inv); +} + +void cm_invite_cleanup(struct cm_invite_pending* inv) { if (inv) u_free(inv); } + +static void cm_tcp_ready_cb(struct stcp_link* link, void* arg) { + struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; + if (!inv || inv->tcp_ready) return; inv->tcp_ready = 1; + struct ETCP_CONN* etcp = stcp_link_get_etcp_conn(link); etcp->peer_node_id = inv->node_id; + struct ll_entry* qe = queue_entry_new(sizeof(struct tcp_conn_entry)); + if (!qe) { cm_invite_fail(inv); return; } + { struct tcp_conn_entry* te = (struct tcp_conn_entry*)qe->data; te->node_id=inv->node_id; te->link=link; te->etcp_conn=etcp; } + queue_data_put_with_index(inv->mgr->instance->tcp_connections, qe); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: TCP invite ready node=0x%016llx", (unsigned long long)inv->node_id); + inv->state = CM_INVITE_WAIT_INFO; + { struct CM_INVITE_REQ req; memset(&req,0,sizeof(req)); + req.cmd=ETCP_RT_ID_CONN_MGR; req.subcmd=CM_SUBCMD_INVITE_INFO_REQ; req.group_id=inv->mgr->group->group_id; + struct ll_entry* qe2 = queue_entry_new(0); if (!qe2) { cm_invite_fail(inv); return; } + qe2->dgram=u_malloc(sizeof(req)); if (!qe2->dgram) { queue_entry_free(qe2); cm_invite_fail(inv); return; } + memcpy(qe2->dgram,&req,sizeof(req)); qe2->len=sizeof(req); etcp_send(etcp, qe2); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: TCP invite sent INVITE_INFO_REQ to 0x%016llx group=0x%016llx", + (unsigned long long)inv->node_id, (unsigned long long)inv->mgr->group->group_id); + } +} + +static void cm_tcp_close_cb(struct stcp_link* link, int err, void* arg) { + struct cm_invite_pending* inv = (struct cm_invite_pending*)arg; + if (!inv) return; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: TCP invite closed node=0x%016llx err=%d", (unsigned long long)inv->node_id, err); + inv->tcp_link = NULL; +} + +static int cm_invite_tcp_connect(struct cm_invite_pending* inv, const struct TOPO_NODE* ni) { + for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { + if (!(a->protocol & TOPO_PROTO_TCP)) 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 stcp_link_config cfg = {.ua=inv->mgr->instance->ua, .my_keys=&inv->mgr->instance->my_keys, .inst=inv->mgr->instance, + .peer_pubkey=ni->public_key, .peer_pubkey_mode=0, .remote_addr=&sa, .remote_port=a->port}; + inv->tcp_link = stcp_link_connect(&cfg); + if (inv->tcp_link) { stcp_link_set_on_ready(inv->tcp_link, cm_tcp_ready_cb, inv); stcp_link_set_on_close(inv->tcp_link, cm_tcp_close_cb, inv); return 1; } + return 0; + } + for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) { + if (!(a6->protocol & TOPO_PROTO_TCP)) 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 sockaddr_storage sa; memcpy(&sa,&sin6,sizeof(sin6)); + struct stcp_link_config cfg = {.ua=inv->mgr->instance->ua, .my_keys=&inv->mgr->instance->my_keys, .inst=inv->mgr->instance, + .peer_pubkey=ni->public_key, .peer_pubkey_mode=0, .remote_addr=&sa, .remote_port=a6->port}; + inv->tcp_link = stcp_link_connect(&cfg); + if (inv->tcp_link) { stcp_link_set_on_ready(inv->tcp_link, cm_tcp_ready_cb, inv); stcp_link_set_on_close(inv->tcp_link, cm_tcp_close_cb, inv); return 1; } + return 0; + } + return 0; +} + +int conn_mgr_open_invite(struct UTUN_INSTANCE* inst, + uint64_t group_id, + struct TOPO_NODE* ni, + 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; + + /* 1. Найти или создать группу → mgr */ + 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; + + /* 2. ni == NULL → обычное 3-фазное подключение */ + if (!ni) return conn_mgr_open(mgr, node_id, 0, cb, cb_arg, out_handle); + + /* 3. ni != NULL → invite-режим */ + uint64_t nid = ni->node_id; + if (!nid || nid == inst->node_id) return -1; + if (!ni->v4_addrs && !ni->v6_addrs) return -1; + if (!ni->public_key[0]) return -1; + + /* 4. Копировать ni в group_ni */ + struct TOPO_GROUPS* groups = inst->topo_groups; + struct TOPO_NODE* gni = u_calloc(1, sizeof(*gni)); if (!gni) return -1; + gni->node_id=nid; gni->ver=1; gni->group_id=group_id; memcpy(gni->public_key,ni->public_key,SC_PUBKEY_SIZE); + for (const struct TOPO_ADDR4* src = ni->v4_addrs; src; src = src->next) { + struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); if (!a4) continue; + memset(a4,0,sizeof(*a4)); memcpy(a4->addr,src->addr,4); a4->port=src->port; + a4->type=TOPO_ADDR_NAT; a4->protocol=src->protocol; a4->next=gni->v4_addrs; gni->v4_addrs=a4; + } + for (const struct TOPO_ADDR6* src6 = ni->v6_addrs; src6; src6 = src6->next) { + struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); if (!a6) continue; + memset(a6,0,sizeof(*a6)); memcpy(a6->addr,src6->addr,16); a6->port=src6->port; + a6->type=TOPO_ADDR_NAT; a6->protocol=src6->protocol; a6->next=gni->v6_addrs; gni->v6_addrs=a6; + } + if (!gni->v4_addrs && !gni->v6_addrs) { u_free(gni); return -1; } + gni = topo_node_registry_store(groups, gni); if (!gni) return -1; + + /* 5. TOPO_GROUP_NODE + invite_pending */ + struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); + if (!qe) { topo_node_registry_unref(groups,gni->node_id); return -1; } + struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)qe; + memset((uint8_t*)nq+sizeof(struct ll_entry),0,sizeof(*nq)-sizeof(struct ll_entry)); nq->node_id=nid; + + struct cm_invite_pending* inv = u_calloc(1, sizeof(*inv)); + if (!inv) { queue_entry_free(qe); topo_node_registry_unref(groups,gni->node_id); return -1; } + inv->mgr=mgr; inv->node_id=nid; inv->state=CM_INVITE_CONNECTING; + inv->temp_nq=nq; inv->next=mgr->invite_list; mgr->invite_list=inv; + inv->cb=cb; inv->cb_arg=cb_arg; + + struct CONN_MGR_HANDLE* h = cm_invite_handle_new(NULL, NULL, nid, group_id); + if (!h) { cm_invite_fail(inv); return -1; } inv->handle=h; if (out_handle) *out_handle=h; + queue_data_put_with_index(group->nodes, &nq->ll); + + /* 6. NCD + TCP */ + int r = node_conn_direct_open_node(inst, nid, cm_invite_ncd_callback, inv, &inv->ncd_handle, gni, NULL); + if (r == NCD_ERR) { cm_invite_fail(inv); if (out_handle) *out_handle=NULL; return -1; } + int udp = (r == NCD_NEW || r == NCD_REUSED), tcp = cm_invite_tcp_connect(inv, gni); + if (!udp && !tcp) { cm_invite_fail(inv); if (out_handle) *out_handle=NULL; return -1; } + + inv->overall_timer = uasync_set_timeout(inst->ua, CM_INVITE_DEFAULT_TIMEOUT_MS*10, inv, cm_invite_overall_timeout, "conn_mgr_invite"); + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: invite started node=0x%016llx group=0x%016llx", + (unsigned long long)nid, (unsigned long long)group_id); + return 0; +} diff --git a/src/routing_layer/conn_mgr_indirect.c b/src/routing_layer/conn_mgr_indirect.c new file mode 100644 index 00000000..689f579b --- /dev/null +++ b/src/routing_layer/conn_mgr_indirect.c @@ -0,0 +1,145 @@ +/* conn_mgr_indirect.c — INDIRECT фаза + exchange-обработчики */ +#include "conn_mgr_priv.h" +#include "etcp.h" +#include +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include "../lib/u_async.h" +#include "etcp_router.h" +#include "utun_instance.h" +#include "topo_group.h" +#include "route_connectivity.h" + +/* INDIRECT фаза: отправляет EXCHANGE_REQ с нашими лучшими кандидатами (best_candidates) + * цели через BGP-маршрут. Ожидаем EXCHANGE_RESP — по RTT-замерам с обеих сторон + * выберем общего посредника с минимальной суммой RTT (cm_compute_intermediaries). */ +void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) { + struct CONN_MGR* mgr = entry->mgr; + if (!mgr->best_candidate_count) { + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx", (unsigned long long)entry->node_id); + cm_deliver_event(entry, CONN_EVENT_TIMEOUT); return; + } + entry->main.phase = 3; entry->main.request_id = ++mgr->next_request_id; + struct CM_EXCHANGE_REQ req; memset(&req,0,sizeof(req)); + req.cmd=ETCP_RT_ID_CONN_MGR; req.subcmd=CM_SUBCMD_INTERM_EXCHANGE_REQ; req.request_id=entry->main.request_id; + req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count; + for (uint8_t i=0;ibest_candidates[i]; + struct ll_entry* qe = queue_entry_new(0); if (!qe) return; + qe->dgram=u_malloc(sizeof(req)); memcpy(qe->dgram,&req,sizeof(req)); qe->len=sizeof(req); + etcp_route_send(mgr->instance, mgr->group->group_id, entry->node_id, qe, 1); + entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS*10, entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch"); + struct cm_exchange_pending* ep = u_calloc(1,sizeof(*ep)); + if (ep) { ep->request_id=entry->main.request_id; ep->entry=entry; ep->timer=entry->main.timer; ep->next=mgr->exchange_pending; mgr->exchange_pending=ep; } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 sent EXCHANGE_REQ to 0x%016llx with %u candidates", + (unsigned long long)entry->node_id, req.candidate_count); +} + +void cm_exchange_timeout_cb(void* arg) { + struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; entry->main.timer=NULL; + { struct cm_exchange_pending** pp=&entry->mgr->exchange_pending; + while(*pp){if((*pp)->entry==entry){struct cm_exchange_pending* ep=*pp; *pp=ep->next; u_free(ep); break;} pp=&(*pp)->next;} } + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id); + cm_deliver_event(entry, CONN_EVENT_TIMEOUT); +} + +/* Вычисляет лучших общих посредников по данным из EXCHANGE_RESP: + * your_candidates — кандидаты инициатора + НАШ RTT до них + * my_candidates — НАШИ кандидаты + RTT цели до них + * Суммирует RTT для каждого общего узла, сортирует, выбирает топ-3. + * Отправляет INTERM_SELECTED инициатору и доставляет CONN_EVENT_UP. */ +void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_RESP* resp) { + struct { uint64_t nid; uint64_t rtt; } all[8]; uint8_t cnt=0; + for (uint8_t i=0;iyour_count&&cnt<8;i++) { + uint16_t our=0; for(uint8_t j=0;jmgr->best_candidates[j].node_id==resp->your_candidates[i].node_id){our=entry->mgr->best_candidates[j].rtt;break;} + all[cnt]=(typeof(all[0])){resp->your_candidates[i].node_id,(uint64_t)our+resp->your_candidates[i].rtt}; cnt++; + } + for (uint8_t i=0;imy_count&&cnt<8;i++) { + struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(entry->mgr->group,resp->my_candidates[i].node_id); + uint16_t our=nq?cm_get_node_min_rtt(nq):0; if(our==0xFFFF)our=0; + all[cnt]=(typeof(all[0])){resp->my_candidates[i].node_id,(uint64_t)our+resp->my_candidates[i].rtt}; cnt++; + } + for (uint8_t i=0;iCONN_MGR_MAX_INTERMEDIARIES?CONN_MGR_MAX_INTERMEDIARIES:cnt; + for(uint8_t i=0;iintermediaries[i]=all[i].nid; + entry->intermediariy_count=sel; entry->rr_idx=0; + struct CM_INTERM_SEL pkt; memset(&pkt,0,sizeof(pkt)); + pkt.cmd=ETCP_RT_ID_CONN_MGR; pkt.subcmd=CM_SUBCMD_INTERM_SELECTED; pkt.count=sel; + for(uint8_t i=0;imgr->group,all[i].nid); if(nq)pkt.selected[i].rtt=cm_get_node_min_rtt(nq);} + struct ll_entry* qe=queue_entry_new(0); + if(qe){qe->dgram=u_malloc(sizeof(pkt));memcpy(qe->dgram,&pkt,sizeof(pkt));qe->len=sizeof(pkt);etcp_route_send(entry->mgr->instance,entry->mgr->group->group_id,entry->node_id,qe,1);} + entry->conn_type=CONN_TYPE_INDIRECT; entry->state=CONN_MGR_STATE_CONNECTED; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: indirect OK for 0x%016llx via %u intermediaries",(unsigned long long)entry->node_id,sel); + cm_update_nodeinfo(entry); cm_deliver_event(entry,CONN_EVENT_UP); +} + +void cm_exchange_probe_retry_cb(void* arg) { + struct cm_exchange_pending* ep=(struct cm_exchange_pending*)arg; ep->timer=NULL; + uint8_t need=0; uint64_t now=get_time_tb(); + for(uint8_t i=0;icached.my_count&&i<4;i++){ + struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(ep->entry->mgr->group,ep->cached.my_candidates[i].node_id); + if(!nq||!cm_is_rtt_fresh(nq,now)){need++;break;} + } + if(!need){cm_compute_intermediaries(ep->entry,&ep->cached);struct cm_exchange_pending**pp=&ep->entry->mgr->exchange_pending;while(*pp){if(*pp==ep){*pp=ep->next;break;}pp=&(*pp)->next;}u_free(ep);} + else ep->timer=uasync_set_timeout(ep->entry->mgr->instance->ua,2000,ep,cm_exchange_probe_retry_cb,"cm_exch_probe"); +} + +/* Инициатор получил EXCHANGE_RESP: кеширует ответ, проверяет свежесть RTT + * до СВОИХ кандидатов (тех, что в my_candidates ответа). Если протухли — + * probe + retry-таймер 2с. Если свежие — cm_compute_intermediaries. */ +void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { + const struct CM_EXCHANGE_RESP* resp=(const struct CM_EXCHANGE_RESP*)data; + if(lenexchange_pending; + while(ep){if(ep->request_id==resp->request_id)break;ep=ep->next;} + if(!ep)return; + if(ep->entry->main.timer){uasync_cancel_timeout(mgr->instance->ua,ep->entry->main.timer);ep->entry->main.timer=NULL;} + memcpy(&ep->cached,resp,lencached)?len:sizeof(ep->cached)); ep->received=1; ep->probed=0; + uint8_t need=0; uint64_t now=get_time_tb(); + for(uint8_t i=0;icached.my_count&&i<4;i++){ + struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(mgr->group,ep->cached.my_candidates[i].node_id); + if(!nq||!cm_is_rtt_fresh(nq,now)){need++;route_connectivity_probe_node(mgr->instance,nq);} + } + if(!need){ep->probed=1;cm_compute_intermediaries(ep->entry,&ep->cached);struct cm_exchange_pending**pp=&mgr->exchange_pending;while(*pp){if(*pp==ep){*pp=ep->next;break;}pp=&(*pp)->next;}u_free(ep);return;} + ep->timer=uasync_set_timeout(mgr->instance->ua,2000,ep,cm_exchange_probe_retry_cb,"cm_exch_probe"); +} + +/* Финальный выбор посредников от инициатора (приходит второй стороне INDIRECT- + * обмена). Сохраняет выбранные intermediaries в entry, помечает CONNECTED. */ +void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) { + const struct CM_INTERM_SEL* sel=(const struct CM_INTERM_SEL*)data; + if(lenentry_count;i++) if(mgr->entries[i].state==CONN_MGR_STATE_CONNECTING){entry=&mgr->entries[i];break;} + if(!entry)return; + uint8_t c=sel->count>CONN_MGR_MAX_INTERMEDIARIES?CONN_MGR_MAX_INTERMEDIARIES:sel->count; + for(uint8_t i=0;iintermediaries[i]=sel->selected[i].node_id; + entry->intermediariy_count=c; entry->rr_idx=0; entry->conn_type=CONN_TYPE_INDIRECT; entry->state=CONN_MGR_STATE_CONNECTED; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: got INTERM_SELECTED count=%u",c); +} + +/* Цель получила EXCHANGE_REQ: измеряет RTT до кандидатов инициатора (если + * замеры протухли — запускает probe), отправляет EXCHANGE_RESP со СВОИМИ + * кандидатами + замерами RTT до кандидатов инициатора. */ +void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CM_EXCHANGE_REQ* req) { + struct CONN_MGR* mgr=conn->instance->conn_mgr; if(!mgr)return; + uint64_t now=get_time_tb(); + for(uint8_t i=0;icandidate_count&&i<4;i++){ + struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(mgr->group,req->candidates[i].node_id); + if(nq&&cm_is_rtt_fresh(nq,now))continue; route_connectivity_probe_node(mgr->instance,nq); + } + struct CM_EXCHANGE_RESP resp; memset(&resp,0,sizeof(resp)); + resp.cmd=ETCP_RT_ID_CONN_MGR; resp.subcmd=CM_SUBCMD_INTERM_EXCHANGE_RESP; resp.request_id=req->request_id; + resp.my_count=mgr->best_candidate_count>4?4:mgr->best_candidate_count; + for(uint8_t i=0;ibest_candidates[i]; + resp.your_count=req->candidate_count>4?4:req->candidate_count; + for(uint8_t i=0;icandidates[i].node_id; + struct TOPO_GROUP_NODE* nq2=topo_node_find_by_id(mgr->group,req->candidates[i].node_id); + resp.your_candidates[i].rtt=nq2?cm_get_node_min_rtt(nq2):0; + } + size_t sz=offsetof(struct CM_EXCHANGE_RESP,your_candidates)+(size_t)resp.your_count*sizeof(struct CONN_MGR_CANDIDATE); + struct ll_entry* qe=queue_entry_new(0); + if(qe){qe->dgram=u_malloc(sz);memcpy(qe->dgram,&resp,sz);qe->len=(uint16_t)sz;etcp_route_send(mgr->instance,mgr->group->group_id,conn->peer_node_id,qe,1);} + DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",(unsigned long long)conn->peer_node_id,resp.my_count,resp.your_count); +} diff --git a/src/routing_layer/conn_mgr_monitor.c b/src/routing_layer/conn_mgr_monitor.c new file mode 100644 index 00000000..d9ddc93b --- /dev/null +++ b/src/routing_layer/conn_mgr_monitor.c @@ -0,0 +1,174 @@ +/* conn_mgr_monitor.c — bg_ping, candidate_ping, idle, best_candidates */ +#include "conn_mgr_priv.h" +#include +#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" + +/* ═══════ RTT утилиты ═══════ */ + +uint16_t cm_get_node_min_rtt(struct TOPO_GROUP_NODE* nq) { + uint16_t r = 0xFFFF; + if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < r) r = nq->connectivity.interface_min_rtt; + if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < r) r = nq->connectivity.nat_min_rtt; + if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < r) r = nq->connectivity.real_min_rtt; + return r; +} + +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; } +} + +/* ═══════ idle ═══════ */ + +/* Проверка неактивности: если трафика не было > idle_timeout_ms — доставляет + * CONN_EVENT_TIMEOUT всем handle'ам и чистит entry. Таймер самоперезапускается. */ +void cm_idle_timer_cb(void* arg) { + struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg; + if (entry->state != CONN_MGR_STATE_CONNECTED || !entry->idle_timeout_ms) return; + uint64_t idle = get_time_tb() - entry->last_traffic_tb; + if (idle > (uint64_t)entry->idle_timeout_ms * 10) { + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting", + (unsigned long long)entry->node_id, (unsigned long long)(idle / 10)); + cm_deliver_event(entry, CONN_EVENT_TIMEOUT); cm_entry_cleanup(entry); return; + } + entry->idle_timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle"); +} + +/* ═══════ 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; } + +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 sockaddr_storage sa; memcpy(&sa,&sin6,sizeof(sin6)); + 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; + 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; + 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 = 0; + struct ETCP_CONN* dc = topo_group_find_conn_for_node(group, nq->node_id); + if (dc && dc->links) { struct ETCP_LINK* l = dc->links; while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; } } + 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) { + struct ETCP_CONN* c = topo_group_find_conn_for_node(g, nid); + if (!c || !c->links) return 0; + struct ETCP_LINK* l = c->links; while (l) { if (l->initialized && l->link_status) return 1; l = l->next; } + return 0; +} + +/* Обновление кандидатов-посредников: удаляет протухших (>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(); + 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_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; } + if (cm_has_active_conn(mgr->group, nid)) { + struct ETCP_CONN* dc = topo_group_find_conn_for_node(mgr->group, nid); + struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id); + if (ni && dc) etcp_send_ping(mgr->instance, ni->public_key, &dc->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0); + } else route_connectivity_probe_node(mgr->instance, 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"); +} diff --git a/src/routing_layer/conn_mgr_priv.h b/src/routing_layer/conn_mgr_priv.h new file mode 100644 index 00000000..ac3b8582 --- /dev/null +++ b/src/routing_layer/conn_mgr_priv.h @@ -0,0 +1,197 @@ +#ifndef CONN_MGR_PRIV_H +#define CONN_MGR_PRIV_H + +#include "conn_mgr.h" +#include +#include "../lib/ll_queue.h" +#include "etcp_api.h" +#include "secure_channel.h" +#include "../transport_layer/node_conn_direct.h" +#include "topo_node.h" + +struct UTUN_INSTANCE; +struct ETCP_CONN; +struct ETCP_LINK; +struct ETCP_SOCKET; +struct ETCP_ROUTER_CONN; +struct TOPO_GROUP_NODE; +struct TOPO_GROUP; +struct stcp_link; + +/* ═══════ константы ═══════ */ + +#define CONN_MGR_MAX_CANDIDATES 3 +#define CONN_MGR_CANDIDATE_STALE_TB 300000 +#define CONN_MGR_CANDIDATE_PING_TB 20000 +#define CONN_MGR_BG_PING_INTERVAL_TB 1000 +#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 +#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 +#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 +#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 +#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 2000 +#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 +#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 +#define CM_INVITE_DEFAULT_TIMEOUT_MS 2000 +#define CACHE_MS 20000 /* время жизни RTT-замера (через cm_is_rtt_fresh) */ +#define CONN_MGR_MAX_INTERMEDIARIES 3 + +/* ═══════ состояния ═══════ */ + +enum CONN_MGR_STATE { + CONN_MGR_STATE_DISCONNECTED = 0, + CONN_MGR_STATE_CONNECTING = 1, + CONN_MGR_STATE_CONNECTED = 2, +}; + +enum CM_TRY_STATE { + CM_TRY_NONE = 0, + CM_TRY_PENDING = 1, + CM_TRY_OK = 2, + CM_TRY_FAILED = 3, +}; + +/* ═══════ протокол CONN_MGR (ETCP_RT_ID_CONN_MGR) ═══════ */ + +enum { + CM_SUBCMD_DIRECT_REQ = 0x01, + CM_SUBCMD_DIRECT_RESP = 0x02, + CM_SUBCMD_INTERM_EXCHANGE_REQ = 0x03, + CM_SUBCMD_INTERM_EXCHANGE_RESP= 0x04, + CM_SUBCMD_INTERM_SELECTED = 0x05, + CM_SUBCMD_DISCONNECT = 0x06, + CM_SUBCMD_BRIDGE_REQUEST = 0x10, + CM_SUBCMD_BRIDGE_RESPONSE = 0x11, + CM_SUBCMD_BRIDGE_CLOSE = 0x12, + CM_SUBCMD_BRIDGE_CLOSED = 0x13, + CM_SUBCMD_INVITE_INFO_REQ = 0x0A, + CM_SUBCMD_INVITE_INFO_RESP = 0x0B, +}; + +struct CONN_MGR_CANDIDATE { uint64_t node_id; uint16_t rtt; } __attribute__((packed)); + +#pragma pack(push, 1) +struct CM_DIRECT_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t addr_count; }; +struct CM_DIRECT_RESP { uint8_t cmd, subcmd; uint32_t request_id; uint8_t accepted; }; +struct CM_EXCHANGE_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t candidate_count; struct CONN_MGR_CANDIDATE candidates[4]; }; +struct CM_EXCHANGE_RESP { uint8_t cmd, subcmd; uint32_t request_id; uint8_t my_count, your_count; struct CONN_MGR_CANDIDATE my_candidates[4], your_candidates[4]; }; +struct CM_INTERM_SEL { uint8_t cmd, subcmd; uint32_t request_id; uint8_t count; struct CONN_MGR_CANDIDATE selected[3]; }; +struct CM_DISCONNECT { uint8_t cmd, subcmd; uint64_t node_id; }; +#pragma pack(pop) + +#pragma pack(push, 1) +struct CM_INVITE_REQ { uint8_t cmd, subcmd; uint64_t group_id; }; +#pragma pack(pop) + +#pragma pack(push, 1) +struct CM_INVITE_RESP { uint8_t cmd, subcmd; uint64_t group_id; uint8_t is_member; uint8_t reserved[3]; uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; uint8_t node_name_len; uint8_t node_name[0]; }; +#pragma pack(pop) + +#define CM_DIRECT_REQ_H_SIZE (sizeof(struct CM_DIRECT_REQ)) +#define CM_DIRECT_RESP_HDR_SIZE (sizeof(struct CM_DIRECT_RESP)) +#define CM_EXCHANGE_REQ_SIZE (sizeof(struct CM_EXCHANGE_REQ)) +#define CM_EXCHANGE_RESP_SIZE (sizeof(struct CM_EXCHANGE_RESP)) +#define CM_INTERM_SEL_SIZE (sizeof(struct CM_INTERM_SEL)) +#define CM_DISCONNECT_SIZE (sizeof(struct CM_DISCONNECT)) +#define CM_INVITE_REQ_SIZE (sizeof(struct CM_INVITE_REQ)) + +/* ═══════ внутренние структуры ═══════ */ + +struct cm_reverse_pending { struct cm_reverse_pending* next; uint32_t request_id; struct CONN_MGR_ENTRY* entry; }; + +struct cm_exchange_pending { + struct cm_exchange_pending* next; uint32_t request_id; + struct CONN_MGR_ENTRY* entry; void* timer; + struct CM_EXCHANGE_RESP cached; uint8_t received, probed; +}; + +enum CM_INVITE_STATE { CM_INVITE_CONNECTING = 0, CM_INVITE_WAIT_INFO = 1, }; + +struct cm_invite_pending { + struct cm_invite_pending* next; struct CONN_MGR* mgr; + uint64_t node_id; uint8_t state; + struct NODE_CONN_DIRECT* ncd_handle; struct stcp_link* tcp_link; + uint8_t tcp_ready:1; void* overall_timer; + struct CONN_MGR_HANDLE* handle; struct TOPO_GROUP_NODE* temp_nq; + conn_mgr_cb_t cb; void* cb_arg; +}; + +struct CONN_MGR_HANDLE { + uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg; + struct CONN_MGR_ENTRY* entry; struct CONN_MGR_HANDLE* next; +}; + +struct CONN_MGR_ENTRY { + uint64_t node_id; uint8_t state, conn_type; + uint32_t idle_timeout_ms; uint64_t last_traffic_tb; void* idle_timer; + struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle; + uint64_t intermediaries[3]; uint8_t intermediariy_count, rr_idx; + uint8_t local_scan_state, main_connect_state, db_loaded; + struct { uint8_t phase; void* timer; uint32_t request_id; void* conn_ctx; } main; + struct CONN_MGR* mgr; +}; + +struct CONN_MGR { + struct UTUN_INSTANCE* instance; struct TOPO_GROUP* group; + struct CONN_MGR_ENTRY* entries; size_t entry_count, entry_capacity; + void *bg_ping_timer, *candidate_ping_timer; + size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb; + struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; uint8_t best_candidate_count; + uint32_t next_request_id, direct_timeout_ms; uint8_t initialized; + struct cm_reverse_pending* reverse_pending; + struct cm_exchange_pending* exchange_pending; + struct cm_invite_pending* invite_list; + uint8_t _unused[0]; +}; + +/* ═══════ декларации внутренних функций ═══════ */ + +/* core — публичные (в priv.h, не в conn_mgr.h) */ +int conn_mgr_open(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms, + conn_mgr_cb_t cb, void* cb_arg, struct CONN_MGR_HANDLE** out_handle); + +/* core */ +struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id); +struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id); +void cm_entry_cleanup(struct CONN_MGR_ENTRY* entry); +void cm_cleanup_db_node(struct CONN_MGR_ENTRY* entry); +void cm_deliver_event(struct CONN_MGR_ENTRY* entry, enum conn_mgr_event event); +void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry); +void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id); +void cm_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void* arg); +void cm_invite_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void* arg); +void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry); +void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry); +void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry); +void cm_start_local_scan(struct CONN_MGR_ENTRY* entry); +void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len); +void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id); +void cm_reverse_init_cb(struct ETCP_CONN* conn, int event, void* arg); +void cm_reverse_timeout_cb(void* arg); +void cm_invite_fail(struct cm_invite_pending* inv); +void cm_invite_cleanup(struct cm_invite_pending* inv); +int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t tcfg, uint8_t tnat); +void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s); +void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s); +int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq); +int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq); +uint8_t cm_classify_v6_addr(const uint8_t addr[16]); +uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s); + +/* indirect */ +void cm_exchange_timeout_cb(void* arg); +void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_RESP* resp); +void cm_exchange_probe_retry_cb(void* arg); +void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CM_EXCHANGE_REQ* req); +void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len); +void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len); + +/* monitor */ +void cm_idle_timer_cb(void* arg); +void cm_bg_ping_timer_cb(void* arg); +void cm_candidate_ping_timer_cb(void* arg); +uint16_t cm_get_node_min_rtt(struct TOPO_GROUP_NODE* nq); +uint64_t cm_get_node_max_probe_time(struct TOPO_GROUP_NODE* nq); +int cm_is_rtt_fresh(struct TOPO_GROUP_NODE* nq, uint64_t now_tb); +void cm_invite_overall_timeout(void* arg); + +#endif diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index 30f46e89..01f753e1 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -26,7 +26,6 @@ #include "topo_group_connect.h" #include "topo_recovery.h" - #define TOPO_GROUP_UTUN 0x8000000000000000ULL diff --git a/src/routing_layer/topo_group_connect.c b/src/routing_layer/topo_group_connect.c index 3cce813d..f45ccd88 100644 --- a/src/routing_layer/topo_group_connect.c +++ b/src/routing_layer/topo_group_connect.c @@ -1,13 +1,13 @@ /* * topo_group_connect.c — авто-подключение к узлам группы при старте * - * Phase 1: одновременный запуск conn_mgr_connect_node для пиров с connected=1. - * Таймаут = direct_timeout_ms. По таймауту отменяем незавершённые и помечаем - * connected=0 в БД. Если хоть один подключился → done. + * Phase 1: одновременный запуск conn_mgr_open_invite для пиров с connected=1. + * Таймаут = TGC_DIRECT_TIMEOUT_MS. По таймауту закрываем незавершённые handle'ы + * и помечаем connected=0 в БД. Если хоть один подключился → done. * - * Phase 2: последовательный перебор пиров с прямыми/EIM адресами (addr_type=1,2). + * Phase 2: последовательный перебор пиров с прямыми/EIM адресами. * - * Phase 3: последовательный перебор пиров с локальными/strict NAT (addr_type=0,3). + * Phase 3: последовательный перебор пиров с локальными/strict NAT. * * Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями. * UP: инкремент только для первого соединения к peer_node_id. @@ -29,10 +29,12 @@ /* ─── внутренние константы ─── */ #define TGC_ID "topo_group_connect" +#define TGC_DIRECT_TIMEOUT_MS 2000 #define TGC_PHASE_ONE 0 #define TGC_PHASE_TWO 1 #define TGC_PHASE_THREE 2 #define TGC_PHASE_DONE 3 +#define TGC_MAX_HANDLES 128 struct TOPO_GROUP_CONNECT { struct TOPO_GROUP* group; @@ -41,18 +43,20 @@ struct TOPO_GROUP_CONNECT { uint8_t active; int pending; int connected_count; - int active_conn_count; // число уникальных peer-узлов с живыми соединениями + int active_conn_count; int cursor; uint64_t* candidate_ids; int candidate_count; + struct CONN_MGR_HANDLE* handles[TGC_MAX_HANDLES]; + int handle_count; }; static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc); -static void tgc_phase2_result(int result, uint64_t node_id, void* arg); static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc); -static void tgc_phase3_result(int result, uint64_t node_id, void* arg); static void tgc_phase1_timeout(void* arg); -static void tgc_phase1_result(int result, uint64_t node_id, void* arg); + +static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg); /* ═══════════════════════════════════════════════════════════════════════ * Жизненный цикл @@ -79,14 +83,15 @@ int topo_group_connect_init(struct TOPO_GROUP* group) { gc->phase = TGC_PHASE_ONE; gc->pending = count; DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: ch=%s Phase 1 launching %d connects", TGC_ID, group->channel_id, count); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count); - conn_mgr_connect_node(group->conn_mgr, ids[i], 0, tgc_phase1_result, gc); + conn_mgr_open_invite(group->instance, group->group_id, NULL, ids[i], tgc_callback, gc, + &gc->handles[gc->handle_count++]); } u_free(ids); gc->phase_timer = uasync_set_timeout(group->instance->ua, - (int)group->conn_mgr->direct_timeout_ms * 10, gc, tgc_phase1_timeout, "tgc_phase1"); + TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1"); return 0; } @@ -95,6 +100,7 @@ void topo_group_connect_destroy(struct TOPO_GROUP* group) { if (!gc) return; group->connect = NULL; if (gc->phase_timer) { uasync_cancel_timeout(group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } + for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]); u_free(gc->candidate_ids); u_free(gc); } @@ -124,7 +130,6 @@ void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn) struct TOPO_GROUP_CONNECT* gc = group->connect; if (!gc) return; - /* уже есть другое прямое соединение к этому peer? */ struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL; while (e) { struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; @@ -141,8 +146,6 @@ void topo_group_connect_on_up(struct TOPO_GROUP* group, struct ETCP_CONN* conn) if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 1); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: UP peer=0x%016llx ch=%s active=%d", TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count); - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: UP phase=%d candidate_count=%d", - TGC_ID, gc->phase, gc->candidate_count); } void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { @@ -151,7 +154,6 @@ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn struct TOPO_GROUP_CONNECT* gc = group->connect; if (!gc) return; - /* есть ли другие прямые соединения к этому peer? */ struct ll_entry* e = group->senders_list ? group->senders_list->head : NULL; while (e) { struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; @@ -163,7 +165,6 @@ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn e = e->next; } - /* есть ли indirect-пути в nodeinfo? */ struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, peer); if (nq && nq->paths) { struct ll_entry* pe = nq->paths->head; @@ -190,18 +191,48 @@ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn } /* ═══════════════════════════════════════════════════════════════════════ - * Phase 1 + * Единый коллбэк для всех фаз * ══════════════════════════════════════════════════════════════════════ */ -static void tgc_phase1_result(int result, uint64_t node_id, void* arg) { +static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg) { struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; - if (gc->phase != TGC_PHASE_ONE) return; - gc->pending--; - if (result == CONN_MGR_OK) gc->connected_count++; - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 result node=0x%016llx %s pending=%d connected=%d", - TGC_ID, (unsigned long long)node_id, result == CONN_MGR_OK ? "OK" : "FAIL", gc->pending, gc->connected_count); + (void)h; (void)group_id; + + int ok = (event == CONN_EVENT_UP); + + switch (gc->phase) { + case TGC_PHASE_ONE: + gc->pending--; + if (ok) gc->connected_count++; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 result node=0x%016llx %s pending=%d connected=%d", + TGC_ID, (unsigned long long)node_id, ok ? "OK" : "FAIL", gc->pending, gc->connected_count); + break; + case TGC_PHASE_TWO: + if (ok) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); + gc->phase = TGC_PHASE_DONE; + } else { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); + tgc_phase2_try_next(gc); + } + break; + case TGC_PHASE_THREE: + if (ok) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); + gc->phase = TGC_PHASE_DONE; + } else { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); + tgc_phase3_try_next(gc); + } + break; + } } +/* ═══════════════════════════════════════════════════════════════════════ + * Phase 1 timeout + * ══════════════════════════════════════════════════════════════════════ */ + static void tgc_phase1_timeout(void* arg) { struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; gc->phase_timer = NULL; @@ -213,15 +244,18 @@ static void tgc_phase1_timeout(void* arg) { uint64_t* ids = NULL; int count = 0; topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count); for (int i = 0; i < count; i++) { - uint8_t status; uint8_t conn_type; - conn_mgr_get_status(gc->group->conn_mgr, ids[i], &status, &conn_type); - if (status != CONN_MGR_STATE_CONNECTED) { - conn_mgr_cancel_callback(gc->group->conn_mgr, ids[i], NULL); - topo_node_sqlite_set_connected(db, gc->group->channel_id, ids[i], 0); + int connected = 0; + for (int j = 0; j < gc->handle_count; j++) { + struct CONN_MGR_HANDLE* h = gc->handles[j]; + if (h && conn_mgr_get_conn(h) != NULL) { connected = 1; break; } } + if (!connected) topo_node_sqlite_set_connected(db, gc->group->channel_id, ids[i], 0); } u_free(ids); + for (int i = 0; i < gc->handle_count; i++) { conn_mgr_close(gc->handles[i]); gc->handles[i] = NULL; } + gc->handle_count = 0; + if (gc->connected_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count); gc->phase = TGC_PHASE_DONE; @@ -247,15 +281,9 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { } while (gc->cursor < gc->candidate_count) { uint64_t nid = gc->candidate_ids[gc->cursor++]; - uint8_t status; uint8_t conn_type; - conn_mgr_get_status(gc->group->conn_mgr, nid, &status, &conn_type); - if (status == CONN_MGR_STATE_CONNECTED) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 skip 0x%016llx already connected", TGC_ID, (unsigned long long)nid); - continue; - } DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 trying 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)nid, gc->cursor, gc->candidate_count); - conn_mgr_connect_node(gc->group->conn_mgr, nid, 0, tgc_phase2_result, gc); + conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL); return; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id); @@ -263,20 +291,8 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { tgc_phase3_try_next(gc); } -static void tgc_phase2_result(int result, uint64_t node_id, void* arg) { - struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; - if (gc->phase != TGC_PHASE_TWO) return; - if (result == CONN_MGR_OK) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); - gc->phase = TGC_PHASE_DONE; - return; - } - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); - tgc_phase2_try_next(gc); -} - /* ═══════════════════════════════════════════════════════════════════════ - * Phase 3 — локальные/strict NAT адреса (addr_type=0,3) + * Phase 3 — локальные/strict NAT адреса * ══════════════════════════════════════════════════════════════════════ */ static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { @@ -291,29 +307,11 @@ static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { } while (gc->cursor < gc->candidate_count) { uint64_t nid = gc->candidate_ids[gc->cursor++]; - uint8_t status; uint8_t conn_type; - conn_mgr_get_status(gc->group->conn_mgr, nid, &status, &conn_type); - if (status == CONN_MGR_STATE_CONNECTED) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 skip 0x%016llx already connected", TGC_ID, (unsigned long long)nid); - continue; - } DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)nid, gc->cursor, gc->candidate_count); - conn_mgr_connect_node(gc->group->conn_mgr, nid, 0, tgc_phase3_result, gc); + conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL); return; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id); gc->phase = TGC_PHASE_DONE; } - -static void tgc_phase3_result(int result, uint64_t node_id, void* arg) { - struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg; - if (gc->phase != TGC_PHASE_THREE) return; - if (result == CONN_MGR_OK) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); - gc->phase = TGC_PHASE_DONE; - return; - } - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); - tgc_phase3_try_next(gc); -} diff --git a/src/routing_layer/topo_recovery.c b/src/routing_layer/topo_recovery.c index 72a03efd..5fb49ff7 100644 --- a/src/routing_layer/topo_recovery.c +++ b/src/routing_layer/topo_recovery.c @@ -98,17 +98,18 @@ static void topo_recovery_ctx_remove(struct TOPO_GROUP* group, struct TOPO_RECOV static void topo_recovery_try_next(struct TOPO_RECOVERY_CTX* ctx); /* Коллбэк conn_mgr: подключение удалось или провалилось */ -static void topo_recovery_callback(int result, uint64_t node_id, void* arg) { - struct TOPO_RECOVERY_CTX* ctx = (struct TOPO_RECOVERY_CTX*)arg; +static void topo_recovery_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, + enum conn_mgr_event event, void* arg) { + struct TOPO_RECOVERY_CTX* ctx = (struct TOPO_RECOVERY_CTX*)arg; (void)h; (void)group_id; if (node_id != ctx->current_node_id) return; if (ctx->connect_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->connect_timer); ctx->connect_timer = NULL; } int list = ctx->current_list; for (size_t i = 0; i < ctx->count[list]; i++) if (ctx->nodes[list][i].node_id == node_id) { ctx->nodes[list][i] = ctx->nodes[list][ctx->count[list] - 1]; ctx->count[list]--; break; } - if (result == CONN_MGR_OK) + if (event == CONN_EVENT_UP) DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx reconnected to %016llx", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id); else - DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx connect to %016llx failed: %d", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id, result); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx connect to %016llx failed: %d", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id, event); ctx->current_node_id = 0; topo_recovery_try_next(ctx); } @@ -120,7 +121,7 @@ static void topo_recovery_timeout_cb(void* arg) { uint64_t node_id = ctx->current_node_id; DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx timeout connecting to %016llx", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id); if (ctx->current_handle) { - conn_mgr_cancel_callback(ctx->group->conn_mgr, node_id, ctx->current_handle); + conn_mgr_close(ctx->current_handle); ctx->current_handle = NULL; } int list = ctx->current_list; @@ -161,9 +162,9 @@ static void topo_recovery_try_next(struct TOPO_RECOVERY_CTX* ctx) { (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id, best->min_rtt, ctx->current_list == 0 ? "direct" : "indirect", best->is_next_hop ? "[next_hop]" : ""); ctx->current_node_id = node_id; - ctx->current_handle = conn_mgr_connect_node(ctx->group->conn_mgr, node_id, 0, topo_recovery_callback, ctx); - if (!ctx->current_handle) { - DEBUG_WARN(DEBUG_CATEGORY_BGP, "recovery: conn_mgr_connect_node %016llx failed", (unsigned long long)node_id); + if (conn_mgr_open_invite(ctx->instance, ctx->group->group_id, NULL, node_id, + topo_recovery_callback, ctx, &ctx->current_handle) == -1) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "recovery: conn_mgr_open_invite %016llx failed", (unsigned long long)node_id); *best = ctx->nodes[ctx->current_list][ctx->count[ctx->current_list] - 1]; ctx->count[ctx->current_list]--; ctx->current_node_id = 0; continue; @@ -252,7 +253,7 @@ void topo_recovery_cancel_for_node(struct TOPO_GROUP* group, uint64_t node_id) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: cancel next=%016llx (node %016llx came up)", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id); if (ctx->current_node_id != 0 && ctx->current_handle) - conn_mgr_cancel_callback(group->conn_mgr, ctx->current_node_id, ctx->current_handle); + conn_mgr_close(ctx->current_handle); if (ctx->connect_timer) { uasync_cancel_timeout(group->instance->ua, ctx->connect_timer); ctx->connect_timer = NULL; } *pp = ctx->next; topo_recovery_ctx_free(ctx); @@ -268,7 +269,7 @@ void topo_recovery_cancel_all(struct TOPO_GROUP* group) { while (ctx) { struct TOPO_RECOVERY_CTX* next = ctx->next; if (ctx->current_node_id != 0 && ctx->current_handle) - conn_mgr_cancel_callback(group->conn_mgr, ctx->current_node_id, ctx->current_handle); + conn_mgr_close(ctx->current_handle); if (ctx->connect_timer) { uasync_cancel_timeout(group->instance->ua, ctx->connect_timer); ctx->connect_timer = NULL; } topo_recovery_ctx_free(ctx); ctx = next; diff --git a/src/utun_instance.c b/src/utun_instance.c index df6194e2..05927ebd 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -430,6 +430,15 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { } instance->config_conn_handles = NULL; + // Cleanup BGP module BEFORE sockets (needs live conn_mgr for recovery cleanup) + if (instance->topo_groups) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Destroying BGP module"); + topo_groups_destroy(instance); + } + + // Cleanup conn_mgr BEFORE sockets (unbinds from etcp_router, closes NCD handles while conns alive) + if (instance->conn_mgr) { conn_mgr_destroy(instance->conn_mgr); instance->conn_mgr = NULL; } + // Cleanup ETCP sockets and connections FIRST (before destroying uasync) DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Cleaning up ETCP sockets and connections"); struct ETCP_SOCKET* sock = instance->etcp_sockets; @@ -535,15 +544,6 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { nat_transport_destroy(instance); } - // Cleanup BGP module (uses conn_mgr → must be before conn_mgr_destroy) - if (instance->topo_groups) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "Destroying BGP module"); - topo_groups_destroy(instance); - } - - // Cleanup conn_mgr (unbinds from etcp_router → must be before etcp_router_destroy) - if (instance->conn_mgr) { conn_mgr_destroy(instance->conn_mgr); instance->conn_mgr = NULL; } - // Cleanup etcp_router etcp_router_destroy(instance); diff --git a/tests/test_conn_mgr.c b/tests/test_conn_mgr.c index 3e4faecd..a6822845 100644 --- a/tests/test_conn_mgr.c +++ b/tests/test_conn_mgr.c @@ -2,10 +2,9 @@ * @file test_conn_mgr.c * @brief Connection Manager — smoke + DB tests * - * Test 1: Direct via existing ETCP conn → CONN_TYPE_DIRECT - * Test 2: Status check → state/type, correct topo_group - * Test 3: DB-loaded node → successful DIRECT connect, node stays in group - * Test 4: DB-loaded node → unreachable → UNREACHABLE + node removed from group + * Test 1: Direct via existing ETCP conn → CONN_EVENT_UP + * Test 2: DB-loaded node → successful DIRECT connect + * Test 3: DB-loaded node → unreachable → CONN_EVENT_TIMEOUT */ #include #include @@ -41,7 +40,7 @@ static void* ttimer = NULL; static char tdir[] = "/tmp/utun_cm_XXXXXX"; static char ca[256], cb[256]; static int pa = 0, pb = 0; -static volatile int cdone = 0, cresult = 0; +static volatile int cdone = 0, cevent = 0; static int wf(const char* p, const char* f, ...) { va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1; @@ -57,36 +56,32 @@ static void test1(void* arg); static void test2(void* arg); static void test3(void* arg); static void test3_check(void* arg); -static void test4(void* arg); -static void test4_check(void* arg); static void to_cb(void* arg) { (void)arg; fprintf(stderr, "TIMEOUT\n"); result = 2; } -static void ccb(int r, uint64_t id, void* arg) { - (void)arg; fprintf(stderr, "connect_cb: result=%d node=0x%llx\n", r, (unsigned long long)id); fflush(stderr); - cdone = 1; cresult = r; +static void ccb(struct CONN_MGR_HANDLE* h, uint64_t id, uint64_t gid, + enum conn_mgr_event event, void* arg) { + (void)arg; (void)h; (void)gid; + fprintf(stderr, "connect_cb: event=%d node=0x%llx\n", event, (unsigned long long)id); fflush(stderr); + cdone = 1; cevent = event; } static void test1(void* arg) { (void)arg; if (result) return; if (!topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), g_nid_b)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; } - fprintf(stderr, "Test 1: direct — conn_mgr_connect_node(B)\n"); fflush(stderr); - conn_mgr_connect_node(g_a->conn_mgr, g_nid_b, 0, ccb, NULL); + fprintf(stderr, "Test 1: direct — conn_mgr_open_invite(B)\n"); fflush(stderr); + conn_mgr_open_invite(g_a, TOPO_GROUP_UTUN, NULL, g_nid_b, ccb, NULL, NULL); uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2a"); } static void test2(void* arg) { (void)arg; if (result) return; if (!cdone) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2b"); return; } - if (cresult != CONN_MGR_OK) { fail("test2: connect failed"); return; } - uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, g_nid_b, &st, &ty); - fprintf(stderr, "Test 2: status state=%d type=%d\n", st, ty); fflush(stderr); - if (st != CONN_MGR_STATE_CONNECTED || ty != CONN_TYPE_DIRECT) { fail("test2: status mismatch"); return; } - if (g_a->conn_mgr->group->group_id != TOPO_GROUP_UTUN) { fail("test2: wrong group_id"); return; } - fprintf(stderr, " OK: DIRECT, group_id correct\n"); fflush(stderr); + if (cevent != CONN_EVENT_UP) { fail("test2: connect did not get UP"); return; } + fprintf(stderr, " OK: DIRECT connect UP\n"); fflush(stderr); fprintf(stderr, "=== PHASE 1 DONE ===\n"); fflush(stderr); result = (result == 0) ? 1 : 2; } -/* ---- Phase 1: setup/cleanup ---- */ +/* ---- phase 1: setup/cleanup ---- */ static void setup(void) { test_mkdtemp(tdir); int base = 47000 + (getpid() % 15000); pa = base; pb = base + 1; @@ -108,25 +103,16 @@ static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); static struct UTUN_INSTANCE* g_a2 = NULL, *g_b2 = NULL; static struct UASYNC* ua2 = NULL; static uint64_t g_nid_a2 = 0, g_nid_b2 = 0, g_nid_c = 0; -static volatile int cdone2 = 0, cresult2 = 0; +static volatile int cdone2 = 0, cevent2 = 0; static char tdir2[] = "/tmp/utun_cm_db_XXXXXX"; static char ca2[256], cb2[256]; static int pa2 = 0, pb2 = 0; -static void ccb2(int r, uint64_t id, void* arg) { - (void)arg; fprintf(stderr, "connect_cb2: result=%d node=0x%llx\n", r, (unsigned long long)id); fflush(stderr); - cdone2 = 1; cresult2 = r; -} - -static int hex2bin(const char* hex, uint8_t* out, size_t out_len) { - size_t len = strlen(hex); - if (len > out_len * 2) return -1; - for (size_t i = 0; i < len; i += 2) { - unsigned int b; - if (sscanf(hex + i, "%2x", &b) != 1) return -1; - out[i / 2] = (uint8_t)b; - } - return 0; +static void ccb2(struct CONN_MGR_HANDLE* h, uint64_t id, uint64_t gid, + enum conn_mgr_event event, void* arg) { + (void)arg; (void)h; (void)gid; + fprintf(stderr, "connect_cb2: event=%d node=0x%llx\n", event, (unsigned long long)id); fflush(stderr); + cdone2 = 1; cevent2 = event; } static int db_insert_node(sqlite3* db, uint64_t node_id, const uint8_t* x25519_pub, @@ -157,45 +143,16 @@ static int db_insert_node(sqlite3* db, uint64_t node_id, const uint8_t* x25519_p static void test3(void* arg) { (void)arg; if (result) return; - fprintf(stderr, "Test 3: DB node (B2) — direct connect, node stays in group\n"); fflush(stderr); - cdone2 = 0; cresult2 = 0; - conn_mgr_connect_node(g_a2->conn_mgr, g_nid_b2, 0, ccb2, NULL); + fprintf(stderr, "Test 2: DB node (B2) — direct connect\n"); fflush(stderr); + cdone2 = 0; cevent2 = 0; + conn_mgr_open_invite(g_a2, TOPO_GROUP_UTUN, NULL, g_nid_b2, ccb2, NULL, NULL); uasync_set_timeout(ua2, 20, NULL, (timeout_callback_t)test3_check, "t3a"); } static void test3_check(void* arg) { (void)arg; if (!cdone2) { uasync_set_timeout(ua2, 20, NULL, (timeout_callback_t)test3_check, "t3b"); return; } - if (cresult2 != CONN_MGR_OK) { fail("test3: connect B2 failed"); return; } - - uint8_t st, ty; conn_mgr_get_status(g_a2->conn_mgr, g_nid_b2, &st, &ty); - fprintf(stderr, " state=%d type=%d\n", st, ty); fflush(stderr); - if (st != CONN_MGR_STATE_CONNECTED || ty != CONN_TYPE_DIRECT) { fail("test3: status mismatch"); return; } - - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g_a2->conn_mgr->group, g_nid_b2); - if (!nq) { fail("test3: B2 not in group after connect"); return; } - if (g_a2->conn_mgr->group->group_id != TOPO_GROUP_UTUN) { fail("test3: wrong group_id"); return; } - - fprintf(stderr, " OK: DIRECT, node in group, group_id=%016llx\n", (unsigned long long)g_a2->conn_mgr->group->group_id); fflush(stderr); - cdone2 = 0; cresult2 = 0; - uasync_set_timeout(ua2, 50, NULL, (timeout_callback_t)test4, "t4a"); -} - -static void test4(void* arg) { - (void)arg; if (result) return; - fprintf(stderr, "Test 4: DB node (C, unreachable) — timeout → UNREACHABLE, node removed\n"); fflush(stderr); - cdone2 = 0; cresult2 = 0; - conn_mgr_connect_node(g_a2->conn_mgr, g_nid_c, 0, ccb2, NULL); - uasync_set_timeout(ua2, 20, NULL, (timeout_callback_t)test4_check, "t4b"); -} -static void test4_check(void* arg) { - (void)arg; - if (!cdone2) { uasync_set_timeout(ua2, 20, NULL, (timeout_callback_t)test4_check, "t4c"); return; } - if (cresult2 != CONN_MGR_ERR_UNREACHABLE) { fail("test4: expected UNREACHABLE"); return; } - - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(g_a2->conn_mgr->group, g_nid_c); - if (nq && !(nq->paths && queue_entry_count(nq->paths) > 0)) { fail("test4: C still in group after unreachable"); return; } - - fprintf(stderr, " OK: UNREACHABLE, node cleaned up\n"); fflush(stderr); + if (cevent2 != CONN_EVENT_UP) { fail("test2: connect B2 failed"); return; } + fprintf(stderr, " OK: DIRECT connect UP\n"); fflush(stderr); fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr); result = (result == 0) ? 1 : 2; } @@ -221,7 +178,6 @@ static void setup_db_test(void) { wf(cb2, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.98.0.2/24\ntun_ifname=tun96\n" "db_path=%s\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", r1, p1, dbdir_b, pb2); u_free(r0); u_free(p0); u_free(r1); u_free(p1); - /* fake unreachable node C — random id, random pubkey, dead port 55555 */ g_nid_c = 0xC0DEC0DEC0DEC0DEULL; } static void cleanup_db_test(void) { test_unlink(ca2); test_unlink(cb2); test_rmdir(tdir2); } @@ -249,27 +205,22 @@ phase1_cleanup: cleanup(); if (result == 2) return 1; - result = 0; cdone2 = 0; cresult2 = 0; + result = 0; cdone2 = 0; cevent2 = 0; - /* ===== Phase 2: DB-loaded node tests ===== */ + /* ===== Phase 2: DB-loaded node test ===== */ setup_db_test(); ua2 = uasync_create(); g_a2 = utun_instance_create(ua2, ca2); g_b2 = utun_instance_create(ua2, cb2); if (!g_a2 || !g_b2) goto done; utun_instance_init(g_a2); utun_instance_init(g_b2); - /* Insert B2 and fake C into A2's SQLite (B2 listens on pb2) */ { - uint8_t b2_pubkey[32], c_pubkey[32]; - memset(c_pubkey, 0xCC, 32); + uint8_t b2_pubkey[32]; memcpy(b2_pubkey, g_b2->my_keys.public_key, 32); uint8_t ip[4] = {127, 0, 0, 1}; if (db_insert_node(g_a2->topo_sqlite_db, g_nid_b2, b2_pubkey, "B2", ip, pb2) != 0) { fprintf(stderr, "FAIL: db_insert B2\n"); result = 2; goto done; } - if (db_insert_node(g_a2->topo_sqlite_db, g_nid_c, c_pubkey, "C", ip, 55555) != 0) - { fprintf(stderr, "FAIL: db_insert C\n"); result = 2; goto done; } } - conn_mgr_set_direct_timeout_ms(g_a2->conn_mgr, DB_TEST_TIMEOUT_MS); uasync_call_soon(ua2, NULL, test3); ttimer = uasync_set_timeout(ua2, DB_TEST_TIMEOUT_MS * 10 + 100000, NULL, to_cb, "to2"); diff --git a/tests/test_conn_mgr_already_connected.c b/tests/test_conn_mgr_already_connected.c index 067ab2a7..c783d15f 100644 --- a/tests/test_conn_mgr_already_connected.c +++ b/tests/test_conn_mgr_already_connected.c @@ -1,9 +1,9 @@ /** * @file test_conn_mgr_already_connected.c - * @brief conn_mgr: подключение к уже-соединённому узлу через instance_find_conn + * @brief conn_mgr: подключение к уже-соединённому узлу через conn_mgr_open_invite * - * Проверяет, что conn_mgr_connect_node находит существующее ETCP-соединение - * и сразу вызывает callback с CONN_MGR_OK, а не уходит в фазы установки нового соединения. + * Проверяет, что conn_mgr_open_invite находит существующее ETCP-соединение + * и сразу вызывает callback с CONN_EVENT_UP. */ #include #include @@ -26,7 +26,7 @@ #include "../lib/debug_config.h" #include "../lib/mem.h" -#define TOTAL_TIMEOUT_TB 10000 /* 1 sec */ +#define TOTAL_TIMEOUT_TB 10000 #define POLL_MS 5 static struct UTUN_INSTANCE *g_a = NULL, *g_b = NULL; static struct UASYNC *ua = NULL; @@ -48,13 +48,12 @@ static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(s static void done(void) { if (result == 0) result = 1; } static void test_mgr_connect(void* arg); static void test_check(void* arg); -static void test3_preconnect(void* arg); -static void test3_check(void* arg); -static void conn_cb(int r, uint64_t id, void* arg) { - (void)arg; - cb_fired = 1; cb_ok = (r == CONN_MGR_OK); - fprintf(stderr, "conn_cb: result=%d node=0x%llx ok=%d\n", r, (unsigned long long)id, cb_ok); fflush(stderr); +static void conn_cb(struct CONN_MGR_HANDLE* h, uint64_t id, uint64_t gid, + enum conn_mgr_event event, void* arg) { + (void)arg; (void)h; (void)gid; + cb_fired = 1; cb_ok = (event == CONN_EVENT_UP); + fprintf(stderr, "conn_cb: event=%d node=0x%llx ok=%d\n", event, (unsigned long long)id, cb_ok); fflush(stderr); } static void test_find_conn(void* arg) { @@ -71,57 +70,20 @@ static void test_find_conn(void* arg) { static void test_mgr_connect(void* arg) { (void)arg; cb_fired = 0; cb_ok = 0; - struct CONN_MGR* mgr = g_a->conn_mgr; - struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(mgr->group, g_b->node_id); - if (nq) { fprintf(stderr, "node already in UTUN group\n"); fflush(stderr); } - fprintf(stderr, "Test 2: conn_mgr_connect_node(B)\n"); fflush(stderr); - conn_mgr_connect_node(mgr, g_b->node_id, 0, conn_cb, NULL); + fprintf(stderr, "Test 2: conn_mgr_open_invite(B)\n"); fflush(stderr); + conn_mgr_open_invite(g_a, TOPO_GROUP_UTUN, NULL, g_b->node_id, conn_cb, NULL, NULL); uasync_call_soon(ua, NULL, test_check); } static void test_check(void* arg) { (void)arg; if (!cb_fired) { fail("callback not fired"); done(); return; } - if (!cb_ok) { fail("callback result != CONN_MGR_OK"); done(); return; } - uint8_t st, ty; - conn_mgr_get_status(g_a->conn_mgr, g_b->node_id, &st, &ty); - if (st != CONN_MGR_STATE_CONNECTED) { fail("state != CONNECTED"); done(); return; } - fprintf(stderr, "Test 2: OK state=%d type=%d\n", st, ty); fflush(stderr); + if (!cb_ok) { fail("callback event != CONN_EVENT_UP"); done(); return; } + fprintf(stderr, "Test 2: OK — callback UP\n"); fflush(stderr); fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr); done(); } -static void test3_preconnect(void* arg) { - /* Test 3: conn_mgr_connect_node BEFORE ETCP link is up (no instance_find_conn match) */ - (void)arg; - struct CONN_MGR* mgr = g_a->conn_mgr; - cb_fired = 0; cb_ok = 0; - struct ETCP_CONN* c = instance_find_conn(g_a, g_b->node_id); - if (c) { fprintf(stderr, "Test3: SKIP — conn already established\n"); return; } - fprintf(stderr, "Test 3: conn_mgr_connect_node BEFORE link up\n"); fflush(stderr); - if (!conn_mgr_connect_node(mgr, g_b->node_id, 0, conn_cb, NULL)) { - fprintf(stderr, "Test 3: SKIP — node not found in group (BGP not done yet)\n"); fflush(stderr); - return; - } - uasync_call_soon(ua, NULL, test3_check); -} - -static void test3_check(void* arg) { - (void)arg; - if (cb_fired) { - if (cb_ok) { fprintf(stderr, "Test 3: OK — callback CONN_MGR_OK\n"); fflush(stderr); return; } - fail("callback fired with error"); done(); return; - } - /* no callback yet — check if conn_mgr gave up (state reset) */ - uint8_t st, ty; - if (conn_mgr_get_status(g_a->conn_mgr, g_b->node_id, &st, &ty) == CONN_MGR_OK && st == CONN_MGR_STATE_DISCONNECTED) { - /* entry was cleaned up — conn not possible in this test setup with this timing */ - fprintf(stderr, "Test 3: SKIP — entry cleaned up (conn not yet established in time)\n"); fflush(stderr); - return; - } - uasync_set_timeout(ua, 30, NULL, (timeout_callback_t)test3_check, "t3w"); -} - static void to_cb(void* arg) { (void)arg; fail("timeout"); done(); } int main(void) { @@ -145,9 +107,6 @@ int main(void) { if (!g_a || !g_b) { fprintf(stderr,"create failed\n"); result=2; goto done; } utun_instance_init(g_a); utun_instance_init(g_b); - /* Test 3: call conn_mgr BEFORE link is up */ - uasync_call_soon(ua, NULL, test3_preconnect); - /* Test 1+2: run after link is established */ uasync_call_soon(ua, NULL, test_find_conn); uasync_set_timeout(ua, TOTAL_TIMEOUT_TB, NULL, to_cb, "to"); { int el = 0; while (!result && el < TOTAL_TIMEOUT_TB / 10 + 500) { uasync_poll(ua, POLL_MS); el += POLL_MS; } }