Measure owned endpoint mapping and inbound reachability separately

This commit is contained in:
evgeny committed 2026-10-12 00:01:40 +02:00
1 parent b54307a0df
commit ff69552a61
51 files changed
+2620 -2483

No files matched your search

+42 -142
View File
@@ -1,161 +1,61 @@
/**
* @file nat_detection.h
* @brief NAT-детекция через третьего пира (STUN-like ping).
*
* Самодостаточный модуль на своём ETCP_ID (0x02):
* сам биндится, сам подписывается на conn_up/conn_down,
* ищет третий узел прямо в instance->connections,
* обновляет ETCP_SOCKET/loc_node и broadcast-ит NODEINFO.
*/
/* Владелец измеряет собственный endpoint. Пиры передают наблюдения и выполняют
* прямые пробы; управление проверяющим идёт через явно выбранного посредника.
* Все операции и callbacks выполняются в uasync-потоке ядра. */
#ifndef NAT_DETECTION_H
#define NAT_DETECTION_H
#include <stddef.h>
#include <stdint.h>
#include "../lib/socket_compat.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "secure_channel.h"
struct UTUN_INSTANCE;
struct TOPO_GROUP;
struct ETCP_CONN;
struct ETCP_LINK;
struct route_ping_pending;
struct ETCP_SOCKET;
struct NAT_DETECTION;
/* ====== 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);
/* ====== Контекст NAT-детекции (один на UTUN_INSTANCE) ====== */
struct NAT_DETECTION {
struct UTUN_INSTANCE* inst;
struct route_ping_pending* ping_pending; // список ожидающих PING_RESP ответов
uint32_t next_ping_req_id; // монотонный счётчик request_id
uint8_t allow_nat_check_local; // разрешить чек для loopback (тесты)
enum nat_check_reason {
NAT_REASON_NONE, NAT_REASON_NO_OBSERVER, NAT_REASON_NO_HELPER, NAT_REASON_CONTROL_LOST,
NAT_REASON_SEND_ERROR, NAT_REASON_TIMEOUT, NAT_REASON_ADDRESS_CHANGED, NAT_REASON_CONTACT_HISTORY,
NAT_REASON_PROXY, NAT_REASON_NO_LISTENER, NAT_REASON_CANCELLED, NAT_REASON_INVALID_OBSERVATION, NAT_REASON_DEPENDENT_MAPPING
};
/** Аргумент, передаваемый в callback nat_link_check_cb */
struct nat_check_arg {
struct ETCP_LINK* link;
uint32_t nat_ip;
uint16_t nat_port;
struct nat_check_status {
uint64_t generation, round;
uint64_t observer, helper;
uint32_t rounds, helper_reselections;
uint8_t phase, reason, history_complete;
};
/* ==================== ПУБЛИЧНОЕ API ==================== */
/* -------- Жизненный цикл -------- */
/** Создать модуль: etcp_bind на 0x02, подписка на new_conn (on_up + on_down). */
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);
/* -------- Служебные: вызываются другими модулями в ответ на события ETCP -------- */
/** Линк поднялся (INIT handshake завершён) — запустить для него NAT-чек.
* Вызывается из etcp_connections: handle_init_response_server(). */
void nat_detection_link_ready(struct NAT_DETECTION* nd, 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);
/** Отправить клиенту 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);
/* -------- Control API -------- */
/** Принудительная перепроверка NAT для всех линков (etcpmon команда "nat").
* Вызывается из control_server. */
void nat_detection_request_check_all(struct NAT_DETECTION* nd);
/* -------- Тестовые хелперы -------- */
/** Разрешить NAT-чек для локальных подсетей (127.x, 10.x, 192.168.x).
* По умолчанию запрещено — только для тестов где узлы на loopback. */
void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow);
/** Отправить 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,
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);
struct NAT_DETECTION *nat_detection_create(struct UTUN_INSTANCE *instance);
void nat_detection_destroy(struct NAT_DETECTION *detector);
void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK *link);
void nat_detection_cancel_for_conn(struct NAT_DETECTION *detector, struct ETCP_CONN *connection);
void nat_detection_socket_removed(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket);
void nat_detection_note_send(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket,
const struct sockaddr_storage *destination);
void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_LINK *link,
const struct sockaddr_storage *observed);
void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket,
const uint8_t peer_key[32], const struct sockaddr_storage *source,
const uint8_t *tag, size_t length);
void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket,
const uint8_t peer_key[32], const struct sockaddr_storage *source,
const uint8_t *tag, size_t length);
void nat_detection_request_check_all(struct NAT_DETECTION *detector);
int nat_detection_request_check_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, uint64_t observer);
void nat_detection_set_allow_local(struct NAT_DETECTION *detector, int allow);
/* При ручном режиме помощники продолжают работать, раунды запускает request_check_all. */
void nat_detection_set_automatic(struct NAT_DETECTION *detector, int automatic);
int nat_detection_get_status(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, struct nat_check_status *status);
const char *nat_check_reason_name(uint8_t reason);
#ifdef __cplusplus
}
#endif
#endif /* NAT_DETECTION_H */
#endif