Browse Source
- 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
31 changed files with 1166 additions and 288 deletions
@ -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); |
||||||
|
} |
||||||
@ -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 */ |
||||||
@ -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); |
||||||
|
} |
||||||
@ -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; |
||||||
|
}; |
||||||
Loading…
Reference in new issue