Browse Source

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)
topo_upd
Evgeny 3 months ago
parent
commit
910cac8dce
  1. 2
      src/Makefile.am
  2. 5
      src/control_server.c
  3. 4
      src/etcp.c
  4. 14
      src/etcp_connections.c
  5. 363
      src/nat_detection.c
  6. 115
      src/nat_detection.h
  7. 87
      src/route_ping.c
  8. 34
      src/route_ping.h
  9. 167
      src/topo_group.c
  10. 52
      src/topo_group.h
  11. 6
      src/topo_node.h
  12. 49
      src/topo_node_sqlite.c
  13. 2
      src/utun_instance.h
  14. 5
      tests/test_nat_detection.c
  15. 2
      tests/test_route_ping.c
  16. 1
      tools/chatgui/CMakeLists.txt
  17. 6
      tools/chatgui/db/db_manager.cpp
  18. 2
      tools/chatgui/db/db_manager.h
  19. 1
      tools/chatgui/libutun/CMakeLists.txt
  20. 127
      tools/chatgui/src/accountlist.cpp
  21. 8
      tools/chatgui/src/accountlist.h
  22. 5
      tools/chatgui/src/memberlistmodel.cpp
  23. 1
      tools/chatgui/src/memberlistmodel.h
  24. 228
      tools/chatgui/src/nodespage.cpp
  25. 29
      tools/chatgui/src/nodespage.h
  26. 9
      tools/chatgui/src/settingsdialog.cpp
  27. 2
      tools/chatgui/src/settingsdialog.h
  28. 111
      tools/chatgui/transport/chat_core.c
  29. 1
      tools/chatgui/transport/chat_core.h
  30. 8
      tools/chatgui/transport/chat_sync.c
  31. 8
      tools/chatgui/transport/member_sync.c

2
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 \

5
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;

4
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)

14
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;
}

363
src/nat_detection.c

@ -0,0 +1,363 @@
/**
* @file nat_detection.c
* @brief NAT-детекция: STUN-like проверка через третьего BGP-пира.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.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 "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);
}

115
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 <stdint.h>
#include <stddef.h>
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 */

87
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) {

34
src/route_ping.h

@ -8,10 +8,8 @@ extern "C" {
#include <stdint.h>
#include <stddef.h>
#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

167
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);

52
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
}

6
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

49
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 <string.h>
#include <stdio.h>
@ -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;
}

2
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;

5
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);

2
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

1
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

6
tools/chatgui/db/db_manager.cpp

@ -273,9 +273,9 @@ QByteArray DbManager::getNodeEdPub(quint64 nodeId) const {
QList<NodeAddr> DbManager::getOnlinePeerAddresses(quint64 excludeNodeId) const {
QList<NodeAddr> 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<NodeAddr> 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);

2
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 {

1
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

127
tools/chatgui/src/accountlist.cpp

@ -3,12 +3,44 @@
#include "../db/db_manager.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QDateTime>
#include <QFont>
#include <arpa/inet.h>
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);
}

8
tools/chatgui/src/accountlist.h

@ -2,6 +2,7 @@
#include <QWidget>
#include <QListView>
#include <QPlainTextEdit>
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;
};

5
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;

1
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();

228
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 <QVBoxLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QDateTime>
#include <QFont>
#include <cstring>
#include <arpa/inet.h>
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);
}

29
tools/chatgui/src/nodespage.h

@ -0,0 +1,29 @@
// nodespage.h — Nodes table + dump in settings
#pragma once
#include <QWidget>
#include <QTableWidget>
#include <QPlainTextEdit>
#include <QPushButton>
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;
};

9
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() {

2
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;
};

111
tools/chatgui/transport/chat_core.c

@ -31,6 +31,7 @@
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
#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++;
}

1
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);

8
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) {

8
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;

Loading…
Cancel
Save