Browse Source

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-тесты классификации и сопоставления
v2
evgeny 3 weeks ago
parent
commit
ddfffad1ed
  1. 2
      src/Makefile.am
  2. 11
      src/ntp_time.c
  3. 1
      src/ntp_time.h
  4. 53
      src/routing_layer/conn_mgr_core.c
  5. 1
      src/routing_layer/conn_mgr_priv.h
  6. 81
      src/routing_layer/route_connectivity.c
  7. 82
      src/routing_layer/sock_match.c
  8. 97
      src/routing_layer/sock_match.h
  9. 11
      src/routing_layer/topo_node.c
  10. 24
      src/transport_layer/node_conn_direct.c
  11. 1
      src/utun_instance.c
  12. 5
      tests/Makefile.am
  13. 87
      tests/test_sock_match.c

2
src/Makefile.am

@ -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 \

11
src/ntp_time.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;

1
src/ntp_time.h

@ -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

53
src/routing_layer/conn_mgr_core.c

@ -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++; }
}
}
}

1
src/routing_layer/conn_mgr_priv.h

@ -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);

81
src/routing_layer/route_connectivity.c

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

82
src/routing_layer/sock_match.c

@ -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;
}

97
src/routing_layer/sock_match.h

@ -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 */

11
src/routing_layer/topo_node.c

@ -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) {

24
src/transport_layer/node_conn_direct.c

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

1
src/utun_instance.c

@ -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') {

5
tests/Makefile.am

@ -52,6 +52,7 @@ check_PROGRAMS = \
test_broadcast \
test_conn_mgr \
test_conn_mgr_already_connected \
test_sock_match \
test_invite_group_create \
test_etcp_connect \
test_node_conn_direct \
@ -333,6 +334,10 @@ test_conn_mgr_already_connected_SOURCES = test_conn_mgr_already_connected.c
test_conn_mgr_already_connected_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_conn_mgr_already_connected_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_sock_match_SOURCES = test_sock_match.c
test_sock_match_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/lib
test_sock_match_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_invite_group_create_SOURCES = test_invite_group_create.c
test_invite_group_create_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_invite_group_create_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)

87
tests/test_sock_match.c

@ -0,0 +1,87 @@
/**
* @file test_sock_match.c
* @brief Unit-тесты классификации сокетов и сопоставления адресов (sock_match).
*
* Тестирует только чистую логику sock_role_of / sock_target_class / sock_match
* без сети и без UTUN_INSTANCE — с синтетическими struct sock_view и адресами.
*/
#include <stdio.h>
#include <string.h>
#include "../src/routing_layer/sock_match.h"
#include "../src/config_parser.h"
#include "../src/transport_layer/etcp_connections.h"
#include "../src/routing_layer/topo_node.h"
static int fails = 0;
static int checks = 0;
#define CHECK(cond, msg) do { checks++; if (!(cond)) { fails++; printf("FAIL: %s\n", msg); } } while (0)
static struct sock_view mk_view(uint8_t role, uint8_t has_inet, uint8_t has_priv, uint8_t is_eim, uint32_t pub4) {
struct sock_view v; memset(&v, 0, sizeof(v));
v.role = role; v.has_inet = has_inet; v.has_priv = has_priv; v.is_eim = is_eim; v.public_ip4 = pub4;
return v;
}
static void test_role_of(void) {
/* config -> role, при ntp_reachable=1 */
CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "public -> DIRECT");
CHECK(sock_role_of(CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_DIRECT, "unknown -> DIRECT");
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 1) == SOCK_ROLE_DIRECT, "nat+EIM -> DIRECT");
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT,1) == SOCK_ROLE_DIRECT, "nat+DIRECT -> DIRECT");
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT,1) == SOCK_ROLE_NAT, "nat+STRICT -> NAT");
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "nat+unknown -> NAT");
CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_NAT, "private -> NAT");
CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 1) == SOCK_ROLE_LOCAL, "local -> LOCAL");
/* NTP offline: все кроме LOCAL демотируются */
CHECK(sock_role_of(CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline public -> LOCAL");
CHECK(sock_role_of(CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0) == SOCK_ROLE_LOCAL, "offline nat+EIM -> LOCAL");
CHECK(sock_role_of(CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline private -> LOCAL");
CHECK(sock_role_of(CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == SOCK_ROLE_LOCAL, "offline local -> LOCAL");
}
static void test_target_class(void) {
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "public INTERFACE -> PUB");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "unknown -> PUB");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "local -> PRIV");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "private -> PRIV");
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PUB, "nat+EIM NAT-addr -> PUB");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PRIV, "nat+EIM INTERFACE -> PRIV");
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT) == SOCK_TC_PUB, "nat+DIRECT NAT-addr -> PUB");
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_UNREACH, "nat+STRICT NAT-addr -> UNREACH");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_PRIV, "nat+STRICT INTERFACE -> PRIV");
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_UNREACH, "nat+unknown NAT-addr -> UNREACH");
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "nat+unknown INTERFACE -> PRIV");
}
static void test_match(void) {
uint32_t pub = 0x01020304u; /* произвольный "публичный" IP */
struct sock_view direct = mk_view(SOCK_ROLE_DIRECT, 1, 0, 0, pub);
struct sock_view natout = mk_view(SOCK_ROLE_NAT, 1, 1, 0, 0);
struct sock_view local = mk_view(SOCK_ROLE_LOCAL, 0, 1, 0, 0);
/* наш DIRECT (публичный) */
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "DIRECT->PUB ok");
CHECK(sock_match(&direct, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 0, "DIRECT->PRIV нет приватного интерфейса");
/* наш NAT (outbound-only) */
CHECK(sock_match(&natout, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "NAT->PUB ok");
CHECK(sock_match(&natout, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, 0) == 1, "NAT->PRIV (есть приватный)");
/* наш LOCAL (без интернета) */
CHECK(sock_match(&local, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 0, "LOCAL->PUB нет интернета");
CHECK(sock_match(&local, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 1, "LOCAL->PRIV ок (LAN)");
/* UNREACH цель */
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, pub) == 0, "строгий NAT-адрес недостижим");
/* hairpin: оба EIM, одинаковый публичный IP */
struct sock_view eim = mk_view(SOCK_ROLE_DIRECT, 1, 1, 1, pub);
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, pub) == 0, "hairpin (одинаковый публичный IP)");
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0x05060708u) == 1, "EIM разные публичные IP -> ok");
}
int main(void) {
test_role_of();
test_target_class();
test_match();
if (fails) { printf("RESULT: %d/%d failed\n", fails, checks); return 1; }
printf("RESULT: all %d checks passed\n", checks);
return 0;
}
Loading…
Cancel
Save