You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1041 lines
52 KiB

#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../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 "route_node.h"
#include "route_bgp.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 NODEINFO_Q* nq);
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
static int cm_has_local_addr(struct NODEINFO_Q* 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 NODEINFO_Q* nq);
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
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_ready_cb(struct ETCP_CONN* conn, void* arg);
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, 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);
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;
};
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: NULL instance"); return NULL; }
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR));
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: alloc failed"); return NULL; }
mgr->instance = instance;
mgr->entry_capacity = 8;
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
etcp_router_bind(instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler);
mgr->bg_ping_timer = uasync_set_timeout(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(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, bg_ping started");
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);
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
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 cm_cb_node* node = entry->cb_list;
while (node) { struct cm_cb_node* next = node->next; node->cb(result, entry->node_id, node->arg); u_free(node); node = next; }
entry->cb_list = NULL;
}
static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
if (!bgp) return;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, 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 NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, node_id);
if (!nq) return;
nq->conn_mgr_type = CONN_TYPE_NONE;
nq->conn_mgr_intermediariy_count = 0;
}
int 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) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: BGP not initialized"); return CONN_MGR_ERR_INTERNAL; }
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, node_id);
if (!target) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx not found", (unsigned long long)node_id); return CONN_MGR_ERR_NOT_FOUND; }
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (entry && entry->state == CONN_MGR_STATE_CONNECTED) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connected", (unsigned long long)node_id);
return CONN_MGR_ERR_ALREADY_CONNECTED;
}
if (entry && entry->state == CONN_MGR_STATE_CONNECTING) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connecting, adding cb", (unsigned long long)node_id);
struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; }
return CONN_MGR_OK;
}
struct ETCP_CONN* existing = route_bgp_find_conn_for_node(bgp, node_id);
if (existing && existing->peer_node_id == node_id && existing->links) {
struct ETCP_LINK* l = existing->links;
while (l) { if (l->link_status && l->initialized) break; l = l->next; }
if (l) {
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTED;
entry->conn_type = CONN_TYPE_DIRECT;
entry->idle_timeout_ms = idle_timeout_ms;
entry->alien = target->alien;
entry->last_traffic_tb = get_time_tb();
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } }
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
return CONN_MGR_OK;
}
}
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTING;
entry->alien = target->alien;
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } }
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;
int has_our_local = cm_has_local_addr(bgp->local_node);
int has_target_local = cm_has_local_addr(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 CONN_MGR_OK;
}
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(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(mgr->instance, 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_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;
}
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_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) {
if (!mgr || !nodeinfo_data || len < sizeof(struct NODEINFO)) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) return CONN_MGR_ERR_INTERNAL;
const struct NODEINFO* ni = (const struct NODEINFO*)nodeinfo_data;
uint64_t node_id = ni->node_id;
struct NODEINFO_Q* existing = nodeinfo_find_by_id(bgp, node_id);
if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; }
size_t dyn_sz = len - sizeof(struct NODEINFO);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node alloc failed"); return CONN_MGR_ERR_INTERNAL; }
memcpy(&nq->node, nodeinfo_data, len);
nq->alien = 1;
nq->dirty = 0;
nq->last_ver = ni->ver;
nq->connectivity.ping_req_time = 0;
if (bgp->nodes) queue_data_put_with_index(bgp->nodes, &nq->ll);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id);
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, 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 NODEINFO_Q* 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 NODEINFO_Q* 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 NODEINFO_Q* 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));
}
static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].config_type == CFG_SERVER_TYPE_PUBLIC || metas[j].config_type == CFG_SERVER_TYPE_UNKNOWN
|| metas[j].nat_type == NAT_VERIFIED_EIM || metas[j].nat_type == NAT_VERIFIED_DIRECT))
return 1;
}
}
}
return 0;
}
static int cm_has_local_addr(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].config_type == CFG_SERVER_TYPE_PRIVATE || metas[j].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 ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { entry->local_scan_state = CM_TRY_FAILED; return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id ||
(metas[j].config_type != CFG_SERVER_TYPE_PRIVATE && metas[j].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, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].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, target->node.public_key, &sa,
CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
return;
}
s = s->next;
}
}
}
entry->local_scan_state = CM_TRY_FAILED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan no candidates for 0x%016llx", (unsigned long long)entry->node_id);
}
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
if (entry->main_connect_state == CM_TRY_OK) return;
if (entry->local_scan_state == CM_TRY_OK) return;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
uint8_t priority_order[] = { ADDR_TYPE_REAL, ADDR_TYPE_NAT, ADDR_TYPE_INTERFACE };
for (int pri = 0; pri < 3; pri++) {
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != priority_order[pri]) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
int compat = cm_nat_compatible(s, metas[j].config_type, metas[j].nat_type);
if (compat && 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, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa));
memcpy(&sa, &sin, sizeof(sin));
struct ETCP_CONN* conn = etcp_connection_create(entry->mgr->instance, NULL);
if (conn) {
etcp_conn_set_ready_cbk(conn, cm_direct_ready_cb, entry);
sc_set_peer_public_key(&conn->crypto_ctx, target->node.public_key, 0);
struct ETCP_LINK* link = etcp_link_new(conn, s, &sa, 0);
if (link) {
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua,
CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS * 10, entry, cm_direct_timeout_cb, "conn_mgr_direct");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) started INIT to 0x%016llx",
(unsigned long long)entry->node_id);
return;
}
}
}
s = s->next;
}
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) no compatible pairs for 0x%016llx",
(unsigned long long)entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = cm_has_direct_ip(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;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = target ? cm_has_direct_ip(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_ready_cb(struct ETCP_CONN* conn, void* arg) {
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) {
etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock,
nodeinfo_find_by_id(entry->mgr->instance->bgp, entry->node_id)->node.public_key,
&ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 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 ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* local = bgp->local_node;
if (!local) { 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;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(local, &addrs);
int meta_count = get_node_v4_sockets_meta(local, &metas);
uint8_t direct_count = 0;
struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8];
for (int i = 0; i < addr_count && direct_count < 8; i++) {
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].config_type == CFG_SERVER_TYPE_PUBLIC || metas[j].config_type == CFG_SERVER_TYPE_UNKNOWN
|| metas[j].nat_type == NAT_VERIFIED_EIM || metas[j].nat_type == NAT_VERIFIED_DIRECT)) {
out_addrs[direct_count].type = addrs[i].type;
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
out_addrs[direct_count].port = htons(addrs[i].port);
out_addrs[direct_count].socket_id = addrs[i].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(pkt_size);
if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
memcpy(qe->data, pkt, pkt_size);
qe->len = (uint16_t)pkt_size;
u_free(pkt);
etcp_route_send(entry->mgr->instance, 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_ready_cb(struct ETCP_CONN* conn, void* arg) {
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",
(unsigned long long)entry->node_id);
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(sizeof(req));
if (!qe) return CONN_MGR_ERR_INTERNAL;
memcpy(qe->data, &req, sizeof(req));
etcp_route_send(entry->mgr->instance, 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 NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, 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 NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, all[i].node_id);
if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq);
}
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(entry->mgr->instance, 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 NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, 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 NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, 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;
}
}
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 NODEINFO_Q* nq = nodeinfo_find_by_id(conn->instance->bgp, src_node_id);
if (nq) sc_set_peer_public_key(&new_conn->crypto_ctx, nq->node.public_key, 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_set_ready_cbk(new_conn, cm_reverse_ready_cb, rp); 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 NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, 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 NODEINFO_Q* nq2 = nodeinfo_find_by_id(mgr->instance->bgp, 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(resp_size);
if (qe) {
memcpy(qe->data, &resp, resp_size);
etcp_route_send(mgr->instance, 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_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp || !bgp->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 = bgp->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 = bgp->nodes->head;
while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; }
if (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
uint64_t node_id = nq->node.node_id;
if (node_id != mgr->instance->node_id) {
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(bgp, 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) {
etcp_send_ping(mgr->instance, nq->node.public_key, NULL, CONN_PROBE_TIMEOUT_MS,
NULL, NULL, NULL, 0);
}
}
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 NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, 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 = route_bgp_find_conn_for_node(mgr->instance->bgp, 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)
etcp_send_ping(mgr->instance, nq->node.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");
}