From 910cac8dcec9d99c89c8103ac49e762a40a16264 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 17 Jul 2026 19:49:54 +0300 Subject: [PATCH] refactor: separate NAT detection from topology into standalone nat_detection module - Extract NAT detection logic (~250 lines) from topo_group.c into new nat_detection.c - NAT_DETECTION struct owns ping_pending, next_ping_req_id, allow_nat_check_local (removed from TOPO_GROUP) - route_ping.* signatures changed: TOPO_GROUP* -> NAT_DETECTION* (+ TOPO_GROUP* where needed) - topo_group.c now only dispatches BGP subcommands to nat_detection_* handlers - UTUN_INSTANCE gets struct NAT_DETECTION* nat_det field (init/destroy in topo_groups_init/destroy) - External callers (etcp_connections, etcp, control_server) call nat_detection_* directly - nat_detection_cancel_for_conn / nat_detection_send_nat_info / nat_detection_link_ready / nat_detection_request_check_all - route_ping_destroy_pending properly implemented (was dead code) - TOPO_SUBCMD_PING_REQ/RESP moved from route_ping.h to topo_group.h (all BGP subcommands in one place) - Tests updated: test_nat_detection, test_route_ping - both PASSED - Build: auto(CORE_SOURCES+libutun) and cmake(chatgui/libutun) --- src/Makefile.am | 2 + src/control_server.c | 5 +- src/etcp.c | 4 +- src/etcp_connections.c | 14 +- src/nat_detection.c | 363 ++++++++++++++++++++++++++ src/nat_detection.h | 115 ++++++++ src/route_ping.c | 87 +++--- src/route_ping.h | 34 ++- src/topo_group.c | 167 +++--------- src/topo_group.h | 52 +--- src/topo_node.h | 6 + src/topo_node_sqlite.c | 49 +++- src/utun_instance.h | 2 + tests/test_nat_detection.c | 5 +- tests/test_route_ping.c | 2 +- tools/chatgui/CMakeLists.txt | 1 + tools/chatgui/db/db_manager.cpp | 6 +- tools/chatgui/db/db_manager.h | 2 +- tools/chatgui/libutun/CMakeLists.txt | 1 + tools/chatgui/src/accountlist.cpp | 127 ++++++++- tools/chatgui/src/accountlist.h | 8 + tools/chatgui/src/memberlistmodel.cpp | 5 + tools/chatgui/src/memberlistmodel.h | 1 + tools/chatgui/src/nodespage.cpp | 228 ++++++++++++++++ tools/chatgui/src/nodespage.h | 29 ++ tools/chatgui/src/settingsdialog.cpp | 9 +- tools/chatgui/src/settingsdialog.h | 2 + tools/chatgui/transport/chat_core.c | 111 ++++++-- tools/chatgui/transport/chat_core.h | 1 + tools/chatgui/transport/chat_sync.c | 8 +- tools/chatgui/transport/member_sync.c | 8 +- 31 files changed, 1166 insertions(+), 288 deletions(-) create mode 100644 src/nat_detection.c create mode 100644 src/nat_detection.h create mode 100644 tools/chatgui/src/nodespage.cpp create mode 100644 tools/chatgui/src/nodespage.h diff --git a/src/Makefile.am b/src/Makefile.am index e4a09e34..fd0a3cb2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ utun_CORE_SOURCES = \ route6_lib.c \ topo_group.c \ route_ping.c \ + nat_detection.c \ topo_node.c \ topo_node_sqlite.c \ route_connectivity.c \ @@ -66,6 +67,7 @@ libutun_a_SOURCES = \ route6_lib.c \ topo_group.c \ route_ping.c \ + nat_detection.c \ topo_node.c \ topo_node_sqlite.c \ route_connectivity.c \ diff --git a/src/control_server.c b/src/control_server.c index 1c206492..1ddcb0ba 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -11,6 +11,7 @@ #include "tun_if.h" #include "route_lib.h" #include "topo_group.h" +#include "nat_detection.h" #include "etcp_router.h" #include "topo_node.h" #include "pkt_normalizer.h" @@ -784,7 +785,9 @@ static void handle_client_data(struct control_server* server, struct control_cli struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload; if (strncmp(cmd->action, "nat", 3) == 0) { DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links"); - topo_group_request_nat_check_all(topo_groups_get_default(server->instance->topo_groups)); + struct TOPO_GROUP* g = topo_groups_get_default(server->instance->topo_groups); + if (server->instance->nat_det && g) + nat_detection_request_check_all(server->instance->nat_det, g); } else if (strncmp(cmd->action, "nodes", 5) == 0) { DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes"); uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024; diff --git a/src/etcp.c b/src/etcp.c index 0772375e..99527832 100644 --- a/src/etcp.c +++ b/src/etcp.c @@ -545,8 +545,8 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме etcp->initialized = 0;// еще раз придёт conn_ready_callback // Отменяем висящие NAT-ping'и для этого соединения - if (etcp->instance && etcp->instance->topo_groups) { - route_ping_cancel_for_conn(topo_groups_get_default(etcp->instance->topo_groups), etcp); + if (etcp->instance && etcp->instance->nat_det) { + nat_detection_cancel_for_conn(etcp->instance->nat_det, etcp); } // Сбрасываем ретрансмиты роутера ДО очистки ETCP-очередей (избегаем гонки с data_pool) diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 42464911..b394c3d3 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -19,7 +19,7 @@ #include "stcp_link.h" #include "topo_node.h" #include "topo_group.h" -#include "route_ping.h" +#include "nat_detection.h" #include "../lib/memory_pool.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" @@ -1298,9 +1298,10 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk { link->nat_type = NAT_TYPE_DIRECT; link->nat_check_status = NAT_CHECK_EIM; - if (link->etcp->instance->topo_groups) { - topo_group_send_nat_info(link->etcp, link->remote_socket_id, - link->nat_ip, link->nat_port, NAT_TYPE_DIRECT); + if (link->etcp->instance->nat_det) { + nat_detection_send_nat_info(link->etcp->instance->nat_det, link->etcp, + link->remote_socket_id, + link->nat_ip, link->nat_port, NAT_TYPE_DIRECT); } DEBUG_INFO(DEBUG_CATEGORY_BGP, "DIRECT IP: %s:%u for %s", ip_to_str(&link->nat_ip, AF_INET).str, link->nat_port, @@ -1381,7 +1382,7 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk loadbalancer_link_ready(link); // Restart NAT check after link is up (e.g. after address change or reinit) { struct TOPO_GROUP* g = topo_groups_get_default(link->etcp->instance->topo_groups); - if (g && link->nat_check_status < NAT_CHECK_IN_PROGRESS) topo_group_start_link_nat_check(g, link); } + if (link->etcp->instance->nat_det && g) nat_detection_link_ready(link->etcp->instance->nat_det, g, link); } } static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, uint8_t pkt_code, size_t pkt_len) { @@ -1752,8 +1753,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { goto ec_fr; } // Cancel pending NAT check and reset status - { struct TOPO_GROUP* g = topo_groups_get_default(link->etcp->instance->topo_groups); - if (g) route_ping_cancel_for_conn(g, link->etcp); } + nat_detection_cancel_for_conn(link->etcp->instance->nat_det, link->etcp); link->nat_check_status = NAT_CHECK_NONE; link->nat_type = NAT_TYPE_UNKNOWN; } diff --git a/src/nat_detection.c b/src/nat_detection.c new file mode 100644 index 00000000..b7f2e5e1 --- /dev/null +++ b/src/nat_detection.c @@ -0,0 +1,363 @@ +/** + * @file nat_detection.c + * @brief NAT-детекция: STUN-like проверка через третьего BGP-пира. + */ +#include +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#endif +#include "../lib/platform_compat.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include "utun_instance.h" +#include "etcp_api.h" +#include "etcp.h" +#include "etcp_connections.h" +#include "config_parser.h" +#include "topo_node.h" +#include "topo_group.h" +#include "route_ping.h" +#include "nat_detection.h" +#include "../lib/u_async.h" + + +/* ====== Internal NAT check logic ====== */ + +static struct ETCP_CONN* nat_check_find_third_node(struct TOPO_GROUP* group, struct ETCP_CONN* exclude) { + if (!group || !group->senders_list) return NULL; + struct ll_entry* e = group->senders_list->head; + while (e) { + struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; + if (item && item->conn && item->conn != exclude && item->conn->links + && item->conn->links->remote_only_local == 0 + && item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE + && item->conn->links->remote_type != CFG_SERVER_TYPE_LOCAL) + return item->conn; + e = e->next; + } + return NULL; +} + +static void nat_link_check_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { + (void)avg_rtt; (void)count_sent; (void)count_ok; + struct nat_check_arg* na = (struct nat_check_arg*)arg; + if (!na || !na->link) { u_free(na); return; } + struct ETCP_LINK* link = na->link; + if (link->nat_check_status != NAT_CHECK_IN_PROGRESS) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "nat check cb: link status=%d, skip (conn down?)", link->nat_check_status); + u_free(na); return; + } + link->nat_type = success ? NAT_TYPE_EIM : NAT_TYPE_STRICT; + link->nat_check_status = success ? NAT_CHECK_EIM : NAT_CHECK_STRICT; + if (link->etcp) + nat_detection_send_nat_info(link->etcp->instance->nat_det, + link->etcp, link->remote_socket_id, + na->nat_ip, na->nat_port, link->nat_type); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT check result: type=%s for conn=%s nat=%s:%u", + success ? "EIM" : "STRICT", link->etcp->log_name, + ip_to_str(&na->nat_ip, AF_INET).str, na->nat_port); + u_free(na); +} + +static void nat_detection_start_link_check(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_LINK* link) { + if (!nd || !group || !link || !link->conn || !link->etcp) return; + if (link->nat_type == NAT_TYPE_DIRECT) return; + if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; + if (link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private/local socket link"); return; + } + struct ETCP_CONN* third_conn = nat_check_find_third_node(group, link->etcp); + if (!third_conn) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no third node found for link"); return; } + + uint32_t target_ip; uint16_t target_port; + if (link->nat_ip != 0 && link->nat_port != 0) { + target_ip = link->nat_ip; target_port = link->nat_port; + } else { + struct sockaddr_in* sin = (struct sockaddr_in*)&link->remote_addr; + target_ip = sin->sin_addr.s_addr; target_port = ntohs(sin->sin_port); + } + if (!nd->allow_nat_check_local && is_local_subnet(target_ip)) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local connection: %s, skip nat check", + ip_to_str(&target_ip, AF_INET).str); return; + } + + struct nat_check_arg* arg = u_calloc(1, sizeof(struct nat_check_arg)); + if (!arg) return; + arg->link = link; arg->nat_ip = target_ip; arg->nat_port = target_port; + const uint8_t* pubkey = link->etcp->crypto_ctx.peer_key_set ? link->etcp->crypto_ctx.peer_public_key : NULL; + if (!pubkey) { u_free(arg); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no peer pubkey for link=%p", (void*)link); return; } + int ret = route_ping_send_req_addr(nd, group, third_conn, target_ip, target_port, + 3, 500, 1000, 5000, nat_link_check_cb, arg, pubkey); + if (ret == 0) { + link->nat_check_status = NAT_CHECK_IN_PROGRESS; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping remote request via=%s (%s:%d)", + third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); + } else { + u_free(arg); + DEBUG_WARN(DEBUG_CATEGORY_BGP, "failed to start for link=%s (%s:%d)", + third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); + } +} + + +/* ====== Lifecycle ====== */ + +struct NAT_DETECTION* nat_detection_create(struct UTUN_INSTANCE* inst) { + if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nat_detection_create: inst is NULL"); return NULL; } + struct NAT_DETECTION* nd = u_calloc(1, sizeof(struct NAT_DETECTION)); + if (!nd) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nat_detection_create: alloc failed"); return NULL; } + nd->inst = inst; + nd->next_ping_req_id = 1; + nd->allow_nat_check_local = 0; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection initialized"); + return nd; +} + +void nat_detection_destroy(struct NAT_DETECTION* nd) { + if (!nd) return; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection destroy"); + route_ping_destroy_pending(nd); + u_free(nd); +} + + +/* ====== BGP peer up/down ====== */ + +void nat_detection_bgp_peer_up(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* conn) { + if (!nd || !group || !conn || !conn->instance) return; + struct ll_entry* entry = conn->instance->connections->head; + while (entry) { + struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; + struct ETCP_LINK* l = ce->conn->links; + while (l) { + if (l->initialized && l->conn && l->nat_check_status < NAT_CHECK_IN_PROGRESS) + nat_detection_start_link_check(nd, group, l); + l = l->next; + } + entry = entry->next; + } +} + +void nat_detection_bgp_peer_down(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* conn) { + if (!nd || !group || !conn) return; + route_ping_cancel_for_conn(nd, conn); + struct ETCP_LINK* lnk = conn->links; + while (lnk) { + if (lnk->nat_check_status == NAT_CHECK_IN_PROGRESS) lnk->nat_check_status = NAT_CHECK_NONE; + lnk = lnk->next; + } +} + + +/* ====== Link lifecycle ====== */ + +void nat_detection_link_ready(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_LINK* link) { + if (!nd || !group || !link) return; + if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) + nat_detection_start_link_check(nd, group, link); +} + +void nat_detection_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) { + if (!nd || !conn) return; + route_ping_cancel_for_conn(nd, conn); +} + + +/* ====== Send NAT messages ====== */ + +void nat_detection_send_nat_info(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, + uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, + uint8_t nat_type) { + (void)nd; + if (!to_conn) return; + struct TOPOMSG_NAT_INFO* pkt = u_calloc(1, sizeof(struct TOPOMSG_NAT_INFO)); + if (!pkt) return; + pkt->cmd = ETCP_ID_TOPO_ENTRY; pkt->subcmd = TOPO_SUBCMD_NAT_INFO; + pkt->socket_id = socket_id; + pkt->nat_ip[0] = (nat_ip >> 24) & 0xFF; pkt->nat_ip[1] = (nat_ip >> 16) & 0xFF; + pkt->nat_ip[2] = (nat_ip >> 8) & 0xFF; pkt->nat_ip[3] = nat_ip & 0xFF; + pkt->nat_port = htons(nat_port); pkt->nat_type = nat_type; + struct ll_entry* e = queue_entry_new(0); + if (!e) { u_free(pkt); return; } + e->dgram = (uint8_t*)pkt; e->len = sizeof(struct TOPOMSG_NAT_INFO); + if (etcp_send(to_conn, e) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_INFO failed"); + u_free(pkt); queue_entry_free(e); + } +} + +void nat_detection_send_nat_check_req(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, + uint8_t socket_id) { + (void)nd; + if (!to_conn) return; + struct TOPOMSG_NAT_CHECK_REQ* pkt = u_calloc(1, sizeof(struct TOPOMSG_NAT_CHECK_REQ)); + if (!pkt) return; + pkt->cmd = ETCP_ID_TOPO_ENTRY; pkt->subcmd = TOPO_SUBCMD_NAT_CHECK_REQ; pkt->socket_id = socket_id; + struct ETCP_LINK* l = to_conn->links; + while (l) { + if (l->remote_socket_id == socket_id) { + struct sockaddr_in* sin = (struct sockaddr_in*)&l->conn->interface_addr; + if (sin->sin_family == AF_INET) { + pkt->interface_ip = sin->sin_addr.s_addr; + pkt->interface_port = sin->sin_port; + } break; + } + l = l->next; + } + struct ll_entry* e = queue_entry_new(0); + if (!e) { u_free(pkt); return; } + e->dgram = (uint8_t*)pkt; e->len = sizeof(struct TOPOMSG_NAT_CHECK_REQ); + if (etcp_send(to_conn, e) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_CHECK_REQ failed"); + u_free(pkt); queue_entry_free(e); + } +} + + +/* ====== Handle incoming NAT messages ====== */ + +void nat_detection_handle_nat_info(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len) { + if (!nd || !group || !from_conn || !data || len < sizeof(struct TOPOMSG_NAT_INFO) + || !group->local_node || !group->local_node->node) return; + + const struct TOPOMSG_NAT_INFO* info = (const struct TOPOMSG_NAT_INFO*)data; + uint32_t nat_ip = (info->nat_ip[0] << 24) | (info->nat_ip[1] << 16) + | (info->nat_ip[2] << 8) | info->nat_ip[3]; + uint16_t nat_port = ntohs(info->nat_port); + uint8_t socket_id = info->socket_id; + + uint8_t verified_type; + if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM; + else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT; + else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT; + else verified_type = NAT_VERIFIED_UNKNOWN; + + int data_changed = 0; + { + struct TOPO_SOCKMETA4* sm = group->local_node->node->v4_sock_meta; + while (sm) { + if (sm->id == socket_id) { + if (sm->nat_type != verified_type) { sm->nat_type = verified_type; data_changed = 1; } + break; + } + sm = sm->next; + } + } + + struct ETCP_SOCKET* es = group->instance->etcp_sockets; + while (es) { + if (es->sock_id == socket_id) { + if (verified_type == NAT_VERIFIED_STRICT) { + struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; + nat_sin->sin_family = AF_INET; + nat_sin->sin_addr.s_addr = nat_ip; + nat_sin->sin_port = htons(nat_port); + es->nat_type = verified_type; + } else { + struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; + uint32_t old_nat_ip = nat_sin->sin_addr.s_addr; + uint16_t old_nat_port = ntohs(nat_sin->sin_port); + nat_sin->sin_family = AF_INET; + nat_sin->sin_addr.s_addr = nat_ip; + nat_sin->sin_port = htons(nat_port); + if (old_nat_ip != nat_ip || old_nat_port != nat_port) data_changed = 1; + es->nat_type = verified_type; + } + break; + } + es = es->next; + } + + if (data_changed) { + int prev_v4a = topo_list_count((struct _topo_head*)group->local_node->node->v4_addrs); + topo_group_update_my_nodeinfo(group->instance, group); + if (topo_list_count((struct _topo_head*)group->local_node->node->v4_addrs) != prev_v4a) { + group->local_node->dirty = 1; + group->local_node->node->ver = (group->local_node->node->ver % 255) + 1; + group->local_node->last_ver = group->local_node->node->ver; + } + if (group->local_node->dirty && group->senders_list) { + struct ll_entry* se = group->senders_list->head; + while (se) { + struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data; + if (item && item->conn) + topo_group_send_nodeinfo(group, group->local_node, item->conn); + se = se->next; + } + } + } + + struct ETCP_LINK* l = from_conn->links; + while (l) { l->nat_type = info->nat_type; l = l->next; } +} + +void nat_detection_handle_nat_check_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len) { + if (!nd || !group || !from_conn || !data || len < sizeof(struct TOPOMSG_NAT_CHECK_REQ)) return; + const struct TOPOMSG_NAT_CHECK_REQ* req = (const struct TOPOMSG_NAT_CHECK_REQ*)data; + uint8_t socket_id = req->socket_id; + struct ETCP_LINK* target_link = NULL; + struct ETCP_LINK* l = from_conn->links; + while (l) { + if (l->remote_socket_id == socket_id) { target_link = l; break; } + l = l->next; + } + if (!target_link) return; + if (target_link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; + nat_detection_start_link_check(nd, group, target_link); +} + + +/* ====== Control API ====== */ + +void nat_detection_request_check_all(struct NAT_DETECTION* nd, struct TOPO_GROUP* group) { + if (!nd || !group || !group->instance) return; + int count = 0; + struct ll_entry* entry = group->instance->connections->head; + while (entry) { + struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; + struct ETCP_LINK* link = ce->conn->links; + while (link) { + if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) + nat_detection_start_link_check(nd, group, link); + count++; link = link->next; + } + entry = entry->next; + } + (void)count; +} + + +/* ====== Test helper ====== */ + +void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow) { + if (!nd) return; + nd->allow_nat_check_local = allow ? 1 : 0; +} + + +/* ====== BGP subcommand wrappers (for topo_group dispatch) ====== */ + +void nat_detection_handle_ping_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len) { + route_ping_handle_req(nd, group, from_conn, data, len); +} + +void nat_detection_handle_ping_resp(struct NAT_DETECTION* nd, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len) { + route_ping_handle_resp(nd, from_conn, data, len); +} diff --git a/src/nat_detection.h b/src/nat_detection.h new file mode 100644 index 00000000..90d57b47 --- /dev/null +++ b/src/nat_detection.h @@ -0,0 +1,115 @@ +/** + * @file nat_detection.h + * @brief NAT-детекция через третьего BGP-пира (STUN-like ping). + * + * Модуль владеет всей логикой определения типа NAT для ETCP-соединений: + * - Запуск NAT-чека при поднятии линка / новом BGP-пире + * - PING_REQ/PING_RESP через route_ping (BGP-пинг через третий узел) + * - NAT_INFO / NAT_CHECK_REQ — уведомление клиента о типе NAT + * - Обновление локальной NODEINFO и broadcast изменений + * + * Отделен от topo_group — топология только диспетчеризует BGP-подкоманды. + */ +#ifndef NAT_DETECTION_H +#define NAT_DETECTION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +struct UTUN_INSTANCE; +struct TOPO_GROUP; +struct ETCP_CONN; +struct ETCP_LINK; +struct route_ping_pending; + +typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, + uint8_t count_sent, uint8_t count_ok, void* arg); + +/** + * @brief Контекст NAT-детекции (один на экземпляр UTUN_INSTANCE) + */ +struct NAT_DETECTION { + struct UTUN_INSTANCE* inst; + struct route_ping_pending* ping_pending; + uint32_t next_ping_req_id; + uint8_t allow_nat_check_local; +}; + +/** Аргумент callback NAT check */ +struct nat_check_arg { + struct ETCP_LINK* link; + uint32_t nat_ip; + uint16_t nat_port; +}; + +/* ====== Lifecycle ====== */ + +struct NAT_DETECTION* nat_detection_create(struct UTUN_INSTANCE* inst); +void nat_detection_destroy(struct NAT_DETECTION* nd); + +/* ====== BGP peer up/down (called from topo_group) ====== */ + +void nat_detection_bgp_peer_up(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* conn); +void nat_detection_bgp_peer_down(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* conn); + +/* ====== Link lifecycle (called from etcp_connections) ====== */ + +void nat_detection_link_ready(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_LINK* link); +void nat_detection_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); + +/* ====== Send NAT messages (called from etcp_connections for DIRECT detection) ====== */ + +void nat_detection_send_nat_info(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, + uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, + uint8_t nat_type); +void nat_detection_send_nat_check_req(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, + uint8_t socket_id); + +/* ====== BGP subcommand handlers (dispatched from topo_group_receive_cbk) ====== */ + +void nat_detection_handle_ping_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len); +void nat_detection_handle_ping_resp(struct NAT_DETECTION* nd, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len); +void nat_detection_handle_nat_info(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len); +void nat_detection_handle_nat_check_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len); + +/* ====== Control API ====== */ + +void nat_detection_request_check_all(struct NAT_DETECTION* nd, struct TOPO_GROUP* group); + +/* ====== Test helpers ====== */ + +void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow); + +/* ====== route_ping wrapper (for test compatibility) ====== */ + +#include "secure_channel.h" + +int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* to_conn, + uint32_t target_ip, uint16_t target_port, + uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, + uint16_t wait_timeout_ms, route_ping_callback_t cb, + void* arg, const uint8_t* pubkey); + +void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); +void route_ping_destroy_pending(struct NAT_DETECTION* nd); + +#ifdef __cplusplus +} +#endif +#endif /* NAT_DETECTION_H */ diff --git a/src/route_ping.c b/src/route_ping.c index 78fd0755..dd6dfe81 100644 --- a/src/route_ping.c +++ b/src/route_ping.c @@ -36,7 +36,7 @@ struct route_ping_series_ctx { struct route_ping_pending { struct route_ping_pending* next; - struct TOPO_GROUP* group; + struct NAT_DETECTION* nd; uint32_t request_id; route_ping_callback_t callback; void* arg; @@ -54,8 +54,8 @@ static void route_ping_pending_timeout(void* arg) { u_free(p); return; } - struct TOPO_GROUP* group = p->group; - struct route_ping_pending** cur = &group->ping_pending; + struct NAT_DETECTION* nd = p->nd; + struct route_ping_pending** cur = &nd->ping_pending; while (*cur) { if (*cur == p) { *cur = p->next; @@ -70,8 +70,9 @@ static void route_ping_pending_timeout(void* arg) { } // прошел ответ "серия пигнов на удаленном узле завершена" -void route_ping_handle_resp(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) { - if (!group || !from_conn || !data || len < sizeof(struct TOPO_PING_RESP)) { +void route_ping_handle_resp(struct NAT_DETECTION* nd, struct ETCP_CONN* from_conn, + const uint8_t* data, size_t len) { + if (!nd || !from_conn || !data || len < sizeof(struct TOPO_PING_RESP)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return; } @@ -80,19 +81,19 @@ void route_ping_handle_resp(struct TOPO_GROUP* group, struct ETCP_CONN* from_con from_conn->log_name, (unsigned)resp->request_id, (unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt); - struct route_ping_pending** cur = &group->ping_pending; + struct route_ping_pending** cur = &nd->ping_pending; while (*cur) { struct route_ping_pending* p = *cur; if (p->request_id == resp->request_id) { *cur = p->next; if (p->timeout_timer) { - uasync_cancel_timeout(group->instance->ua, p->timeout_timer); + uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); p->timeout_timer = NULL; } - if (p->callback) { - int success = (resp->count_ok > 0) ? 1 : 0; - p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg); - } + if (p->callback) { + int success = (resp->count_ok > 0) ? 1 : 0; + p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg); + } u_free(p); return; } @@ -102,10 +103,10 @@ void route_ping_handle_resp(struct TOPO_GROUP* group, struct ETCP_CONN* from_con (unsigned)resp->request_id); } -void route_ping_cancel_for_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { - if (!group || !conn) return; +void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) { + if (!nd || !conn) return; - if (!group->ping_pending) { + if (!nd->ping_pending) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: ping_pending is NULL for %s", conn->log_name); return; } @@ -113,7 +114,7 @@ void route_ping_cancel_for_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: checking pending for %s", conn->log_name); int cancelled = 0; - struct route_ping_pending** cur = &group->ping_pending; + struct route_ping_pending** cur = &nd->ping_pending; while (*cur) { struct route_ping_pending* p = *cur; struct nat_check_arg* na = (struct nat_check_arg*)p->arg; @@ -126,7 +127,7 @@ void route_ping_cancel_for_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn if ((na && na->link && na->link->etcp == conn) || p->via_conn == conn) { *cur = p->next; if (p->timeout_timer) { - uasync_cancel_timeout(group->instance->ua, p->timeout_timer); + uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); p->timeout_timer = NULL; } p->cancelled = 1; @@ -146,14 +147,31 @@ void route_ping_cancel_for_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn } } +void route_ping_destroy_pending(struct NAT_DETECTION* nd) { + if (!nd) return; + struct route_ping_pending* p = nd->ping_pending; + while (p) { + struct route_ping_pending* next = p->next; + if (p->timeout_timer) { + uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); + p->timeout_timer = NULL; + } + p->cancelled = 1; + u_free(p); + p = next; + } + nd->ping_pending = NULL; +} + // ======================================================================== -int route_ping_send_req_addr(struct TOPO_GROUP* group, struct ETCP_CONN* to_conn, +int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* to_conn, uint32_t target_ip, uint16_t target_port, uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, const uint8_t* pubkey) { - if (!group || !to_conn || count == 0 || timeout_ms == 0) { + if (!nd || !group || !to_conn || count == 0 || timeout_ms == 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; } @@ -165,7 +183,7 @@ int route_ping_send_req_addr(struct TOPO_GROUP* group, struct ETCP_CONN* to_conn } req_pkt->cmd = ETCP_ID_TOPO_ENTRY; req_pkt->subcmd = TOPO_SUBCMD_PING_REQ; - uint32_t new_req_id = group->next_ping_req_id++; + uint32_t new_req_id = nd->next_ping_req_id++; req_pkt->request_id = new_req_id; req_pkt->count = count; req_pkt->interval_ms = interval_ms; @@ -203,15 +221,16 @@ int route_ping_send_req_addr(struct TOPO_GROUP* group, struct ETCP_CONN* to_conn DEBUG_ERROR(DEBUG_CATEGORY_BGP, "pending alloc failed"); return -5; } - pending->group = group; - pending->request_id = new_req_id; // req_pkt may be freed by etcp_send callback + pending->nd = nd; + pending->request_id = new_req_id; pending->callback = cb; pending->arg = arg; pending->via_conn = to_conn; - pending->next = group->ping_pending; - group->ping_pending = pending; + pending->next = nd->ping_pending; + nd->ping_pending = pending; - pending->timeout_timer = uasync_set_timeout(group->instance->ua, wait_timeout_ms * 10, pending, route_ping_pending_timeout, "route_ping"); + pending->timeout_timer = uasync_set_timeout(nd->inst->ua, wait_timeout_ms * 10, pending, + route_ping_pending_timeout, "route_ping"); return 0; } @@ -303,10 +322,11 @@ static void route_ping_single_cb(int success, } // Обработчик PING_REQ (нам запросили "пропингуй узел и верни результат") -void route_ping_handle_req(struct TOPO_GROUP* group, +void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) { + (void)nd; if (!group || !from_conn || !data || len < offsetof(struct TOPO_PING_REQ, pubkey)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len); return; @@ -340,22 +360,7 @@ void route_ping_handle_req(struct TOPO_GROUP* group, memcpy(&sin->sin_addr.s_addr, req_pkt->target_ipv4, 4); /* network byte order */ sin->sin_port = req_pkt->target_port; /* порт в network byte order */ - /* Если target не указан — можно разрешить из nodeinfo */ - /* - if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) { - struct TOPO_NODEQ* nq = topo_node_find_by_id(bgp, req_pkt->node_id); - if (nq) { - const struct TOPOMSG_ADDR4* addrs; - int count = get_node_v4_addrs(nq, &addrs); - if (count > 0) { - memcpy(&sin->sin_addr.s_addr, addrs[0].addr, 4); - sin->sin_port = addrs[0].port; - } - } - } - */ - - /* Ищем первый IPv4-сокет (как в старом коде) */ + /* Ищем первый IPv4-сокет */ struct ETCP_SOCKET* ls = group->instance->etcp_sockets; while (ls) { if (ls->local_addr.ss_family == AF_INET) { diff --git a/src/route_ping.h b/src/route_ping.h index dff7730c..bb07c924 100644 --- a/src/route_ping.h +++ b/src/route_ping.h @@ -8,10 +8,8 @@ extern "C" { #include #include -#include "topo_group.h" - -#define TOPO_SUBCMD_PING_REQ 0x07 -#define TOPO_SUBCMD_PING_RESP 0x08 +#include "nat_detection.h" +#include "secure_channel.h" struct TOPO_PING_REQ { uint8_t cmd; // ETCP_ID_TOPO_ENTRY @@ -37,26 +35,26 @@ struct TOPO_PING_RESP { uint16_t avg_rtt; // средний RTT в 0.1ms } __attribute__((packed)); -typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg); - // Отправить запрос пинга по произвольному IP:port (используется для NAT-детекции) -// Если target_ip == 0, используется node_id из списка известных узлов // Если pubkey != NULL, он передается в пакете (для пинга без локального nodeinfo) -int route_ping_send_req_addr(struct TOPO_GROUP* group, struct ETCP_CONN* to_conn, - uint32_t target_ip, uint16_t target_port, - uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, - uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, - const uint8_t* pubkey); +int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* to_conn, + uint32_t target_ip, uint16_t target_port, + uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, + uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, + const uint8_t* pubkey); -// Очистить список ожидающих запросов (при уничтожении BGP) -void route_ping_destroy_pending(struct TOPO_GROUP* group); +// Очистить список ожидающих запросов +void route_ping_destroy_pending(struct NAT_DETECTION* nd); // Отменить pending запросы для указанного соединения (при удалении conn) -void route_ping_cancel_for_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn); +void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); -// Обработчики, вызываемые из topo_group_receive_cbk -void route_ping_handle_req(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); -void route_ping_handle_resp(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); +// Обработчики, вызываемые из topo_group_receive_cbk (через nat_detection) +void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, + struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); +void route_ping_handle_resp(struct NAT_DETECTION* nd, + struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); #ifdef __cplusplus diff --git a/src/topo_group.c b/src/topo_group.c index 45f08a21..2aba220c 100644 --- a/src/topo_group.c +++ b/src/topo_group.c @@ -19,7 +19,7 @@ #include "topo_node_sqlite.h" #include "route_lib.h" #include "topo_group.h" -#include "route_ping.h" +#include "nat_detection.h" #include "route_connectivity.h" #include "control_server.h" @@ -145,9 +145,6 @@ static void topo_group_broadcast_withdraw(struct TOPO_GROUP* group, uint64_t nod // Приём пакетов // ============================================================================ -static void topo_group_handle_nat_info(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); -static void topo_group_handle_nat_check_req(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); - static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) { if (!from_conn || !entry || entry->len < 2) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; } struct UTUN_INSTANCE* instance = from_conn->instance; @@ -162,10 +159,22 @@ static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* if (subcmd == TOPO_SUBCMD_NODEINFO) { nodeinfo_dump_log(data, entry->len); topo_group_process_nodeinfo(group, from_conn, data, entry->len); } else if (subcmd == TOPO_SUBCMD_WITHDRAW) topo_group_process_withdraw(group, from_conn, data, entry->len); else if (subcmd == TOPO_SUBCMD_REQUEST_TABLE) topo_group_handle_request_table(group, from_conn); - else if (subcmd == TOPO_SUBCMD_PING_REQ) route_ping_handle_req(group, from_conn, data, entry->len); - else if (subcmd == TOPO_SUBCMD_PING_RESP) route_ping_handle_resp(group, from_conn, data, entry->len); - else if (subcmd == TOPO_SUBCMD_NAT_INFO) topo_group_handle_nat_info(group, from_conn, data, entry->len); - else if (subcmd == TOPO_SUBCMD_NAT_CHECK_REQ) topo_group_handle_nat_check_req(group, from_conn, data, entry->len); + else if (subcmd == TOPO_SUBCMD_PING_REQ) { + struct NAT_DETECTION* nd = group->instance->nat_det; + if (nd) nat_detection_handle_ping_req(nd, group, from_conn, data, entry->len); + } + else if (subcmd == TOPO_SUBCMD_PING_RESP) { + struct NAT_DETECTION* nd = group->instance->nat_det; + if (nd) nat_detection_handle_ping_resp(nd, from_conn, data, entry->len); + } + else if (subcmd == TOPO_SUBCMD_NAT_INFO) { + struct NAT_DETECTION* nd = group->instance->nat_det; + if (nd) nat_detection_handle_nat_info(nd, group, from_conn, data, entry->len); + } + else if (subcmd == TOPO_SUBCMD_NAT_CHECK_REQ) { + struct NAT_DETECTION* nd = group->instance->nat_det; + if (nd) nat_detection_handle_nat_check_req(nd, group, from_conn, data, entry->len); + } else if (subcmd == TOPO_SUBCMD_TABLE_COMPLETE) { etcp_set_routing_exchange_state(from_conn, 3); DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP initial sync complete with %s", from_conn->log_name); } else if (subcmd == TOPO_SUBCMD_ERR_GROUP_MISMATCH) { if (entry->len >= sizeof(struct TOPOMSG_ERR_GROUP_MISMATCH)) { @@ -216,7 +225,6 @@ static struct TOPO_GROUP* topo_group_create(struct UTUN_INSTANCE* instance, uint group->group_id = group_id; group->group_type = group_type; group->instance = instance; - group->next_ping_req_id = 1; sc_derive_ed25519_pubkey(instance->my_keys.private_key, group->ed25519_public_key); DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key"); @@ -281,6 +289,11 @@ struct TOPO_GROUPS* topo_groups_init(struct UTUN_INSTANCE* instance) { instance->topo_groups = g; + g->instance->nat_det = nat_detection_create(instance); + if (!g->instance->nat_det) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection creation failed"); + } + struct TOPO_GROUP* default_group = topo_group_create(instance, TOPO_GROUP_UTUN, TOPO_GROUP_TYPE_UTUN); if (!default_group) { memory_pool_destroy(g->v4_sock_meta_pool); memory_pool_destroy(g->v4_addr_pool); @@ -307,6 +320,8 @@ void topo_groups_destroy(struct UTUN_INSTANCE* instance) { etcp_unbind(instance, ETCP_ID_TOPO_ENTRY); route_connectivity_cancel_all(instance); + if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; } + struct TOPO_GROUPS* g = instance->topo_groups; struct ll_entry* ge; while ((ge = queue_data_get(g->group_list)) != NULL) { struct TOPO_GROUP* grp = (struct TOPO_GROUP*)ge; topo_group_destroy(grp); queue_entry_free(ge); } @@ -351,7 +366,10 @@ void topo_groups_set_sqlite_db(struct TOPO_GROUPS* g, sqlite3* db) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_sqlite_db set"); } -void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow) { if (!group) return; group->allow_nat_check_local = allow ? 1 : 0; } +void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow) { + if (!group || !group->instance || !group->instance->nat_det) return; + nat_detection_set_allow_local(group->instance->nat_det, allow); +} void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updated_fn fn) { if (groups) groups->node_updated_cb = fn; @@ -368,8 +386,7 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s alien=%d", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id, peer_nq ? peer_nq->alien : -1); - struct ll_entry* entry = conn->instance->connections->head; - while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_LINK* l = ce->conn->links; while (l) { if (l->initialized && l->conn && l->nat_check_status < NAT_CHECK_IN_PROGRESS) topo_group_start_link_nat_check(group, l); l = l->next; } entry = entry->next; } + if (group->instance->nat_det) nat_detection_bgp_peer_up(group->instance->nat_det, group, conn); topo_group_send_table_request(group, conn); } @@ -385,8 +402,7 @@ void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "peer=%016llx", (unsigned long long)conn->peer_node_id); - route_ping_cancel_for_conn(group, conn); - struct ETCP_LINK* lnk = conn->links; while (lnk) { if (lnk->nat_check_status == NAT_CHECK_IN_PROGRESS) lnk->nat_check_status = NAT_CHECK_NONE; lnk = lnk->next; } + nat_detection_bgp_peer_down(group->instance->nat_det, group, conn); struct ROUTE_TABLE* rt = conn->instance->rt; int nodes_removed = 0; @@ -476,50 +492,6 @@ int topo_group_remove_path(struct TOPO_NODEQ* nq, struct ETCP_CONN* conn) { return 0; } -// NAT check context -static void nat_link_check_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { - (void)avg_rtt; (void)count_sent; (void)count_ok; - struct nat_check_arg* na = (struct nat_check_arg*)arg; - if (!na || !na->link) { u_free(na); return; } - struct ETCP_LINK* link = na->link; - if (link->nat_check_status != NAT_CHECK_IN_PROGRESS) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "nat check cb: link status=%d, skip (conn down?)", link->nat_check_status); u_free(na); return; } - link->nat_type = success ? NAT_TYPE_EIM : NAT_TYPE_STRICT; - link->nat_check_status = success ? NAT_CHECK_EIM : NAT_CHECK_STRICT; - if (link->etcp) topo_group_send_nat_info(link->etcp, link->remote_socket_id, na->nat_ip, na->nat_port, link->nat_type); - DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT check result: type=%s for conn=%s nat=%s:%u", success ? "EIM" : "STRICT", link->etcp->log_name, ip_to_str(&na->nat_ip, AF_INET).str, na->nat_port); - u_free(na); -} - -static struct ETCP_CONN* topo_group_find_third_node(struct TOPO_GROUP* group, struct ETCP_CONN* exclude) { - if (!group || !group->senders_list) return NULL; - struct ll_entry* e = group->senders_list->head; - while (e) { struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data; if (item && item->conn && item->conn != exclude && item->conn->links && item->conn->links->remote_only_local==0 && item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE && item->conn->links->remote_type != CFG_SERVER_TYPE_LOCAL) return item->conn; e = e->next; } - return NULL; -} - -void topo_group_start_link_nat_check(struct TOPO_GROUP* group, struct ETCP_LINK* link) { - if (!group || !link || !link->conn || !link->etcp) return; - if (link->nat_type == NAT_TYPE_DIRECT) return; - if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; - if (link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private/local socket link"); return; } - struct ETCP_CONN* third_conn = topo_group_find_third_node(group, link->etcp); - if (!third_conn) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no third node found for link"); return; } - - uint32_t target_ip; uint16_t target_port; - if (link->nat_ip != 0 && link->nat_port != 0) { target_ip = link->nat_ip; target_port = link->nat_port; } - else { struct sockaddr_in* sin = (struct sockaddr_in*)&link->remote_addr; target_ip = sin->sin_addr.s_addr; target_port = ntohs(sin->sin_port); } - - if (!group->allow_nat_check_local && is_local_subnet(target_ip)) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local connection: %s, skip nat check", ip_to_str(&target_ip, AF_INET).str); return; } - - struct nat_check_arg* arg = u_calloc(1, sizeof(struct nat_check_arg)); - if (!arg) return; arg->link = link; arg->nat_ip = target_ip; arg->nat_port = target_port; - const uint8_t* pubkey = link->etcp->crypto_ctx.peer_key_set ? link->etcp->crypto_ctx.peer_public_key : NULL; - if (!pubkey) { u_free(arg); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no peer pubkey for link=%p", (void*)link); return; } - int ret = route_ping_send_req_addr(group, third_conn, target_ip, target_port, 3, 500, 1000, 5000, nat_link_check_cb, arg, pubkey); - if (ret == 0) { link->nat_check_status = NAT_CHECK_IN_PROGRESS; DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping remote request via=%s (%s:%d)", third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); } - else { u_free(arg); DEBUG_WARN(DEBUG_CATEGORY_BGP, "failed to start for link=%s (%s:%d)", third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); } -} - // ===== NODEINFO process ===== static void topo_group_send_err_group_mismatch(struct TOPO_GROUP* group, struct ETCP_CONN* conn, uint8_t expected_type, uint8_t received_flags) { @@ -769,85 +741,6 @@ static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETC topo_group_send_table_complete(group, conn); } -static void topo_group_handle_nat_info(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) { - if (!group || !from_conn || !data || len < sizeof(struct TOPOMSG_NAT_INFO) || !group->local_node || !group->local_node->node) return; - - const struct TOPOMSG_NAT_INFO* info = (const struct TOPOMSG_NAT_INFO*)data; - uint32_t nat_ip = (info->nat_ip[0] << 24) | (info->nat_ip[1] << 16) | (info->nat_ip[2] << 8) | info->nat_ip[3]; - uint16_t nat_port = ntohs(info->nat_port); uint8_t socket_id = info->socket_id; - - uint8_t verified_type; - if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM; - else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT; - else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT; - else verified_type = NAT_VERIFIED_UNKNOWN; - - int data_changed = 0; - { struct TOPO_SOCKMETA4* sm = group->local_node->node->v4_sock_meta; while (sm) { if (sm->id == socket_id) { if (sm->nat_type != verified_type) { sm->nat_type = verified_type; data_changed = 1; } break; } sm = sm->next; } } - - struct ETCP_SOCKET* es = group->instance->etcp_sockets; - while (es) { if (es->sock_id == socket_id) { - if (verified_type == NAT_VERIFIED_STRICT) { struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; nat_sin->sin_family = AF_INET; nat_sin->sin_addr.s_addr = nat_ip; nat_sin->sin_port = htons(nat_port); es->nat_type = verified_type; } - else { struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; uint32_t old_nat_ip = nat_sin->sin_addr.s_addr; uint16_t old_nat_port = ntohs(nat_sin->sin_port); nat_sin->sin_family = AF_INET; nat_sin->sin_addr.s_addr = nat_ip; nat_sin->sin_port = htons(nat_port); if (old_nat_ip != nat_ip || old_nat_port != nat_port) data_changed = 1; es->nat_type = verified_type; } break; } - es = es->next; } - - if (data_changed) { - int prev_v4a = topo_list_count((struct _topo_head*)group->local_node->node->v4_addrs); - topo_group_update_my_nodeinfo(group->instance, group); - if (topo_list_count((struct _topo_head*)group->local_node->node->v4_addrs) != prev_v4a) { - group->local_node->dirty = 1; - group->local_node->node->ver = (group->local_node->node->ver % 255) + 1; - group->local_node->last_ver = group->local_node->node->ver; - } - if (group->local_node->dirty && group->senders_list) { - struct ll_entry* se = group->senders_list->head; - while (se) { struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data; if (item && item->conn) topo_group_send_nodeinfo(group, group->local_node, item->conn); se = se->next; } - } - } - - struct ETCP_LINK* l = from_conn->links; while (l) { l->nat_type = info->nat_type; l = l->next; } -} - -static void topo_group_handle_nat_check_req(struct TOPO_GROUP* group, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) { - if (!group || !from_conn || !data || len < sizeof(struct TOPOMSG_NAT_CHECK_REQ)) return; - const struct TOPOMSG_NAT_CHECK_REQ* req = (const struct TOPOMSG_NAT_CHECK_REQ*)data; - uint8_t socket_id = req->socket_id; - struct ETCP_LINK* target_link = NULL; - struct ETCP_LINK* l = from_conn->links; while (l) { if (l->remote_socket_id == socket_id) { target_link = l; break; } l = l->next; } - if (!target_link) return; - if (target_link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; - topo_group_start_link_nat_check(group, target_link); -} - -void topo_group_send_nat_info(struct ETCP_CONN* conn, uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type) { - if (!conn) return; - struct TOPOMSG_NAT_INFO* pkt = u_calloc(1, sizeof(struct TOPOMSG_NAT_INFO)); if (!pkt) return; - pkt->cmd = ETCP_ID_TOPO_ENTRY; pkt->subcmd = TOPO_SUBCMD_NAT_INFO; - pkt->socket_id = socket_id; - pkt->nat_ip[0] = (nat_ip >> 24) & 0xFF; pkt->nat_ip[1] = (nat_ip >> 16) & 0xFF; - pkt->nat_ip[2] = (nat_ip >> 8) & 0xFF; pkt->nat_ip[3] = nat_ip & 0xFF; - pkt->nat_port = htons(nat_port); pkt->nat_type = nat_type; - struct ll_entry* e = queue_entry_new(0); if (!e) { u_free(pkt); return; } - e->dgram = (uint8_t*)pkt; e->len = sizeof(struct TOPOMSG_NAT_INFO); - if (etcp_send(conn, e) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_INFO failed"); u_free(pkt); queue_entry_free(e); } -} - -void topo_group_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id) { - if (!conn) return; - struct TOPOMSG_NAT_CHECK_REQ* pkt = u_calloc(1, sizeof(struct TOPOMSG_NAT_CHECK_REQ)); if (!pkt) return; - pkt->cmd = ETCP_ID_TOPO_ENTRY; pkt->subcmd = TOPO_SUBCMD_NAT_CHECK_REQ; pkt->socket_id = socket_id; - struct ETCP_LINK* l = conn->links; while (l) { if (l->remote_socket_id == socket_id) { struct sockaddr_in* sin = (struct sockaddr_in*)&l->conn->interface_addr; if (sin->sin_family == AF_INET) { pkt->interface_ip = sin->sin_addr.s_addr; pkt->interface_port = sin->sin_port; } break; } l = l->next; } - struct ll_entry* e = queue_entry_new(0); if (!e) { u_free(pkt); return; } - e->dgram = (uint8_t*)pkt; e->len = sizeof(struct TOPOMSG_NAT_CHECK_REQ); - if (etcp_send(conn, e) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_CHECK_REQ failed"); u_free(pkt); queue_entry_free(e); } -} - -void topo_group_request_nat_check_all(struct TOPO_GROUP* group) { - if (!group || !group->instance) return; - int count = 0; - struct ll_entry* entry = group->instance->connections->head; while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_LINK* link = ce->conn->links; while (link) { if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) topo_group_start_link_nat_check(group, link); count++; link = link->next; } entry = entry->next; } -} - void topo_group_send_withdraw(struct TOPO_GROUP* group, uint64_t node_id) { if (!group) return; DEBUG_INFO(DEBUG_CATEGORY_BGP, "node=%016llx", (unsigned long long)node_id); diff --git a/src/topo_group.h b/src/topo_group.h index bb8434ff..b1dddcdb 100644 --- a/src/topo_group.h +++ b/src/topo_group.h @@ -15,7 +15,7 @@ * - Каждая группа изолирована: узлы из utun-группы не видны в чат-группе и наоборот * * Дополнительные функции: - * - NAT-детекция и проверка связности (NAT_INFO, NAT_CHECK_REQ, PING) + * - NAT-детекция: выделена в отдельный модуль nat_detection.h * - Поиск оптимального маршрута до узла (topo_group_find_conn_for_node) * * Хранение: все узлы сохраняются в SQLite (таблицы nodes, node_addresses, @@ -40,6 +40,7 @@ extern "C" { #include "secure_channel.h" struct UTUN_INSTANCE; +struct NAT_DETECTION; /** Callback when a node's info (pubkeys, addresses) is persisted in the DB */ typedef void (*topo_node_updated_fn)(struct UTUN_INSTANCE* inst, uint64_t node_id, @@ -54,6 +55,8 @@ typedef void (*topo_node_updated_fn)(struct UTUN_INSTANCE* inst, uint64_t node_i #define TOPO_SUBCMD_NODEINFO 0x04 // полная информация об узле + подсети #define TOPO_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы #define TOPO_SUBCMD_WITHDRAW 0x06 // узел стал недоступен +#define TOPO_SUBCMD_PING_REQ 0x07 // запрос удаленного пинга +#define TOPO_SUBCMD_PING_RESP 0x08 // ответ на удаленный пинг #define TOPO_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента #define TOPO_SUBCMD_NAT_CHECK_REQ 0x0A // запрос от клиента на проверку NAT для сокета #define TOPO_SUBCMD_TABLE_COMPLETE 0x0B // завершение начальной синхронизации таблицы @@ -141,9 +144,6 @@ struct TOPO_GROUP { struct ll_queue* senders_list; struct ll_queue* nodes; struct TOPO_NODEQ* local_node; - struct route_ping_pending* ping_pending; - uint32_t next_ping_req_id; - uint8_t allow_nat_check_local; uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; char channel_id[64]; // channel_id для групп типа CHAT }; @@ -268,51 +268,13 @@ int topo_group_add_path(struct TOPO_NODEQ* nq, struct ETCP_CONN* conn, uint64_t* */ int topo_group_remove_path(struct TOPO_NODEQ* nq, struct ETCP_CONN* conn); -/** - * @brief Отправляет NAT_INFO клиенту с его типом NAT. - * - * @param conn соединение к клиенту - * @param nat_ip IP клиента (network byte order) - * @param nat_port порт клиента (network byte order) - * @param nat_type NAT_TYPE_EIM или NAT_TYPE_STRICT - */ -void topo_group_send_nat_info(struct ETCP_CONN* conn, uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type); - -/** - * @brief Отправляет запрос на проверку NAT для указанного socket_id серверу. - * - * @param conn соединение к серверу - * @param socket_id ID сокета для проверки - */ -void topo_group_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id); - -/** - * @brief Разрешить/запретить NAT check для локальных подсетей (127.0.0.1, 10.x.x.x и т.д.) - * По умолчанию запрещено. Полезно для тестов. - */ -void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow); - void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updated_fn fn); -/** - * @brief Запускает NAT check для всех линков всех соединений. - */ -void topo_group_request_nat_check_all(struct TOPO_GROUP* group); - -/** - * @brief Запускает NAT check для одного линка. - */ -void topo_group_start_link_nat_check(struct TOPO_GROUP* group, struct ETCP_LINK* link); - -/* Аргумент callback NAT check */ -struct nat_check_arg { - struct ETCP_LINK* link; - uint32_t nat_ip; - uint16_t nat_port; -}; - void topo_groups_set_sqlite_db(struct TOPO_GROUPS* g, sqlite3* db); +/** Разрешить/запретить NAT check для локальных подсетей (тестовый хелпер, делегат в nat_detection) */ +void topo_group_set_nat_check_local(struct TOPO_GROUP* group, int allow); + #ifdef __cplusplus } diff --git a/src/topo_node.h b/src/topo_node.h index af473b5e..a2e123f0 100644 --- a/src/topo_node.h +++ b/src/topo_node.h @@ -48,6 +48,12 @@ struct UTUN_INSTANCE; // NAT_TYPE_UNKNOWN(0), NAT_TYPE_EIM(1), NAT_TYPE_STRICT(2) // NAT_VERIFIED_UNKNOWN(4), NAT_VERIFIED_EIM(5), NAT_VERIFIED_STRICT(6), NAT_VERIFIED_DIRECT(7) +// ---- типы адресов для БД (node_addresses.addr_type) ---- +#define ADDR_TYPE_NETIF 0 // interface / auto-detected +#define ADDR_TYPE_DIRECT 1 // прямой публичный адрес (detected no NAT) +#define ADDR_TYPE_NAT_EIM 2 // EIM NAT +#define ADDR_TYPE_NAT_STRICT 3 // strict NAT + // ---- статусы зондирования связности ---- #define PROBE_STATUS_NONE 0 diff --git a/src/topo_node_sqlite.c b/src/topo_node_sqlite.c index 98af5747..6d67f6a6 100644 --- a/src/topo_node_sqlite.c +++ b/src/topo_node_sqlite.c @@ -1,5 +1,6 @@ #include "topo_node_sqlite.h" #include "topo_node.h" +#include "etcp_connections.h" #include "../lib/debug_config.h" #include #include @@ -48,7 +49,7 @@ int topo_node_sqlite_init(sqlite3* db) { " address BLOB NOT NULL," " port INTEGER NOT NULL CHECK(port > 0 AND port <= 65535)," " rtt INTEGER," - " is_nat INTEGER DEFAULT 0," + " addr_type INTEGER DEFAULT 0," " created_at INTEGER DEFAULT (unixepoch())" ");" "CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);" @@ -105,29 +106,69 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) { sqlite3_stmt* addr_stmt = NULL; if (sqlite3_prepare_v2(db, - "INSERT INTO node_addresses(node_id, family, protocol, address, port, rtt, is_nat)" + "INSERT INTO node_addresses(node_id, family, protocol, address, port, rtt, addr_type)" " VALUES(?,?,?,?,?,?,?)", -1, &addr_stmt, NULL) == SQLITE_OK) { + struct TOPO_ADDR4* a4 = ni->v4_addrs; while (a4) { + /* filter private IPs */ + uint32_t ip; memcpy(&ip, a4->addr, 4); + { + uint8_t b0 = (uint8_t)ip; + if (b0 == 10 || b0 == 127) goto next_a4; + if (b0 == 172 && ((uint8_t)(ip >> 8) & 0xF0) == 16) goto next_a4; + if (b0 == 192 && (uint8_t)(ip >> 8) == 168) goto next_a4; + if (b0 == 169 && (uint8_t)(ip >> 8) == 254) goto next_a4; + } + + int at = ADDR_TYPE_NETIF; + if (a4->type == TOPO_ADDR_REAL) { + at = ADDR_TYPE_DIRECT; + } else if (a4->type == TOPO_ADDR_NAT) { + struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; + while (sm) { if (sm->id == a4->socket_id) break; sm = sm->next; } + if (sm && (sm->nat_type == NAT_TYPE_STRICT || sm->nat_type == NAT_VERIFIED_STRICT)) + at = ADDR_TYPE_NAT_STRICT; + else + at = ADDR_TYPE_NAT_EIM; + } + sqlite3_bind_int64(addr_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_int(addr_stmt, 2, 4); sqlite3_bind_int(addr_stmt, 3, a4->protocol); sqlite3_bind_blob(addr_stmt, 4, a4->addr, 4, SQLITE_STATIC); sqlite3_bind_int(addr_stmt, 5, a4->port); sqlite3_bind_null(addr_stmt, 6); - sqlite3_bind_int(addr_stmt, 7, a4->type == 1 ? 1 : 0); + sqlite3_bind_int(addr_stmt, 7, at); sqlite3_step(addr_stmt); sqlite3_reset(addr_stmt); + next_a4: a4 = a4->next; } + struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) { + /* filter link-local IPv6 */ + if (a6->addr[0] == 0xFE && (a6->addr[1] & 0xC0) == 0x80) { a6 = a6->next; continue; } + + int at = ADDR_TYPE_NETIF; + if (a6->type == TOPO_ADDR_REAL) { + at = ADDR_TYPE_DIRECT; + } else if (a6->type == TOPO_ADDR_NAT) { + struct TOPO_SOCKMETA6* sm = ni->v6_sock_meta; + while (sm) { if (sm->id == a6->socket_id) break; sm = sm->next; } + if (sm && (sm->nat_type == NAT_TYPE_STRICT || sm->nat_type == NAT_VERIFIED_STRICT)) + at = ADDR_TYPE_NAT_STRICT; + else + at = ADDR_TYPE_NAT_EIM; + } + sqlite3_bind_int64(addr_stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_int(addr_stmt, 2, 6); sqlite3_bind_int(addr_stmt, 3, a6->protocol); sqlite3_bind_blob(addr_stmt, 4, a6->addr, 16, SQLITE_STATIC); sqlite3_bind_int(addr_stmt, 5, a6->port); sqlite3_bind_null(addr_stmt, 6); - sqlite3_bind_int(addr_stmt, 7, a6->type == 1 ? 1 : 0); + sqlite3_bind_int(addr_stmt, 7, at); sqlite3_step(addr_stmt); sqlite3_reset(addr_stmt); a6 = a6->next; } diff --git a/src/utun_instance.h b/src/utun_instance.h index f59dd506..af9d2af5 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -44,6 +44,7 @@ struct PING_CONTEXT; struct CONN_MGR; struct ETCP_CONNECT; struct DB_SYNC; +struct NAT_DETECTION; struct NETWORK_ENTRY { uint64_t id; // 56-bit (offset 0 = index key) @@ -74,6 +75,7 @@ struct UTUN_INSTANCE { struct ROUTE_TABLE* rt; struct TOPO_GROUPS* topo_groups; // Groups module for topology exchange + struct NAT_DETECTION* nat_det; // NAT detection module // Identification uint64_t node_id; diff --git a/tests/test_nat_detection.c b/tests/test_nat_detection.c index 623a3561..338a35d7 100644 --- a/tests/test_nat_detection.c +++ b/tests/test_nat_detection.c @@ -23,6 +23,7 @@ #include "../src/routing.h" #include "../src/topo_group.h" #include "../src/route_ping.h" +#include "../src/nat_detection.h" #include "../src/topo_node.h" #include "../src/tun_if.h" #include "../src/secure_channel.h" @@ -429,7 +430,7 @@ int main(void) { uint32_t target_ip_net = link_sc1->nat_ip; DEBUG_INFO(DEBUG_CATEGORY_BGP, "TEST: sending ping with pubkey, nat_ip_net=0x%08x nat_port=%u", link_sc1->nat_ip, link_sc1->nat_port); - int ret = route_ping_send_req_addr(topo_groups_get_default(inst_s->topo_groups), conn_sc2, + int ret = route_ping_send_req_addr(inst_s->nat_det, topo_groups_get_default(inst_s->topo_groups), conn_sc2, target_ip_net, link_sc1->nat_port, 3, 10, 200, 3000, nat_ping_resp_cb, NULL, node_c1->node->public_key); @@ -477,7 +478,7 @@ int main(void) { target_ip_net = link_sc1->nat_ip; DEBUG_INFO(DEBUG_CATEGORY_BGP, "TEST: sending ping NO pubkey, nat_ip_net=0x%08x nat_port=%u", link_sc1->nat_ip, link_sc1->nat_port); - ret = route_ping_send_req_addr(topo_groups_get_default(inst_s->topo_groups), conn_sc2, + ret = route_ping_send_req_addr(inst_s->nat_det, topo_groups_get_default(inst_s->topo_groups), conn_sc2, target_ip_net, link_sc1->nat_port, 3, 10, 200, 3000, nat_ping_resp_cb, NULL, node_c1->node->public_key); diff --git a/tests/test_route_ping.c b/tests/test_route_ping.c index f843711b..73852bfb 100644 --- a/tests/test_route_ping.c +++ b/tests/test_route_ping.c @@ -284,7 +284,7 @@ int main(void) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending route ping request A->B for C (%s:%u)", ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port); memset(&ping_result, 0, sizeof(ping_result)); - int ret = route_ping_send_req_addr(topo_groups_get_default(inst_a->topo_groups), conn_ab, target_ip, target_port, + int ret = route_ping_send_req_addr(inst_a->nat_det, topo_groups_get_default(inst_a->topo_groups), conn_ab, target_ip, target_port, 3, // count 10, // interval_ms 200, // timeout_ms diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 94b04e9f..ff67ba3b 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -70,6 +70,7 @@ add_executable(chatgui src/networksettingspage.cpp src/databasesettingspage.cpp src/statuspage.cpp + src/nodespage.cpp transport/utun_node.cpp transport/node_config.cpp transport/config_updater.cpp diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index b341df7a..c1142934 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -273,9 +273,9 @@ QByteArray DbManager::getNodeEdPub(quint64 nodeId) const { QList DbManager::getOnlinePeerAddresses(quint64 excludeNodeId) const { QList list; sqlite3_stmt* stmt = prepareOrNull( - "SELECT na.node_id, na.family, na.protocol, na.address, na.port, na.rtt, na.is_nat" + "SELECT na.node_id, na.family, na.protocol, na.address, na.port, na.rtt, na.addr_type" " FROM node_addresses na JOIN nodes n ON na.node_id = n.node_id" - " WHERE na.is_nat=0 AND na.node_id != ?" + " WHERE na.addr_type=0 AND na.node_id != ?" " ORDER BY RANDOM()"); if (!stmt) return list; sqlite3_bind_int64(stmt, 1, (sqlite3_int64)excludeNodeId); @@ -287,7 +287,7 @@ QList DbManager::getOnlinePeerAddresses(quint64 excludeNodeId) const { a.address = colBlob(stmt, 3); a.port = (quint16)sqlite3_column_int(stmt, 4); a.rtt = sqlite3_column_int(stmt, 5); - a.isNat = sqlite3_column_int(stmt, 6) != 0; + a.addrType = sqlite3_column_int(stmt, 6); list.append(a); } sqlite3_finalize(stmt); diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index 8fced4ef..3fd999eb 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -59,7 +59,7 @@ struct NodeAddr { QByteArray address; quint16 port = 0; int rtt = 0; - bool isNat = false; + int addrType = 0; // ADDR_TYPE_* }; struct IntegrityResult { diff --git a/tools/chatgui/libutun/CMakeLists.txt b/tools/chatgui/libutun/CMakeLists.txt index 64718f27..e92eeaa1 100644 --- a/tools/chatgui/libutun/CMakeLists.txt +++ b/tools/chatgui/libutun/CMakeLists.txt @@ -59,6 +59,7 @@ set(UTUN_COMMON_SOURCES ${SRC_DIR}/route6_lib.c ${SRC_DIR}/topo_group.c ${SRC_DIR}/route_ping.c + ${SRC_DIR}/nat_detection.c ${SRC_DIR}/topo_node.c ${SRC_DIR}/topo_node_sqlite.c ${SRC_DIR}/route_connectivity.c diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index 17644e5d..a0b8a21c 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -3,12 +3,44 @@ #include "../db/db_manager.h" #include #include +#include +#include +#include + +static QString shortenKey(const QByteArray& key) { + if (key.size() < 8) return key.toHex(' ').toUpper(); + return key.left(4).toHex(' ').toUpper() + ".." + key.right(4).toHex(' ').toUpper(); +} + +static QString addrStr(const void* addr, int family) { + if (family == 4) { + const uint8_t* a = (const uint8_t*)addr; + return QString("%1.%2.%3.%4").arg(a[0]).arg(a[1]).arg(a[2]).arg(a[3]); + } + if (family == 6) { + const uint16_t* a = (const uint16_t*)addr; + return QString("%1:%2:%3:%4:%5:%6:%7:%8") + .arg(ntohs(a[0]), 0, 16).arg(ntohs(a[1]), 0, 16) + .arg(ntohs(a[2]), 0, 16).arg(ntohs(a[3]), 0, 16) + .arg(ntohs(a[4]), 0, 16).arg(ntohs(a[5]), 0, 16) + .arg(ntohs(a[6]), 0, 16).arg(ntohs(a[7]), 0, 16); + } + return "?"; +} + +static QString protoStr(int protocol) { + switch (protocol) { case 1: return "UDP"; case 2: return "TCP"; case 3: return "UDP+TCP"; default: return QString::number(protocol); } +} +static QString addrTypeStr(int t) { + switch (t) { case 0: return "NETIF"; case 1: return "DIRECT"; case 2: return "NAT_EIM"; case 3: return "NAT_STRICT"; default: return QString("?%1").arg(t); } +} AccountList::AccountList(DbManager* db, QWidget *parent) : QWidget(parent) , m_listView(new QListView(this)) , m_model(new MemberListModel(db, this)) , m_db(db) + , m_detailText(nullptr) { auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -25,8 +57,99 @@ AccountList::AccountList(DbManager* db, QWidget *parent) m_listView->setIconSize(QSize(24, 24)); m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - layout->addWidget(m_listView); + layout->addWidget(m_listView, 3); + + m_detailText = new QPlainTextEdit(this); + m_detailText->setReadOnly(true); + m_detailText->setFont(QFont("monospace", 9)); + m_detailText->setStyleSheet( + "QPlainTextEdit { border-top: 1px solid palette(mid); border-bottom: none;" + " border-left: none; border-right: none; padding: 6px; background: palette(base); }"); + m_detailText->setPlaceholderText("Select a member"); + layout->addWidget(m_detailText, 2); + + connect(m_listView, &QListView::clicked, this, &AccountList::onMemberClicked); +} + +void AccountList::setChannel(const QString& channelId) { + m_channelId = channelId; + m_model->setChannel(channelId); + m_detailText->clear(); } -void AccountList::setChannel(const QString& channelId) { m_model->setChannel(channelId); } void AccountList::refresh() { m_model->refresh(); } + +void AccountList::onMemberClicked(const QModelIndex& index) { + if (!index.isValid()) return; + quint64 nodeId = m_model->nodeIdAt(index.row()); + if (nodeId) showMemberDetail(nodeId); +} + +void AccountList::showMemberDetail(quint64 nodeId) { + if (!m_db || !m_db->m_db) return; + sqlite3* d = m_db->m_db; + + QString text; + + sqlite3_stmt* st = nullptr; + if (sqlite3_prepare_v2(d, + "SELECT node_id, name, online, x25519_pubkey, ed25519_pubkey," + " last_seen_at, created_at FROM nodes WHERE node_id=?", + -1, &st, nullptr) != SQLITE_OK) { m_detailText->clear(); return; } + sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId); + + if (sqlite3_step(st) == SQLITE_ROW) { + QString name = DbManager::colText(st, 1); + int online = sqlite3_column_int(st, 2); + QByteArray x25 = DbManager::colBlob(st, 3); + QByteArray ed = DbManager::colBlob(st, 4); + qint64 last_seen = sqlite3_column_int64(st, 5); + qint64 created = sqlite3_column_int64(st, 6); + + text += QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0')); + if (!name.isEmpty()) text += QString(" (%1)").arg(name); + text += QString("\n online: %1\n").arg(online ? "yes" : "no"); + text += QString(" x25519: %1\n").arg(x25.isEmpty() ? "-" : shortenKey(x25)); + text += QString(" ed25519: %1\n").arg(ed.isEmpty() ? "-" : shortenKey(ed)); + if (last_seen > 0) + text += QString(" last_seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss")); + if (created > 0) + text += QString(" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss")); + } else { + text = QString("Node 0x%1 not found in database.").arg(nodeId, 16, 16, QChar('0')); + } + sqlite3_finalize(st); + + sqlite3_stmt* na = nullptr; + sqlite3_prepare_v2(d, + "SELECT family, protocol, address, port, rtt, addr_type" + " FROM node_addresses WHERE node_id=? ORDER BY family, port", + -1, &na, nullptr); + if (na) { + sqlite3_bind_int64(na, 1, (sqlite3_int64)nodeId); + int ac = 0; + while (sqlite3_step(na) == SQLITE_ROW) { + if (ac == 0) text += " addresses:\n"; + int family = sqlite3_column_int(na, 0); + int proto = sqlite3_column_int(na, 1); + const void* addr = sqlite3_column_blob(na, 2); + int addrSz = sqlite3_column_bytes(na, 2); + int port = sqlite3_column_int(na, 3); + int rtt = sqlite3_column_int(na, 4); + int addrType = sqlite3_column_int(na, 5); + + if (addr && addrSz > 0) + text += QString(" %1:%2 %3 %4 rtt=%5ms\n") + .arg(addrStr(addr, family)) + .arg(port) + .arg(protoStr(proto)) + .arg(addrTypeStr(addrType)) + .arg(rtt > 0 ? QString::number(rtt) : "-"); + ac++; + } + if (ac == 0) text += " addresses: none\n"; + sqlite3_finalize(na); + } + + m_detailText->setPlainText(text); +} diff --git a/tools/chatgui/src/accountlist.h b/tools/chatgui/src/accountlist.h index 5856c0a2..9e0fe604 100644 --- a/tools/chatgui/src/accountlist.h +++ b/tools/chatgui/src/accountlist.h @@ -2,6 +2,7 @@ #include #include +#include class MemberListModel; class DbManager; @@ -14,8 +15,15 @@ public: void setChannel(const QString& channelId); void refresh(); +private slots: + void onMemberClicked(const QModelIndex& index); + private: + void showMemberDetail(quint64 nodeId); + QListView *m_listView; MemberListModel *m_model; DbManager *m_db; + QPlainTextEdit *m_detailText; + QString m_channelId; }; diff --git a/tools/chatgui/src/memberlistmodel.cpp b/tools/chatgui/src/memberlistmodel.cpp index 1b054778..1c8d40d8 100644 --- a/tools/chatgui/src/memberlistmodel.cpp +++ b/tools/chatgui/src/memberlistmodel.cpp @@ -45,6 +45,11 @@ void MemberListModel::setChannel(const QString& channelId) { void MemberListModel::refresh() { if (!m_channelId.isEmpty()) setChannel(m_channelId); } +quint64 MemberListModel::nodeIdAt(int row) const { + auto it = m_cache.find(row); + return it != m_cache.end() ? it->nodeId : 0; +} + void MemberListModel::scheduleBatch(int centerRow) const { if (m_pendingFrom >= 0) return; int from = centerRow - BATCH / 2; diff --git a/tools/chatgui/src/memberlistmodel.h b/tools/chatgui/src/memberlistmodel.h index 67920d5b..7d72885c 100644 --- a/tools/chatgui/src/memberlistmodel.h +++ b/tools/chatgui/src/memberlistmodel.h @@ -30,6 +30,7 @@ public: void setChannel(const QString& channelId); void refresh(); + quint64 nodeIdAt(int row) const; private slots: void loadPendingBatch(); diff --git a/tools/chatgui/src/nodespage.cpp b/tools/chatgui/src/nodespage.cpp new file mode 100644 index 00000000..0833613e --- /dev/null +++ b/tools/chatgui/src/nodespage.cpp @@ -0,0 +1,228 @@ +// nodespage.cpp — Nodes table + dump in settings +#include "nodespage.h" +#include "../db/db_manager.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +static QString hex4(uint32_t v) { + return QString("%1").arg(v, 4, 16, QChar('0')).toUpper(); +} + +QString NodesPage::shortenKey(const QByteArray& key) { + if (key.size() < 8) return key.toHex(' ').toUpper(); + return key.left(4).toHex(' ').toUpper() + ".." + key.right(4).toHex(' ').toUpper(); +} + +QString NodesPage::addrStr(const void* addr, int family) { + if (family == 4) { + const uint8_t* a = (const uint8_t*)addr; + return QString("%1.%2.%3.%4").arg(a[0]).arg(a[1]).arg(a[2]).arg(a[3]); + } + if (family == 6) { + const uint16_t* a = (const uint16_t*)addr; + return QString("%1:%2:%3:%4:%5:%6:%7:%8") + .arg(ntohs(a[0]), 0, 16).arg(ntohs(a[1]), 0, 16) + .arg(ntohs(a[2]), 0, 16).arg(ntohs(a[3]), 0, 16) + .arg(ntohs(a[4]), 0, 16).arg(ntohs(a[5]), 0, 16) + .arg(ntohs(a[6]), 0, 16).arg(ntohs(a[7]), 0, 16); + } + return "?"; +} + +QString NodesPage::protoStr(int protocol) { + switch (protocol) { + case 1: return "UDP"; + case 2: return "TCP"; + case 3: return "UDP+TCP"; + default: return QString::number(protocol); + } +} + +static QString addrTypeStr(int t) { + switch (t) { case 0: return "NETIF"; case 1: return "DIRECT"; case 2: return "NAT_EIM"; case 3: return "NAT_STRICT"; default: return QString("?%1").arg(t); } +} + +NodesPage::NodesPage(DbManager* db, QWidget* parent) + : QWidget(parent) + , m_db(db) +{ + auto* layout = new QVBoxLayout(this); + layout->setSpacing(8); + layout->setContentsMargins(0, 0, 0, 0); + + auto* title = new QLabel("Known Nodes", this); + title->setStyleSheet("font-weight: bold; font-size: 13px;"); + layout->addWidget(title); + + m_table = new QTableWidget(0, 6, this); + m_table->setHorizontalHeaderLabels({"Node ID", "Name", "Online", "x25519", "ed25519", "Addrs"}); + m_table->horizontalHeader()->setStretchLastSection(true); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->verticalHeader()->setVisible(false); + m_table->setFont(QFont("monospace", 9)); + m_table->setStyleSheet( + "QTableWidget { border: 1px solid palette(mid); border-radius: 4px;" + " gridline-color: palette(midlight); background: palette(base); }" + "QHeaderView::section { background: palette(window); padding: 4px;" + " font-weight: bold; font-size: 9px; }"); + layout->addWidget(m_table, 2); + + auto* dumpLabel = new QLabel("Details", this); + dumpLabel->setStyleSheet("font-weight: bold; font-size: 12px; margin-top: 8px;"); + layout->addWidget(dumpLabel); + + m_dumpText = new QPlainTextEdit(this); + m_dumpText->setReadOnly(true); + m_dumpText->setFont(QFont("monospace", 10)); + m_dumpText->setStyleSheet( + "QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;" + " padding: 8px; background: palette(base); }"); + m_dumpText->setPlaceholderText("Click Refresh to load nodes"); + layout->addWidget(m_dumpText, 3); + + auto* btnLayout = new QHBoxLayout(); + btnLayout->addStretch(); + m_refreshBtn = new QPushButton("Refresh", this); + m_refreshBtn->setFixedWidth(90); + m_refreshBtn->setStyleSheet( + "QPushButton { border: 1px solid palette(mid); border-radius: 4px;" + " padding: 6px 16px; font-size: 13px; background: palette(button); }" + "QPushButton:hover { background: palette(light); }"); + connect(m_refreshBtn, &QPushButton::clicked, this, &NodesPage::onRefreshClicked); + btnLayout->addWidget(m_refreshBtn); + layout->addLayout(btnLayout); +} + +void NodesPage::onRefreshClicked() { refreshNodes(); } + +void NodesPage::refreshNodes() { + if (!m_db || !m_db->m_db) return; + + sqlite3* d = m_db->m_db; + + /* ── table ── */ + m_table->setRowCount(0); + + sqlite3_stmt* st = nullptr; + if (sqlite3_prepare_v2(d, + "SELECT node_id, name, online, x25519_pubkey, ed25519_pubkey," + " last_seen_at, created_at FROM nodes ORDER BY node_id", + -1, &st, nullptr) != SQLITE_OK) return; + + int row = 0; + while (sqlite3_step(st) == SQLITE_ROW) { + m_table->insertRow(row); + quint64 nid = (quint64)sqlite3_column_int64(st, 0); + + auto setCell = [&](int col, const QString& v) { + m_table->setItem(row, col, new QTableWidgetItem(v)); + }; + + setCell(0, hex4((uint32_t)(nid & 0xFFFF))); + setCell(1, DbManager::colText(st, 1)); + setCell(2, sqlite3_column_int(st, 2) ? "yes" : "no"); + + QByteArray x25 = DbManager::colBlob(st, 3); + QByteArray ed = DbManager::colBlob(st, 4); + setCell(3, x25.isEmpty() ? "-" : shortenKey(x25)); + setCell(4, ed.isEmpty() ? "-" : shortenKey(ed)); + + sqlite3_stmt* ac = nullptr; + sqlite3_prepare_v2(d, "SELECT COUNT(*) FROM node_addresses WHERE node_id=?", -1, &ac, nullptr); + if (ac) { + sqlite3_bind_int64(ac, 1, (sqlite3_int64)nid); + setCell(5, sqlite3_step(ac) == SQLITE_ROW + ? QString::number(sqlite3_column_int(ac, 0)) + : "0"); + sqlite3_finalize(ac); + } else { + setCell(5, "0"); + } + row++; + } + sqlite3_finalize(st); + m_table->resizeColumnsToContents(); + + /* ── text dump ── */ + QString dump; + + st = nullptr; + sqlite3_prepare_v2(d, + "SELECT node_id, name, online, x25519_pubkey, ed25519_pubkey," + " last_seen_at, created_at FROM nodes ORDER BY node_id", + -1, &st, nullptr); + if (!st) { m_dumpText->setPlainText("Query error"); return; } + + int nodeCount = 0; + while (sqlite3_step(st) == SQLITE_ROW) { + if (nodeCount > 0) dump += "\n"; + nodeCount++; + + quint64 nid = (quint64)sqlite3_column_int64(st, 0); + QString name = DbManager::colText(st, 1); + int online = sqlite3_column_int(st, 2); + QByteArray x25 = DbManager::colBlob(st, 3); + QByteArray ed = DbManager::colBlob(st, 4); + qint64 last_seen = sqlite3_column_int64(st, 5); + qint64 created = sqlite3_column_int64(st, 6); + + dump += QString("Node: %1").arg(hex4((uint32_t)(nid & 0xFFFF))); + if (!name.isEmpty()) dump += QString(" (%1)").arg(name); + dump += QString(" id=0x%1\n").arg(nid, 16, 16, QChar('0')); + dump += QString(" online: %1\n").arg(online ? "yes" : "no"); + dump += QString(" x25519: %1\n").arg(x25.isEmpty() ? "-" : shortenKey(x25)); + dump += QString(" ed25519: %1\n").arg(ed.isEmpty() ? "-" : shortenKey(ed)); + if (last_seen > 0) + dump += QString(" last_seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss")); + if (created > 0) + dump += QString(" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss")); + + sqlite3_stmt* na = nullptr; + sqlite3_prepare_v2(d, + "SELECT family, protocol, address, port, rtt, addr_type" + " FROM node_addresses WHERE node_id=? ORDER BY family, port", + -1, &na, nullptr); + if (na) { + sqlite3_bind_int64(na, 1, (sqlite3_int64)nid); + int ac = 0; + while (sqlite3_step(na) == SQLITE_ROW) { + if (ac == 0) dump += " addresses:\n"; + int family = sqlite3_column_int(na, 0); + int proto = sqlite3_column_int(na, 1); + const void* addr = sqlite3_column_blob(na, 2); + int addrSz = sqlite3_column_bytes(na, 2); + int port = sqlite3_column_int(na, 3); + int rtt = sqlite3_column_int(na, 4); + int addrType = sqlite3_column_int(na, 5); + + if (addr && addrSz > 0) { + dump += QString(" %1:%2 %3 %4 rtt=%5ms\n") + .arg(addrStr(addr, family)) + .arg(port) + .arg(protoStr(proto)) + .arg(addrTypeStr(addrType)) + .arg(rtt > 0 ? QString::number(rtt) : "-"); + } + ac++; + } + if (ac == 0) dump += " addresses: none\n"; + sqlite3_finalize(na); + } else { + dump += " addresses: none\n"; + } + } + sqlite3_finalize(st); + + if (nodeCount == 0) dump = "No nodes found."; + m_dumpText->setPlainText(dump); +} diff --git a/tools/chatgui/src/nodespage.h b/tools/chatgui/src/nodespage.h new file mode 100644 index 00000000..99603d95 --- /dev/null +++ b/tools/chatgui/src/nodespage.h @@ -0,0 +1,29 @@ +// nodespage.h — Nodes table + dump in settings +#pragma once + +#include +#include +#include +#include + +class DbManager; + +class NodesPage : public QWidget { + Q_OBJECT +public: + explicit NodesPage(DbManager* db, QWidget* parent = nullptr); + void refreshNodes(); + +private slots: + void onRefreshClicked(); + +private: + static QString shortenKey(const QByteArray& key); + static QString addrStr(const void* addr, int family); + static QString protoStr(int protocol); + + DbManager* m_db; + QTableWidget* m_table; + QPlainTextEdit* m_dumpText; + QPushButton* m_refreshBtn; +}; diff --git a/tools/chatgui/src/settingsdialog.cpp b/tools/chatgui/src/settingsdialog.cpp index 02e42335..706f40a6 100644 --- a/tools/chatgui/src/settingsdialog.cpp +++ b/tools/chatgui/src/settingsdialog.cpp @@ -3,6 +3,7 @@ #include "networksettingspage.h" #include "databasesettingspage.h" #include "statuspage.h" +#include "nodespage.h" #include "../transport/gui_bridge.h" #include "../../lib/mem.h" #include "../db/db_manager.h" @@ -23,7 +24,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget , m_configPath(configPath) { setWindowTitle("Settings"); - setMinimumSize(560, 460); + setMinimumSize(760, 460); setModal(true); auto* mainLayout = new QHBoxLayout(this); @@ -42,6 +43,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget m_categoryList->addItem("Network"); m_categoryList->addItem("Database"); m_categoryList->addItem("Status"); + m_categoryList->addItem("Nodes"); mainLayout->addWidget(m_categoryList); // --- Right panel: stacked pages --- @@ -77,6 +79,10 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget // Status page m_statusPage = new StatusPage(m_pages); m_pages->addWidget(m_statusPage); + + // Nodes page + m_nodesPage = new NodesPage(db, m_pages); + m_pages->addWidget(m_nodesPage); rightLayout->addWidget(m_pages, 1); // --- Bottom buttons --- @@ -134,6 +140,7 @@ void SettingsDialog::loadProfileFromConfig(const QString& path) { void SettingsDialog::onCategoryChanged(int row) { m_pages->setCurrentIndex(row); if (row == 3) m_statusPage->refreshStatus(); + if (row == 4) m_nodesPage->refreshNodes(); } void SettingsDialog::onSave() { diff --git a/tools/chatgui/src/settingsdialog.h b/tools/chatgui/src/settingsdialog.h index 4f2aa4d3..aacb8534 100644 --- a/tools/chatgui/src/settingsdialog.h +++ b/tools/chatgui/src/settingsdialog.h @@ -11,6 +11,7 @@ class NetworkSettingsPage; class DatabaseSettingsPage; class StatusPage; +class NodesPage; class DbManager; class SettingsDialog : public QDialog { @@ -34,4 +35,5 @@ private: NetworkSettingsPage* m_networkPage; DatabaseSettingsPage* m_databasePage; StatusPage* m_statusPage; + NodesPage* m_nodesPage; }; diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index 30176da7..f010bee0 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -31,6 +31,7 @@ #include #include +#include #define CC_ID "chat_core" @@ -136,6 +137,8 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { } } + chat_core_sync_my_addresses(); + db_exec( "CREATE TABLE IF NOT EXISTS local_identity (" " id INTEGER PRIMARY KEY CHECK (id = 1)," @@ -304,6 +307,53 @@ void chat_core_update_my_name(const char* name) { if (grp) topo_group_update_my_nodeinfo(g_cc.inst, grp); } +void chat_core_sync_my_addresses(void) { + if (!g_cc.initialized || !g_cc.db || !g_cc.inst) return; + + sqlite3_stmt* del = NULL; + sqlite3_prepare_v2(g_cc.db, "DELETE FROM node_addresses WHERE node_id=? AND addr_type=0", -1, &del, NULL); + if (del) { sqlite3_bind_int64(del, 1, (sqlite3_int64)g_cc.my_node_id); sqlite3_step(del); sqlite3_finalize(del); } + + sqlite3_stmt* ins = NULL; + sqlite3_prepare_v2(g_cc.db, + "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type)" + " VALUES(?,?,1,?,?,0)", -1, &ins, NULL); + if (!ins) return; + + struct ETCP_SOCKET* sock = g_cc.inst->etcp_sockets; + while (sock) { + struct sockaddr_storage* sa = sock->interface_addr.ss_family ? &sock->interface_addr : NULL; + if (!sa) sa = sock->local_addr.ss_family ? &sock->local_addr : NULL; + if (!sa) { sock = sock->next; continue; } + + if (sa->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)sa; + uint8_t b0 = (uint8_t)(ntohl(sin->sin_addr.s_addr) >> 24); + if (b0 == 10 || b0 == 127 || + (b0 == 172 && ((ntohl(sin->sin_addr.s_addr) >> 20) & 0xF0) == 16) || + (b0 == 192 && (uint8_t)(ntohl(sin->sin_addr.s_addr) >> 16) == 168) || + (b0 == 169 && (uint8_t)(ntohl(sin->sin_addr.s_addr) >> 16) == 254)) + { sock = sock->next; continue; } + sqlite3_bind_int64(ins, 1, (sqlite3_int64)g_cc.my_node_id); + sqlite3_bind_int(ins, 2, 4); + sqlite3_bind_blob(ins, 3, &sin->sin_addr, 4, SQLITE_STATIC); + sqlite3_bind_int(ins, 4, (int)ntohs(sin->sin_port)); + sqlite3_step(ins); sqlite3_reset(ins); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; + if (sin6->sin6_addr.s6_addr[0] == 0xFE && (sin6->sin6_addr.s6_addr[1] & 0xC0) == 0x80) + { sock = sock->next; continue; } + sqlite3_bind_int64(ins, 1, (sqlite3_int64)g_cc.my_node_id); + sqlite3_bind_int(ins, 2, 6); + sqlite3_bind_blob(ins, 3, &sin6->sin6_addr, 16, SQLITE_STATIC); + sqlite3_bind_int(ins, 4, (int)ntohs(sin6->sin6_port)); + sqlite3_step(ins); sqlite3_reset(ins); + } + sock = sock->next; + } + sqlite3_finalize(ins); +} + void chat_core_update_my_name_trampoline(void* arg) { chat_core_update_my_name((const char*)arg); u_free(arg); @@ -1225,6 +1275,19 @@ static const char* nat_type_str(uint8_t t) { switch (t) { case 0: return "UNKNOWN"; case 1: return "EIM"; case 2: return "STRICT"; case 3: return "DIRECT"; default: return "?"; } } +static void get_node_name(uint64_t node_id, char* out, size_t sz) { + out[0] = '\0'; + if (!g_cc.db || node_id == 0) return; + sqlite3_stmt* st = NULL; + if (sqlite3_prepare_v2(g_cc.db, "SELECT name FROM nodes WHERE node_id=?", -1, &st, NULL) != SQLITE_OK) return; + sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); + if (sqlite3_step(st) == SQLITE_ROW) { + const char* n = (const char*)sqlite3_column_text(st, 0); + if (n) snprintf(out, sz, "%s", n); + } + sqlite3_finalize(st); +} + static void chat_core_collect_status(void) { char buf[8192]; int off = 0; @@ -1259,6 +1322,7 @@ static void chat_core_collect_status(void) { while (entry) { conn_count++; entry = entry->next; } off += snprintf(buf + off, sizeof(buf) - off, "=== Connections (%d) ===\n", conn_count); + uint64_t my_id = g_cc.inst->node_id; entry = g_cc.inst->connections->head; while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; @@ -1267,30 +1331,45 @@ static void chat_core_collect_status(void) { uint64_t pid = ce->peer_node_id; struct ETCP_CONN* conn = ce->conn; + int link_count = 0; + struct ETCP_LINK* tl = conn->links; + while (tl) { link_count++; tl = tl->next; } + uint8_t state = 0, ctype = 0; conn_mgr_get_status(g_cc.inst->conn_mgr, pid, &state, &ctype); - off += snprintf(buf + off, sizeof(buf) - off, - "Peer 0x%016llx links_up=%d initialized=%d\n", - (unsigned long long)pid, conn->links_up, conn->initialized); - off += snprintf(buf + off, sizeof(buf) - off, " State: %s Type: %s\n", - conn_state_str(state), conn_type_str(ctype)); + char mgr_str[64]; + if (ctype == 0) snprintf(mgr_str, sizeof(mgr_str), "%s", conn_state_str(state)); + else snprintf(mgr_str, sizeof(mgr_str), "%s/%s", conn_state_str(state), conn_type_str(ctype)); + + const char* init_str = conn->initialized ? "" : "(!init)"; + + char myhex[5], peerhex[5]; + snprintf(myhex, sizeof(myhex), "%04llX", (unsigned long long)(my_id & 0xFFFF)); + snprintf(peerhex, sizeof(peerhex), "%04llX", (unsigned long long)(pid & 0xFFFF)); + + char peername[64]; + get_node_name(pid, peername, sizeof(peername)); + + if (peername[0]) + off += snprintf(buf + off, sizeof(buf) - off, "[%s]→[%s] %s ETCP:%s%s(%dL) MGR:%s\n", + myhex, peerhex, peername, + conn->links_up ? "UP" : "DOWN", init_str, link_count, mgr_str); + else + off += snprintf(buf + off, sizeof(buf) - off, "[%s]→[%s] ETCP:%s%s(%dL) MGR:%s\n", + myhex, peerhex, + conn->links_up ? "UP" : "DOWN", init_str, link_count, mgr_str); int link_idx = 0; struct ETCP_LINK* link = conn->links; while (link) { - off += snprintf(buf + off, sizeof(buf) - off, - " Link #%d: local_id=%d remote_id=%d status=%s recv=%s remote=%s NAT=%s", - link_idx, link->local_link_id, link->remote_link_id, + char rtt_str[32]; rtt_str[0] = '\0'; + if (link->rtt_last > 0) snprintf(rtt_str, sizeof(rtt_str), " / rtt=%ums", link->rtt_last); + off += snprintf(buf + off, sizeof(buf) - off, " LINK#%d: %s /NAT=%s%s\n", + link_idx, link->link_status ? "UP" : "DOWN", - link->recv_keepalive ? "UP" : "DOWN", - link->remote_keepalive ? "UP" : "DOWN", - nat_type_str(link->nat_type)); - if (link->rtt_last > 0) - off += snprintf(buf + off, sizeof(buf) - off, " rtt=%ums", link->rtt_last); - if (link->bandwidth > 0) - off += snprintf(buf + off, sizeof(buf) - off, " bw=%uKbps", link->bandwidth); - off += snprintf(buf + off, sizeof(buf) - off, "\n"); + nat_type_str(link->nat_type), + rtt_str); link = link->next; link_idx++; } diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h index b38a4720..c903636c 100644 --- a/tools/chatgui/transport/chat_core.h +++ b/tools/chatgui/transport/chat_core.h @@ -88,6 +88,7 @@ void chat_core_create_channel_trampoline(void* arg); void chat_core_set_my_node_id(uint64_t node_id); void chat_core_update_my_name(const char* name); void chat_core_update_my_name_trampoline(void* arg); +void chat_core_sync_my_addresses(void); /* Трамплин для gui_bridge_post_uasync (GUI → uasync) */ void chat_core_submit_trampoline(void* arg); diff --git a/tools/chatgui/transport/chat_sync.c b/tools/chatgui/transport/chat_sync.c index 075861e8..5ff2a39a 100644 --- a/tools/chatgui/transport/chat_sync.c +++ b/tools/chatgui/transport/chat_sync.c @@ -931,7 +931,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, struct sockaddr_in* sin = (struct sockaddr_in*)&lk->remote_addr; uint8_t ip[4]; memcpy(ip, &sin->sin_addr, 4); uint16_t port = ntohs(sin->sin_port); char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat) VALUES(?,4,?,?,0)"); + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,addr_type) VALUES(?,4,?,?,0)"); sqlite3_stmt* as = NULL; if (sqlite3_prepare_v2(vdb, sql, -1, &as, NULL) == SQLITE_OK) { sqlite3_bind_int64(as, 1, (sqlite3_int64)peer); @@ -1071,7 +1071,7 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,addr_type)" " VALUES(?,?,?,?,0)"); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { @@ -1193,7 +1193,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,addr_type)" " VALUES(?,?,?,?,0)"); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { @@ -1275,7 +1275,7 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,is_nat)" + "INSERT OR REPLACE INTO node_addresses(node_id,family,address,port,addr_type)" " VALUES(?,?,?,?,0)"); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { diff --git a/tools/chatgui/transport/member_sync.c b/tools/chatgui/transport/member_sync.c index ecd99027..047e91d9 100644 --- a/tools/chatgui/transport/member_sync.c +++ b/tools/chatgui/transport/member_sync.c @@ -1,5 +1,6 @@ #include "member_sync.h" #include "topo_node_sqlite.h" +#include "chat_core.h" #include "../../../src/utun_instance.h" #include "../../../src/topo_group.h" @@ -163,7 +164,7 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level, sqlite3_stmt* as = NULL; sqlite3_prepare_v2(db, - "SELECT family, address, port FROM node_addresses WHERE node_id=? AND is_nat=0" + "SELECT family, address, port FROM node_addresses WHERE node_id=? AND addr_type=0" " ORDER BY family, address, port", -1, &as, NULL); uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0; if (as) { @@ -286,6 +287,7 @@ static void _on_node_updated(struct UTUN_INSTANCE* inst, uint64_t node_id, const uint8_t* x25519, const uint8_t* ed25519) { (void)x25519; (void)ed25519; if (!inst) return; + if (node_id == inst->node_id) chat_core_sync_my_addresses(); sqlite3* db = _db(inst); if (!db) return; int rc = 0; sqlite3_stmt* cs = NULL; @@ -357,12 +359,12 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, if (addrs_data && addr_count > 0) { sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); sqlite3_stmt* ds = NULL; - sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=? AND is_nat=0", -1, &ds, NULL); + sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=? AND addr_type=0", -1, &ds, NULL); if (ds) { sqlite3_bind_int64(ds, 1, (sqlite3_int64)member_id); sqlite3_step(ds); sqlite3_finalize(ds); } sqlite3_stmt* as = NULL; sqlite3_prepare_v2(db, - "INSERT INTO node_addresses(node_id,family,protocol,address,port,is_nat)" + "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type)" " VALUES(?,?,1,?,?,0)", -1, &as, NULL); if (as) { const uint8_t* p = addrs_data;