From 90363650e3e5ad2acc99754f80948dfb192faf65 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 17 Jul 2026 20:45:49 +0300 Subject: [PATCH] refactor(nat_detection): self-sufficient module with own ETCP_ID (0x02) and docs - Assign ETCP_ID_NAT_DETECTION = 0x02, etcp_bind in create, etcp_unbind in destroy - nat_detection_receive_cbk: own packet dispatcher (PING_REQ/RESP, NAT_INFO/CHECK_REQ) - 4 handle_* become static internal, send_nat_check_req stays static (dead code) - Rename TOPO_* -> NATDET_*: all subcommands, structs, message types - Remove all NAT/PING constants and structs from topo_group.h - Remove dispatch lines and subcmd_name cases from topo_group_receive_cbk - API: clear sections (lifecycle/internal service/control/test helpers) - trigger_checks: marked with IMPORTANT note about call order (must be after add_to_senders) --- src/nat_detection.c | 335 +++++++++++++++++++++++++------------------- src/nat_detection.h | 171 ++++++++++++++++------ src/route_ping.c | 26 ++-- src/route_ping.h | 45 +----- src/topo_group.c | 25 +--- src/topo_group.h | 28 ---- 6 files changed, 338 insertions(+), 292 deletions(-) diff --git a/src/nat_detection.c b/src/nat_detection.c index b7f2e5e1..7547431c 100644 --- a/src/nat_detection.c +++ b/src/nat_detection.c @@ -1,6 +1,11 @@ /** * @file nat_detection.c * @brief NAT-детекция: STUN-like проверка через третьего BGP-пира. + * + * Самодостаточный модуль: + * - Сам биндится на ETCP_ID_NAT_DETECTION (0x02) и принимает свои пакеты + * - Сам подписывается на on_down новых ETCP-соединений (отмена pending) + * - Получает trigger_checks из topo_group после заполнения senders_list */ #include #include @@ -106,132 +111,15 @@ static void nat_detection_start_link_check(struct NAT_DETECTION* nd, struct TOPO } -/* ====== 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); -} +/* ====== Handle incoming NAT messages (static, dispatched from receive_cbk) ====== */ - -/* ====== 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) +static 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 NATDET_MSG_NAT_INFO) || !group->local_node || !group->local_node->node) return; - const struct TOPOMSG_NAT_INFO* info = (const struct TOPOMSG_NAT_INFO*)data; + const struct NATDET_MSG_NAT_INFO* info = (const struct NATDET_MSG_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); @@ -302,11 +190,11 @@ void nat_detection_handle_nat_info(struct NAT_DETECTION* nd, struct TOPO_GROUP* 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; +static 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 NATDET_MSG_NAT_CHECK_REQ)) return; + const struct NATDET_MSG_NAT_CHECK_REQ* req = (const struct NATDET_MSG_NAT_CHECK_REQ*)data; uint8_t socket_id = req->socket_id; struct ETCP_LINK* target_link = NULL; struct ETCP_LINK* l = from_conn->links; @@ -320,6 +208,182 @@ void nat_detection_handle_nat_check_req(struct NAT_DETECTION* nd, struct TOPO_GR } +/* ====== ETCP callbacks (self-registering) ====== */ + +static void nat_detection_on_conn_down(struct ETCP_CONN* conn, void* arg) { + struct NAT_DETECTION* nd = (struct NAT_DETECTION*)arg; + if (!nd || !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; + } +} + +static void nat_detection_new_conn_cbk(struct ETCP_CONN* conn, void* arg) { + if (!conn || !conn->instance) return; + struct NAT_DETECTION* nd = (struct NAT_DETECTION*)arg; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection set callbacks: %s", conn->log_name); + etcp_conn_add_down_cbk(conn, nat_detection_on_conn_down, nd); +} + + +/* ====== Receive callback (self-binded on ETCP_ID_NAT_DETECTION) ====== */ + +static const char* nat_subcmd_name(uint8_t subcmd) { + switch (subcmd) { + case NATDET_SUBCMD_PING_REQ: return "PING_REQ"; + case NATDET_SUBCMD_PING_RESP: return "PING_RESP"; + case NATDET_SUBCMD_NAT_INFO: return "NAT_INFO"; + case NATDET_SUBCMD_NAT_CHECK_REQ: return "NAT_CHECK_REQ"; + default: return "?"; + } +} + +static void nat_detection_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; + if (!instance || !instance->nat_det || !instance->topo_groups) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid instance/nat_det/topo_groups"); + queue_dgram_free(entry); queue_entry_free(entry); return; + } + struct TOPO_GROUP* group = topo_groups_get_default(instance->topo_groups); + if (!group) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "no default group"); + queue_dgram_free(entry); queue_entry_free(entry); return; + } + struct NAT_DETECTION* nd = instance->nat_det; + uint8_t* data = entry->dgram; uint8_t subcmd = data[1]; + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT recv %s from %s len=%zu", + nat_subcmd_name(subcmd), from_conn->log_name, entry->len); + + if (subcmd == NATDET_SUBCMD_PING_REQ) + route_ping_handle_req(nd, group, from_conn, data, entry->len); + else if (subcmd == NATDET_SUBCMD_PING_RESP) + route_ping_handle_resp(nd, from_conn, data, entry->len); + else if (subcmd == NATDET_SUBCMD_NAT_INFO) + nat_detection_handle_nat_info(nd, group, from_conn, data, entry->len); + else if (subcmd == NATDET_SUBCMD_NAT_CHECK_REQ) + nat_detection_handle_nat_check_req(nd, group, from_conn, data, entry->len); + + queue_dgram_free(entry); queue_entry_free(entry); +} + + +/* ====== NAT check trigger (called from topo_group after senders_list updated) ====== */ + +void nat_detection_trigger_checks(struct NAT_DETECTION* nd, struct TOPO_GROUP* group) { + if (!nd || !group || !nd->inst) return; + struct ll_entry* entry = nd->inst->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; + } +} + + +/* ====== 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; + etcp_bind(inst, ETCP_ID_NAT_DETECTION, nat_detection_receive_cbk); + etcp_add_new_conn_cbk(inst, nat_detection_new_conn_cbk, nd); + 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"); + etcp_remove_new_conn_cbk(nd->inst, nat_detection_new_conn_cbk, nd); + etcp_unbind(nd->inst, ETCP_ID_NAT_DETECTION); + route_ping_destroy_pending(nd); + u_free(nd); +} + + +/* ====== 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 NATDET_MSG_NAT_INFO* pkt = u_calloc(1, sizeof(struct NATDET_MSG_NAT_INFO)); + if (!pkt) return; + pkt->cmd = ETCP_ID_NAT_DETECTION; pkt->subcmd = NATDET_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 NATDET_MSG_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); + } +} + +static 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 NATDET_MSG_NAT_CHECK_REQ* pkt = u_calloc(1, sizeof(struct NATDET_MSG_NAT_CHECK_REQ)); + if (!pkt) return; + pkt->cmd = ETCP_ID_NAT_DETECTION; pkt->subcmd = NATDET_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 NATDET_MSG_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); + } +} + + /* ====== Control API ====== */ void nat_detection_request_check_all(struct NAT_DETECTION* nd, struct TOPO_GROUP* group) { @@ -346,18 +410,3 @@ void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow) { if (!nd) return; nd->allow_nat_check_local = allow ? 1 : 0; } - - -/* ====== BGP subcommand wrappers (for topo_group dispatch) ====== */ - -void nat_detection_handle_ping_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, - struct ETCP_CONN* from_conn, - const uint8_t* data, size_t len) { - route_ping_handle_req(nd, group, from_conn, data, len); -} - -void nat_detection_handle_ping_resp(struct NAT_DETECTION* nd, - struct ETCP_CONN* from_conn, - const uint8_t* data, size_t len) { - route_ping_handle_resp(nd, from_conn, data, len); -} diff --git a/src/nat_detection.h b/src/nat_detection.h index 90d57b47..589faa4b 100644 --- a/src/nat_detection.h +++ b/src/nat_detection.h @@ -2,13 +2,24 @@ * @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 изменений + * Модуль полностью самодостаточен: + * - Владеет своим ETCP_ID (0x02), сам биндится на приём своих пакетов + * - Сам подписывается на conn_down новых ETCP-соединений (отмена pending пингов) + * - NAT-чек запускается через trigger_checks (из topo_group, после senders_list) + * или link_ready (из etcp_connections, при поднятии линка) + * - Получив результат NAT-чека — сам обновляет ETCP_SOCKET, local_node и + * делает broadcast NODEINFO через topo_group * - * Отделен от topo_group — топология только диспетчеризует BGP-подкоманды. + * Публичное API (9 функций): + * create / destroy — жизненный цикл, вызывается из topo_groups_init/destroy + * trigger_checks — запуск чека всех линков, из topo_group_new_conn + * link_ready — запуск чека одного линка, из etcp_connections + * cancel_for_conn — отмена pending при реините/смене адреса + * send_nat_info — немедленное уведомление (DIRECT detection) + * request_check_all — принудительная перепроверка (control API) + * set_allow_local — разрешить чек loopback (тесты) + * route_ping_send_req_addr — низкоуровневый STUN-пинг (тесты) + * route_ping_cancel_for_conn — отмена одного pending-пинга */ #ifndef NAT_DETECTION_H #define NAT_DETECTION_H @@ -19,6 +30,7 @@ extern "C" { #include #include +#include "secure_channel.h" struct UTUN_INSTANCE; struct TOPO_GROUP; @@ -26,78 +38,145 @@ struct ETCP_CONN; struct ETCP_LINK; struct route_ping_pending; +/* ====== ETCP ID ====== */ + +#define ETCP_ID_NAT_DETECTION 0x02 // cmd-байт всех пакетов модуля (etcp_bind / etcp_send) + +/* ====== Sub-команды протокола ====== */ + +#define NATDET_SUBCMD_PING_REQ 0x07 // сервер → третий узел: "пропингуй IP:port" +#define NATDET_SUBCMD_PING_RESP 0x08 // третий узел → сервер: результат пинга +#define NATDET_SUBCMD_NAT_INFO 0x09 // сервер → клиент: "твой NAT — EIM/STRICT/DIRECT" +#define NATDET_SUBCMD_NAT_CHECK_REQ 0x0A // клиент → сервер: "проверь мой NAT, вот мой interface_addr" + +/* ====== Wire-формат сообщений ====== */ + +/** Сервер → третий узел: запрос серии ETCP-пингов на target_ip:target_port */ +struct NATDET_PING_REQ { + uint8_t cmd; // ETCP_ID_NAT_DETECTION + uint8_t subcmd; // NATDET_SUBCMD_PING_REQ + uint16_t reserved; + uint32_t request_id; // для корреляции запрос/ответ + uint8_t count; // число пингов в серии + uint8_t socket_id; // сокет для отправки (на третьем узле) + uint16_t interval_ms; // интервал между пингами + uint16_t timeout_ms; // таймаут одного пинга + uint8_t target_ipv4[4]; // IP цели (network byte order) + uint16_t target_port; // порт цели (network byte order) + uint8_t pubkey[SC_PUBKEY_SIZE]; // опционально: pubkey цели для шифрования пинга +} __attribute__((packed)); + +/** Третий узел → сервер: агрегированная статистика серии пингов */ +struct NATDET_PING_RESP { + uint8_t cmd; + uint8_t subcmd; // NATDET_SUBCMD_PING_RESP + uint16_t reserved; + uint32_t request_id; + uint8_t count_sent; // всего отправлено пингов + uint8_t count_ok; // успешно (получен PONG) + uint16_t avg_rtt; // средний RTT в 0.1ms +} __attribute__((packed)); + +/** Сервер → клиент: результат определения типа NAT */ +struct NATDET_MSG_NAT_INFO { + uint8_t cmd; + uint8_t subcmd; // NATDET_SUBCMD_NAT_INFO + uint8_t socket_id; // ID сокета клиента + uint8_t nat_ip[4]; // NAT-адрес клиента (network byte order) + uint16_t nat_port; // NAT-порт клиента (network byte order) + uint8_t nat_type; // NAT_TYPE_EIM / NAT_TYPE_STRICT / NAT_TYPE_DIRECT +} __attribute__((packed)); + +/** Клиент → сервер: запрос на проверку NAT для указанного сокета */ +struct NATDET_MSG_NAT_CHECK_REQ { + uint8_t cmd; + uint8_t subcmd; // NATDET_SUBCMD_NAT_CHECK_REQ + uint8_t socket_id; // ID сокета который надо проверить + uint32_t interface_ip; // IP клиента на интерфейсе (network byte order) + uint16_t interface_port; // порт клиента на интерфейсе (network byte order) +} __attribute__((packed)); + +/* ====== Callback для завершения пинга ====== */ + 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) - */ +/* ====== Контекст 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; + struct route_ping_pending* ping_pending; // список ожидающих PING_RESP ответов + uint32_t next_ping_req_id; // монотонный счётчик request_id + uint8_t allow_nat_check_local; // разрешить чек для loopback (тесты) }; -/** Аргумент callback NAT check */ +/** Аргумент, передаваемый в callback nat_link_check_cb */ struct nat_check_arg { struct ETCP_LINK* link; uint32_t nat_ip; uint16_t nat_port; }; -/* ====== Lifecycle ====== */ +/* ==================== ПУБЛИЧНОЕ API ==================== */ + +/* -------- Жизненный цикл -------- */ + +/** Создать модуль: etcp_bind на 0x02, подписка на new_conn для on_down очистки. + * Вызывается из topo_groups_init(). */ struct NAT_DETECTION* nat_detection_create(struct UTUN_INSTANCE* inst); + +/** Уничтожить: отписаться от new_conn, etcp_unbind, очистить pending пинги. + * Вызывается из topo_groups_destroy(). */ + void nat_detection_destroy(struct NAT_DETECTION* nd); -/* ====== BGP peer up/down (called from topo_group) ====== */ +/* -------- Служебные: вызываются другими модулями в ответ на события ETCP -------- */ + +/** + * Запустить NAT-чек для всех линков у которых nat_check_status < IN_PROGRESS. + * + * ВАЖНО: должен вызываться из topo_group_new_conn() ПОСЛЕ topo_group_add_to_senders() — + * только тогда новый пир есть в senders_list и find_third_node сможет его найти. + */ -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); +void nat_detection_trigger_checks(struct NAT_DETECTION* nd, struct TOPO_GROUP* group); -/* ====== Link lifecycle (called from etcp_connections) ====== */ +/** Линк поднялся (INIT handshake завершён) — запустить для него NAT-чек. + * Вызывается из etcp_connections: handle_init_response_server(). */ void nat_detection_link_ready(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct ETCP_LINK* link); + +/** Отменить все pending NAT-пинги для соединения и сбросить nat_check_status. + * Вызывается из etcp.c при реините и etcp_connections.c при смене адреса линка. */ + void nat_detection_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); -/* ====== Send NAT messages (called from etcp_connections for DIRECT detection) ====== */ +/** Отправить клиенту NAT_INFO — результат DIRECT detection (публичный IP без пинга). + * Вызывается из etcp_connections: handle_init_response_server(). */ 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 ====== */ + +/* -------- Control API -------- */ + +/** Принудительная перепроверка NAT для всех линков (etcpmon команда "nat"). + * Вызывается из control_server. */ 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); +/** Разрешить NAT-чек для локальных подсетей (127.x, 10.x, 192.168.x). + * По умолчанию запрещено — только для тестов где узлы на loopback. */ -/* ====== route_ping wrapper (for test compatibility) ====== */ +void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow); -#include "secure_channel.h" +/** Отправить STUN-пинг через BGP-посредника (to_conn) к target_ip:target_port. + * pubkey — опционально (NULL = взять из nodeinfo). + * count пингов, интервал interval_ms, таймаут одного timeout_ms, общий wait_timeout_ms. */ int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct ETCP_CONN* to_conn, @@ -106,8 +185,10 @@ int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, 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 } diff --git a/src/route_ping.c b/src/route_ping.c index dd6dfe81..d6ce3ef1 100644 --- a/src/route_ping.c +++ b/src/route_ping.c @@ -72,11 +72,11 @@ static void route_ping_pending_timeout(void* arg) { // прошел ответ "серия пигнов на удаленном узле завершена" 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)) { + if (!nd || !from_conn || !data || len < sizeof(struct NATDET_PING_RESP)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return; } - const struct TOPO_PING_RESP* resp = (const struct TOPO_PING_RESP*)data; + const struct NATDET_PING_RESP* resp = (const struct NATDET_PING_RESP*)data; DEBUG_INFO(DEBUG_CATEGORY_BGP, "from=%s request_id=%08x sent=%u ok=%u avg_rtt=%u", from_conn->log_name, (unsigned)resp->request_id, (unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt); @@ -175,14 +175,14 @@ int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; } - size_t pkt_size = pubkey ? sizeof(struct TOPO_PING_REQ) : offsetof(struct TOPO_PING_REQ, pubkey); - struct TOPO_PING_REQ* req_pkt = u_calloc(1, pkt_size); + size_t pkt_size = pubkey ? sizeof(struct NATDET_PING_REQ) : offsetof(struct NATDET_PING_REQ, pubkey); + struct NATDET_PING_REQ* req_pkt = u_calloc(1, pkt_size); if (!req_pkt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed"); return -2; } - req_pkt->cmd = ETCP_ID_TOPO_ENTRY; - req_pkt->subcmd = TOPO_SUBCMD_PING_REQ; + req_pkt->cmd = ETCP_ID_NAT_DETECTION; + req_pkt->subcmd = NATDET_SUBCMD_PING_REQ; uint32_t new_req_id = nd->next_ping_req_id++; req_pkt->request_id = new_req_id; req_pkt->count = count; @@ -247,10 +247,10 @@ static void route_ping_series_finish(struct route_ping_series_ctx* ctx) { : (uint16_t)(ctx->timeout_ms * 10); /* таймаут как "плохо" */ /* Отправляем ответ по BGP */ - struct TOPO_PING_RESP* resp = u_calloc(1, sizeof(struct TOPO_PING_RESP)); + struct NATDET_PING_RESP* resp = u_calloc(1, sizeof(struct NATDET_PING_RESP)); if (resp) { - resp->cmd = ETCP_ID_TOPO_ENTRY; - resp->subcmd = TOPO_SUBCMD_PING_RESP; + resp->cmd = ETCP_ID_NAT_DETECTION; + resp->subcmd = NATDET_SUBCMD_PING_RESP; resp->request_id = ctx->request_id; resp->count_sent = ctx->count_sent; resp->count_ok = ctx->count_ok; @@ -259,7 +259,7 @@ static void route_ping_series_finish(struct route_ping_series_ctx* ctx) { struct ll_entry* e = queue_entry_new(0); if (e) { e->dgram = (uint8_t*)resp; - e->len = sizeof(struct TOPO_PING_RESP); + e->len = sizeof(struct NATDET_PING_RESP); etcp_send(ctx->reply_conn, e); DEBUG_INFO(DEBUG_CATEGORY_BGP, "PING series done %s request_id=%08x sent=%u ok=%u avg_rtt=%u", @@ -327,12 +327,12 @@ void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, const uint8_t* data, size_t len) { (void)nd; - if (!group || !from_conn || !data || len < offsetof(struct TOPO_PING_REQ, pubkey)) { + if (!group || !from_conn || !data || len < offsetof(struct NATDET_PING_REQ, pubkey)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len); return; } - const struct TOPO_PING_REQ* req_pkt = (const struct TOPO_PING_REQ*)data; + const struct NATDET_PING_REQ* req_pkt = (const struct NATDET_PING_REQ*)data; /* Создаём контекст серии */ struct route_ping_series_ctx* ctx = u_calloc(1, sizeof(struct route_ping_series_ctx)); @@ -350,7 +350,7 @@ void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, struct TOPO_NODEQ* req_nq = topo_node_find_by_id(group, from_conn->peer_node_id); if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb(); } - if (len >= sizeof(struct TOPO_PING_REQ)) { + if (len >= sizeof(struct NATDET_PING_REQ)) { memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE); } diff --git a/src/route_ping.h b/src/route_ping.h index bb07c924..a038b2c5 100644 --- a/src/route_ping.h +++ b/src/route_ping.h @@ -5,58 +5,25 @@ extern "C" { #endif - #include #include #include "nat_detection.h" -#include "secure_channel.h" - -struct TOPO_PING_REQ { - uint8_t cmd; // ETCP_ID_TOPO_ENTRY - uint8_t subcmd; // TOPO_SUBCMD_PING_REQ - uint16_t reserved; // padding для выравнивания request_id - uint32_t request_id; // для корреляции - uint8_t count; // число пингов - uint8_t socket_id; // id сокета пингуемого узла - uint16_t interval_ms; // интервал между пингами - uint16_t timeout_ms; // таймаут одного пинга - uint8_t target_ipv4[4]; // custom target IP (0 = use nodeinfo sockets) - uint16_t target_port; // custom target port - uint8_t pubkey[SC_PUBKEY_SIZE]; // pubkey for encryption if target not in nodes -} __attribute__((packed)); - -struct TOPO_PING_RESP { - uint8_t cmd; - uint8_t subcmd; // TOPO_SUBCMD_PING_RESP - uint16_t reserved; // padding для выравнивания - uint32_t request_id; - uint8_t count_sent; - uint8_t count_ok; - uint16_t avg_rtt; // средний RTT в 0.1ms -} __attribute__((packed)); -// Отправить запрос пинга по произвольному IP:port (используется для NAT-детекции) -// Если pubkey != NULL, он передается в пакете (для пинга без локального nodeinfo) -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); +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_destroy_pending(struct NAT_DETECTION* nd); - -// Отменить pending запросы для указанного соединения (при удалении conn) void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); -// Обработчики, вызываемые из 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 } #endif diff --git a/src/topo_group.c b/src/topo_group.c index 2aba220c..6f3b7606 100644 --- a/src/topo_group.c +++ b/src/topo_group.c @@ -107,10 +107,6 @@ static const char* group_subcmd_name(uint8_t subcmd) { case TOPO_SUBCMD_NODEINFO: return "NODEINFO"; case TOPO_SUBCMD_REQUEST_TABLE: return "REQ_TABLE"; case TOPO_SUBCMD_WITHDRAW: return "WITHDRAW"; - case TOPO_SUBCMD_PING_REQ: return "PING_REQ"; - case TOPO_SUBCMD_PING_RESP: return "PING_RESP"; - case TOPO_SUBCMD_NAT_INFO: return "NAT_INFO"; - case TOPO_SUBCMD_NAT_CHECK_REQ: return "NAT_CHECK_REQ"; case TOPO_SUBCMD_TABLE_COMPLETE: return "TABLE_COMPLETE"; case TOPO_SUBCMD_ERR_GROUP_MISMATCH: return "ERR_GROUP_MISMATCH"; default: return "?"; @@ -159,22 +155,6 @@ 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) { - 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)) { @@ -382,12 +362,11 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { struct TOPO_NODEQ* peer_nq = topo_node_find_by_id(group, conn->peer_node_id); topo_group_add_to_senders(group, conn); + if (group->instance->nat_det) nat_detection_trigger_checks(group->instance->nat_det, group); if (peer_nq && peer_nq->alien) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "peer 0x%016llx is alien, skipping route exchange", (unsigned long long)conn->peer_node_id); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s ALIEN=1 — SKIP", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id); return; } 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); - if (group->instance->nat_det) nat_detection_bgp_peer_up(group->instance->nat_det, group, conn); - topo_group_send_table_request(group, conn); } @@ -402,8 +381,6 @@ 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); - nat_detection_bgp_peer_down(group->instance->nat_det, group, conn); - struct ROUTE_TABLE* rt = conn->instance->rt; int nodes_removed = 0; struct ll_entry* node_entry = group->nodes ? group->nodes->head : NULL; diff --git a/src/topo_group.h b/src/topo_group.h index b1dddcdb..1e6d3fe0 100644 --- a/src/topo_group.h +++ b/src/topo_group.h @@ -55,10 +55,6 @@ 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 // завершение начальной синхронизации таблицы #define TOPO_SUBCMD_ERR_GROUP_MISMATCH 0x0C // ошибка несоответствия типа группы @@ -107,30 +103,6 @@ struct TOPO_GROUP_CONN_ITEM { struct ETCP_CONN* conn; }; -/** - * @brief Пакет NAT_INFO (фиксированный) - */ -struct TOPOMSG_NAT_INFO { - uint8_t cmd; - uint8_t subcmd; - uint8_t socket_id; // MY socket id (link->conn->sock_id) - uint8_t nat_ip[4]; // network byte order - uint16_t nat_port; // network byte order - uint8_t nat_type; // NAT_TYPE_* -} __attribute__((packed)); - -/** - * @brief Пакет запроса на проверку NAT от клиента к серверу - */ -struct TOPOMSG_NAT_CHECK_REQ { - uint8_t cmd; - uint8_t subcmd; // TOPO_SUBCMD_NAT_CHECK_REQ - uint8_t socket_id; // ID сокета клиента, который надо проверить - uint32_t interface_ip; // interface_addr IP клиента (network byte order) - uint16_t interface_port; // interface_addr port клиента (network byte order) -} __attribute__((packed)); - -struct route_ping_pending; struct ETCP_LINK; /**