conn: сопоставление сокетов по ролям (direct/nat/local) + hairpin; NTP-детект оффлайна
- sock_match: единая классификация сокетов (DIRECT/NAT/LOCAL) и адресов (PUB/PRIV/UNREACH) - eim приравнен к direct; на приватный адрес — только с приватного интерфейса; оба EIM за одним NAT (одинаковый публичный IP) — только через LAN - topo_node: LOCAL-сокеты рекламируются (INTERFACE), private скрыт, NAT-адрес не рекламируется для strict и при оффлайне - ntp: флаг reachable по циклам + пере-анонс nodeinfo при смене доступности - cm_direct_add_links / ncd_create_links / conn_match_candidate_sockets на sock_match - test_sock_match: unit-тесты классификации и сопоставления
This commit is contained in:
1 parent
ff53c735dd
commit
ddfffad1ed
13 files changed
+349
-107
No files matched your search
@@ -15,6 +15,7 @@ utun_CORE_SOURCES = \
|
||||
routing_layer/topo_node.c \
|
||||
routing_layer/topo_node_sqlite.c \
|
||||
routing_layer/route_connectivity.c \
|
||||
routing_layer/sock_match.c \
|
||||
routing_layer/conn_mgr_core.c \
|
||||
routing_layer/conn_mgr_indirect.c \
|
||||
routing_layer/conn_mgr_monitor.c \
|
||||
@@ -104,6 +105,7 @@ libutun_a_SOURCES = \
|
||||
routing_layer/topo_node.c \
|
||||
routing_layer/topo_node_sqlite.c \
|
||||
routing_layer/route_connectivity.c \
|
||||
routing_layer/sock_match.c \
|
||||
routing_layer/conn_mgr_core.c \
|
||||
routing_layer/conn_mgr_indirect.c \
|
||||
routing_layer/conn_mgr_monitor.c \
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ntp_time.h"
|
||||
#include "ntp_node_time.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/u_async.h"
|
||||
@@ -294,6 +295,15 @@ static void ntp_finish_cycle(struct ntp_sync* ctx, int synced) {
|
||||
ntp_node_sync_peers(inst);
|
||||
}
|
||||
|
||||
/* Доступность интернет-NTP меняет классификацию сокетов (LOCAL при оффлайне) */
|
||||
{ int prev = ntp->reachable;
|
||||
ntp->reachable = synced ? 1 : 0;
|
||||
if (prev != ntp->reachable) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP: reachability changed %d -> %d, updating nodeinfo", prev, ntp->reachable);
|
||||
topo_node_update_my_addresses(inst);
|
||||
}
|
||||
}
|
||||
|
||||
ntp->timer = uasync_set_timeout(inst->ua, ntp->resync_interval_sec * 10000,
|
||||
inst, ntp_time_sync_cb, "ntp_sync");
|
||||
}
|
||||
@@ -343,6 +353,7 @@ int ntp_time_init(struct UTUN_INSTANCE* instance) {
|
||||
struct global_config* g = &instance->config->global;
|
||||
ntp->enabled = g->ntp_enabled;
|
||||
ntp->synced = 0;
|
||||
ntp->reachable = 1; /* оптимистично: до первого результата считаем, что интернет есть */
|
||||
ntp->offset_us = 0;
|
||||
ntp->last_sync_tb = 0;
|
||||
ntp->timer = NULL;
|
||||
|
||||
@@ -17,6 +17,7 @@ struct UTUN_INSTANCE;
|
||||
struct NTP_TIME {
|
||||
int enabled;
|
||||
int synced;
|
||||
int reachable; // доступность интернет-NTP по циклам (1=есть интернет, 0=нет); для классификации сокетов
|
||||
int64_t offset_us; // local_time - ntp_time (positive = local clock ahead)
|
||||
uint64_t last_sync_tb; // monotonic time of last successful sync (0.1ms units)
|
||||
void* timer; // uasync timer handle
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "config_parser.h"
|
||||
#include "topo_node.h"
|
||||
#include "topo_node_sqlite.h"
|
||||
#include "sock_match.h"
|
||||
#include "topo_group.h"
|
||||
#include "route_ping.h"
|
||||
#ifdef UTUN_HAVE_STANDBY
|
||||
@@ -70,19 +71,6 @@ void cm_deliver_event(struct CONN_MGR_ENTRY* entry, enum conn_mgr_event event) {
|
||||
|
||||
/* ═══════ NAT / адресные утилиты ═══════ */
|
||||
|
||||
/* Проверяет NAT-совместимость нашего сокета с целевым: возвращает 1 (публичный/EIM —
|
||||
* можно пробить), 2 (локальная сеть — оба PRIVATE), 0 (несовместим). Используется
|
||||
* в DIRECT фазе чтобы не создавать заведомо непробиваемые линки. */
|
||||
int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t tcfg, uint8_t tnat) {
|
||||
uint8_t tt = tnat >= NAT_VERIFIED_UNKNOWN ? CFG_SERVER_TYPE_PUBLIC : tcfg;
|
||||
uint8_t ot = our->type;
|
||||
if (ot == CFG_SERVER_TYPE_PUBLIC || ot == CFG_SERVER_TYPE_UNKNOWN) return 1;
|
||||
if (tt == CFG_SERVER_TYPE_PUBLIC) return 1; /* цель публичная/проверенная — исходящее работает из-за любого NAT */
|
||||
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
|
||||
if ((ot == CFG_SERVER_TYPE_PRIVATE || ot == CFG_SERVER_TYPE_LOCAL) && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Классификация IPv6-адреса: link-local (fe80), локальный ULA (fc/fd),
|
||||
* глобальный/прямой, другое (loopback, multicast, ::). */
|
||||
uint8_t cm_classify_v6_addr(const uint8_t addr[16]) {
|
||||
@@ -158,7 +146,7 @@ int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq) {
|
||||
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
|
||||
if (a->type != TOPO_ADDR_INTERFACE) continue;
|
||||
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next)
|
||||
if (m->id == a->socket_id && (m->config_type == CFG_SERVER_TYPE_PRIVATE || m->config_type == CFG_SERVER_TYPE_LOCAL)) return 1;
|
||||
if (m->id == a->socket_id && sock_target_class(a->type, m->config_type, m->nat_type) == SOCK_TC_PRIV) return 1;
|
||||
}
|
||||
for (const struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) {
|
||||
if (a6->type != TOPO_ADDR_INTERFACE) continue;
|
||||
@@ -540,14 +528,16 @@ static void cm_ping_cb_impl(int success, uint16_t rtt, void* arg, uint64_t nonce
|
||||
/* ═══════ DIRECT фаза (NCD + ручная NAT-фильтрация линков) ═══════ */
|
||||
|
||||
/* Добавляет IPv4/IPv6 линки к conn. Единый цикл для адресов из BGP и из базы:
|
||||
* протокол из a->protocol (TCP — без NAT-фильтра, UDP — NAT-фильтрация),
|
||||
* приоритет NAT > INTERFACE. Если ни одного линка не создалось — закрывает NCD
|
||||
* протокол из a->protocol, приоритет NAT > INTERFACE, сопоставление сокетов
|
||||
* через sock_match (UDP и TCP). Если ни одного линка не создалось — закрывает NCD
|
||||
* и переходит к REVERSE/INDIRECT (для db_loaded — cleanup + TIMEOUT). */
|
||||
#define CM_MAX_SOCKET_VIEWS 32
|
||||
static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* entry,
|
||||
struct TOPO_NODE* ni, int db_loaded) {
|
||||
int any = 0, v4_cnt = 0, v6_cnt = 0, v4_tcp = 0, v6_tcp = 0;
|
||||
/* v4 — единый цикл для адресов из BGP и из базы: протокол из a->protocol,
|
||||
* приоритет NAT > INTERFACE, NAT-фильтрация только для UDP */
|
||||
/* Плоский список наших сокетов: роль + локальный/публичный IP + тип */
|
||||
struct sock_view views[CM_MAX_SOCKET_VIEWS];
|
||||
int nviews = sock_collect_views(entry->mgr->instance, views, CM_MAX_SOCKET_VIEWS);
|
||||
uint8_t prio[] = {TOPO_ADDR_NAT, TOPO_ADDR_INTERFACE};
|
||||
for (int pr = 0; pr < 2; pr++) {
|
||||
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
|
||||
@@ -555,21 +545,26 @@ static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* e
|
||||
struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); sin.sin_family=AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr,a->addr,4); sin.sin_port=htons(a->port);
|
||||
struct sockaddr_storage sa; memcpy(&sa,&sin,sizeof(sin));
|
||||
uint32_t tip; memcpy(&tip, a->addr, 4);
|
||||
const struct TOPO_SOCKMETA4* m = NULL;
|
||||
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;
|
||||
if (a->protocol & TOPO_PROTO_TCP) {
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) { if (s->is_tcp && s->local_addr.ss_family == AF_INET && s->type != CFG_SERVER_TYPE_PRIVATE) {
|
||||
struct ETCP_LINK *tlink = etcp_link_new(conn, s, &sa, 0);
|
||||
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;
|
||||
struct ETCP_LINK *tlink = etcp_link_new(conn, v->sock, &sa, 0);
|
||||
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); any = 1; v4_tcp++; v4_cnt++; }
|
||||
} s = s->next; }
|
||||
}
|
||||
}
|
||||
if (a->protocol & TOPO_PROTO_UDP) {
|
||||
for (const struct TOPO_SOCKMETA4* m = ni->v4_sock_meta; m; m = m->next) {
|
||||
if (m->id != a->socket_id) continue;
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) { if (cm_nat_compatible(s, m->config_type, m->nat_type) && s->local_addr.ss_family == AF_INET) {
|
||||
cm_warn_addr_busy(conn, s, &sa);
|
||||
if (etcp_link_new(conn, s, &sa, 0)) { any = 1; v4_cnt++; } } s = s->next; }
|
||||
break;
|
||||
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;
|
||||
cm_warn_addr_busy(conn, v->sock, &sa);
|
||||
if (etcp_link_new(conn, v->sock, &sa, 0)) { any = 1; v4_cnt++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ void cm_handle_direct_req(struct ETCP_CONN* conn, struct CONN_MGR* mgr, const ui
|
||||
void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
void cm_reverse_init_cb(struct ETCP_CONN* conn, int event, void* arg);
|
||||
void cm_reverse_timeout_cb(void* arg);
|
||||
int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t tcfg, uint8_t tnat);
|
||||
void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s);
|
||||
void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s);
|
||||
int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "topo_group.h"
|
||||
#include "route_connectivity.h"
|
||||
#include "topo_node_sqlite.h"
|
||||
#include "sock_match.h"
|
||||
#ifdef UTUN_HAVE_STANDBY
|
||||
#include "standby.h"
|
||||
#endif
|
||||
@@ -80,9 +81,9 @@ static int addr_eq(const struct TOPO_ADDR4* a, uint32_t ip, uint16_t port) {
|
||||
}
|
||||
|
||||
// собирает список локальных сокетов-кандидатов для probing заданного адреса
|
||||
// сортирует: лучшие (по совпадению подсети / типу NAT) первые
|
||||
// (сопоставление через sock_match — роли сокетов и классы адресов)
|
||||
static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
|
||||
uint8_t addr_type,
|
||||
uint8_t addr_type, uint8_t tcfg, uint8_t tnat,
|
||||
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;
|
||||
@@ -120,69 +121,14 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
|
||||
return found;
|
||||
}
|
||||
|
||||
// IPv4 target — existing logic
|
||||
// IPv4: фильтруем наши сокеты через sock_match
|
||||
uint32_t target_ip = ((const struct sockaddr_in*)target_addr)->sin_addr.s_addr;
|
||||
e_sock = instance->etcp_sockets;
|
||||
|
||||
// Проход 1: точное совпадение подсети (для INTERFACE) или PUBLIC/NAT_VERIFIED (для NAT)
|
||||
while (e_sock && found < (int)max_count) {
|
||||
if (e_sock->is_tcp) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family != AF_INET) { e_sock = e_sock->next; continue; }
|
||||
int match = 0;
|
||||
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
|
||||
uint32_t if_ip = if_sin->sin_addr.s_addr;
|
||||
|
||||
if (addr_type == TOPO_ADDR_INTERFACE) {
|
||||
if ((e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL)
|
||||
&& (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
|
||||
match = 1;
|
||||
else if (e_sock->nat_type == NAT_VERIFIED_DIRECT || e_sock->nat_type == NAT_VERIFIED_EIM
|
||||
|| e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
|
||||
match = 1;
|
||||
} else {
|
||||
if (e_sock->nat_type == NAT_VERIFIED_DIRECT) match = 1;
|
||||
else if (e_sock->nat_type == NAT_VERIFIED_EIM) match = 1;
|
||||
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN) match = 1;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
int dup = 0;
|
||||
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
|
||||
if (!dup) out_sockets[found++] = e_sock;
|
||||
}
|
||||
e_sock = e_sock->next;
|
||||
}
|
||||
|
||||
// Проход 2: все остальные подходящие
|
||||
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->local_addr.ss_family != AF_INET) { 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; }
|
||||
if (!dup) {
|
||||
if (addr_type == TOPO_ADDR_INTERFACE) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL
|
||||
|| e_sock->nat_type == NAT_VERIFIED_DIRECT || e_sock->nat_type == NAT_VERIFIED_EIM
|
||||
|| e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
|
||||
out_sockets[found++] = e_sock;
|
||||
} else {
|
||||
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE && e_sock->type != CFG_SERVER_TYPE_LOCAL)
|
||||
out_sockets[found++] = e_sock;
|
||||
}
|
||||
}
|
||||
e_sock = e_sock->next;
|
||||
}
|
||||
|
||||
// Проход 3: остальные IPv4 (fallback)
|
||||
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->local_addr.ss_family != AF_INET) { 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; }
|
||||
if (!dup) out_sockets[found++] = e_sock;
|
||||
e_sock = e_sock->next;
|
||||
struct sock_view views[CONN_MAX_SOCKET_CANDIDATES];
|
||||
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;
|
||||
out_sockets[found++] = views[i].sock;
|
||||
}
|
||||
|
||||
return found;
|
||||
@@ -377,8 +323,11 @@ 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;
|
||||
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; }
|
||||
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
|
||||
int cand_count = conn_match_candidate_sockets(instance, a->type, &target, candidates, CONN_MAX_SOCKET_CANDIDATES);
|
||||
int cand_count = conn_match_candidate_sockets(instance, a->type, tcfg, tnat, &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;
|
||||
@@ -413,7 +362,7 @@ 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, &target, 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);
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/* sock_match.c — классификация сокетов и сопоставление адресов при подключении */
|
||||
#include "sock_match.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "utun_instance.h"
|
||||
#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_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable) {
|
||||
uint8_t role;
|
||||
if (type == CFG_SERVER_TYPE_LOCAL)
|
||||
role = SOCK_ROLE_LOCAL; /* без интернета (из конфига) */
|
||||
else if (type == CFG_SERVER_TYPE_PUBLIC || type == CFG_SERVER_TYPE_UNKNOWN)
|
||||
role = SOCK_ROLE_DIRECT;
|
||||
else if (type == CFG_SERVER_TYPE_NAT)
|
||||
role = (nat_is_eim(nat_type) || nat_is_direct(nat_type)) ? SOCK_ROLE_DIRECT : SOCK_ROLE_NAT;
|
||||
else
|
||||
role = SOCK_ROLE_NAT; /* PRIVATE — скрытый, только исходящие */
|
||||
if (!ntp_reachable && role != SOCK_ROLE_LOCAL)
|
||||
role = SOCK_ROLE_LOCAL; /* NTP недоступен — интернета нет */
|
||||
return role;
|
||||
}
|
||||
|
||||
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: публичный адрес не пробиваем */
|
||||
}
|
||||
|
||||
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->role == SOCK_ROLE_LOCAL) 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;
|
||||
}
|
||||
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 ntp_reachable = inst->ntp.reachable;
|
||||
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->role = sock_role_of(s->type, s->nat_type, ntp_reachable);
|
||||
v->has_inet = (uint8_t)(v->role != SOCK_ROLE_LOCAL);
|
||||
v->has_priv = (uint8_t)(s->type != CFG_SERVER_TYPE_PUBLIC && s->type != CFG_SERVER_TYPE_UNKNOWN);
|
||||
v->is_eim = (uint8_t)(s->type == CFG_SERVER_TYPE_NAT && nat_is_eim(s->nat_type));
|
||||
v->family = AF_INET;
|
||||
v->is_tcp = s->is_tcp;
|
||||
v->local_ip4 = 0; v->public_ip4 = 0;
|
||||
{ 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; }
|
||||
{ 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;
|
||||
else if (s->type == CFG_SERVER_TYPE_PUBLIC || s->type == CFG_SERVER_TYPE_UNKNOWN || nat_is_direct(s->nat_type))
|
||||
v->public_ip4 = v->local_ip4; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "sock_view[%d] sock=%s type=%d nat=%d role=%d inet=%d priv=%d eim=%d tcp=%d local=%08x public=%08x",
|
||||
n - 1, s->name, s->type, s->nat_type, v->role, v->has_inet, v->has_priv, v->is_eim, v->is_tcp,
|
||||
v->local_ip4, v->public_ip4);
|
||||
s = s->next;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @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) — приватный адрес достижим лишь из той же сети.
|
||||
*/
|
||||
#ifndef SOCK_MATCH_H
|
||||
#define SOCK_MATCH_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.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_TC_PUB 0 /* публичный — достижим из интернета */
|
||||
#define SOCK_TC_PRIV 1 /* приватный / LAN */
|
||||
#define SOCK_TC_UNREACH 2 /* напрямую недостижим (strict/unknown NAT) */
|
||||
|
||||
/**
|
||||
* Плоское представление нашего сокета для сопоставления.
|
||||
* "ip сокета" — local_ip4 (interface), "публичный" — public_ip4 (NAT/прямой),
|
||||
* "тип" — role + флаги has_inet/has_priv/is_eim.
|
||||
*/
|
||||
struct sock_view {
|
||||
struct ETCP_SOCKET* sock;
|
||||
uint8_t role; /* SOCK_ROLE_* */
|
||||
uint8_t has_inet; /* 1 — есть интернет (role != LOCAL) */
|
||||
uint8_t has_priv; /* 1 — есть приватный интерфейс (type != PUBLIC/UNKNOWN) */
|
||||
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 если direct, network order (0 если нет) */
|
||||
};
|
||||
|
||||
/* Роль нашего сокета по конфиг-типу, NAT-типу и доступности интернета. */
|
||||
uint8_t sock_role_of(uint8_t type, uint8_t nat_type, int ntp_reachable);
|
||||
|
||||
/* Класс адреса пира по типу адреса (TOPO_ADDR_*), конфиг-типу и NAT-типу сокета. */
|
||||
uint8_t sock_target_class(uint8_t a_type, uint8_t tcfg, uint8_t tnat);
|
||||
|
||||
/* 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 */
|
||||
@@ -930,7 +930,7 @@ 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->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { 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;
|
||||
@@ -947,9 +947,10 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
|
||||
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 (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0
|
||||
/* адрес ПОСЛЕ 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) {
|
||||
struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
|
||||
@@ -972,7 +973,7 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
|
||||
}
|
||||
{ struct ETCP_SOCKET* ts = instance->etcp_sockets;
|
||||
while (ts) { if (!ts->is_tcp) { ts = ts->next; continue; }
|
||||
if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; }
|
||||
if (ts->type == CFG_SERVER_TYPE_PRIVATE) { ts = ts->next; continue; } /* private скрыт, LOCAL рекламируем (LAN) */
|
||||
struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr;
|
||||
if (!addr || !addr->ss_family) { ts = ts->next; continue; }
|
||||
if (addr->ss_family == AF_INET) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "utun_instance.h"
|
||||
#include "topo_node.h"
|
||||
#include "topo_node_sqlite.h"
|
||||
#include "sock_match.h"
|
||||
#include "topo_group.h"
|
||||
#include "config_parser.h"
|
||||
#include "../lib/debug_config.h"
|
||||
@@ -126,6 +127,7 @@ static struct TOPO_NODE* ncd_lookup_node(struct UTUN_INSTANCE* inst, uint64_t no
|
||||
*/
|
||||
|
||||
/* Создаёт один UDP-линк на use_sock → sa, с вытеснением stale-линка и дедупом. */
|
||||
#define NCD_MAX_SOCKET_VIEWS 32
|
||||
static int ncd_add_udp_link(struct ETCP_CONN* conn, struct ETCP_SOCKET* use_sock, struct sockaddr_storage* sa) {
|
||||
struct ETCP_LINK* stale = etcp_link_find_by_addr(use_sock, sa, 0);
|
||||
if (stale && stale->etcp == conn) return 0;
|
||||
@@ -167,6 +169,8 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
uint64_t nid = entry->node_id;
|
||||
|
||||
/* ---- IPv4 ---- */
|
||||
struct sock_view views[NCD_MAX_SOCKET_VIEWS];
|
||||
int nviews = sock_collect_views(inst, views, NCD_MAX_SOCKET_VIEWS);
|
||||
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
|
||||
v4_addrs++;
|
||||
if (a->port == 0 || !(a->protocol & (TOPO_PROTO_UDP | TOPO_PROTO_TCP))) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[ncd] v4 skip proto=%02x port=%d node=0x%016llx", a->protocol, (int)a->port, (unsigned long long)nid); v4_skip++; continue; }
|
||||
@@ -175,11 +179,18 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, a->addr, 4); sin.sin_port = htons(a->port);
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
|
||||
uint32_t tip; memcpy(&tip, a->addr, 4);
|
||||
const struct TOPO_SOCKMETA4* m = NULL;
|
||||
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;
|
||||
|
||||
if (a->protocol & TOPO_PROTO_TCP) {
|
||||
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
|
||||
if (!s->is_tcp || s->local_addr.ss_family != AF_INET || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
struct ETCP_LINK* tlink = etcp_link_new(conn, s, &sa, 0);
|
||||
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;
|
||||
struct ETCP_LINK* tlink = etcp_link_new(conn, v->sock, &sa, 0);
|
||||
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
|
||||
}
|
||||
}
|
||||
@@ -187,9 +198,10 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
if (specific_sock && !specific_sock->is_tcp && specific_sock->local_addr.ss_family == AF_INET) {
|
||||
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_INET || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
link_count += ncd_add_udp_link(conn, s, &sa);
|
||||
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;
|
||||
link_count += ncd_add_udp_link(conn, v->sock, &sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
instance->running = 0;
|
||||
instance->ua = ua;
|
||||
instance->config = config;
|
||||
instance->ntp.reachable = 1; /* оптимистично: до первого NTP-цикла считаем, что интернет есть */
|
||||
|
||||
// Set name from config
|
||||
if (config->global.name[0] != '\0') {
|
||||
|
||||
Reference in new issue
Block a user