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

+51
View File
@@ -0,0 +1,51 @@
/* Профиль конкретного endpoint: изменение адреса и доступность входа независимы.
* checked_at — время измерения владельца в Unix microseconds, не версия NODEINFO.
* Профиль помогает выбирать попытки; работоспособность подтверждает транспорт. */
#ifndef ADDRESS_PROFILE_H
#define ADDRESS_PROFILE_H
#include <stdint.h>
enum address_mapping {
ADDRESS_MAP_UNKNOWN,
ADDRESS_MAP_UNCHANGED,
ADDRESS_MAP_EIM,
ADDRESS_MAP_DEPENDENT
};
enum address_inbound {
ADDRESS_IN_UNKNOWN,
ADDRESS_IN_OPEN,
ADDRESS_IN_CONTACT
};
#define ADDRESS_PROFILE_TTL_US UINT64_C(300000000)
struct address_profile {
uint8_t mapping;
uint8_t inbound;
uint64_t evidence_id;
uint64_t checked_at;
};
static inline const char *address_mapping_name(uint8_t value) {
static const char *const names[] = {"UNKNOWN", "UNCHANGED", "EIM", "DEPENDENT"};
return value <= ADDRESS_MAP_DEPENDENT ? names[value] : "INVALID";
}
static inline const char *address_inbound_name(uint8_t value) {
static const char *const names[] = {"UNKNOWN", "OPEN", "CONTACT_REQUIRED"};
return value <= ADDRESS_IN_CONTACT ? names[value] : "INVALID";
}
static inline int address_profile_valid(const struct address_profile *profile) {
return profile && profile->mapping <= ADDRESS_MAP_DEPENDENT && profile->inbound <= ADDRESS_IN_CONTACT &&
((!profile->mapping && !profile->inbound) || (profile->evidence_id && profile->checked_at));
}
static inline int address_profile_fresh(const struct address_profile *profile, uint64_t now_us) {
return profile && profile->evidence_id && profile->checked_at && now_us >= profile->checked_at &&
now_us - profile->checked_at <= ADDRESS_PROFILE_TTL_US;
}
#endif
+27 -7
View File
@@ -24,6 +24,7 @@
#include "sock_match.h"
#include "topo_group.h"
#include "route_ping.h"
#include "ntp_time.h"
#ifdef UTUN_HAVE_STANDBY
#include "standby.h"
#endif
@@ -96,17 +97,33 @@ uint8_t cm_classify_v6_addr(const uint8_t addr[16]) {
return CM_V6_DIR;
}
/* Та же классификация, что при создании линков; EIM и прямой IPv6 доступны извне. */
/* Подсказка для расписания проб, не доказательство входа. PUBLIC-политика также
* разрешает сначала попробовать объявленный LAN listener перед запуском резерва. */
int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) {
struct TOPO_NODE* ni = nq ? topo_node_registry_find(mgr->instance->topo_groups, nq->node_id) : NULL;
if (!ni) return 0;
int64_t current = ntp_time_get_us(mgr->instance);
uint64_t now_us = current > 0 ? (uint64_t)current : 0;
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
if (!(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) continue;
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next)
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, m->nat_type) == SOCK_TC_PUB) return 1;
uint32_t ip; memcpy(&ip, a->addr, 4);
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) {
if (m->id != a->socket_id) continue;
uint8_t cls = sock_target_class(a->type, m->config_type, &m->address_profile, ip_is_public(ip), now_us);
if (cls != SOCK_TC_PRIV && cls != SOCK_TC_UNREACH) return 1;
if (a->type == TOPO_ADDR_INTERFACE && m->config_type == CFG_SERVER_TYPE_PUBLIC) return 1;
}
}
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
if (!(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) continue;
for (const struct TOPO_SOCKMETA6 *meta = ni->v6_sock_meta; meta; meta = meta->next) {
if (meta->id != a->socket_id) continue;
uint8_t cls = sock_target_class(a->type, meta->config_type, &meta->address_profile,
cm_classify_v6_addr(a->addr) == CM_V6_DIR, now_us);
if (cls != SOCK_TC_PRIV && cls != SOCK_TC_UNREACH) return 1;
if (a->type == TOPO_ADDR_INTERFACE && meta->config_type == CFG_SERVER_TYPE_PUBLIC) return 1;
}
}
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next)
if ((a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP)) && cm_classify_v6_addr(a->addr) == CM_V6_DIR) return 1;
return 0;
}
@@ -454,7 +471,7 @@ static void cm_start_attempt(struct CONN_MGR_ENTRY* entry, int recovery) {
entry->indirect_start_tb = entry->start_tb + (recovery || (!local_public && !peer_public) ? 0 : CONN_MGR_INDIRECT_DELAY_MS * 10);
entry->deadline_tb = entry->indirect_start_tb + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10;
if (!recovery) entry->initial_deadline_tb = entry->deadline_tb;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s group=%016llx peer=%016llx public=%d/%d reverse=%llums indirect=%llums reserve=%d",
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s group=%016llx peer=%016llx inbound_candidates=%d/%d reverse=%llums indirect=%llums reserve=%d",
recovery ? "recover" : "start", (unsigned long long)mgr->group->group_id, (unsigned long long)entry->node_id,
local_public, peer_public, (unsigned long long)((entry->reverse_start_tb - entry->start_tb) / 10),
(unsigned long long)((entry->indirect_start_tb - entry->start_tb) / 10), entry->indirect_confirmed);
@@ -608,10 +625,13 @@ void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
(unsigned long long)entry->node_id); return;
}
int addresses = 0;
int64_t current = ntp_time_get_us(entry->mgr->instance);
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
if (!(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) continue;
uint32_t ip; memcpy(&ip, a->addr, 4);
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next)
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, m->nat_type) == SOCK_TC_PRIV) {
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, &m->address_profile,
ip_is_public(ip), current > 0 ? (uint64_t)current : 0) == SOCK_TC_PRIV) {
addresses++; break;
}
}
File diff suppressed because it is too large. Load diff
+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
+10 -8
View File
@@ -93,7 +93,7 @@ static int addr_eq(const struct TOPO_ADDR4* a, uint32_t ip, uint16_t port) {
// собирает список локальных сокетов-кандидатов для probing заданного адреса
// (сопоставление через sock_match — роли сокетов и классы адресов)
static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
uint8_t addr_type, uint8_t tcfg, uint8_t tnat,
uint8_t addr_type, uint8_t tcfg, const struct address_profile *profile,
const struct sockaddr_storage* target_addr,
struct ETCP_SOCKET** out_sockets, uint8_t max_count) {
if (!instance || !target_addr || !out_sockets || max_count == 0) return 0;
@@ -107,7 +107,7 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
// IPv6: prefer sockets in same /64 subnet
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->is_tcp) { e_sock = e_sock->next; continue; }
if (e_sock->is_tcp || e_sock->probe_only) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET6) {
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
if (memcmp(&if_sin6->sin6_addr, &target_sin6->sin6_addr, 8) == 0) {
@@ -121,7 +121,7 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
// Pass 2: all other IPv6 sockets
e_sock = instance->etcp_sockets;
while (e_sock && found < (int)max_count) {
if (e_sock->is_tcp) { e_sock = e_sock->next; continue; }
if (e_sock->is_tcp || e_sock->probe_only) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family != AF_INET6) { e_sock = e_sock->next; continue; }
int dup = 0;
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
@@ -137,7 +137,7 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
int nviews = sock_collect_views(instance, views, CONN_MAX_SOCKET_CANDIDATES);
for (int i = 0; i < nviews && found < (int)max_count; i++) {
if (views[i].is_tcp) continue;
if (!sock_match(&views[i], addr_type, tcfg, tnat, target_ip)) continue;
if (!sock_match(&views[i], addr_type, tcfg, profile, target_ip)) continue;
out_sockets[found++] = views[i].sock;
}
@@ -346,11 +346,12 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
ctx->is_tcp = 1;
ctx->candidate_count = 1; ctx->candidate_index = 0; ctx->count_total = 1;
} else {
uint8_t tcfg = CFG_SERVER_TYPE_UNKNOWN, tnat = NAT_TYPE_UNKNOWN;
uint8_t tcfg = CFG_SERVER_TYPE_UNKNOWN;
const struct address_profile *profile = NULL;
for (const struct TOPO_SOCKMETA4* mm = ni->v4_sock_meta; mm; mm = mm->next)
if (mm->id == a->socket_id) { tcfg = mm->config_type; tnat = mm->nat_type; break; }
if (mm->id == a->socket_id) { tcfg = mm->config_type; profile = &mm->address_profile; break; }
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, a->type, tcfg, tnat, &target, candidates, CONN_MAX_SOCKET_CANDIDATES);
int cand_count = conn_match_candidate_sockets(instance, a->type, tcfg, profile, &target, candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for target %s:%u type=%d", ip_to_str(&ip, AF_INET).str, port, a->type); u_free(ctx); continue; }
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count; ctx->candidate_index = 0; ctx->count_total = CONN_PROBE_COUNT;
@@ -393,7 +394,8 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
ctx->candidate_count = 1; ctx->candidate_index = 0; ctx->count_total = 1;
} else {
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
int cand_count = conn_match_candidate_sockets(instance, a6->type, CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN, &target, candidates, CONN_MAX_SOCKET_CANDIDATES);
int cand_count = conn_match_candidate_sockets(instance, a6->type, CFG_SERVER_TYPE_UNKNOWN, NULL,
&target, candidates, CONN_MAX_SOCKET_CANDIDATES);
if (cand_count == 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for IPv6 target type=%d", a6->type); u_free(ctx); continue; }
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
ctx->candidate_count = cand_count; ctx->candidate_index = 0; ctx->count_total = CONN_PROBE_COUNT;
+59 -410
View File
@@ -1,428 +1,77 @@
#include <stdlib.h>
#include "route_ping.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 "topo_node.h"
#include "topo_group.h"
#include "route_ping.h"
#include "../lib/u_async.h"
struct route_ping_series_ctx {
struct ETCP_CONN* reply_conn;
uint32_t request_id;
struct sockaddr_storage target_addr;
uint8_t pubkey[SC_PUBKEY_SIZE];
struct ETCP_SOCKET* local_sock;
uint8_t count_total;
uint8_t count_sent;
uint8_t count_ok;
uint32_t sum_rtt; /* в 0.1 ms */
uint16_t timeout_ms;
};
// ==================================== блок обработки запросов на удаленные пинги
struct route_ping_pending {
struct route_ping_pending* next;
struct NAT_DETECTION* nd;
uint32_t request_id;
route_ping_callback_t callback;
void* arg;
void* timeout_timer;
uint8_t cancelled;
struct ETCP_CONN* via_conn; // через кого отправили запрос (third_conn)
};
// если удаленный узел долго не отвечает - вызываем коллбэк по таймауту
static void route_ping_pending_timeout(void* arg) {
struct route_ping_pending* p = (struct route_ping_pending*)arg;
if (!p) return;
p->timeout_timer = NULL;
if (p->cancelled) {
u_free(p);
return;
}
struct NAT_DETECTION* nd = p->nd;
struct route_ping_pending** cur = &nd->ping_pending;
while (*cur) {
if (*cur == p) {
*cur = p->next;
break;
}
cur = &(*cur)->next;
}
if (p->callback) {
p->callback(0, 0, 0, 0, p->arg);
}
u_free(p);
static void write64(uint8_t *out, uint64_t value) {
for (unsigned i = 0; i < 8; i++) out[i] = (uint8_t)(value >> (56 - i * 8));
}
// прошел ответ "серия пигнов на удаленном узле завершена"
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 NATDET_PING_RESP)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
return;
}
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);
topo_node_ping_update_rtt(nd->inst->topo_groups, from_conn->peer_node_id, resp->avg_rtt);
struct route_ping_pending** cur = &nd->ping_pending;
while (*cur) {
struct route_ping_pending* p = *cur;
if (p->request_id == resp->request_id) {
*cur = p->next;
if (p->timeout_timer) {
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
if (p->callback) {
int success = (resp->count_ok > 0) ? 1 : 0;
p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg);
}
u_free(p);
return;
}
cur = &(*cur)->next;
}
DEBUG_WARN(DEBUG_CATEGORY_BGP, "request_id=%08x not found in pending list",
(unsigned)resp->request_id);
}
void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) {
if (!nd || !conn) return;
if (!nd->ping_pending) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: ping_pending is NULL for %s", conn->log_name);
return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: checking pending for %s", conn->log_name);
int cancelled = 0;
struct route_ping_pending** cur = &nd->ping_pending;
while (*cur) {
struct route_ping_pending* p = *cur;
struct nat_check_arg* na = (struct nat_check_arg*)p->arg;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, " checking pending req_id=%08x via=%s link->etcp=%s",
(unsigned)p->request_id,
p->via_conn ? p->via_conn->log_name : "NULL",
(na && na->link && na->link->etcp) ? na->link->etcp->log_name : "NULL");
if ((na && na->link && na->link->etcp == conn) || p->via_conn == conn) {
*cur = p->next;
uint32_t rid = p->request_id;
if (p->timeout_timer) {
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
p->cancelled = 1;
if (na) na->link = NULL;
u_free(na);
u_free(p);
cancelled++;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_conn: cancelled req_id=%08x", (unsigned)rid);
} else {
cur = &(*cur)->next;
}
}
if (cancelled == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: no pending found for %s", conn->log_name);
}
}
void route_ping_cancel_for_link(struct NAT_DETECTION* nd, struct ETCP_LINK* link) {
if (!nd || !link) return;
struct route_ping_pending** cur = &nd->ping_pending;
while (*cur) {
struct route_ping_pending* p = *cur;
struct nat_check_arg* na = (struct nat_check_arg*)p->arg;
if (na && na->link == link) {
*cur = p->next;
uint32_t rid = p->request_id;
if (p->timeout_timer) {
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
p->cancelled = 1;
na->link = NULL;
u_free(na);
u_free(p);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_link: cancelled req_id=%08x link=%p", (unsigned)rid, (void*)link);
} else {
cur = &(*cur)->next;
}
}
}
void route_ping_destroy_pending(struct NAT_DETECTION* nd) {
if (!nd) return;
struct route_ping_pending* p = nd->ping_pending;
while (p) {
struct route_ping_pending* next = p->next;
if (p->timeout_timer) {
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
p->timeout_timer = NULL;
}
p->cancelled = 1;
u_free(p);
p = next;
}
nd->ping_pending = NULL;
}
// ========================================================================
int route_ping_send_req_addr(struct 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)group;
if (!nd || !to_conn || count == 0 || timeout_ms == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
return -1;
}
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_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;
req_pkt->interval_ms = interval_ms;
req_pkt->timeout_ms = timeout_ms;
/* IP уже в network byte order, порт конвертируем */
memcpy(req_pkt->target_ipv4, &target_ip, 4);
req_pkt->target_port = htons(target_port);
if (pubkey) {
memcpy(req_pkt->pubkey, pubkey, SC_PUBKEY_SIZE);
}
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(req_pkt);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "queue_entry_new failed");
return -3;
}
e->dgram = (uint8_t*)req_pkt;
e->len = pkt_size;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
(unsigned)new_req_id,
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
int ret = etcp_send(to_conn, e);
if (ret != 0) {
u_free(req_pkt);
queue_entry_free(e);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send failed");
return -4;
}
struct route_ping_pending* pending = u_calloc(1, sizeof(struct route_ping_pending));
if (!pending) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "pending alloc failed");
return -5;
}
pending->nd = nd;
pending->request_id = new_req_id;
pending->callback = cb;
pending->arg = arg;
pending->via_conn = to_conn;
pending->next = nd->ping_pending;
nd->ping_pending = pending;
pending->timeout_timer = uasync_set_timeout(nd->inst->ua, wait_timeout_ms * 10, pending,
route_ping_pending_timeout, "route_ping");
int route_ping_addr_encode(uint8_t out[ROUTE_ADDR_WIRE_SIZE], const struct sockaddr_storage *address) {
if (!out || !address) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address encode: missing argument"); return -1; }
memset(out, 0, ROUTE_ADDR_WIRE_SIZE);
if (address->ss_family == AF_INET) {
const struct sockaddr_in *v4 = (const struct sockaddr_in *)address;
out[0] = 4; memcpy(out + 1, &v4->sin_addr, 4); memcpy(out + 17, &v4->sin_port, 2);
} else if (address->ss_family == AF_INET6) {
const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)address;
out[0] = 6; memcpy(out + 1, &v6->sin6_addr, 16); memcpy(out + 17, &v6->sin6_port, 2);
} else { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address encode: unsupported family=%d", address->ss_family); return -1; }
return 0;
}
static void route_ping_series_finish(struct route_ping_series_ctx* ctx) {
if (!ctx || !ctx->reply_conn) {
u_free(ctx);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "zero args");
return;
}
uint16_t avg_rtt = (ctx->count_ok > 0)
? (uint16_t)(ctx->sum_rtt / ctx->count_ok)
: (uint16_t)(ctx->timeout_ms * 10); /* таймаут как "плохо" */
/* Отправляем ответ по BGP */
struct NATDET_PING_RESP* resp = u_calloc(1, sizeof(struct NATDET_PING_RESP));
if (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;
resp->avg_rtt = avg_rtt;
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = (uint8_t*)resp;
e->len = sizeof(struct NATDET_PING_RESP);
if (etcp_send(ctx->reply_conn, e) != 0) {
queue_dgram_free(e); queue_entry_free(e);
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP,
"PING series done %s request_id=%08x sent=%u ok=%u avg_rtt=%u",
ctx->reply_conn->log_name,
(unsigned)ctx->request_id,
(unsigned)ctx->count_sent,
(unsigned)ctx->count_ok,
(unsigned)avg_rtt);
}
} else {
u_free(resp);
}
}
u_free(ctx);
int route_ping_addr_decode(const uint8_t in[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address) {
if (!in || !address) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address decode: missing argument"); return -1; }
memset(address, 0, sizeof(*address));
if (in[0] == 4) {
struct sockaddr_in *v4 = (struct sockaddr_in *)address;
v4->sin_family = AF_INET; memcpy(&v4->sin_addr, in + 1, 4); memcpy(&v4->sin_port, in + 17, 2);
static const uint8_t zero[12];
if (memcmp(in + 5, zero, 12)) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: noncanonical IPv4"); return -1; }
} else if (in[0] == 6) {
struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)address;
v6->sin6_family = AF_INET6; memcpy(&v6->sin6_addr, in + 1, 16); memcpy(&v6->sin6_port, in + 17, 2);
} else { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: invalid family=%u", in[0]); return -1; }
if (!in[17] && !in[18]) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: zero port"); return -1; }
return 0;
}
/* Callback одного пинга из серии */
static void route_ping_single_cb(int success,
uint16_t rtt,
void* arg,
uint64_t nonce,
const uint8_t* resp_data,
size_t resp_data_len) {
(void)nonce; (void)resp_data; (void)resp_data_len;
struct route_ping_series_ctx* ctx = (struct route_ping_series_ctx*)arg;
if (!ctx) return;
/* Обновляем статистику */
ctx->count_sent++;
if (success) {
ctx->count_ok++;
ctx->sum_rtt += rtt;
int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b, int include_port) {
if (!a || !b || a->ss_family != b->ss_family) return 0;
if (a->ss_family == AF_INET) {
const struct sockaddr_in *av = (const struct sockaddr_in *)a, *bv = (const struct sockaddr_in *)b;
return av->sin_addr.s_addr == bv->sin_addr.s_addr && (!include_port || av->sin_port == bv->sin_port);
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping cb: ok=%d rtt=%u sent=%d/%d nonce=%016llx",
success, rtt, ctx->count_sent, ctx->count_total, (unsigned long long)nonce);
/* Если ещё не все пакеты отправлены — сразу шлём следующий */
if (ctx->count_sent < ctx->count_total) {
int ret = etcp_send_ping_to_socket(
ctx->reply_conn->instance,
ctx->local_sock,
ctx->pubkey,
&ctx->target_addr,
ctx->timeout_ms,
route_ping_single_cb,
ctx,
NULL, 0, 0);
if (ret != 0) {
/* Не смогли отправить следующий — завершаем серию досрочно */
route_ping_series_finish(ctx);
}
/* else: следующий пинг запущен, ждём его callback */
} else {
/* Все пакеты обработаны */
route_ping_series_finish(ctx);
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6 *av = (const struct sockaddr_in6 *)a, *bv = (const struct sockaddr_in6 *)b;
return !memcmp(&av->sin6_addr, &bv->sin6_addr, 16) && (!include_port || av->sin6_port == bv->sin6_port);
}
return 0;
}
// Обработчик PING_REQ (нам запросили "пропингуй узел и верни результат")
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) {
if (!from_conn || !nd || !data || len < offsetof(struct NATDET_PING_REQ, pubkey)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len);
return;
void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t round) {
memcpy(out, "NAT2UDP!", 8); out[8] = socket->sock_id;
write64(out + 9, socket->generation); write64(out + 17, round);
}
int route_ping_observe(struct ETCP_SOCKET *socket, const uint8_t peer_key[SC_PUBKEY_SIZE],
const struct sockaddr_storage *peer_address, uint64_t round, int timeout_ms,
etcp_ping_callback_t callback, void *arg) {
if (!socket || !socket->generation || !round) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "observe: invalid endpoint/round"); return -1; }
uint8_t tag[ROUTE_OBSERVE_TAG_SIZE]; route_ping_make_tag(tag, socket, round);
return etcp_send_ping_to_socket(socket->instance, socket, peer_key, peer_address, timeout_ms,
callback, arg, tag, sizeof(tag), ETCP_PING_FLAG_OBSERVE);
}
int route_ping_observation(const uint8_t *data, size_t length, const struct ETCP_SOCKET *socket,
uint64_t round, struct sockaddr_storage *observed) {
if (!data || !socket || length != ROUTE_ADDR_WIRE_SIZE + ROUTE_OBSERVE_TAG_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "observation: invalid size=%zu", length); return -1;
}
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));
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: alloc ctx failed");
return;
}
ctx->reply_conn = from_conn;
ctx->request_id = req_pkt->request_id;
ctx->count_total = req_pkt->count;
ctx->timeout_ms = req_pkt->timeout_ms;
{
if (group) {
struct TOPO_GROUP_NODE* 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 NATDET_PING_REQ)) {
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE);
}
/* Целевой адрес - в sockaddr храним в network byte order (как в пакете) */
struct sockaddr_in* sin = (struct sockaddr_in*)&ctx->target_addr;
sin->sin_family = AF_INET;
memcpy(&sin->sin_addr.s_addr, req_pkt->target_ipv4, 4); /* network byte order */
sin->sin_port = req_pkt->target_port; /* порт в network byte order */
/* Ищем первый IPv4-сокет */
struct ETCP_SOCKET* ls = (group ? group->instance : nd->inst)->etcp_sockets;
while (ls) {
if (ls->local_addr.ss_family == AF_INET) {
ctx->local_sock = ls;
break;
}
ls = ls->next;
}
if (!ctx->local_sock) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "ping req: no IPv4 socket");
route_ping_series_finish(ctx); /* отправит 0/0 */
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "PING series start request_id=%08x target=%s:%u count=%u timeout=%u",
(unsigned)ctx->request_id, ip_to_str(&sin->sin_addr, AF_INET).str,
ntohs(sin->sin_port), (unsigned)ctx->count_total, (unsigned)ctx->timeout_ms);
/* Запускаем первый пинг (дальше цепочка через callback) */
int ret = etcp_send_ping_to_socket(
(group ? group->instance : nd->inst),
ctx->local_sock,
ctx->pubkey,
&ctx->target_addr,
ctx->timeout_ms,
route_ping_single_cb,
ctx,
NULL, 0, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: cannot start first ping");
route_ping_series_finish(ctx);
uint8_t tag[ROUTE_OBSERVE_TAG_SIZE]; route_ping_make_tag(tag, socket, round);
if (memcmp(data + ROUTE_ADDR_WIRE_SIZE, tag, sizeof(tag))) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "observation: stale endpoint generation/round"); return -1;
}
return route_ping_addr_decode(data, observed);
}
+20 -43
View File
@@ -1,56 +1,33 @@
/**
* @file route_ping.h
* @brief Удалённый пинг через BGP-посредника.
*
* A просит B пропинговать C и вернуть сводную статистику:
* A --PING_REQ--> B --ETCP ping--> C
* A <--PING_RESP-- B (sent/ok/avg_rtt)
*
* Промежуточный узел задаётся ЯВНО в to_conn — вызывающий сам выбирает,
* через кого слать запрос. Автоподбор посредника (nat_check_find_third_node)
* находится в nat_detection.c, не здесь.
*/
/* Прямые UDP-наблюдения с указанного рабочего сокета. Управление помощниками —
* nat_detection; этот модуль не вычисляет NAT-тип и не выбирает чужой сокет. */
#ifndef ROUTE_PING_H
#define ROUTE_PING_H
#include <stddef.h>
#include <stdint.h>
#include "etcp_connections.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include "nat_detection.h"
#define ROUTE_ADDR_WIRE_SIZE 19
#define ROUTE_OBSERVE_TAG_SIZE 25
/** Запросить у to_conn серию пингов на target_ip:target_port.
* cb вызывается всегда ровно 1 раз:
* success=1 если хоть один пинг успешен (PONG получен)
* success=0 при общем таймауте или все пинги провалились
* @param to_conn промежуточный узел — выбирается вызывающим явно */
int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group,
struct ETCP_CONN* to_conn,
uint32_t target_ip, uint16_t target_port,
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms,
uint16_t wait_timeout_ms, route_ping_callback_t cb,
void* arg, const uint8_t* pubkey);
int route_ping_addr_encode(uint8_t out[ROUTE_ADDR_WIRE_SIZE], const struct sockaddr_storage *address);
int route_ping_addr_decode(const uint8_t in[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address);
int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b, int include_port);
/** Отменить все ожидающие ответы (при остановке NAT_DETECTION). */
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);
/** Отменить pending для конкретного линка (при закрытии линка без разрыва conn). */
void route_ping_cancel_for_link(struct NAT_DETECTION* nd, struct ETCP_LINK* link);
/** Входящий PING_REQ — мы посредник: пингуем цель, шлём PING_RESP. */
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);
/** Входящий PING_RESP — мы инициатор: находим pending по request_id, вызываем cb. */
void route_ping_handle_resp(struct NAT_DETECTION* nd,
struct ETCP_CONN* from_conn, const uint8_t* data, size_t len);
/* Ответ: address(19) || tag(25), все числа BE, IP — неизменённые сетевые байты. */
int route_ping_observe(struct ETCP_SOCKET *socket, const uint8_t peer_key[SC_PUBKEY_SIZE],
const struct sockaddr_storage *peer_address, uint64_t round, int timeout_ms,
etcp_ping_callback_t callback, void *arg);
int route_ping_observation(const uint8_t *data, size_t length, const struct ETCP_SOCKET *socket,
uint64_t round, struct sockaddr_storage *observed);
void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t round);
#ifdef __cplusplus
}
#endif
#endif // ROUTE_PING_H
#endif
+22 -56
View File
@@ -1,62 +1,28 @@
# route_ping — удалённый пинг через BGP (NAT-детекция, проверка живости)
# route_ping — наблюдение рабочего UDP endpoint
## 1. Назначение
Механизм запроса удалённого пинга через BGP-канал. Узел A отправляет узлу B TOPO_PING_REQ
с IP:port цели, узел B выполняет серию пингов (`etcp_send_ping_to_socket`) и возвращает
TOPO_PING_RESP с результатами (count_ok, avg_rtt).
Модуль кодирует адреса и выполняет прямое UDP-наблюдение через конкретный
`ETCP_SOCKET`. Выбор B/C, пересылка управляющих сообщений и классификация
профиля принадлежат `nat_detection`; алгоритм — в `doc/nat_detection.txt`.
Используется:
- `route_connectivity` — для зондирования связности (прямые пинги, без BGP)
- `topo_group` — для NAT-детекции через промежуточный узел (third-party ping)
- Общий механизм проверки живости адресов
`route_ping_observe(socket, peer_key, peer_address, round, timeout_ms, callback, arg)`
передаёт PING с `ETCP_PING_FLAG_OBSERVE`. PONG содержит фактически наблюдаемый
source IP:port и исходный тег. Профиль не меняется от единственного ответа.
`route_ping_observation` проверяет размер, ID/поколение сокета и round.
## 2. Как пользоваться
```c
// Отправить запрос пинга адреса target_ip:target_port через промежуточный узел to_conn
int ret = route_ping_send_req_addr(group, to_conn,
target_ip, target_port,
count=2, interval_ms=100, timeout_ms=500,
wait_timeout_ms=2000, callback, arg, pubkey);
```
Адрес занимает 19 байт: family(1: 4/6), IP(16: IPv4 + 12 нулей либо IPv6),
port(2, BE). Тег занимает 25 байт: `NAT2UDP!`(8), socket_id(1),
generation(8, BE), round(8, BE). Неправильные family, скрытые IPv4-байты
и нулевой порт отвергаются. Новый wire не использует native packed integers.
Промежуточный узел (to_conn) получает запрос → выполняет серию `etcp_send_ping_to_socket()`
→ собирает статистику → отправляет ответ.
В транспорте nonce генерируется CSPRNG; PONG должен соответствовать ожидаемому
ключу, source IP:port и принимающему socket generation. Pending снимается
до callback. `etcp_cancel_pings(instance, callback, arg)` отменяет только
совпадающие запросы без callback; arg остаётся собственностью вызывающего.
Ключевой нюанс: `wait_timeout_ms` задаёт таймаут ожидания ответа на локальной стороне.
Если ответ не приходит вовремя, `route_ping_pending_timeout` вызывает callback с `success=0`.
`ETCP_PING_FLAG_ADDRESS_CHECK` обозначает прямое входящее испытание. Его
receipt фиксируется на рабочем сокете владельца до отправки PONG. Потеря
обратного PONG поэтому не превращает полученный ingress в отрицательный результат.
UDP-результаты не используются для TCP listener и SOCKS5 endpoint.
## 3. API
### Протокольные структуры (packed, wire-format)
| Структура | Поля |
|-----------|------|
| `TOPO_PING_REQ` | `cmd=ETCP_ID_TOPO_ENTRY`, `subcmd=0x07`, `request_id`, `count`, `interval_ms`, `timeout_ms`, `target_ipv4`, `target_port`, `pubkey` (опционально) |
| `TOPO_PING_RESP` | `cmd`, `subcmd=0x08`, `request_id`, `count_sent`, `count_ok`, `avg_rtt` (0.1ms) |
### Внутренние структуры
| Структура | Назначение |
|-----------|------------|
| `route_ping_series_ctx` | Контекст серии пингов (на отвечающей стороне): `reply_conn`, `request_id`, `target_addr`, `local_sock`, счётчики, статистика |
| `route_ping_pending` | Ожидающий запрос (на запрашивающей стороне): `request_id`, `callback`, `timeout_timer`, `via_conn` |
### Функции
| Функция | Назначение |
|---------|------------|
| `route_ping_send_req_addr(group, to_conn, target_ip, target_port, count, interval, timeout, wait_timeout, cb, arg, pubkey)` | Отправить TOPO_PING_REQ через BGP, зарегистрировать callback |
| `route_ping_handle_req(group, from_conn, data, len)` | Обработать входящий PING_REQ: найти локальный IPv4-сокет, запустить серию `etcp_send_ping_to_socket()`, по завершении отправить PING_RESP |
| `route_ping_handle_resp(group, from_conn, data, len)` | Обработать PING_RESP: найти pending по `request_id`, отменить таймер, вызвать callback |
| `route_ping_destroy_pending(group)` | Очистить весь список pending (при уничтожении BGP) |
| `route_ping_cancel_for_conn(group, conn)` | Отменить pending запросы, связанные с конкретным `ETCP_CONN` (при разрыве) |
### Callback
```c
typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt,
uint8_t count_sent, uint8_t count_ok, void* arg);
```
Вызывается при получении ответа или по таймауту (`success=0`).
### Зависимости
- `src/topo_group.h` — BGP-канал (список `ping_pending`, `next_ping_req_id`)
- `src/etcp_api.h` — `etcp_send()` для отправки через BGP
- `src/etcp_connections.h` — `etcp_send_ping_to_socket()` для собственно пинга
- `src/topo_node.h` — `topo_node_find_by_id()`
Проверки: `tests/test_route_ping.c`, `tests/test_nat_detection.c`,
изолированный стенд `tests/stream_netns/run.py --nat-only`.
+40 -59
View File
@@ -1,4 +1,3 @@
/* sock_match.c — классификация сокетов и сопоставление адресов при подключении */
#include "sock_match.h"
#include <string.h>
@@ -8,66 +7,48 @@
#include "etcp_connections.h"
#include "config_parser.h"
#include "topo_node.h"
#include "ntp_time.h"
/* Нормализация NAT-типа: сырые (NAT_TYPE_*) или проверенные (NAT_VERIFIED_*) значения. */
static int nat_is_eim(uint8_t n) { return n == NAT_TYPE_EIM || n == NAT_VERIFIED_EIM; }
static int nat_is_direct(uint8_t n) { return n == NAT_TYPE_DIRECT || n == NAT_VERIFIED_DIRECT; }
uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat) {
if (tcfg == CFG_SERVER_TYPE_PUBLIC || tcfg == CFG_SERVER_TYPE_UNKNOWN) return SOCK_TC_PUB;
if (tcfg == CFG_SERVER_TYPE_PRIVATE || tcfg == CFG_SERVER_TYPE_LOCAL) return SOCK_TC_PRIV;
/* CFG_SERVER_TYPE_NAT */
if (nat_is_eim(tnat) || nat_is_direct(tnat))
return (a_type == TOPO_ADDR_INTERFACE) ? SOCK_TC_PRIV : SOCK_TC_PUB; /* EIM=direct: NAT-адрес публичный, INTERFACE — приватный */
return (a_type == TOPO_ADDR_INTERFACE) ? SOCK_TC_PRIV : SOCK_TC_UNREACH; /* strict/unknown: публичный адрес не пробиваем */
uint8_t sock_target_class(uint8_t address_type, uint8_t config_type, const struct address_profile *profile,
int public_address, uint64_t now_us) {
if (!public_address || config_type == CFG_SERVER_TYPE_PRIVATE || config_type == CFG_SERVER_TYPE_LOCAL) return SOCK_TC_PRIV;
if (address_type == TOPO_ADDR_NAT && profile && profile->mapping == ADDRESS_MAP_DEPENDENT) return SOCK_TC_UNREACH;
if (!address_profile_fresh(profile, now_us)) return SOCK_TC_PROBE;
if (profile->inbound == ADDRESS_IN_OPEN) return SOCK_TC_PUB;
if (profile->inbound == ADDRESS_IN_CONTACT) return SOCK_TC_CONTACT;
return SOCK_TC_PROBE;
}
int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t tnat, uint32_t target_ip4) {
uint8_t tc = sock_target_class(a_type, tcfg, tnat);
if (tc == SOCK_TC_UNREACH) return 0; /* strict/unknown NAT-адрес — напрямую не пробьём */
if (tc == SOCK_TC_PUB) {
if (!v->has_inet) return 0; /* у нашего сокета нет интернета */
if (a_type == TOPO_ADDR_NAT && v->is_eim && nat_is_eim(tnat)
&& v->public_ip4 && v->public_ip4 == target_ip4) return 0; /* оба EIM за одним NAT — hairpin */
return 1;
int sock_match(const struct sock_view *view, uint8_t address_type, uint8_t config_type,
const struct address_profile *profile, uint32_t target_ip4) {
if (!view) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "socket match: missing local view"); return 0; }
uint8_t target = sock_target_class(address_type, config_type, profile, target_ip4 && ip_is_public(target_ip4), view->now_us);
if (target == SOCK_TC_UNREACH) return 0;
/* Одинаковый внешний IP не запрещает hairpin и не доказывает общую LAN.
Учитываем маршруты при фактической попытке, затем проверяем ключ пира. */
return target == SOCK_TC_PRIV ? view->has_priv : view->has_inet;
}
int sock_collect_views(struct UTUN_INSTANCE *instance, struct sock_view *out, int max) {
if (!instance || !out || max <= 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "socket views: invalid arguments"); return 0; }
int count = 0;
int64_t time = ntp_time_get_us(instance);
for (struct ETCP_SOCKET *socket = instance->etcp_sockets; socket && count < max; socket = socket->next) {
if (socket->probe_only || socket->local_addr.ss_family != AF_INET) continue;
struct sock_view *view = &out[count++]; memset(view, 0, sizeof(*view));
view->sock = socket; view->family = AF_INET; view->is_tcp = socket->is_tcp;
view->now_us = time > 0 ? (uint64_t)time : 0;
const struct sockaddr_storage *local = socket->is_tcp ? &socket->interface_addr : &socket->local_addr;
if (local->ss_family == AF_INET) view->local_ip4 = ((const struct sockaddr_in *)local)->sin_addr.s_addr;
if (socket->nat_addr.ss_family == AF_INET)
view->public_ip4 = ((const struct sockaddr_in *)&socket->nat_addr)->sin_addr.s_addr;
if (!view->public_ip4 && ip_is_public(view->local_ip4)) view->public_ip4 = view->local_ip4;
view->address_profile = socket->address_profile;
view->has_priv = 1; /* LAN-кандидат проверяется реальной маршрутизацией и handshake. */
view->has_inet = socket->type != CFG_SERVER_TYPE_LOCAL;
view->role = !view->has_inet ? SOCK_ROLE_LOCAL :
address_profile_fresh(&view->address_profile, view->now_us) && view->address_profile.inbound == ADDRESS_IN_OPEN ?
SOCK_ROLE_DIRECT : SOCK_ROLE_OUTGOING;
}
return v->has_priv; /* приватный адрес — только с нашего приватного интерфейса */
}
int sock_collect_views(struct UTUN_INSTANCE* inst, struct sock_view* out, int max) {
if (!inst || !out || max <= 0) return 0;
int n = 0;
struct ETCP_SOCKET* s = inst->etcp_sockets;
while (s && n < max) {
if (s->local_addr.ss_family != AF_INET) { s = s->next; continue; }
struct sock_view* v = &out[n++];
v->sock = s;
v->family = AF_INET;
v->is_tcp = s->is_tcp;
v->local_ip4 = 0; v->public_ip4 = 0;
/* Локальный адрес (interface, иначе local). */
{ const struct sockaddr_storage* ia = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr;
if (ia && ia->ss_family == AF_INET) v->local_ip4 = ((const struct sockaddr_in*)ia)->sin_addr.s_addr; }
/* Публичный адрес (NAT): nat_addr, иначе локальный если он публичный. */
{ const struct sockaddr_in* na = (const struct sockaddr_in*)&s->nat_addr;
if (s->nat_addr.ss_family == AF_INET && na->sin_addr.s_addr != 0)
v->public_ip4 = na->sin_addr.s_addr; }
/* Классификация по адресу, а не по type из конфига:
* приватный локальный адрес → можем ходить в LAN (has_priv);
* исходящие считаем доступными (интернет + локалка). */
int local_public = v->local_ip4 && ip_is_public(v->local_ip4);
v->has_priv = (uint8_t)!local_public;
v->has_inet = 1;
/* NAT-результат (главный критерий если есть) — для hairpin-фильтра. */
v->is_eim = (uint8_t)nat_is_eim(s->nat_type);
if (!v->public_ip4 && local_public) v->public_ip4 = v->local_ip4;
v->role = local_public ? SOCK_ROLE_DIRECT : (v->is_eim ? SOCK_ROLE_DIRECT : SOCK_ROLE_NAT);
s = s->next;
}
return n;
return count;
}
+23 -85
View File
@@ -1,100 +1,38 @@
/**
* @file sock_match.h
* @brief Классификация сокетов и сопоставление адресов при подключении.
*
* ─── Общая идея ────────────────────────────────────────────────────────────
* Узел имеет несколько сокетов с разной природой сети (публичный IP, NAT,
* скрытый/приватный, без интернета). Чтобы при подключении не создавать
* заведомо мёртвые линки, каждый наш сокет сводится к одной из трёх РОЛЕЙ,
* а каждый адрес пира — к одному из трёх КЛАССОВ. Соединение пробуем только
* для совместимых пар «наш сокет → адрес пира». Для удобства формируем
* список наших сокетов в плоском виде (struct sock_view): роль, локальный IP
* (interface), публичный IP (NAT) и флаги достижимости.
*
* ─── Роли нашего сокета (что он умеет как ИСТОЧНИК) ───────────────────────
* DIRECT — публичный IP / EIM NAT / verified-direct: интернет в обе стороны.
* NAT — только исходящие (strict/unknown NAT или скрытый private):
* интернет есть, но входящих соединений нет.
* LOCAL — без интернета: type=local из конфига, либо любой сокет при
* недоступном NTP (узел оффлайн). Работает только в LAN.
*
* EIM-сокет приравнивается к DIRECT (endpoint-independent mapping пробивается
* снаружи, поэтому его публичный адрес достижим как у public).
*
* ─── Реклама адресов (что узел публикует в nodeinfo) ──────────────────────
* PUBLIC → свой публичный IP (INTERFACE).
* NAT + EIM → приватный IP (INTERFACE) + публичный IP (NAT).
* NAT + STRICT → только приватный IP (INTERFACE), публичный НЕ светим.
* LOCAL → только приватный IP (INTERFACE) — для связи двух клиентов
* в одной локалке.
* PRIVATE → скрыт, не рекламируется вовсе (но usable как источник).
*
* ─── Сопоставление (наш сокет → адрес пира) ───────────────────────────────
* Адрес пира классифицируется: PUB (публичный, из интернета), PRIV
* (приватный/LAN), UNREACH (напрямую недостижим). Правила:
* UNREACH → не пробуем (strict/unknown NAT-адрес);
* PUB → нужен интернет у нашего сокета (role != LOCAL), плюс
* hairpin-фильтр: если оба узла EIM за одним NAT (одинаковый
* публичный IP) — по публичному адресу не ходим, остаётся LAN;
* PRIV → только с нашего сокета, имеющего приватный интерфейс
* (has_priv) — приватный адрес достижим лишь из той же сети.
*/
/* Подбор кандидатов для прямого транспорта. PUBLIC в конфиге — политика,
* OPEN/CONTACT — измеренные свойства endpoint. Совместимость разрешает пробу;
* только аутентифицированный handshake подтверждает связь с нужным узлом. */
#ifndef SOCK_MATCH_H
#define SOCK_MATCH_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "address_profile.h"
struct UTUN_INSTANCE;
struct ETCP_SOCKET;
/* Роли нашего сокета (как источника соединения) */
#define SOCK_ROLE_DIRECT 0 /* публичный / EIM / verified-direct — интернет в обе стороны */
#define SOCK_ROLE_NAT 1 /* только исходящие (strict/unknown NAT, скрытый private) */
#define SOCK_ROLE_LOCAL 2 /* без интернета (type=local или NTP недоступен) */
#define SOCK_ROLE_DIRECT 0
#define SOCK_ROLE_OUTGOING 1
#define SOCK_ROLE_LOCAL 2
/* Классы целевого адреса пира */
#define SOCK_TC_PUB 0 /* публичный — достижим из интернета */
#define SOCK_TC_PRIV 1 /* приватный / LAN */
#define SOCK_TC_UNREACH 2 /* напрямую недостижим (strict/unknown NAT) */
#define SOCK_TC_PUB 0
#define SOCK_TC_PRIV 1
#define SOCK_TC_UNREACH 2
#define SOCK_TC_CONTACT 3
#define SOCK_TC_PROBE 4
/**
* Плоское представление нашего сокета для сопоставления.
* "ip сокета" — local_ip4 (interface), "публичный" — public_ip4 (NAT/прямой),
* "тип" — role + флаги has_inet/has_priv/is_eim.
*
* Флаги вычисляются из КЛАССИФИКАЦИИ АДРЕСА и результата NAT-детекции,
* а не из type сокета в конфиге:
* has_priv — локальный IPv4 приватный/не-маршрутизируемый (можем ходить в LAN);
* has_inet — исходящие считаем доступными в интернет (всегда 1);
* is_eim — результат NAT-детекции EIM (для hairpin-фильтра).
*/
struct sock_view {
struct ETCP_SOCKET* sock;
uint8_t role; /* SOCK_ROLE_* (производное, для логов) */
uint8_t has_inet; /* 1 — интернет доступен */
uint8_t has_priv; /* 1 — есть приватный (LAN) интерфейс */
uint8_t is_eim; /* 1 — NAT + EIM (для hairpin) */
uint8_t family; /* AF_INET / AF_INET6 */
uint8_t is_tcp;
uint32_t local_ip4; /* interface_addr, network order (0 если нет) */
uint32_t public_ip4; /* nat_addr / interface если public, network order (0 если нет) */
struct ETCP_SOCKET *sock;
uint8_t role, has_inet, has_priv, family, is_tcp;
uint32_t local_ip4, public_ip4;
uint64_t now_us;
struct address_profile address_profile;
};
/* Класс адреса пира по типу адреса (TOPO_ADDR_*), конфиг-типу и NAT-типу сокета. */
uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat);
/* public_address — классификация IP в соответствующем семействе, не доказательство входа. */
uint8_t sock_target_class(uint8_t address_type, uint8_t config_type, const struct address_profile *profile,
int public_address, uint64_t now_us);
int sock_match(const struct sock_view *view, uint8_t address_type, uint8_t config_type,
const struct address_profile *profile, uint32_t target_ip4);
int sock_collect_views(struct UTUN_INSTANCE *instance, struct sock_view *out, int max);
/* 1 — наш сокет (view) подходит для подключения к целевому адресу (target_ip4, network order). */
int sock_match(const struct sock_view* v, uint8_t a_type, uint8_t tcfg, uint8_t tnat, uint32_t target_ip4);
/* Собирает список наших сокетов в плоском виде (v4). Возвращает число элементов. */
int sock_collect_views(struct UTUN_INSTANCE* inst, struct sock_view* out, int max);
#ifdef __cplusplus
}
#endif
#endif /* SOCK_MATCH_H */
+116 -34
View File
@@ -91,7 +91,10 @@ void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_S
prepend даёт одинаковый порядок при неизменном наборе сокетов). */
static int sockmeta4_list_equal(struct TOPO_SOCKMETA4* a, struct TOPO_SOCKMETA4* b) {
while (a && b) {
if (a->id != b->id || a->config_type != b->config_type || a->nat_type != b->nat_type) return 0;
if (a->id != b->id || a->config_type != b->config_type ||
a->address_profile.mapping != b->address_profile.mapping || a->address_profile.inbound != b->address_profile.inbound ||
a->address_profile.evidence_id != b->address_profile.evidence_id || a->address_profile.checked_at != b->address_profile.checked_at)
return 0;
a = a->next; b = b->next;
}
return a == NULL && b == NULL;
@@ -106,7 +109,10 @@ static int addr4_list_equal(struct TOPO_ADDR4* a, struct TOPO_ADDR4* b) {
}
static int sockmeta6_list_equal(struct TOPO_SOCKMETA6* a, struct TOPO_SOCKMETA6* b) {
while (a && b) {
if (a->id != b->id || a->config_type != b->config_type || a->nat_type != b->nat_type) return 0;
if (a->id != b->id || a->config_type != b->config_type ||
a->address_profile.mapping != b->address_profile.mapping || a->address_profile.inbound != b->address_profile.inbound ||
a->address_profile.evidence_id != b->address_profile.evidence_id || a->address_profile.checked_at != b->address_profile.checked_at)
return 0;
a = a->next; b = b->next;
}
return a == NULL && b == NULL;
@@ -339,6 +345,24 @@ void topo_node_registry_unref(struct TOPO_GROUPS* groups, uint64_t node_id) {
// ===== serialization =====
static void sockmeta_wire_write(uint8_t *out, uint8_t id, uint8_t config, const struct address_profile *profile) {
out[0] = id; out[1] = config; out[2] = profile->mapping; out[3] = profile->inbound;
for (unsigned i = 0; i < 8; i++) {
out[4 + i] = (uint8_t)(profile->evidence_id >> (56 - i * 8));
out[12 + i] = (uint8_t)(profile->checked_at >> (56 - i * 8));
}
}
static int sockmeta_wire_read(const uint8_t *in, struct address_profile *profile) {
memset(profile, 0, sizeof(*profile)); profile->mapping = in[2]; profile->inbound = in[3];
for (unsigned i = 0; i < 8; i++) {
profile->evidence_id = (profile->evidence_id << 8) | in[4 + i];
profile->checked_at = (profile->checked_at << 8) | in[12 + i];
}
if (!address_profile_valid(profile)) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid address profile in NODEINFO"); return -1; }
return 0;
}
/* Собирает канонический буфер для Ed25519-подписи узла (id, ver, pubkey, имя, тип, адреса). */
int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size) {
if (!ni || !buf || buf_size < 64 + 9) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "build_sig_msg: invalid args"); return -1; }
@@ -356,13 +380,24 @@ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size)
if (name_len) { memcpy(buf + off, ni->node_name, name_len); off += name_len; }
buf[off++] = '\0';
if (off + 2 > buf_size) return -1;
if (off + 7 > buf_size) return -1;
buf[off++] = ni->client_type;
buf[off++] = ni->client_activity;
/* Границы списков тоже подписаны: нельзя переразбить то же тело на другие секции. */
int counts[] = {topo_list_count((struct _topo_head *)ni->v4_sock_meta), topo_list_count((struct _topo_head *)ni->v4_addrs),
topo_list_count((struct _topo_head *)ni->v6_sock_meta), topo_list_count((struct _topo_head *)ni->v6_addrs),
topo_list_count((struct _topo_head *)ni->reality_socks)};
for (unsigned i = 0; i < sizeof(counts) / sizeof(counts[0]); i++) {
if (counts[i] > UINT8_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "signed node section exceeds wire limits"); return -1; }
buf[off++] = (uint8_t)counts[i];
}
{ struct TOPO_SOCKMETA4* e = ni->v4_sock_meta; while (e) {
if (off + 3 > buf_size) return -1;
buf[off++] = e->id; buf[off++] = e->config_type; buf[off++] = e->nat_type; e = e->next;
if (off + sizeof(struct TOPOMSG_SOCKMETA4) > buf_size || !address_profile_valid(&e->address_profile)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid/oversized v4 socket profile"); return -1;
}
sockmeta_wire_write(buf + off, e->id, e->config_type, &e->address_profile);
off += sizeof(struct TOPOMSG_SOCKMETA4); e = e->next;
}}
{ struct TOPO_ADDR4* e = ni->v4_addrs; while (e) {
if (off + 9 > buf_size) return -1;
@@ -371,8 +406,11 @@ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size)
buf[off++] = e->type; buf[off++] = e->socket_id; buf[off++] = e->protocol; e = e->next;
}}
{ struct TOPO_SOCKMETA6* e = ni->v6_sock_meta; while (e) {
if (off + 3 > buf_size) return -1;
buf[off++] = e->id; buf[off++] = e->config_type; buf[off++] = e->nat_type; e = e->next;
if (off + sizeof(struct TOPOMSG_SOCKMETA6) > buf_size || !address_profile_valid(&e->address_profile)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid/oversized v6 socket profile"); return -1;
}
sockmeta_wire_write(buf + off, e->id, e->config_type, &e->address_profile);
off += sizeof(struct TOPOMSG_SOCKMETA6); e = e->next;
}}
{ struct TOPO_ADDR6* e = ni->v6_addrs; while (e) {
if (off + 21 > buf_size) return -1;
@@ -504,16 +542,16 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
if (msg.node_name_len) { memcpy(dp, ni->node_name, msg.node_name_len); dp += msg.node_name_len; }
{ struct TOPO_SOCKMETA4* e = ni->v4_sock_meta; while (e) {
struct TOPOMSG_SOCKMETA4 w = { .id=e->id, .config_type=e->config_type, .nat_type=e->nat_type };
memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next;
sockmeta_wire_write(dp, e->id, e->config_type, &e->address_profile);
dp += sizeof(struct TOPOMSG_SOCKMETA4); e = e->next;
}}
{ struct TOPO_ADDR4* e = ni->v4_addrs; while (e) {
struct TOPOMSG_ADDR4 w = { .port=e->port, .type=e->type, .socket_id=e->socket_id, .protocol=e->protocol };
memcpy(w.addr, e->addr, 4); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next;
}}
{ struct TOPO_SOCKMETA6* e = ni->v6_sock_meta; while (e) {
struct TOPOMSG_SOCKMETA6 w = { .id=e->id, .config_type=e->config_type, .nat_type=e->nat_type };
memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next;
sockmeta_wire_write(dp, e->id, e->config_type, &e->address_profile);
dp += sizeof(struct TOPOMSG_SOCKMETA6); e = e->next;
}}
{ struct TOPO_ADDR6* e = ni->v6_addrs; while (e) {
struct TOPOMSG_ADDR6 w = { .port=e->port, .type=e->type, .socket_id=e->socket_id, .protocol=e->protocol };
@@ -582,7 +620,11 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t
const struct TOPOMSG_SOCKMETA4* w = (const struct TOPOMSG_SOCKMETA4*)dp; dp += sizeof(*w);
struct TOPO_SOCKMETA4* e = memory_pool_alloc(group->instance->topo_groups->v4_sock_meta_pool);
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
e->id = w->id; e->config_type = w->config_type; e->nat_type = w->nat_type; e->next = NULL;
e->id = w->id; e->config_type = w->config_type; e->next = NULL;
if (sockmeta_wire_read((const uint8_t *)w, &e->address_profile)) {
memory_pool_free(group->instance->topo_groups->v4_sock_meta_pool, e);
ni->v4_sock_meta = head; topo_node_free_raw(group->instance->topo_groups, ni); return -1;
}
if (!head) head = tail = e; else { tail->next = e; tail = e; }
}
ni->v4_sock_meta = head; }
@@ -600,7 +642,11 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t
const struct TOPOMSG_SOCKMETA6* w = (const struct TOPOMSG_SOCKMETA6*)dp; dp += sizeof(*w);
struct TOPO_SOCKMETA6* e = memory_pool_alloc(group->instance->topo_groups->v6_sock_meta_pool);
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
e->id = w->id; e->config_type = w->config_type; e->nat_type = w->nat_type; e->next = NULL;
e->id = w->id; e->config_type = w->config_type; e->next = NULL;
if (sockmeta_wire_read((const uint8_t *)w, &e->address_profile)) {
memory_pool_free(group->instance->topo_groups->v6_sock_meta_pool, e);
ni->v6_sock_meta = head; topo_node_free_raw(group->instance->topo_groups, ni); return -1;
}
if (!head) head = tail = e; else { tail->next = e; tail = e; }
}
ni->v6_sock_meta = head; }
@@ -784,7 +830,10 @@ void topo_node_dump_all(struct TOPO_GROUP* group) {
int si = 0;
{ struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; while (sm) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u config=%u nat=%u", si++, sm->id, sm->config_type, sm->nat_type);
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u config=%u mapping=%s inbound=%s evidence=%016llx checked=%llu",
si++, sm->id, sm->config_type, address_mapping_name(sm->address_profile.mapping),
address_inbound_name(sm->address_profile.inbound), (unsigned long long)sm->address_profile.evidence_id,
(unsigned long long)sm->address_profile.checked_at);
sm = sm->next;
}}
{ const char* atn[] = {"INTERFACE","NAT","REAL"};
@@ -795,7 +844,10 @@ void topo_node_dump_all(struct TOPO_GROUP* group) {
a = a->next;
}}
{ int si6 = 0; struct TOPO_SOCKMETA6* sm6 = ni->v6_sock_meta; while (sm6) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u config=%u nat=%u", si6++, sm6->id, sm6->config_type, sm6->nat_type);
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u config=%u mapping=%s inbound=%s evidence=%016llx checked=%llu",
si6++, sm6->id, sm6->config_type, address_mapping_name(sm6->address_profile.mapping),
address_inbound_name(sm6->address_profile.inbound), (unsigned long long)sm6->address_profile.evidence_id,
(unsigned long long)sm6->address_profile.checked_at);
sm6 = sm6->next;
}}
{ int ai6 = 0; struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) {
@@ -875,7 +927,9 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) {
FMT_ADD(" ed25519: "); for (int k = 0; k < SC_PUBKEY_SIZE; k++) FMT_ADD("%02x", ni->ed25519_public_key[k]); FMT_ADD("\n");
{ struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; int si = 0; while (sm) {
FMT_ADD(" v4sock[%d]: id=%u config=%u nat=%u\n", si++, sm->id, sm->config_type, sm->nat_type); sm = sm->next;
FMT_ADD(" v4sock[%d]: id=%u config=%u mapping=%s inbound=%s checked=%llu\n", si++, sm->id, sm->config_type,
address_mapping_name(sm->address_profile.mapping), address_inbound_name(sm->address_profile.inbound),
(unsigned long long)sm->address_profile.checked_at); sm = sm->next;
}}
{ const char* atn[] = {"INTERFACE","NAT","REAL"}; struct TOPO_ADDR4* a = ni->v4_addrs; int ai = 0; while (a) {
const char* proto = (a->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?";
@@ -884,7 +938,9 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) {
a = a->next;
}}
{ struct TOPO_SOCKMETA6* sm6 = ni->v6_sock_meta; int s6i = 0; while (sm6) {
FMT_ADD(" v6sock[%d]: id=%u config=%u nat=%u\n", s6i++, sm6->id, sm6->config_type, sm6->nat_type); sm6 = sm6->next;
FMT_ADD(" v6sock[%d]: id=%u config=%u mapping=%s inbound=%s checked=%llu\n", s6i++, sm6->id, sm6->config_type,
address_mapping_name(sm6->address_profile.mapping), address_inbound_name(sm6->address_profile.inbound),
(unsigned long long)sm6->address_profile.checked_at); sm6 = sm6->next;
}}
{ struct TOPO_ADDR6* a6 = ni->v6_addrs; int a6i = 0; while (a6) {
const char* proto = (a6->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?";
@@ -1098,11 +1154,12 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->is_tcp) { e_sock = e_sock->next; continue; }
if (e_sock->is_tcp || e_sock->probe_only) { e_sock = e_sock->next; continue; }
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) { e_sock = e_sock->next; continue; } /* private скрыт, LOCAL рекламируем (LAN) */
if (e_sock->local_addr.ss_family == AF_INET) {
{ struct TOPO_SOCKMETA4* sm = memory_pool_alloc(instance->topo_groups->v4_sock_meta_pool);
sm->id = e_sock->sock_id; sm->config_type = e_sock->type; sm->nat_type = e_sock->nat_type;
if (!sm) goto allocation_failed;
sm->id = e_sock->sock_id; sm->config_type = e_sock->type; sm->address_profile = e_sock->address_profile;
sm->next = m4_head; m4_head = sm; }
struct sockaddr_in* local_sin = (struct sockaddr_in*)&e_sock->local_addr;
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
@@ -1112,17 +1169,17 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
uint32_t pre_ip = use_local ? local_sin->sin_addr.s_addr : if_sin->sin_addr.s_addr;
uint16_t pre_port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port);
{ struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
if (!a) goto allocation_failed;
memcpy(a->addr, &pre_ip, 4); a->port = pre_port;
a->type = TOPO_ADDR_INTERFACE; a->socket_id = e_sock->sock_id;
a->protocol = TOPO_PROTO_UDP;
a->next = v4_head; v4_head = a; }
/* адрес ПОСЛЕ NAT — дополнительно, если NAT определён (EIM) и есть интернет.
DIRECT (nat==интерфейс), STRICT и оффлайн не рекламируем отдельным адресом. */
if (instance->ntp.reachable
&& e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0
&& e_sock->nat_type != NAT_VERIFIED_STRICT
&& e_sock->nat_type != NAT_VERIFIED_DIRECT) {
/* Общий внешний адрес публикует только владелец после наблюдений EIM.
Наличие ответа интернет-NTP не связано с работоспособностью этого endpoint. */
if (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0 &&
e_sock->address_profile.mapping == ADDRESS_MAP_EIM) {
struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
if (!a) goto allocation_failed;
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id;
a->protocol = TOPO_PROTO_UDP;
@@ -1130,13 +1187,23 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
}
} else if (e_sock->local_addr.ss_family == AF_INET6) {
{ struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(instance->topo_groups->v6_sock_meta_pool);
sm6->id = e_sock->sock_id; sm6->config_type = e_sock->type; sm6->nat_type = e_sock->nat_type;
if (!sm6) goto allocation_failed;
sm6->id = e_sock->sock_id; sm6->config_type = e_sock->type; sm6->address_profile = e_sock->address_profile;
sm6->next = m6_head; m6_head = sm6; }
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
{ struct TOPO_ADDR6* a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool);
if (!a6) goto allocation_failed;
memcpy(a6->addr, &if_sin6->sin6_addr, 16); a6->port = ntohs(if_sin6->sin6_port);
a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = e_sock->sock_id; a6->protocol = TOPO_PROTO_UDP;
a6->next = v6_head; v6_head = a6; }
if (e_sock->nat_addr.ss_family == AF_INET6 && e_sock->address_profile.mapping == ADDRESS_MAP_EIM) {
const struct sockaddr_in6 *mapped = (const struct sockaddr_in6 *)&e_sock->nat_addr;
struct TOPO_ADDR6 *a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool);
if (!a6) goto allocation_failed;
memcpy(a6->addr, &mapped->sin6_addr, 16); a6->port = ntohs(mapped->sin6_port);
a6->type = TOPO_ADDR_NAT; a6->socket_id = e_sock->sock_id; a6->protocol = TOPO_PROTO_UDP;
a6->next = v6_head; v6_head = a6;
}
}
e_sock = e_sock->next;
}
@@ -1150,25 +1217,29 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
if (addr->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
{ struct TOPO_SOCKMETA4* sm = memory_pool_alloc(instance->topo_groups->v4_sock_meta_pool);
sm->id = ts->sock_id; sm->config_type = ts->type; sm->nat_type = ts->nat_type;
if (!sm) goto allocation_failed;
sm->id = ts->sock_id; sm->config_type = ts->type; sm->address_profile = ts->address_profile;
sm->next = m4_head; m4_head = sm; }
{ struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
if (!a) goto allocation_failed;
memcpy(a->addr, &sin->sin_addr.s_addr, 4); a->port = ntohs(sin->sin_port);
a->type = TOPO_ADDR_INTERFACE; a->socket_id = ts->sock_id; a->protocol = proto;
a->next = v4_head; v4_head = a; }
} else if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
{ struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(instance->topo_groups->v6_sock_meta_pool);
sm6->id = ts->sock_id; sm6->config_type = ts->type; sm6->nat_type = ts->nat_type;
if (!sm6) goto allocation_failed;
sm6->id = ts->sock_id; sm6->config_type = ts->type; sm6->address_profile = ts->address_profile;
sm6->next = m6_head; m6_head = sm6; }
{ struct TOPO_ADDR6* a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool);
if (!a6) goto allocation_failed;
memcpy(a6->addr, &sin6->sin6_addr, 16); a6->port = ntohs(sin6->sin6_port);
a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = ts->sock_id; a6->protocol = proto;
a6->next = v6_head; v6_head = a6; }
}
if (is_reality) {
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK));
if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "reality_sock alloc failed"); }
if (!r) goto allocation_failed;
else {
r->socket_id = ts->sock_id;
memcpy(r->short_id, r_short_id, 8);
@@ -1217,14 +1288,17 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my addresses unchanged (ver=%d), persist DB", ni->ver);
}
int database_failed = 0;
/* всегда: адреса (addrs_put) + классификация node_type/storage (nodeinfo_updated) */
if (instance->topo_sqlite_db) {
time_t now_sec = ntp_time_get_seconds(instance);
topo_node_sqlite_node_update_verified(instance->topo_sqlite_db, instance->node_id,
if (topo_node_sqlite_node_update_verified(instance->topo_sqlite_db, instance->node_id,
instance->name, instance->my_keys.public_key, instance->my_ed25519_pubkey,
(uint64_t)now_sec, now_sec);
topo_node_sqlite_addrs_put(instance->topo_sqlite_db, instance->node_id, ni);
topo_node_sqlite_nodeinfo_updated(instance->topo_sqlite_db, instance->node_id);
(uint64_t)now_sec, now_sec) < 0 ||
topo_node_sqlite_addrs_put(instance->topo_sqlite_db, instance->node_id, ni) < 0 ||
topo_node_sqlite_nodeinfo_updated(instance->topo_sqlite_db, instance->node_id) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local address projection update failed"); database_failed = 1;
}
}
if (changed) {
@@ -1244,7 +1318,15 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
ge = ge->next;
}
}
return changed ? 1 : 0;
return database_failed ? -1 : changed ? 1 : 0;
allocation_failed:
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local address snapshot allocation failed");
free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, m4_head);
free_v4_addr_list(instance->topo_groups->v4_addr_pool, v4_head);
free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, m6_head);
free_v6_addr_list(instance->topo_groups->v6_addr_pool, v6_head);
free_reality_sock_list(r_head);
return -1;
}
/* Callback изменения сокета (адрес/статус) — инициирует пересборку адресов своего узла. */
+14 -5
View File
@@ -19,6 +19,7 @@ extern "C" {
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "secure_channel.h"
#include "address_profile.h"
struct TOPO_GROUP;
struct TOPO_GROUPS;
@@ -27,6 +28,7 @@ struct ETCP_LINK;
struct NODE_CONN_DIRECT;
struct ETCP_CONN;
struct UTUN_INSTANCE;
struct reality_client_config;
#define TOPO_ADDR_INTERFACE 0
#define TOPO_ADDR_NAT 1
@@ -41,6 +43,7 @@ struct UTUN_INSTANCE;
#define ADDR_TYPE_NAT_EIM 2
#define ADDR_TYPE_NAT_STRICT 3
#define ADDR_TYPE_NAT 4 /* за NAT, тип неизвестен */
#define ADDR_TYPE_CONTACT 5 /* адрес не изменён, вход требует предварительного контакта */
#define PROBE_STATUS_NONE 0
#define PROBE_STATUS_IN_PROGRESS 1
@@ -101,9 +104,15 @@ struct TOPO_CONNECTIVITY {
#define PING_TTL_TB (86400ULL * 10000ULL)
struct TOPOMSG_SOCKMETA4 { uint8_t id, config_type, nat_type; } __attribute__((packed));
struct TOPOMSG_SOCKMETA4 {
uint8_t id, config_type, mapping, inbound;
uint8_t evidence_id[8], checked_at[8];
} __attribute__((packed));
struct TOPOMSG_ADDR4 { uint8_t addr[4]; uint16_t port; uint8_t type, socket_id, protocol; } __attribute__((packed));
struct TOPOMSG_SOCKMETA6 { uint8_t id, config_type, nat_type; } __attribute__((packed));
struct TOPOMSG_SOCKMETA6 {
uint8_t id, config_type, mapping, inbound;
uint8_t evidence_id[8], checked_at[8];
} __attribute__((packed));
struct TOPOMSG_ADDR6 { uint8_t addr[16]; uint16_t port; uint8_t type, socket_id, protocol; } __attribute__((packed));
struct TOPOMSG_SUBNET4 { uint8_t addr[4]; uint8_t prefix_length; } __attribute__((packed));
struct TOPOMSG_SUBNET6 { uint8_t addr[16]; uint8_t prefix_length; } __attribute__((packed));
@@ -134,9 +143,9 @@ struct TOPOMSG_NODE {
#define TOPOMSG_NODE_HDR_SIZE sizeof(struct TOPOMSG_NODE)
struct TOPO_SOCKMETA4 { struct TOPO_SOCKMETA4* next; uint8_t id, config_type, nat_type; };
struct TOPO_SOCKMETA4 { struct TOPO_SOCKMETA4* next; uint8_t id, config_type; struct address_profile address_profile; };
struct TOPO_ADDR4 { struct TOPO_ADDR4* next; uint8_t addr[4]; uint16_t port; uint8_t type, socket_id, protocol; };
struct TOPO_SOCKMETA6 { struct TOPO_SOCKMETA6* next; uint8_t id, config_type, nat_type; };
struct TOPO_SOCKMETA6 { struct TOPO_SOCKMETA6* next; uint8_t id, config_type; struct address_profile address_profile; };
struct TOPO_ADDR6 { struct TOPO_ADDR6* next; uint8_t addr[16]; uint16_t port; uint8_t type, socket_id, protocol; };
struct TOPO_SUBNET4 { struct TOPO_SUBNET4* next; uint8_t addr[4]; uint8_t prefix_length; };
struct TOPO_SUBNET6 { struct TOPO_SUBNET6* next; uint8_t addr[16]; uint8_t prefix_length; };
@@ -269,7 +278,7 @@ void topo_node_ping_update_rtt(struct TOPO_GROUPS* groups, uint64_t node_id, uin
uint16_t topo_get_chain_rtt(struct TOPO_GROUP_NODE* nq);
/* Лимиты буфера подписываемых данных и полного сетевого NODEINFO. */
#define TOPO_SIG_MSG_MAX_SIZE 2048
#define TOPO_SIG_MSG_MAX_SIZE 8192
#define TOPO_NODE_WIRE_MAX_SIZE 16384
/* Канонические байты подписи: node_id, ver, timestamp, X25519-ключ, имя,
* тип/активность и полные списки сокетов/адресов/Reality. Длина или -1. */
+128 -111
View File
@@ -62,28 +62,29 @@ static int addr_type_from_ip(int family, const uint8_t* addr) {
}
/* Итоговая классификация: конфиг + автоопределение + IP (всё локально). */
static int addr_type_classify(int config_type, int nat_type) {
if (config_type == CFG_SERVER_TYPE_PUBLIC) return ADDR_TYPE_DIRECT;
static int addr_type_classify(int config_type, int global, const struct address_profile *profile) {
if (config_type == CFG_SERVER_TYPE_PRIVATE || config_type == CFG_SERVER_TYPE_LOCAL) return ADDR_TYPE_NETIF;
if (nat_type == NAT_TYPE_EIM) return ADDR_TYPE_NAT_EIM;
if (nat_type == NAT_TYPE_STRICT) return ADDR_TYPE_NAT_STRICT;
if (nat_type == NAT_TYPE_DIRECT) return ADDR_TYPE_DIRECT;
if (nat_type == NAT_VERIFIED_EIM) return ADDR_TYPE_NAT_EIM;
if (nat_type == NAT_VERIFIED_STRICT) return ADDR_TYPE_NAT_STRICT;
if (nat_type == NAT_VERIFIED_DIRECT) return ADDR_TYPE_DIRECT;
return ADDR_TYPE_NAT; /* за NAT, тип неизвестен */
if (!profile) return ADDR_TYPE_NAT;
if (profile->mapping == ADDRESS_MAP_EIM) return ADDR_TYPE_NAT_EIM;
if (profile->mapping == ADDRESS_MAP_DEPENDENT) return ADDR_TYPE_NAT_STRICT;
if (!global) return config_type == CFG_SERVER_TYPE_NAT ? ADDR_TYPE_NAT : ADDR_TYPE_NETIF;
if (profile->mapping == ADDRESS_MAP_UNCHANGED && profile->inbound == ADDRESS_IN_OPEN) return ADDR_TYPE_DIRECT;
if (profile->mapping == ADDRESS_MAP_UNCHANGED && profile->inbound == ADDRESS_IN_CONTACT) return ADDR_TYPE_CONTACT;
return ADDR_TYPE_NAT;
}
static void smeta4_lookup(struct TOPO_SOCKMETA4* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) {
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA4* m = head; m; m = m->next)
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; }
static const struct address_profile *smeta4_lookup(struct TOPO_SOCKMETA4 *head, uint8_t sid, uint8_t *config) {
*config = CFG_SERVER_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA4 *meta = head; meta; meta = meta->next)
if (meta->id == sid) { *config = meta->config_type; return &meta->address_profile; }
return NULL;
}
static void smeta6_lookup(struct TOPO_SOCKMETA6* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) {
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA6* m = head; m; m = m->next)
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; }
static const struct address_profile *smeta6_lookup(struct TOPO_SOCKMETA6 *head, uint8_t sid, uint8_t *config) {
*config = CFG_SERVER_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA6 *meta = head; meta; meta = meta->next)
if (meta->id == sid) { *config = meta->config_type; return &meta->address_profile; }
return NULL;
}
/* reality-параметры сокета → фиксированный blob для node_addresses.options.
@@ -103,90 +104,78 @@ static int reality_opts_build(struct TOPO_NODE* ni, uint8_t sid, struct TOPO_ADD
/* ── Персистенция адресов узла (источник истины — BGP/topo_group) ── */
int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni) {
if (!db || !ni) return -1;
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
sqlite3_stmt* ds = NULL;
int deleted = 0;
if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &ds, NULL) == SQLITE_OK) {
sqlite3_bind_int64(ds, 1, (sqlite3_int64)node_id);
int drc = sqlite3_step(ds);
deleted = sqlite3_changes(db);
if (drc != SQLITE_DONE)
DEBUG_WARN(DEBUG_CATEGORY_BGP, "addrs_put: DELETE rc=%d (not DONE) node=%016llx: %s",
drc, (unsigned long long)node_id, sqlite3_errmsg(db));
sqlite3_finalize(ds);
} else {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "addrs_put: DELETE prepare failed node=%016llx: %s",
(unsigned long long)node_id, sqlite3_errmsg(db));
}
sqlite3_stmt* is = NULL;
if (sqlite3_prepare_v2(db,
"INSERT OR IGNORE INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,nat_type,socket_id,options)"
" VALUES(?,?,?,?,?,?,?,?,?,?)", -1, &is, NULL) != SQLITE_OK) {
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
static int address_row_put(sqlite3_stmt *statement, struct TOPO_NODE *node, uint64_t node_id,
int family, const uint8_t *address, uint16_t port, uint8_t kind, uint8_t socket_id,
uint8_t protocol, uint8_t config, const struct address_profile *profile) {
static const struct address_profile unknown = {0};
if (!profile) profile = &unknown;
int summary = addr_type_classify(config, addr_type_from_ip(family, address) == ADDR_TYPE_DIRECT, profile);
struct TOPO_ADDR_REALITY_OPTS reality;
int has_reality = (protocol & TOPO_PROTO_REALITY) && reality_opts_build(node, socket_id, &reality);
sqlite3_bind_int64(statement, 1, (sqlite3_int64)node_id);
sqlite3_bind_int(statement, 2, family); sqlite3_bind_int(statement, 3, protocol);
sqlite3_bind_blob(statement, 4, address, family == 4 ? 4 : 16, SQLITE_STATIC);
sqlite3_bind_int(statement, 5, port); sqlite3_bind_int(statement, 6, summary);
sqlite3_bind_int(statement, 7, config); sqlite3_bind_int(statement, 8, profile->mapping);
sqlite3_bind_int(statement, 9, socket_id);
if (has_reality) sqlite3_bind_blob(statement, 10, &reality, sizeof(reality), SQLITE_STATIC);
else sqlite3_bind_null(statement, 10);
sqlite3_bind_int(statement, 11, kind); sqlite3_bind_int(statement, 12, profile->inbound);
sqlite3_bind_int64(statement, 13, (sqlite3_int64)profile->evidence_id);
sqlite3_bind_int64(statement, 14, (sqlite3_int64)profile->checked_at);
int result = sqlite3_step(statement);
sqlite3_reset(statement);
if (result != SQLITE_DONE) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection insert failed node=%016llx family=%d socket=%u kind=%u"
" mapping=%s inbound=%s: %s", (unsigned long long)node_id, family, socket_id, kind,
address_mapping_name(profile->mapping), address_inbound_name(profile->inbound),
sqlite3_errmsg(sqlite3_db_handle(statement)));
return -1;
}
int written = 0;
struct TOPO_ADDR_REALITY_OPTS ropts;
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
uint8_t cfg, nat; smeta4_lookup(ni->v4_sock_meta, a->socket_id, &cfg, &nat);
int at = (a->type == TOPO_ADDR_INTERFACE) ? addr_type_from_ip(4, a->addr)
: addr_type_classify(cfg, nat);
int has_reality = (a->protocol & TOPO_PROTO_REALITY) && reality_opts_build(ni, a->socket_id, &ropts);
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id);
sqlite3_bind_int(is, 2, 4);
sqlite3_bind_int(is, 3, (int)a->protocol);
sqlite3_bind_blob(is, 4, a->addr, 4, SQLITE_STATIC);
sqlite3_bind_int(is, 5, (int)a->port);
sqlite3_bind_int(is, 6, at);
sqlite3_bind_int(is, 7, (int)cfg);
sqlite3_bind_int(is, 8, (int)nat);
sqlite3_bind_int(is, 9, (int)a->socket_id);
if (has_reality) sqlite3_bind_blob(is, 10, &ropts, sizeof(ropts), SQLITE_STATIC);
else sqlite3_bind_null(is, 10);
if (sqlite3_step(is) != SQLITE_DONE)
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=4 sock=%d at=%d cfg=%d nat=%d addr=%d.%d.%d.%d:%d: %s",
(unsigned long long)node_id, a->socket_id, at, cfg, nat,
a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->port, sqlite3_errmsg(db));
sqlite3_reset(is);
written++;
}
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
uint8_t cfg, nat; smeta6_lookup(ni->v6_sock_meta, a->socket_id, &cfg, &nat);
int at = (a->type == TOPO_ADDR_INTERFACE) ? ADDR_TYPE_NETIF
: addr_type_classify(cfg, nat);
int has_reality = (a->protocol & TOPO_PROTO_REALITY) && reality_opts_build(ni, a->socket_id, &ropts);
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id);
sqlite3_bind_int(is, 2, 6);
sqlite3_bind_int(is, 3, (int)a->protocol);
sqlite3_bind_blob(is, 4, a->addr, 16, SQLITE_STATIC);
sqlite3_bind_int(is, 5, (int)a->port);
sqlite3_bind_int(is, 6, at);
sqlite3_bind_int(is, 7, (int)cfg);
sqlite3_bind_int(is, 8, (int)nat);
sqlite3_bind_int(is, 9, (int)a->socket_id);
if (has_reality) sqlite3_bind_blob(is, 10, &ropts, sizeof(ropts), SQLITE_STATIC);
else sqlite3_bind_null(is, 10);
if (sqlite3_step(is) != SQLITE_DONE)
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=6 sock=%d at=%d cfg=%d nat=%d: %s",
(unsigned long long)node_id, a->socket_id, at, cfg, nat, sqlite3_errmsg(db));
sqlite3_reset(is);
written++;
}
sqlite3_finalize(is);
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "addrs_put: node=%016llx deleted=%d v4=%d v6=%d written=%d",
(unsigned long long)node_id, deleted,
topo_list_count((struct _topo_head*)ni->v4_addrs),
topo_list_count((struct _topo_head*)ni->v6_addrs), written);
return 0;
}
int topo_node_sqlite_addrs_put(sqlite3 *db, uint64_t node_id, struct TOPO_NODE *node) {
if (!db || !node) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection: invalid arguments"); return -1; }
if (sqlite3_exec(db, "SAVEPOINT node_address_projection", NULL, NULL, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection savepoint failed: %s", sqlite3_errmsg(db)); return -1;
}
sqlite3_stmt *statement = NULL;
if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &statement, NULL) != SQLITE_OK) goto fail;
sqlite3_bind_int64(statement, 1, (sqlite3_int64)node_id);
if (sqlite3_step(statement) != SQLITE_DONE) goto fail;
sqlite3_finalize(statement); statement = NULL;
if (sqlite3_prepare_v2(db, "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,"
"nat_mapping,socket_id,options,address_kind,nat_inbound,nat_evidence,nat_checked)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)", -1, &statement, NULL) != SQLITE_OK) goto fail;
int written = 0;
for (const struct TOPO_ADDR4 *address = node->v4_addrs; address; address = address->next) {
uint8_t config;
const struct address_profile *profile = smeta4_lookup(node->v4_sock_meta, address->socket_id, &config);
if (address_row_put(statement, node, node_id, 4, address->addr, address->port, address->type,
address->socket_id, address->protocol, config, profile)) goto fail;
written++;
}
for (const struct TOPO_ADDR6 *address = node->v6_addrs; address; address = address->next) {
uint8_t config;
const struct address_profile *profile = smeta6_lookup(node->v6_sock_meta, address->socket_id, &config);
if (address_row_put(statement, node, node_id, 6, address->addr, address->port, address->type,
address->socket_id, address->protocol, config, profile)) goto fail;
written++;
}
sqlite3_finalize(statement); statement = NULL;
if (sqlite3_exec(db, "RELEASE node_address_projection", NULL, NULL, NULL) != SQLITE_OK) goto fail;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "address projection stored node=%016llx endpoints=%d",
(unsigned long long)node_id, written);
return 0;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection failed node=%016llx: %s", (unsigned long long)node_id, sqlite3_errmsg(db));
sqlite3_finalize(statement);
if (sqlite3_exec(db, "ROLLBACK TO node_address_projection; RELEASE node_address_projection", NULL, NULL, NULL) != SQLITE_OK)
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address projection rollback failed: %s", sqlite3_errmsg(db));
return -1;
}
int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id) {
if (!db) return 0;
sqlite3_stmt* s = NULL;
@@ -252,6 +241,29 @@ int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
int topo_node_sqlite_init(sqlite3* db) {
if (!db) return -1;
/* Проекция адресов производная. Старый формат пересоздаём, не интерпретируя NAT_TYPE_* как новый профиль. */
sqlite3_stmt *schema = NULL;
if (sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='node_addresses'",
-1, &schema, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address schema inspection failed: %s", sqlite3_errmsg(db)); return -1;
}
int exists = sqlite3_step(schema) == SQLITE_ROW;
sqlite3_finalize(schema); schema = NULL;
if (exists) {
int compatible = sqlite3_prepare_v2(db,
"SELECT nat_mapping,nat_inbound,nat_evidence,nat_checked,address_kind FROM node_addresses LIMIT 0",
-1, &schema, NULL);
sqlite3_finalize(schema);
if (compatible == SQLITE_ERROR) {
if (sqlite3_exec(db, "DROP TABLE node_addresses", NULL, NULL, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "old address projection reset failed: %s", sqlite3_errmsg(db)); return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "old address projection reset; signed NODEINFO will rebuild endpoint profiles");
} else if (compatible != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address schema read failed: %s", sqlite3_errmsg(db)); return -1;
}
}
const char* sql =
"CREATE TABLE IF NOT EXISTS node_snapshots ("
"node_id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, record BLOB NOT NULL);"
@@ -277,13 +289,17 @@ int topo_node_sqlite_init(sqlite3* db) {
" rtt INTEGER,"
" addr_type INTEGER DEFAULT 0,"
" config_type INTEGER DEFAULT 0,"
" nat_type INTEGER DEFAULT 0,"
" nat_mapping INTEGER NOT NULL DEFAULT 0,"
" nat_inbound INTEGER NOT NULL DEFAULT 0,"
" nat_evidence INTEGER NOT NULL DEFAULT 0,"
" nat_checked INTEGER NOT NULL DEFAULT 0,"
" address_kind INTEGER NOT NULL DEFAULT 0,"
" socket_id INTEGER NOT NULL DEFAULT 0,"
" options BLOB," // reality-параметры (TOPO_ADDR_REALITY_OPTS)
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);"
"CREATE UNIQUE INDEX IF NOT EXISTS idx_na_unique ON node_addresses(node_id, family, socket_id, addr_type);"
"CREATE UNIQUE INDEX IF NOT EXISTS idx_na_unique ON node_addresses(node_id,family,socket_id,protocol,address,port,address_kind);"
"CREATE INDEX IF NOT EXISTS idx_na_addr_lookup ON node_addresses(family, address, port, socket_id);"
"CREATE TABLE IF NOT EXISTS channels ("
@@ -863,8 +879,11 @@ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
}
}
static const int at2type[] = { [ADDR_TYPE_NETIF]=1, [ADDR_TYPE_DIRECT]=1, [ADDR_TYPE_NAT_EIM]=2, [ADDR_TYPE_NAT_STRICT]=1, [ADDR_TYPE_NAT]=2 };
int base_type = (best_at < 0) ? 0 : at2type[best_at];
static const int at2type[] = { [ADDR_TYPE_NETIF]=1, [ADDR_TYPE_DIRECT]=1, [ADDR_TYPE_NAT_EIM]=2,
[ADDR_TYPE_NAT_STRICT]=1, [ADDR_TYPE_NAT]=2, [ADDR_TYPE_CONTACT]=2 };
int base_type = best_at < 0 || (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]) ? 0 : at2type[best_at];
if (best_at >= 0 && (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]))
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid projected address type=%d node=%016llx", best_at, (unsigned long long)node_id);
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
@@ -1054,11 +1073,10 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL;
struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL;
struct TOPO_REALITY_SOCK* r_head = NULL; struct TOPO_REALITY_SOCK* r_tail = NULL;
uint8_t sock_id_counter = 0;
{
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type,options FROM node_addresses"
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
" WHERE node_id=? AND family=4 ORDER BY addr_type",
-1, &s, NULL) == SQLITE_OK) {
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
@@ -1066,10 +1084,9 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
const void* addr = sqlite3_column_blob(s, 0);
if (!addr || sqlite3_column_bytes(s, 0) < 4) continue;
int port = sqlite3_column_int(s, 1);
int at = sqlite3_column_int(s, 2);
int sock_id_raw = sqlite3_column_int(s, 3);
uint8_t sock_id = (uint8_t)(sock_id_raw ? sock_id_raw : ++sock_id_counter);
uint8_t type = (at == ADDR_TYPE_NETIF) ? TOPO_ADDR_INTERFACE : TOPO_ADDR_NAT;
uint8_t sock_id = (uint8_t)sock_id_raw;
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool);
if (!a4) continue;
memset(a4, 0, sizeof(*a4));
@@ -1086,7 +1103,8 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
if (m4) {
m4->id = sock_id;
m4->config_type = (uint8_t)sqlite3_column_int(s, 5);
m4->nat_type = (uint8_t)sqlite3_column_int(s, 6);
/* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */
memset(&m4->address_profile, 0, sizeof(m4->address_profile));
m4->next = NULL;
if (!m4_head) m4_head = m4_tail = m4;
else { m4_tail->next = m4; m4_tail = m4; }
@@ -1118,7 +1136,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
{
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type,options FROM node_addresses"
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
" WHERE node_id=? AND family=6 ORDER BY addr_type",
-1, &s, NULL) == SQLITE_OK) {
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
@@ -1126,10 +1144,9 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
const void* addr = sqlite3_column_blob(s, 0);
if (!addr || sqlite3_column_bytes(s, 0) < 16) continue;
int port = sqlite3_column_int(s, 1);
int at = sqlite3_column_int(s, 2);
int sock_id_raw = sqlite3_column_int(s, 3);
uint8_t sock_id = (uint8_t)(sock_id_raw ? sock_id_raw : ++sock_id_counter);
uint8_t type = (at == ADDR_TYPE_NETIF) ? TOPO_ADDR_INTERFACE : TOPO_ADDR_NAT;
uint8_t sock_id = (uint8_t)sock_id_raw;
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool);
if (!a6) continue;
memset(a6, 0, sizeof(*a6));
@@ -1146,7 +1163,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
if (m6) {
m6->id = sock_id;
m6->config_type = (uint8_t)sqlite3_column_int(s, 5);
m6->nat_type = (uint8_t)sqlite3_column_int(s, 6);
memset(&m6->address_profile, 0, sizeof(m6->address_profile));
m6->next = NULL;
if (!m6_head) m6_head = m6_tail = m6;
else { m6_tail->next = m6; m6_tail = m6; }
+1 -1
View File
@@ -25,7 +25,7 @@ uint64_t topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id);
struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id);
/* Персистентная запись адресов узла в node_addresses (источник истины — BGP/topo_group).
* DELETE + INSERT из ni->v4_addrs/v6_addrs, классификация config_type/nat_type из SOCKMETA. */
* DELETE + INSERT из ni->v4_addrs/v6_addrs; mapping/inbound/evidence из подписанного SOCKMETA. */
int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni);
/* Есть ли у узла запись мембера хотя бы в одной peers_<ch> (реальный мембер с ключами). */