Measure owned endpoint mapping and inbound reachability separately
This commit is contained in:
1 parent
b54307a0df
commit
ff69552a61
51 files changed
+2620
-2483
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
bin_PROGRAMS = utun
|
||||
noinst_LIBRARIES = libutun.a
|
||||
|
||||
EXTRA_DIST = lwip_tcp/COPYING dnsmasq/COPYING dnsmasq/COPYING-v3
|
||||
EXTRA_DIST = lwip_tcp/COPYING dnsmasq/COPYING dnsmasq/COPYING-v3 routing_layer/address_profile.h routing_layer/route_ping_doc.md
|
||||
|
||||
stream_core_files = \
|
||||
stream/stream.h \
|
||||
|
||||
@@ -214,19 +214,20 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin
|
||||
}
|
||||
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT node_id, MIN(rtt),"
|
||||
"SELECT node_id, MIN(CASE WHEN rtt > 0 THEN rtt END),"
|
||||
" CASE WHEN MAX(addr_type=1) THEN 1"
|
||||
" WHEN MAX(addr_type=5) THEN 5"
|
||||
" WHEN MAX(addr_type=2) THEN 2"
|
||||
" WHEN MAX(addr_type=4) THEN 4"
|
||||
" WHEN MAX(addr_type=3) THEN 3"
|
||||
" ELSE 0 END"
|
||||
" FROM node_addresses WHERE node_id IN (%s) AND rtt > 0 GROUP BY node_id", id_list);
|
||||
" FROM node_addresses WHERE node_id IN (%s) GROUP BY node_id", id_list);
|
||||
u_free(id_list);
|
||||
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
int rtt_val = sqlite3_column_int(st, 1);
|
||||
int rtt_val = sqlite3_column_type(st, 1) == SQLITE_NULL ? 0xFFFF : sqlite3_column_int(st, 1);
|
||||
int at = sqlite3_column_int(st, 2);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
if (members[i].node_id == nid) {
|
||||
@@ -315,17 +316,18 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u
|
||||
|
||||
/* RTT / NAT */
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT MIN(rtt),"
|
||||
"SELECT MIN(CASE WHEN rtt > 0 THEN rtt END),"
|
||||
" CASE WHEN MAX(addr_type=1) THEN 1"
|
||||
" WHEN MAX(addr_type=5) THEN 5"
|
||||
" WHEN MAX(addr_type=2) THEN 2"
|
||||
" WHEN MAX(addr_type=4) THEN 4"
|
||||
" WHEN MAX(addr_type=3) THEN 3"
|
||||
" ELSE 0 END"
|
||||
" FROM node_addresses WHERE node_id=? AND rtt > 0");
|
||||
" FROM node_addresses WHERE node_id=?");
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
int rtt_val = sqlite3_column_int(st, 0);
|
||||
int rtt_val = sqlite3_column_type(st, 0) == SQLITE_NULL ? 0xFFFF : sqlite3_column_int(st, 0);
|
||||
m.rtt = (uint16_t)(rtt_val > 0xFFFF ? 0xFFFF : rtt_val);
|
||||
m.nat_type = (uint8_t)sqlite3_column_int(st, 1);
|
||||
}
|
||||
|
||||
+34
-4
@@ -20,8 +20,38 @@
|
||||
#include "../routing_layer/topo_group.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char* nat_type_str(uint8_t t) {
|
||||
switch (t) { case 0: return "UNKNOWN"; case 1: return "EIM"; case 2: return "STRICT"; case 3: return "DIRECT"; default: return "?"; }
|
||||
struct address_profile_text { char text[80]; };
|
||||
|
||||
static struct address_profile_text link_address_profile_text(const struct ETCP_LINK *link) {
|
||||
static const struct address_profile unknown = {0};
|
||||
const struct address_profile *profile = &unknown;
|
||||
const struct TOPO_NODE *peer = topo_node_registry_find(link->etcp->instance->topo_groups, link->etcp->peer_node_id);
|
||||
uint8_t socket_id = link->remote_socket_id;
|
||||
/* У входящего TCP source port относится к соединению, не к listener пира. */
|
||||
if (link->is_tcp && link->is_server) peer = NULL;
|
||||
if (peer && link->is_tcp && link->remote_addr.ss_family == AF_INET) {
|
||||
const struct sockaddr_in *target = (const struct sockaddr_in *)&link->remote_addr;
|
||||
const struct TOPO_ADDR4 *address = peer->v4_addrs;
|
||||
while (address && (!(address->protocol & TOPO_PROTO_TCP) || address->port != ntohs(target->sin_port) ||
|
||||
memcmp(address->addr, &target->sin_addr, 4))) address = address->next;
|
||||
if (address) socket_id = address->socket_id; else peer = NULL;
|
||||
} else if (peer && link->is_tcp && link->remote_addr.ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *target = (const struct sockaddr_in6 *)&link->remote_addr;
|
||||
const struct TOPO_ADDR6 *address = peer->v6_addrs;
|
||||
while (address && (!(address->protocol & TOPO_PROTO_TCP) || address->port != ntohs(target->sin6_port) ||
|
||||
memcmp(address->addr, &target->sin6_addr, 16))) address = address->next;
|
||||
if (address) socket_id = address->socket_id; else peer = NULL;
|
||||
}
|
||||
if (peer && link->remote_addr.ss_family == AF_INET) {
|
||||
for (const struct TOPO_SOCKMETA4 *meta = peer->v4_sock_meta; meta; meta = meta->next)
|
||||
if (meta->id == socket_id) { profile = &meta->address_profile; break; }
|
||||
} else if (peer && link->remote_addr.ss_family == AF_INET6) {
|
||||
for (const struct TOPO_SOCKMETA6 *meta = peer->v6_sock_meta; meta; meta = meta->next)
|
||||
if (meta->id == socket_id) { profile = &meta->address_profile; break; }
|
||||
}
|
||||
struct address_profile_text result;
|
||||
snprintf(result.text, sizeof(result.text), "%s/%s", address_mapping_name(profile->mapping), address_inbound_name(profile->inbound));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char* out, size_t sz) {
|
||||
@@ -117,7 +147,7 @@ static void chat_core_collect_status(struct UTUN_INSTANCE* inst) {
|
||||
link->is_tcp ? "TCP" : "UDP",
|
||||
link->conn ? link->conn->name : "?",
|
||||
link->link_status ? "UP" : "DOWN",
|
||||
nat_type_str(link->nat_type),
|
||||
link_address_profile_text(link).text,
|
||||
rtt_str);
|
||||
link = link->next; link_idx++;
|
||||
}
|
||||
@@ -282,7 +312,7 @@ static void collect_conn_metrics(struct UTUN_INSTANCE* inst, uint64_t peer_node_
|
||||
"Keepalive: sent=%u recv=%u period=%ums\n"
|
||||
"Inflight: %u / %u B (%u pkts)\n"
|
||||
"Encrypt: sent=%zu err=%zu Decrypt: rcvd=%zu err=%zu\n",
|
||||
link_idx, link->link_status ? "UP" : "DOWN", is_tcp_str, nat_type_str(link->nat_type),
|
||||
link_idx, link->link_status ? "UP" : "DOWN", is_tcp_str, link_address_profile_text(link).text,
|
||||
local_str, remote_str,
|
||||
link->rtt_last / 10.0f, link->tt_last / 10.0f,
|
||||
link->keepalive_sent_count, link->keepalive_recv_count, link->keepalive_interval,
|
||||
|
||||
+10
-5
@@ -955,7 +955,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
uint8_t count = 0;
|
||||
struct ETCP_SOCKET* sock = instance->etcp_sockets;
|
||||
while (sock && count < ETCPMON_MAX_CONNECTIONS) {
|
||||
count++;
|
||||
if (!sock->probe_only) count++;
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
@@ -980,6 +980,8 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
struct etcpmon_socket_info* info = (struct etcpmon_socket_info*)(buffer + sizeof(*hdr) + sizeof(*rsp));
|
||||
sock = instance->etcp_sockets;
|
||||
for (uint8_t i = 0; i < count && sock; i++) {
|
||||
while (sock && sock->probe_only) sock = sock->next;
|
||||
if (!sock) break;
|
||||
strncpy(info[i].name, sock->name, ETCPMON_MAX_CONN_NAME - 1);
|
||||
info[i].name[ETCPMON_MAX_CONN_NAME - 1] = '\0';
|
||||
info[i].is_ipv6 = (sock->interface_addr.ss_family == AF_INET6) ? 1 : 0;
|
||||
@@ -997,9 +999,11 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
info[i].port = 0;
|
||||
}
|
||||
info[i].config_type = sock->type;
|
||||
info[i].nat_type = sock->nat_type;
|
||||
info[i].mapping = sock->address_profile.mapping; info[i].inbound = sock->address_profile.inbound;
|
||||
info[i].is_tcp = sock->is_tcp; info[i].generation = sock->generation;
|
||||
info[i].evidence_id = sock->address_profile.evidence_id; info[i].checked_at = sock->address_profile.checked_at;
|
||||
|
||||
/* NAT address from socket (set when INIT_RESPONSE received) */
|
||||
/* Общий внешний адрес подтверждён измерениями владельца. */
|
||||
info[i].nat_ip[0] = '\0';
|
||||
info[i].nat_port = 0;
|
||||
if (sock->nat_addr.ss_family == AF_INET) {
|
||||
@@ -1011,8 +1015,9 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
|
||||
info[i].nat_port = ntohs(sin6->sin6_port);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%d nat_type=%d nat_ip=%s nat_port=%u",
|
||||
sock->name, sock->type, sock->nat_type,
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%u mapping=%s inbound=%s external=%s:%u",
|
||||
sock->name, sock->type, address_mapping_name(sock->address_profile.mapping),
|
||||
address_inbound_name(sock->address_profile.inbound),
|
||||
info[i].nat_ip[0] ? info[i].nat_ip : "none", info[i].nat_port);
|
||||
|
||||
sock = sock->next;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+978
-336
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 изменения сокета (адрес/статус) — инициирует пересборку адресов своего узла. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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> (реальный мембер с ключами). */
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "etcp_router.h"
|
||||
#include "routing.h"
|
||||
#include "topo_group.h"
|
||||
#include "route_ping.h"
|
||||
#include "nat_detection.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
@@ -667,14 +667,6 @@ void etcp_conn_reinit_id(struct ETCP_CONN* etcp, const char* reason, uint64_t re
|
||||
// Сбрасываем ретрансмиты роутера ДО очистки ETCP-очередей (избегаем гонки с data_pool)
|
||||
if (etcp->peer_node_id) etcp_router_pause_retrans_for_node(etcp->instance, etcp->peer_node_id);
|
||||
|
||||
// Сбрасываем NAT-статус у всех линков этого соединения
|
||||
struct ETCP_LINK* l = etcp->links;
|
||||
while (l) {
|
||||
l->nat_type = NAT_TYPE_UNKNOWN;
|
||||
l->nat_check_status = NAT_CHECK_NONE;
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
// Вызываем etcp_conn_reset для сброса состояния
|
||||
etcp_conn_reset(etcp);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "topo_group.h"
|
||||
#include "node_conn_direct.h"
|
||||
#include "nat_detection.h"
|
||||
#include "route_ping.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
@@ -42,11 +43,22 @@ static const char* server_type_str(uint8_t type) {
|
||||
static const char* names[] = {"UNKNOWN", "PUBLIC", "NAT", "PRIVATE", "LOCAL"};
|
||||
return type < 5 ? names[type] : "?";
|
||||
}
|
||||
// Читаемое имя NAT-типа (NAT_TYPE_*/NAT_VERIFIED_*) для логов.
|
||||
static const char* nat_type_str(uint8_t nat_type) {
|
||||
if (nat_type >= 4 && nat_type <= 7) nat_type -= 4;
|
||||
static const char* names[] = {"UNKNOWN", "EIM", "STRICT", "DIRECT"};
|
||||
return nat_type < 4 ? names[nat_type] : "?";
|
||||
/* ID уникален среди живых сокетов; поколение не повторяется при переиспользовании ID. */
|
||||
static int socket_identity_init(struct ETCP_SOCKET *socket, struct UTUN_INSTANCE *instance) {
|
||||
for (unsigned attempt = 0; attempt < 256; attempt++) {
|
||||
uint8_t candidate = instance->next_socket_id++;
|
||||
struct ETCP_SOCKET *existing = instance->etcp_sockets;
|
||||
while (existing && existing->sock_id != candidate) existing = existing->next;
|
||||
if (existing) continue;
|
||||
socket->sock_id = candidate;
|
||||
do {
|
||||
if (random_bytes((uint8_t *)&socket->generation, sizeof(socket->generation))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket generation RNG failed name=%s", socket->name); return -1;
|
||||
}
|
||||
} while (!socket->generation);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "no free socket ID name=%s", socket->name); return -1;
|
||||
}
|
||||
|
||||
/* ── Forward declarations ────────────────────────────────────────────
|
||||
@@ -809,8 +821,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
|
||||
e_sock->errorcode = 0;
|
||||
e_sock->pkt_format_errors = 0;
|
||||
e_sock->type = type;
|
||||
e_sock->sock_id = instance->next_socket_id++;
|
||||
e_sock->nat_type = NAT_TYPE_UNKNOWN; // только результат детекции NAT, серверные сокеты стартуют с unknown
|
||||
if (socket_identity_init(e_sock, instance)) { socket_close_wrapper(e_sock->fd); u_free(e_sock); return NULL; }
|
||||
e_sock->mtu = mtu;
|
||||
e_sock->only_local = only_local;
|
||||
e_sock->links_queue = queue_new(instance->ua, 256, offsetof(struct link_queue_entry, key), LINK_ADDR_KEY_SIZE, "links");
|
||||
@@ -872,6 +883,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
|
||||
void etcp_socket_remove(struct ETCP_SOCKET* conn) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!conn) return;
|
||||
if (conn->instance && conn->instance->nat_det) nat_detection_socket_removed(conn->instance->nat_det, conn);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] Removing socket %p, socket_id=%p", conn, conn->socket_id);
|
||||
|
||||
@@ -928,7 +940,7 @@ struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SE
|
||||
strncpy(ts->name, server->name, MAX_CONN_NAME_LEN - 1);
|
||||
ts->name[MAX_CONN_NAME_LEN - 1] = '\0';
|
||||
}
|
||||
ts->sock_id = instance->next_socket_id++;
|
||||
if (socket_identity_init(ts, instance)) { u_free(ts); return NULL; }
|
||||
|
||||
if (server->ip.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&server->ip;
|
||||
@@ -1005,6 +1017,7 @@ struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SE
|
||||
// Удаление TCP-сокета и всех его линков, включая созданные вне NCD.
|
||||
void tcp_socket_remove(struct ETCP_SOCKET* sock) {
|
||||
if (!sock || !sock->instance) return;
|
||||
if (sock->instance->nat_det) nat_detection_socket_removed(sock->instance->nat_det, sock);
|
||||
struct UTUN_INSTANCE* inst = sock->instance;
|
||||
struct ETCP_SOCKET** pp = &inst->etcp_sockets;
|
||||
while (*pp && *pp != sock) pp = &(*pp)->next;
|
||||
@@ -1390,6 +1403,8 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
|
||||
// Единая точка отправки UDP: SOCKS5-релей (если сокет proxied) → send_hook (dummynet) → socket_sendto.
|
||||
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
|
||||
const struct sockaddr* addr, socklen_t addr_len) {
|
||||
if (link && link->conn && link->conn->instance->nat_det)
|
||||
nat_detection_note_send(link->conn->instance->nat_det, link->conn, (const struct sockaddr_storage *)addr);
|
||||
if (link && link->conn && link->conn->socks_udp)
|
||||
return socks_udp_sendto(link->conn->socks_udp, buf, len, (const struct sockaddr_storage*)addr);
|
||||
if (link && link->send_hook)
|
||||
@@ -1498,6 +1513,7 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, struct ETCP_SOCKET* e_so
|
||||
}
|
||||
memcpy(enc_buf + enc_buf_len, dgram->data + len, dgram->noencrypt_len);
|
||||
size_t total = enc_buf_len + dgram->noencrypt_len;
|
||||
if (e_sock->instance->nat_det) nat_detection_note_send(e_sock->instance->nat_det, e_sock, addr);
|
||||
ssize_t sent;
|
||||
if (e_sock->socks_udp) {
|
||||
sent = socks_udp_sendto(e_sock->socks_udp, enc_buf, total, addr);
|
||||
@@ -1528,12 +1544,8 @@ static size_t etcp_build_ping_response_data(const uint8_t* req_data, size_t req_
|
||||
// Таймаут пинга: снять из pending_pings и вызвать callback с success=0.
|
||||
static void ping_timeout_cbk(void* arg) {
|
||||
struct PING_CONTEXT* ctx = (struct PING_CONTEXT*)arg;
|
||||
if (!ctx || !ctx->cb) return;
|
||||
if (ctx->timeout_timer) {
|
||||
uasync_cancel_timeout(ctx->instance->ua, ctx->timeout_timer);
|
||||
ctx->timeout_timer = NULL;
|
||||
}
|
||||
ctx->cb(0, 0, ctx->arg, ctx->nonce, NULL, 0);
|
||||
if (!ctx) return;
|
||||
ctx->timeout_timer = NULL;
|
||||
if (ctx->instance->pending_pings == ctx) {
|
||||
ctx->instance->pending_pings = ctx->next;
|
||||
} else {
|
||||
@@ -1541,10 +1553,25 @@ static void ping_timeout_cbk(void* arg) {
|
||||
while (prev && prev->next != ctx) prev = prev->next;
|
||||
if (prev) prev->next = ctx->next;
|
||||
}
|
||||
if (ctx->cb) ctx->cb(0, 0, ctx->arg, ctx->nonce, NULL, 0);
|
||||
if (ctx->user_data) u_free(ctx->user_data);
|
||||
u_free(ctx);
|
||||
}
|
||||
|
||||
void etcp_cancel_pings(struct UTUN_INSTANCE *instance, etcp_ping_callback_t callback, void *arg) {
|
||||
if (!instance) return;
|
||||
struct PING_CONTEXT **cursor = &instance->pending_pings;
|
||||
while (*cursor) {
|
||||
struct PING_CONTEXT *ping = *cursor;
|
||||
if (ping->cb != callback || ping->arg != arg) { cursor = &ping->next; continue; }
|
||||
*cursor = ping->next;
|
||||
if (ping->timeout_timer) uasync_cancel_timeout(instance->ua, ping->timeout_timer);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "UDP probe cancelled nonce=%016llx socket=%u generation=%016llx",
|
||||
(unsigned long long)ping->nonce, ping->socket_id, (unsigned long long)ping->socket_generation);
|
||||
u_free(ping->user_data); u_free(ping);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправка UDP-пинга через конкретный сокет: сборка PING-кодограммы (nonce, user_data,
|
||||
// обфусцированный pubkey), регистрация в pending_pings с таймаутом.
|
||||
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
|
||||
@@ -1577,7 +1604,15 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
|
||||
ctx->instance = instance;
|
||||
ctx->cb = cb;
|
||||
ctx->arg = user_arg;
|
||||
ctx->nonce = get_current_timestamp() ^ (uint64_t)rand();
|
||||
do {
|
||||
if (random_bytes((uint8_t *)&ctx->nonce, sizeof(ctx->nonce))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "UDP ping nonce RNG failed"); u_free(ctx); return -3;
|
||||
}
|
||||
struct PING_CONTEXT *pending = instance->pending_pings;
|
||||
while (pending && pending->nonce != ctx->nonce) pending = pending->next;
|
||||
if (ctx->nonce && !pending) break;
|
||||
} while (1);
|
||||
ctx->socket_id = e_sock->sock_id; ctx->socket_generation = e_sock->generation; ctx->peer_addr = *addr;
|
||||
ctx->timeout_timer = NULL;
|
||||
ctx->send_time = get_time_tb();
|
||||
ctx->user_data = NULL;
|
||||
@@ -1664,7 +1699,7 @@ int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bi
|
||||
return -1;
|
||||
}
|
||||
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
|
||||
while (e_sock && (e_sock->is_tcp || e_sock->local_addr.ss_family != addr->ss_family)) e_sock = e_sock->next;
|
||||
while (e_sock && (e_sock->is_tcp || e_sock->probe_only || e_sock->local_addr.ss_family != addr->ss_family)) e_sock = e_sock->next;
|
||||
if (!e_sock) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no UDP socket for addr_family=%d", addr->ss_family);
|
||||
return -2;
|
||||
@@ -1755,6 +1790,14 @@ static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
|
||||
}
|
||||
const uint8_t* udata = (ulen > 0) ? (pkt->data + 20) : NULL;
|
||||
|
||||
if ((flags & ETCP_PING_FLAG_OBSERVE) && (flags & ETCP_PING_FLAG_ADDRESS_CHECK)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "UDP probe has incompatible flags=%02x", flags); return 7;
|
||||
}
|
||||
if ((flags & ETCP_PING_FLAG_OBSERVE) && e_sock->instance->nat_det)
|
||||
nat_detection_record_observation(e_sock->instance->nat_det, e_sock, decrypted_pubkey, addr, udata, ulen);
|
||||
if ((flags & ETCP_PING_FLAG_ADDRESS_CHECK) && e_sock->instance->nat_det)
|
||||
nat_detection_receive_ingress(e_sock->instance->nat_det, e_sock, decrypted_pubkey, addr, udata, ulen);
|
||||
|
||||
if ((flags & ETCP_PING_FLAG_SEND_RTT) && ulen >= 2) {
|
||||
uint16_t rtt_val = wire_read16(udata);
|
||||
topo_node_ping_update_rtt(e_sock->instance->topo_groups, peer_id, rtt_val);
|
||||
@@ -1776,8 +1819,13 @@ static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
|
||||
memcpy(p, &nid, 8); p += 8;
|
||||
uint64_t n = htobe64(nonce);
|
||||
memcpy(p, &n, 8); p += 8;
|
||||
uint16_t resp_ulen_be = htobe16(ulen);
|
||||
uint16_t response_size = ulen + ((flags & ETCP_PING_FLAG_OBSERVE) ? ROUTE_ADDR_WIRE_SIZE : 0);
|
||||
uint16_t resp_ulen_be = htobe16(response_size);
|
||||
memcpy(p, &resp_ulen_be, 2); p += 2;
|
||||
if (flags & ETCP_PING_FLAG_OBSERVE) {
|
||||
if (route_ping_addr_encode(p, addr)) { u_free(resp); return 7; }
|
||||
p += ROUTE_ADDR_WIRE_SIZE;
|
||||
}
|
||||
size_t copied = etcp_build_ping_response_data(udata, ulen, p, PACKET_DATA_SIZE - (p - resp->data) - SC_PUBKEY_ENC_SIZE);
|
||||
p += copied;
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
@@ -1807,7 +1855,8 @@ static void rtt_send_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonc
|
||||
}
|
||||
|
||||
// Обработка входящего PONG: поиск pending_pings по nonce, вызов callback с RTT и данными.
|
||||
static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr, size_t pkt_len) {
|
||||
static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr,
|
||||
const uint8_t *decrypted_pubkey, size_t pkt_len) {
|
||||
if (pkt_len < 23) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(addr).str);
|
||||
return 7;
|
||||
@@ -1835,6 +1884,13 @@ static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
|
||||
int found = 0;
|
||||
while (ctx) {
|
||||
if (ctx->nonce == nonce) {
|
||||
if (ctx->socket_id != e_sock->sock_id || ctx->socket_generation != e_sock->generation ||
|
||||
memcmp(ctx->peer_pubkey, decrypted_pubkey, SC_PUBKEY_SIZE) ||
|
||||
!route_ping_addr_equal(&ctx->peer_addr, addr, 1)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "UDP PONG identity/endpoint mismatch nonce=%016llx socket=%u generation=%016llx",
|
||||
(unsigned long long)nonce, e_sock->sock_id, (unsigned long long)e_sock->generation);
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt); return 0;
|
||||
}
|
||||
found = 1;
|
||||
if (prev) prev->next = ctx->next;
|
||||
else e_sock->instance->pending_pings = ctx->next;
|
||||
@@ -1894,62 +1950,21 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
|
||||
resp->mtu[0]=link->mtu_local>>8;
|
||||
resp->mtu[1]=link->mtu_local;
|
||||
resp->link_id = link->local_link_id;
|
||||
resp->remote_socket_id = req->socket_id;
|
||||
resp->remote_socket_id = e_sock->sock_id;
|
||||
resp->only_local = e_sock->only_local;
|
||||
resp->type = e_sock->type;
|
||||
// Add client's IP:port (so client behind NAT can know its external address)
|
||||
/* Наблюдение относится к клиенту; адрес сервера хранится только в remote_addr. */
|
||||
if (addr->ss_family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in*)addr;
|
||||
memcpy(resp->peer_ipv4, &sin->sin_addr.s_addr, 4);
|
||||
uint16_t port = ntohs(sin->sin_port);
|
||||
resp->peer_port[0] = port >> 8;
|
||||
resp->peer_port[1] = port & 0xFF;
|
||||
link->nat_ip = sin->sin_addr.s_addr;
|
||||
link->nat_port = port;
|
||||
const struct sockaddr_in *source = (const struct sockaddr_in *)addr;
|
||||
memcpy(resp->peer_ipv4, &source->sin_addr.s_addr, 4);
|
||||
memcpy(resp->peer_port, &source->sin_port, 2);
|
||||
} else {
|
||||
// For IPv6, set to 0 (not supported for NAT traversal)
|
||||
memset(resp->peer_ipv4, 0, 4);
|
||||
memset(resp->peer_port, 0, 2);
|
||||
link->nat_ip = 0;
|
||||
link->nat_port = 0;
|
||||
}
|
||||
// DIRECT detection: if client reports its own address and it matches observed source → real public IP
|
||||
if (pkt_len >= ETCP_INIT_REQ_SIZE && addr->ss_family == AF_INET && link->nat_ip != 0) {
|
||||
if (req_src_ip != 0 && req_src_ip == link->nat_ip
|
||||
&& req_src_port == link->nat_port
|
||||
&& !is_local_subnet(link->nat_ip))
|
||||
{
|
||||
link->nat_type = NAT_TYPE_DIRECT;
|
||||
link->nat_check_status = NAT_CHECK_EIM;
|
||||
if (link->etcp->instance->nat_det) {
|
||||
nat_detection_send_nat_info(link->etcp->instance->nat_det, link->etcp,
|
||||
link->remote_socket_id,
|
||||
link->nat_ip, link->nat_port, NAT_TYPE_DIRECT);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "DIRECT IP: %s:%u for %s",
|
||||
ip_to_str(&link->nat_ip, AF_INET).str, link->nat_port,
|
||||
link->etcp->log_name);
|
||||
}
|
||||
}
|
||||
/* Self-detection: if my IP is non-local, I'm on a public IP */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "self-detect check: socket=%s type=%s nat=%s if_addr=%s local_addr=%s",
|
||||
e_sock->name, server_type_str(e_sock->type), nat_type_str(e_sock->nat_type),
|
||||
sockaddr_storage_to_str(&e_sock->interface_addr).str,
|
||||
sockaddr_storage_to_str(&e_sock->local_addr).str);
|
||||
if (e_sock->nat_type != NAT_VERIFIED_DIRECT && e_sock->interface_addr.ss_family == AF_INET) {
|
||||
uint32_t my_ip = ((struct sockaddr_in*)&e_sock->interface_addr)->sin_addr.s_addr;
|
||||
if (my_ip != 0 && !is_local_subnet(my_ip)) {
|
||||
e_sock->nat_type = NAT_VERIFIED_DIRECT;
|
||||
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
|
||||
nat_sin->sin_family = AF_INET;
|
||||
nat_sin->sin_addr.s_addr = my_ip;
|
||||
nat_sin->sin_port = ((struct sockaddr_in*)&e_sock->interface_addr)->sin_port;
|
||||
if (link->etcp->instance->topo_groups) topo_node_update_my_addresses(link->etcp->instance);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Self-detected PUBLIC: socket=%s ip=%s port=%u sock_id=%d",
|
||||
e_sock->name, ip_to_str(&my_ip, AF_INET).str,
|
||||
ntohs(nat_sin->sin_port), e_sock->sock_id);
|
||||
}
|
||||
memset(resp->peer_ipv4, 0, sizeof(resp->peer_ipv4));
|
||||
memset(resp->peer_port, 0, sizeof(resp->peer_port));
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "INIT client address claim=%s:%u observed_peer=%s bytes=%zu server_socket=%s client_sid=%u",
|
||||
ip_to_str(&req_src_ip, AF_INET).str, req_src_port, sockaddr_storage_to_str(addr).str, pkt_len,
|
||||
e_sock->name, req->socket_id);
|
||||
memcpy(resp->ed25519_pubkey, e_sock->instance->my_ed25519_pubkey, SC_PUBKEY_SIZE);
|
||||
resp->device_type = e_sock->instance->client_type;
|
||||
wire_write16(resp->keepalive,e_sock->instance->keepalive_interval);
|
||||
@@ -2017,6 +2032,9 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
|
||||
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
|
||||
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
|
||||
uint64_t server_node_id = wire_read64(resp->node_id);
|
||||
if (server_node_id != sc_derive_node_id_from_pubkey(link->etcp->crypto_ctx.peer_public_key)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE node ID does not match authenticated peer key"); return 46;
|
||||
}
|
||||
uint64_t resp_reset_id = wire_read64(resp->reset_id);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE rid=%016llx", (unsigned long long)resp_reset_id);
|
||||
etcp_conn_apply_peer_reset_id(link->etcp, resp_reset_id);
|
||||
@@ -2030,55 +2048,22 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
|
||||
link->remote_type = resp->type;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] INIT_RESPONSE received (link=%d, mtu=%d, rid=%016llx)", link->etcp->log_name, link->remote_link_id, link->mtu, (unsigned long long)resp_reset_id);
|
||||
|
||||
// Parse NAT IP:port from response (new format includes 4+2 bytes)
|
||||
if (pkt_len >= ETCP_INIT_RESP_SIZE) {
|
||||
uint32_t new_nat_ip;
|
||||
memcpy(&new_nat_ip, resp->peer_ipv4, 4);
|
||||
uint16_t new_nat_port = wire_read16(resp->peer_port);
|
||||
|
||||
// Check if NAT address changed
|
||||
if (link->nat_ip == 0 && link->nat_port == 0) {
|
||||
// First time receiving NAT info
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
struct in_addr addr;
|
||||
addr.s_addr = new_nat_ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u",
|
||||
link->etcp->log_name, ip_to_str(&addr, AF_INET).str, new_nat_port);
|
||||
} else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) {
|
||||
// NAT address changed
|
||||
struct in_addr old_addr, new_addr;
|
||||
old_addr.s_addr = link->nat_ip;
|
||||
new_addr.s_addr = new_nat_ip;
|
||||
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
if (!link->is_tcp && e_sock->local_addr.ss_family == AF_INET && wire_read16(resp->peer_port)) {
|
||||
struct sockaddr_storage observed; memset(&observed, 0, sizeof(observed));
|
||||
struct sockaddr_in *source = (struct sockaddr_in *)&observed;
|
||||
source->sin_family = AF_INET; memcpy(&source->sin_addr, resp->peer_ipv4, 4);
|
||||
memcpy(&source->sin_port, resp->peer_port, 2);
|
||||
if (link->local_as_seen_by_peer.ss_family && !route_ping_addr_equal(&link->local_as_seen_by_peer, &observed, 1)) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Own endpoint observation changed peer=%s local=%s before=%s after=%s",
|
||||
link->etcp->log_name, e_sock->name, sockaddr_storage_to_str(&link->local_as_seen_by_peer).str,
|
||||
sockaddr_storage_to_str(&observed).str);
|
||||
link->nat_changes_count++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)",
|
||||
link->etcp->log_name, ip_to_str(&old_addr.s_addr, AF_INET).str, link->nat_port, ip_to_str(&new_addr.s_addr, AF_INET).str, new_nat_port,
|
||||
link->nat_changes_count);
|
||||
}
|
||||
|
||||
// Update socket NAT address (only if not already verified)
|
||||
{
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "init_resp nat: socket=%s nat=%s new_ip=%s new_port=%u",
|
||||
e_sock->name, nat_type_str(e_sock->nat_type),
|
||||
ip_to_str(&new_nat_ip, AF_INET).str, new_nat_port);
|
||||
if (e_sock->nat_type < NAT_VERIFIED_UNKNOWN) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = new_nat_ip;
|
||||
sin->sin_port = htons(new_nat_port);
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "init_resp nat update SKIPPED (already verified): socket=%s nat=%s",
|
||||
e_sock->name, nat_type_str(e_sock->nat_type));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy format without NAT info
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Received legacy INIT_RESPONSE without NAT info",
|
||||
link->etcp->log_name);
|
||||
} else link->nat_hits_count++;
|
||||
link->local_as_seen_by_peer = observed;
|
||||
if (e_sock->instance->nat_det) nat_detection_observed_by_peer(e_sock->instance->nat_det, link, &observed);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "Own endpoint observed peer=%s socket=%s generation=%016llx address=%s",
|
||||
link->etcp->log_name, e_sock->name, (unsigned long long)e_sock->generation,
|
||||
sockaddr_storage_to_str(&observed).str);
|
||||
}
|
||||
|
||||
memcpy(link->etcp->peer_ed25519_pubkey, resp->ed25519_pubkey, SC_PUBKEY_SIZE);
|
||||
@@ -2259,6 +2244,10 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
|
||||
pkt->noencrypt_len=0;
|
||||
uint8_t code = pkt->data[0];
|
||||
uint64_t peer_id = wire_read64(pkt->data + 2);
|
||||
if ((code == ETCP_PING || code == ETCP_PONG) && peer_id != sc_derive_node_id_from_pubkey(decrypted_pubkey)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "UDP probe sender ID does not match authenticated key from=%s", sockaddr_storage_to_str(&addr).str);
|
||||
errorcode = 7; goto ec_fr;
|
||||
}
|
||||
if (code == ETCP_PING) {
|
||||
#ifdef UTUN_HAVE_STANDBY
|
||||
{ char who[64]; snprintf(who, sizeof(who), "addr=%s", sockaddr_storage_to_str(&addr).str); standby_log_rx("udp ping", who); }
|
||||
@@ -2275,7 +2264,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
|
||||
#endif
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "X25519 decrypted: PONG from peer=0x%016llx src=%s",
|
||||
(unsigned long long)peer_id, sockaddr_storage_to_str(&addr).str);
|
||||
int ret = handle_pong(e_sock, pkt, &addr, pkt_len);
|
||||
int ret = handle_pong(e_sock, pkt, &addr, decrypted_pubkey, pkt_len);
|
||||
if (ret) { errorcode = ret; goto ec_fr; }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ extern "C" {
|
||||
#include "reality.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp_bbr.h"
|
||||
#include "../routing_layer/address_profile.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include <stdint.h>
|
||||
@@ -46,6 +47,8 @@ extern "C" {
|
||||
|
||||
#define ETCP_PING_FLAG_WANT_RTT 0x01 // в PONG: хочу получить RTT от пингера
|
||||
#define ETCP_PING_FLAG_SEND_RTT 0x02 // в PING: несу RTT для получателя
|
||||
#define ETCP_PING_FLAG_OBSERVE 0x04 // PONG несёт фактически наблюдаемый IP:port
|
||||
#define ETCP_PING_FLAG_ADDRESS_CHECK 0x08 // прямое входящее испытание endpoint
|
||||
|
||||
/* Адаптивный keepalive */
|
||||
#define KA_PERIOD_MIN_MS 200
|
||||
@@ -126,6 +129,9 @@ struct PING_CONTEXT {
|
||||
void* timeout_timer;
|
||||
uint64_t send_time; // время отправки пинга в 0.1ms
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
uint8_t socket_id;
|
||||
uint64_t socket_generation;
|
||||
struct sockaddr_storage peer_addr;
|
||||
uint8_t* user_data;
|
||||
size_t user_data_len;
|
||||
};
|
||||
@@ -148,6 +154,8 @@ struct ETCP_SOCKET {
|
||||
uint8_t type; // CFG_SERVER_TYPE_*
|
||||
uint8_t sock_id; // unique id (0-255) for NAT matching
|
||||
uint8_t is_tcp; // 1=TCP (STCP), 0=UDP
|
||||
uint8_t probe_only; // вспомогательный UDP-порт наблюдателя, не транспорт NCD
|
||||
uint64_t generation; // случайная эпоха endpoint; sock_id может переиспользоваться
|
||||
uint8_t reality_enabled; // 1=TCP-порт с REALITY-камуфляжем
|
||||
uint8_t only_local; // 1 = only local, no forwarding (UDP)
|
||||
uint32_t netif_index; // для sin6_scope_id
|
||||
@@ -162,33 +170,12 @@ struct ETCP_SOCKET {
|
||||
struct ll_queue* links_queue; // hash-очередь линков
|
||||
int errorcode;
|
||||
size_t pkt_format_errors;
|
||||
uint8_t nat_type; // NAT_TYPE_*
|
||||
struct address_profile address_profile;
|
||||
uint32_t local_defaultroute_ip; // auto-detected IPv4 (network byte order)
|
||||
uint8_t local_defaultroute_ip6[16];
|
||||
struct socks_udp* socks_udp; // SOCKS5 UDP ASSOCIATE (NULL = напрямую)
|
||||
};
|
||||
|
||||
// NAT check status
|
||||
#define NAT_CHECK_NONE 0
|
||||
#define NAT_CHECK_WAITING 1
|
||||
#define NAT_CHECK_IN_PROGRESS 2
|
||||
#define NAT_CHECK_EIM 3
|
||||
#define NAT_CHECK_STRICT 4
|
||||
|
||||
// NAT type (detected by server during NAT check)
|
||||
#define NAT_TYPE_UNKNOWN 0
|
||||
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
|
||||
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
|
||||
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
|
||||
|
||||
// Verified NAT types (published in nodeinfo after server-side NAT detection)
|
||||
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
|
||||
#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive
|
||||
#define NAT_VERIFIED_EIM 5 // EIM NAT
|
||||
#define NAT_VERIFIED_STRICT 6 // strict NAT
|
||||
#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT
|
||||
|
||||
|
||||
#define LINK_STATE_HANDSHAKE 1
|
||||
#define LINK_STATE_TRY_RECONNECT 2
|
||||
#define LINK_STATE_CONNECTED 3
|
||||
@@ -226,7 +213,6 @@ struct ETCP_LINK {
|
||||
uint8_t remote_socket_id; // socket id peer
|
||||
uint8_t remote_only_local; // only_local flag from peer
|
||||
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
|
||||
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
|
||||
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
|
||||
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
|
||||
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
|
||||
@@ -273,11 +259,9 @@ struct ETCP_LINK {
|
||||
uint32_t bandwidth; // Link bandwidth in Kbits/sec
|
||||
|
||||
// NAT address tracking (from INIT_RESPONSE)
|
||||
uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set
|
||||
uint16_t nat_port; // NAT port (host byte order)
|
||||
struct sockaddr_storage local_as_seen_by_peer; // только наш endpoint, наблюдаемый удалённым пиром
|
||||
uint32_t nat_changes_count; // Counter of NAT address changes
|
||||
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
|
||||
uint8_t nat_check_status; // NAT_CHECK_*
|
||||
|
||||
// Keepalive state
|
||||
void* keepalive_timer; // Таймер обычного keepalive (только в normal-режиме)
|
||||
@@ -422,6 +406,9 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
|
||||
const uint8_t* user_data, size_t user_data_len,
|
||||
uint8_t flags);
|
||||
|
||||
/* Отмена без callback: вызывающий владеет arg и освобождает его после отмены. */
|
||||
void etcp_cancel_pings(struct UTUN_INSTANCE *instance, etcp_ping_callback_t callback, void *arg);
|
||||
|
||||
// TCP-ping: подключение к TCP-адресу peer + хендшейк с флагом PING. RTT меряется
|
||||
// по хендшейку (send→response). cb вызывается один раз (success=1/rtt или success=0).
|
||||
// out_cli (опционально) — возвращает хендл stcp_client для досрочной отмены (stcp_client_destroy).
|
||||
|
||||
@@ -151,8 +151,8 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->encrypt_errors, link->decrypt_errors,
|
||||
link->send_errors, link->recv_errors,
|
||||
link->total_encrypted, link->total_decrypted, link->total_retransmissions);
|
||||
DLOG("NAT: type=%d chk=%d chg=%u hits=%u",
|
||||
link->nat_type, link->nat_check_status,
|
||||
DLOG("OWN_ENDPOINT_OBSERVED: addr=%s changes=%u hits=%u",
|
||||
sockaddr_storage_to_str(&link->local_as_seen_by_peer).str,
|
||||
link->nat_changes_count, link->nat_hits_count);
|
||||
DLOG("LAST_RECV: tb=%llu ts=%u",
|
||||
(unsigned long long)link->last_recv_local_time, link->last_recv_timestamp);
|
||||
@@ -187,8 +187,9 @@ void etcp_dump_sockets(struct UTUN_INSTANCE* instance) {
|
||||
int idx = 0;
|
||||
while (s) {
|
||||
char abuf[32]; sockaddr_to_str(&s->local_addr, abuf, sizeof(abuf));
|
||||
DLOG("SOCK %d: '%s' fd=%d addr=%s type=%d nat=%d mtu=%d links=%d",
|
||||
idx, s->name, (int)s->fd, abuf, s->type, s->nat_type,
|
||||
DLOG("SOCK %d: '%s' fd=%d addr=%s config=%u mapping=%s inbound=%s mtu=%d links=%d",
|
||||
idx, s->name, (int)s->fd, abuf, s->type, address_mapping_name(s->address_profile.mapping),
|
||||
address_inbound_name(s->address_profile.inbound),
|
||||
s->mtu, s->links_queue ? queue_entry_count(s->links_queue) : 0);
|
||||
s = s->next; idx++;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "topo_node_sqlite.h"
|
||||
#include "sock_match.h"
|
||||
#include "topo_group.h"
|
||||
#include "ntp_time.h"
|
||||
#include "config_parser.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
@@ -258,7 +259,10 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
for (const struct TOPO_SOCKMETA4* mm = ni->v4_sock_meta; mm; mm = mm->next)
|
||||
if (mm->id == a->socket_id) { m = mm; break; }
|
||||
uint8_t tcfg = m ? m->config_type : CFG_SERVER_TYPE_UNKNOWN;
|
||||
uint8_t tnat = m ? m->nat_type : NAT_TYPE_UNKNOWN;
|
||||
const struct address_profile *profile = m ? &m->address_profile : NULL;
|
||||
uint8_t target_class = sock_target_class(a->type, tcfg, profile, ip_is_public(tip),
|
||||
nviews ? views[0].now_us : (uint64_t)ntp_time_get_us(inst));
|
||||
if (target_class == SOCK_TC_UNREACH) { v4_skip++; continue; }
|
||||
|
||||
if (a->protocol & TOPO_PROTO_TCP) {
|
||||
if ((a->protocol & TOPO_PROTO_REALITY) && !topo_node_find_reality_sock(ni, a->socket_id)) {
|
||||
@@ -270,7 +274,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
const struct sock_view* v = &views[vi];
|
||||
if (!v->is_tcp) continue;
|
||||
tcp_sockets++;
|
||||
if (specific_sock ? v->sock != specific_sock : !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
||||
if (specific_sock ? v->sock != specific_sock : !sock_match(v, a->type, tcfg, profile, tip)) continue;
|
||||
if (check_inst) return 1;
|
||||
int added = ncd_add_tcp_link(conn, v->sock, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||
if (added > 0) link_count += added;
|
||||
@@ -288,7 +292,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
} else if (!specific_sock) {
|
||||
for (int vi = 0; vi < nviews; vi++) {
|
||||
const struct sock_view* v = &views[vi];
|
||||
if (v->is_tcp || !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
||||
if (v->is_tcp || !sock_match(v, a->type, tcfg, profile, tip)) continue;
|
||||
if (check_inst) return 1;
|
||||
link_count += ncd_add_udp_link(conn, v->sock, &sa);
|
||||
}
|
||||
@@ -303,6 +307,14 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j] != 0) { zero = 0; break; }
|
||||
if (zero) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ncd] v6 skip zero-addr node=0x%016llx", (unsigned long long)nid); continue; }
|
||||
int is_ll = (a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80);
|
||||
if (a->addr[0] == 0xff) { v6_skip++; continue; }
|
||||
const struct TOPO_SOCKMETA6 *metadata = ni->v6_sock_meta;
|
||||
while (metadata && metadata->id != a->socket_id) metadata = metadata->next;
|
||||
int global = (a->addr[0] & 0xe0) == 0x20;
|
||||
int64_t current = ntp_time_get_us(inst);
|
||||
uint8_t target_class = sock_target_class(a->type, metadata ? metadata->config_type : CFG_SERVER_TYPE_UNKNOWN,
|
||||
metadata ? &metadata->address_profile : NULL, global, current > 0 ? (uint64_t)current : 0);
|
||||
if (target_class == SOCK_TC_UNREACH) { v6_skip++; continue; }
|
||||
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6;
|
||||
memcpy(&sin6.sin6_addr, a->addr, 16); sin6.sin6_port = htons(a->port);
|
||||
|
||||
@@ -313,12 +325,12 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
}
|
||||
int tcp_sockets = 0;
|
||||
if (is_ll) { struct ETCP_SOCKET* sv = inst->etcp_sockets;
|
||||
while (sv) { if (sv->local_addr.ss_family == AF_INET6 && sv->netif_index) { sin6.sin6_scope_id = sv->netif_index; break; } sv = sv->next; } }
|
||||
while (sv) { if (!sv->probe_only && sv->local_addr.ss_family == AF_INET6 && sv->netif_index) { sin6.sin6_scope_id = sv->netif_index; break; } sv = sv->next; } }
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
|
||||
if (!s->is_tcp || s->local_addr.ss_family != AF_INET6) continue;
|
||||
if (!s->is_tcp || s->probe_only || s->local_addr.ss_family != AF_INET6) continue;
|
||||
tcp_sockets++;
|
||||
if (specific_sock ? s != specific_sock : s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
if (specific_sock ? s != specific_sock : global && s->type == CFG_SERVER_TYPE_LOCAL) continue;
|
||||
if (check_inst) return 1;
|
||||
if (is_ll) { sin6.sin6_scope_id = s->netif_index; memcpy(&sa, &sin6, sizeof(sin6)); }
|
||||
int added = ncd_add_tcp_link(conn, s, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||
@@ -338,7 +350,8 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
link_count += ncd_add_udp_link(conn, specific_sock, &sa);
|
||||
} else if (!specific_sock) {
|
||||
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
|
||||
if (s->is_tcp || s->local_addr.ss_family != AF_INET6 || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
if (s->is_tcp || s->probe_only || s->local_addr.ss_family != AF_INET6 ||
|
||||
(global && s->type == CFG_SERVER_TYPE_LOCAL)) continue;
|
||||
if (check_inst) return 1;
|
||||
if (is_ll) sin6.sin6_scope_id = s->netif_index;
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||
@@ -367,13 +380,17 @@ int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
* Это возможность попытки, не подтверждение маршрута/работоспособности скрытых сокетов пира. */
|
||||
const struct TOPO_NODE* self = topo_node_registry_find(inst->topo_groups, inst->node_id);
|
||||
if (!self) return 0;
|
||||
int64_t current = ntp_time_get_us(inst);
|
||||
uint64_t now_us = current > 0 ? (uint64_t)current : 0;
|
||||
for (const struct TOPO_ADDR4* a = self->v4_addrs; a; a = a->next) {
|
||||
uint32_t ip; memcpy(&ip, a->addr, 4);
|
||||
if (!ip || !a->port) continue;
|
||||
const struct TOPO_SOCKMETA4* meta = self->v4_sock_meta;
|
||||
while (meta && meta->id != a->socket_id) meta = meta->next;
|
||||
uint8_t cfg = meta ? meta->config_type : CFG_SERVER_TYPE_UNKNOWN, nat = meta ? meta->nat_type : NAT_TYPE_UNKNOWN;
|
||||
if (sock_target_class(a->type, cfg, nat) != SOCK_TC_PUB) continue;
|
||||
uint8_t cfg = meta ? meta->config_type : CFG_SERVER_TYPE_UNKNOWN;
|
||||
const struct address_profile *profile = meta ? &meta->address_profile : NULL;
|
||||
uint8_t target_class = sock_target_class(a->type, cfg, profile, ip_is_public(ip), now_us);
|
||||
if (target_class == SOCK_TC_PRIV || target_class == SOCK_TC_UNREACH) continue;
|
||||
if ((a->protocol & TOPO_PROTO_REALITY) && !topo_node_find_reality_sock(self, a->socket_id)) continue;
|
||||
if (a->protocol & TOPO_PROTO_TCP) return 1;
|
||||
if (!(a->protocol & TOPO_PROTO_UDP)) continue;
|
||||
@@ -381,11 +398,11 @@ int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!(remote->protocol & TOPO_PROTO_UDP)) continue;
|
||||
const struct TOPO_SOCKMETA4* rm = node->v4_sock_meta;
|
||||
while (rm && rm->id != remote->socket_id) rm = rm->next;
|
||||
struct sock_view view = {.has_inet = 1};
|
||||
view.is_eim = rm && (rm->nat_type == NAT_TYPE_EIM || rm->nat_type == NAT_VERIFIED_EIM);
|
||||
struct sock_view view = {.has_inet = 1, .now_us = now_us};
|
||||
if (rm) view.address_profile = rm->address_profile;
|
||||
for (const struct TOPO_ADDR4* ra = node->v4_addrs; ra; ra = ra->next)
|
||||
if (ra->socket_id == remote->socket_id && ra->type == TOPO_ADDR_NAT) memcpy(&view.public_ip4, ra->addr, 4);
|
||||
if (sock_match(&view, a->type, cfg, nat, ip)) return 1;
|
||||
if (sock_match(&view, a->type, cfg, profile, ip)) return 1;
|
||||
}
|
||||
}
|
||||
for (const struct TOPO_ADDR6* a = self->v6_addrs; a; a = a->next) {
|
||||
|
||||
+3
-1
@@ -432,6 +432,9 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
auto_socket_destroy(instance);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] E done — socket_monitor+auto_socket");
|
||||
|
||||
/* Детектор владеет таймерами и вспомогательными сокетами: остановить до групп/транспорта. */
|
||||
if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; }
|
||||
|
||||
/* Phase F: BGP */
|
||||
if (instance->topo_groups) {
|
||||
etcp_router_unbind(instance, ETCP_RT_ID_CONN_MGR);
|
||||
@@ -542,7 +545,6 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
/* etcp_router_destroy откладывает queue_entry_free(rconn->ll) через uasync_call_soon
|
||||
(router_close_finalize) — выкачать их, пока ua ещё жив. */
|
||||
uasync_drain_immediate(instance->ua);
|
||||
if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; }
|
||||
fw_free(&instance->fw);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] N done — router/nat_det/fw");
|
||||
|
||||
|
||||
Reference in new issue
Block a user