Browse Source

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)
topo_upd
Evgeny 3 months ago
parent
commit
90363650e3
  1. 335
      src/nat_detection.c
  2. 171
      src/nat_detection.h
  3. 26
      src/route_ping.c
  4. 45
      src/route_ping.h
  5. 25
      src/topo_group.c
  6. 28
      src/topo_group.h

335
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 <stdlib.h>
#include <string.h>
@ -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);
}

171
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 <stdint.h>
#include <stddef.h>
#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
}

26
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);
}

45
src/route_ping.h

@ -5,58 +5,25 @@
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#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

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

28
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;
/**

Loading…
Cancel
Save