auto_socket: TCP links support, chat_sync retry on socket change, link dedup by is_tcp

This commit is contained in:
evgeny committed 2026-08-11 18:57:24 +03:00
1 parent fec3ea38a7
commit d4a170b5c7
11 files changed
+945 -117

No files matched your search

+14
View File
@@ -653,6 +653,20 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst) {
u_free(cs);
}
void chat_sync_retry_channels_on_socket_change(struct UTUN_INSTANCE* inst) {
if (!g_cs || !g_cs->initialized || !inst->topo_groups) return;
for (int i = 0; i < g_cs->channel_count; i++) {
uint64_t gid = strtoull(g_cs->channels[i].channel_id, NULL, 10);
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, gid);
if (!group || !group->connect) continue;
if (topo_group_connect_active_count(group) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: socket change — restarting connect for ch=%s (no active connections)",
CS_ID, g_cs->channels[i].channel_id);
topo_group_connect_restart(group);
}
}
}
void chat_sync_connect_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
(void)inst; (void)node_id;
}
+4
View File
@@ -108,6 +108,10 @@ void chat_sync_deny_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint
Канал (DB + TOPO_GROUP) создаётся локально при получении CHANNEL_INFO_RESP. */
void chat_sync_join_channel(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id);
/* Перезапустить групповые подключения для каналов без активных соединений.
Вызывается из auto_socket при появлении/изменении сетевой связности. */
void chat_sync_retry_channels_on_socket_change(struct UTUN_INSTANCE* inst);
#ifdef __cplusplus
}
#endif
+1 -2
View File
@@ -109,9 +109,8 @@ void topo_group_connect_destroy(struct TOPO_GROUP* group) {
void topo_group_connect_restart(struct TOPO_GROUP* group) {
struct TOPO_GROUP_CONNECT* gc = group->connect;
if (!gc) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s no gc → init", TGC_ID, group->channel_id); topo_group_connect_init(group); return; }
if (gc->phase != TGC_PHASE_DONE) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s skip phase=%d", TGC_ID, group->channel_id, gc->phase); return; }
if (gc->active_conn_count > 0) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: restart ch=%s skip active=%d", TGC_ID, group->channel_id, gc->active_conn_count); return; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: restart ch=%s", TGC_ID, group->channel_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: restart ch=%s phase=%d", TGC_ID, group->channel_id, gc->phase);
topo_group_connect_destroy(group);
topo_group_connect_init(group);
}
+254 -68
View File
@@ -1,25 +1,45 @@
/*
* auto_socket.c — автоматическое управление UDP-сокетами при изменении сетевых интерфейсов
* auto_socket.c — автоматическое управление сокетами (UDP+TCP) при изменении сетевых интерфейсов
*
* При auto_sockets=yes:
* - Для каждого не-loopback интерфейса (UP, с IP-адресами) создаётся UDP-сокет
* с bind на 0.0.0.0/[::] + случайный порт, привязанный к интерфейсу (SO_BINDTODEVICE)
* - Тип: PUBLIC если есть публичный адрес, иначе NAT
* - При добавлении сокета — линки ко всем активным NCD-соединениям
* - При удалении интерфейса — линки удаляются, сокет закрывается
* === Зачем ===
* При auto_sockets=yes модуль сам создаёт UDP и TCP сокеты на всех не-loopback
* интерфейсах с IP-адресами. Не нужно вручную описывать серверы в конфиге.
*
* Стратегия reconcile:
* При любом изменении адресов/линков сканируем фактические IP интерфейса,
* сравниваем с состоянием сокетов и устраняем расхождения:
* === Что создаёт ===
* Для каждого интерфейса с v4/v6 адресами:
* 1. UDP-сокет (etcp_socket_add) — bind на 0.0.0.0/[::] + SO_BINDTODEVICE, случайный порт
* 2. TCP-сокет (tcp_socket_add + stcp_server) — слушает входящие STCP-подключения
* 3. Для каждого сокета сразу добавляются линки (etcp_link_new) ко ВСЕМ активным
* ETCP-соединениям (inst->connections), случайно выбирая один совместимый
* адрес пира отдельно для v4 и v6.
*
* === Тип сокета ===
* PUBLIC — если на интерфейсе есть глобальный (не RFC1918) адрес; иначе NAT.
* При смене типа (например VPN включился/выключился) сокет пересоздаётся.
*
* === Persistence портов ===
* Порт сохраняется в SQLite-таблицу auto_socket_ports и переиспользуется
* при перезапуске utun. Если сохранённый порт занят — генерируется новый случайный.
*
* === Стратегия reconcile ===
* При любом изменении адресов/линков сканируем фактические IP интерфейса,
* сравниваем с состоянием сокетов и устраняем расхождения:
* - появился адрес нужного family → создать сокет + линки
* - исчезли все адреса family → удалить сокет + линки
* - изменился тип (NAT↔PUBLIC) → пересоздать сокет с новым типом + линки
* - без изменений → только обновить interface_addr
* - изменился сам адрес → обновить interface_addr, оповестить подписчиков
* - интерфейс стал DOWN → удалить все сокеты
*
* Слои:
* Platform-agnostic: auto_socket_add_interface / auto_socket_remove_interface
* Platform monitoring: netlink (Linux), route socket (BSD), IP Helper (Windows)
* === Линки ===
* Каждый созданный сокет получает линки ко всем активным соединениям через
* ncd_add_socket_links() — по одному случайному адресу пира на каждый address family.
* На линке сохраняется local_bound_addr (interface_addr в момент создания) для
* последующей проверки принадлежности.
*
* === Platform monitoring ===
* Встроенные обработчики: netlink (Linux), route socket (BSD), IP Helper (Windows).
* При отсутствии поддержки платформы мониторинг неактивен — можно вызывать
* auto_socket_add_interface / auto_socket_on_network_change извне (JNI и т.д.).
*/
#include "auto_socket.h"
@@ -31,6 +51,7 @@
#include "utun_instance.h"
#include "config_parser.h"
#include "../chat/chat_event.h"
#include "../chat/chat_sync.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
@@ -65,7 +86,9 @@
#define AS_PROTO_UDP 0
#define AS_PROTO_TCP 1
/* ─── IPv4-адрес: публичный (интернет) или приватный/NAT ─── */
/* Классифицирует IPv4-адрес: 1 = глобальный/публичный (можно использовать как PUBLIC-сокет),
* 0 = приватный, CGNAT, loopback, link-local, multicast или зарезервированный.
* Определяет тип создаваемого сокета по адресам на интерфейсе. */
static int is_ipv4_public(uint32_t addr_be) {
uint32_t a = ntohl(addr_be);
if ((a & 0xFF000000u) == 0x0A000000u) return 0; /* 10.0.0.0/8 */
@@ -80,7 +103,11 @@ static int is_ipv4_public(uint32_t addr_be) {
return 1;
}
/* ─── IPv6-адрес: глобальный или локальный (аналог cm_classify_v6_addr) ─── */
/* Классифицирует IPv6-адрес для определения типа сокета:
* AS_V6_LL = link-local (fe80::) — создаём NAT-сокет
* AS_V6_LOC = ULA (fc00::/fd00::) — локальный, NAT-сокет
* AS_V6_DIR = глобальный — создаём PUBLIC-сокет
* AS_V6_OTH = loopback, multicast, :: — пропускаем */
#define AS_V6_LL 0
#define AS_V6_LOC 1
#define AS_V6_DIR 2
@@ -95,31 +122,40 @@ static int v6_classify(const uint8_t addr[16]) {
return AS_V6_DIR;
}
/* ─── Внутреннее состояние модуля ─── */
/* Один отслеживаемый интерфейс: хранит указатели на созданные сокеты (UDP — ETCP_SOCKET,
* TCP — stcp_server) и их актуальный тип (NAT/PUBLIC). По типу определяется когда
* нужно пересоздать сокет при изменении адресов. */
struct auto_sock_iface {
struct auto_sock_iface* next;
uint32_t netif_index;
struct ETCP_SOCKET* v4_udp;
struct ETCP_SOCKET* v6_udp;
struct stcp_server* v4_tcp;
struct stcp_server* v6_tcp;
uint8_t v4_type;
uint8_t v6_type;
uint32_t netif_index; // индекс интерфейса (if_nametoindex)
struct ETCP_SOCKET* v4_udp; // UDP-сокет для IPv4 (NULL если не создан)
struct ETCP_SOCKET* v6_udp; // UDP-сокет для IPv6
struct stcp_server* v4_tcp; // TCP STCP-сервер для IPv4
struct stcp_server* v6_tcp; // TCP STCP-сервер для IPv6
uint8_t v4_type; // CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
uint8_t v6_type; // CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
};
/* Внутреннее состояние модуля auto_socket: хранит связный список отслеживаемых
* интерфейсов и платформенные handle'ы для мониторинга изменений сети.
* Создаётся в auto_socket_init, уничтожается в auto_socket_destroy.
* Хранится в inst->auto_socket_state. */
struct AUTO_SOCKET {
struct UTUN_INSTANCE* instance;
struct auto_sock_iface* ifaces;
struct UTUN_INSTANCE* instance; // родительский utun instance
struct auto_sock_iface* ifaces; // связный список интерфейсов
uint8_t v4_usable; // есть ли сейчас рабочий v4-сокет с реальным IP
uint8_t v6_usable; // есть ли сейчас рабочий v6-сокет с реальным IP
uint8_t v4_addr_changed; // v4-адрес или тип изменился с прошлой проверки
uint8_t v6_addr_changed; // v6-адрес или тип изменился с прошлой проверки
#ifdef __linux__
socket_t nl_sock;
void* uasync_handle;
socket_t nl_sock; // netlink socket fd для RTMGRP_LINK/IPV4/IPV6
void* uasync_handle; // handle в uasync для netlink fd
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
socket_t route_sock;
void* uasync_handle;
socket_t route_sock; // route socket fd (PF_ROUTE)
void* uasync_handle; // handle в uasync для route fd
#elif defined(_WIN32)
HANDLE addr_notify_handle;
HANDLE route_notify_handle;
HANDLE addr_notify_handle; // NotifyUnicastIpAddressChange handle
HANDLE route_notify_handle; // NotifyRouteChange2 handle
#endif
};
@@ -131,8 +167,12 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, int* o
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname);
static void auto_socket_scan_all(struct AUTO_SOCKET* as);
/* ─── DB helpers (port persistence) ─── */
/* ─── DB helpers (port persistence) ───
* Сохраняют порт сокета в SQLite чтобы переиспользовать его при перезапуске utun.
* Это сохраняет NAT-привязку (endpoint mapping) на роутере и позволяет пирам
* продолжать достукиваться на тот же порт. */
/* Возвращает сохранённый порт для указанного интерфейса/family/протокола, или 0 если нет. */
static uint16_t load_port_from_db(struct AUTO_SOCKET* as, const char* ifname, int family, int protocol) {
sqlite3* db = as->instance->topo_sqlite_db;
if (!db) return 0;
@@ -149,6 +189,7 @@ static uint16_t load_port_from_db(struct AUTO_SOCKET* as, const char* ifname, in
return port;
}
/* Сохраняет порт в БД для переиспользования при следующем запуске. */
static void save_port_to_db(struct AUTO_SOCKET* as, const char* ifname, int family, int protocol, uint16_t port) {
sqlite3* db = as->instance->topo_sqlite_db;
if (!db) return;
@@ -169,6 +210,7 @@ static void save_port_to_db(struct AUTO_SOCKET* as, const char* ifname, int fami
sqlite3_finalize(stmt);
}
/* Удаляет запись порта из БД после закрытия сокета. */
static void delete_port_from_db(struct AUTO_SOCKET* as, const char* ifname, int family, int protocol) {
sqlite3* db = as->instance->topo_sqlite_db;
if (!db) return;
@@ -183,19 +225,62 @@ static void delete_port_from_db(struct AUTO_SOCKET* as, const char* ifname, int
sqlite3_finalize(stmt);
}
/* Отправляет событие CHAT_EVT_LOCAL_SOCKETS в GUI/headless — оповещает что
* список локальных сокетов изменился (добавился/удалился интерфейс). */
static int as_check_usable(struct AUTO_SOCKET* as, int family) {
struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) {
if (s->local_addr.ss_family == family && s->interface_addr.ss_family == family) return 1;
s = s->next;
}
return 0;
}
static void auto_socket_post_sockets_changed(struct AUTO_SOCKET* as) {
int old_v4 = as->v4_usable, old_v6 = as->v6_usable;
as->v4_usable = as_check_usable(as, AF_INET);
as->v6_usable = as_check_usable(as, AF_INET6);
int need_reconnect = 0;
if ((!old_v4 && as->v4_usable) || (!old_v6 && as->v6_usable))
need_reconnect = 1;
if (as->v4_addr_changed || as->v6_addr_changed)
need_reconnect = 1;
as->v4_addr_changed = 0; as->v6_addr_changed = 0;
chat_event_post(CHAT_EVT_LOCAL_SOCKETS, NULL, 0);
(void)as;
if (need_reconnect) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] connectivity change: v4 %d→%d v6 %d→%d — triggering reconnect",
old_v4, as->v4_usable, old_v6, as->v6_usable);
chat_sync_retry_channels_on_socket_change(as->instance);
}
}
/* ═══════════ Platform-agnostic core ═══════════ */
/* Генерирует случайный порт в диапазоне 1024..65535 для нового сокета. */
static uint16_t random_port(void) {
uint32_t r;
random_bytes((uint8_t*)&r, sizeof(r));
return (uint16_t)((r % (65535 - 1024)) + 1024);
}
/*
* Создаёт UDP-сокет привязанный к конкретному интерфейсу и добавляет к нему
* линки ко всем активным ETCP-соединениям.
*
* Алгоритм:
* 1. Пытается переиспользовать сохранённый порт из БД (на случай перезапуска)
* 2. Если занят — генерирует случайный (до 10 попыток)
* 3. Вызывает etcp_socket_add — создаёт ETCP_SOCKET с bind на интерфейс
* 4. Обновляет interface_addr через socket_monitor_update_if_addr
* 5. Отбрасывает сокет если адрес нулевой (INADDR_ANY / ::) — интерфейс без реального IP
* 6. Сохраняет порт в БД для будущих перезапусков
* 7. Вызывает ncd_add_socket_links — добавляет по одному случайному линку
* к каждому активному соединению (для каждого address family)
*
* Возвращает 0 при успехе, -1 при ошибке. */
static int create_iface_udp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname, int family, uint8_t type) {
struct UTUN_INSTANCE* inst = as->instance;
struct CFG_SERVER server;
@@ -266,6 +351,20 @@ static int create_iface_udp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, con
return -1;
}
/*
* Создаёт TCP-сокет и STCP-сервер на интерфейсе, добавляет исходящие TCP-линки
* ко всем активным ETCP-соединениям.
*
* Алгоритм аналогичен create_iface_udp_socket:
* 1. Переиспользует сохранённый порт или генерирует случайный
* 2. Создаёт ETCP_SOCKET через tcp_socket_add
* 3. Отбрасывает если адрес нулевой
* 4. Создаёт stcp_server (слушает входящие STCP-подключения)
* 5. Регистрирует сервер через stcp_server_list_add
* 6. Вызывает ncd_add_socket_links — добавляет исходящие TCP-линки
* (etcp_link_new + is_tcp=1 + etcp_tcp_link_start_connect) к каждому соединению
*
* Возвращает stcp_server* при успехе, NULL при ошибке. */
static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname, int family, uint8_t type) {
struct UTUN_INSTANCE* inst = as->instance;
@@ -323,6 +422,11 @@ static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint3
save_port_to_db(as, ifname, family, AS_PROTO_TCP, port);
{ int tcp_links = ncd_add_socket_links(inst, ts);
if (tcp_links > 0)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] added %d TCP links on if=%s", tcp_links, ifname);
}
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] TCP socket created: if=%s(%u) type=%d port=%u", ifname, ifindex, type, port);
return tsrv;
}
@@ -386,6 +490,14 @@ void auto_socket_remove_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex)
remove_iface_sockets(as, ifindex);
}
/*
* Удаляет все сокеты и линки для указанного интерфейса.
* Для каждого из 4 сокетов (v4 UDP/TCP, v6 UDP/TCP):
* 1. Закрывает ETCP-линки через ncd_remove_socket_links
* 2. Удаляет ETCP_SOCKET (etcp_socket_remove / tcp_socket_remove)
* 3. Для TCP — дополнительно останавливает stcp_server
* 4. Удаляет запись порта из БД
* Затем освобождает запись auto_sock_iface и оповещает GUI. */
static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex) {
struct auto_sock_iface** pp = &as->ifaces;
while (*pp) {
@@ -450,12 +562,19 @@ static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex) {
}
}
/* ─── Сканирование адресов интерфейса и определение типа ─── */
/* ─── Сканирование адресов интерфейса и определение типа ───
*
* Эти функции анализируют реальное состояние интерфейса: есть ли v4/v6 адреса,
* являются ли они публичными или приватными. Результат используется reconcile_iface
* для принятия решений о создании/удалении/пересоздании сокетов. */
#if !defined(_WIN32)
#include <stdio.h>
/* Проверяет через /proc/net/if_inet6: есть ли на интерфейсе постоянные (не temporary)
* IPv6-адреса. Временные адреса (privacy extensions) пропускаются — они нестабильны
* и не подходят для долгоживущих P2P-соединений. */
/* /proc/net/if_inet6 format: hex_addr(32) ifindex(2) prefixlen(2) scope(2) flags(2) ifname
flags bit 0x20 = IFA_F_TEMPORARY */
static int iface_has_permanent_v6(const char* ifname) {
@@ -476,6 +595,8 @@ static int iface_has_permanent_v6(const char* ifname) {
return found;
}
/* Проверяет является ли конкретный IPv6-адрес временным (IFA_F_TEMPORARY,
* privacy extensions). Такие адреса меняются и не годятся для P2P. */
static int is_v6_addr_temporary(const uint8_t addr[16], const char* ifname) {
char hex[33];
for (int i = 0; i < 16; i++) snprintf(hex + i * 2, 3, "%02x", addr[i]);
@@ -495,6 +616,13 @@ static int is_v6_addr_temporary(const uint8_t addr[16], const char* ifname) {
return tmp;
}
/*
* Сканирует все IP-адреса на интерфейсе и определяет:
* - есть ли IPv4/IPv6 адреса (out_has_v4, out_has_v6)
* - тип адресов: PUBLIC (глобальный) или NAT (приватный/link-local)
* Для IPv6 временные адреса (privacy extensions) пропускаются.
* Если интерфейс не UP — все out-параметры сбрасываются в 0/NAT.
* Используется reconcile_iface для сравнения ожидаемого и фактического состояния. */
static int scan_and_classify_iface(uint32_t ifindex, const char* ifname,
int* out_has_v4, int* out_has_v6,
uint8_t* out_v4_type, uint8_t* out_v6_type) {
@@ -539,6 +667,10 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname,
return 0;
}
/*
* Полное сканирование: обходит все UP-интерфейсы (не loopback) и для каждого
* вызывает reconcile_iface. Используется при старте и при ручном запросе
* пересканирования (auto_socket_on_network_change). */
static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
struct ifaddrs* ifa_list = NULL;
if (getifaddrs(&ifa_list) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] getifaddrs: %s", strerror(errno)); return; }
@@ -585,9 +717,16 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
#endif /* _WIN32 */
/* ═══════════ reconcile: сверка и исправление состояния сокетов ═══════════ */
/* ═══════════ reconcile: сверка и исправление состояния сокетов ═══════════
*
* reconcile — ключевой механизм модуля. При любом изменении сети сравнивает
* фактическое состояние интерфейса (IP-адреса, их тип) с текущим набором сокетов
* и устраняет расхождения: создаёт недостающие сокеты, удаляет лишние,
* пересоздаёт при смене типа (NAT↔PUBLIC), обновляет interface_addr. */
/* helper: ensure ifa record exists, allocate if needed */
/* Находит или создаёт запись auto_sock_iface для интерфейса.
* Нужна потому что reconcile_iface может вызываться до auto_socket_add_interface
* (например при старте через auto_socket_scan_all). */
static struct auto_sock_iface* reconcile_get_ifa(struct AUTO_SOCKET* as, uint32_t ifindex) {
struct auto_sock_iface* ifa = as->ifaces;
while (ifa) { if (ifa->netif_index == ifindex) return ifa; ifa = ifa->next; }
@@ -599,7 +738,8 @@ static struct auto_sock_iface* reconcile_get_ifa(struct AUTO_SOCKET* as, uint32_
return ifa;
}
/* helper: remove ifa record if all 4 sockets gone */
/* Удаляет запись auto_sock_iface если все 4 сокета (v4/v6 UDP+TCP) отсутствуют.
* Вызывается после reconcile чтобы подчистить записи для интерфейсов без сокетов. */
static void reconcile_prune_ifa(struct AUTO_SOCKET* as, struct auto_sock_iface* ifa, uint32_t ifindex, const char* ifname) {
if (!ifa) return;
if (ifa->v4_udp || ifa->v6_udp || ifa->v4_tcp || ifa->v6_tcp) return;
@@ -610,6 +750,19 @@ static void reconcile_prune_ifa(struct AUTO_SOCKET* as, struct auto_sock_iface*
u_free(ifa);
}
/*
* Сверяет состояние интерфейса с набором сокетов и устраняет расхождения.
* Это основная логика модуля — вызывается при ЛЮБОМ изменении сети.
*
* Для каждого из 4 сокетов (v4/v6 UDP/TCP) проверяет 4 сценария:
* 1. Адрес есть а сокета нет → создать сокет + линки
* 2. Адреса нет а сокет есть → удалить сокет + линки
* 3. Тип изменился (NAT↔PUBLIC) → пересоздать сокет с новым типом
* 4. Адрес изменился (тот же семейство/тип, другой IP) → обновить
* interface_addr, оповестить через ETCP_SOCKET_EVENT_ADDR_CHANGED
*
* В конце удаляет запись auto_sock_iface если все сокеты отсутствуют,
* и оповещает GUI если были изменения. */
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) {
if (ifindex == 0) return;
int changed = 0;
@@ -639,22 +792,28 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v4_udp = NULL; changed = 1;
} else if (has_v4 && sock && cur_type != v4_need_type) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP type changed: %s type %d→%d", ifname, cur_type, v4_need_type);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
create_iface_udp_socket(as, ifindex, ifname, AF_INET, v4_need_type);
ifa->v4_type = v4_need_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET) { ifa->v4_udp = s; break; } s = s->next; } }
changed = 1;
} else if (has_v4 && sock) {
struct sockaddr_storage old4 = sock->interface_addr;
socket_monitor_update_if_addr(sock);
if (memcmp(&old4, &sock->interface_addr, sizeof(old4)) != 0) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP (%s) addr changed: %s → %s",
ifname, sockaddr_storage_to_str(&old4).str, sockaddr_storage_to_str(&sock->interface_addr).str);
etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED);
int addr_changed = (memcmp(&old4, &sock->interface_addr, sizeof(old4)) != 0);
int type_changed = (cur_type != v4_need_type);
if (addr_changed || type_changed) {
uint8_t new_type = type_changed ? v4_need_type : cur_type;
if (addr_changed && type_changed)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP (%s) addr+type changed: %s→%s type %d→%d", ifname,
sockaddr_storage_to_str(&old4).str, sockaddr_storage_to_str(&sock->interface_addr).str, cur_type, new_type);
else if (type_changed)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP (%s) type changed: %d→%d", ifname, cur_type, new_type);
else
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP (%s) addr changed: %s→%s", ifname,
sockaddr_storage_to_str(&old4).str, sockaddr_storage_to_str(&sock->interface_addr).str);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
create_iface_udp_socket(as, ifindex, ifname, AF_INET, new_type);
ifa->v4_type = new_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET) { ifa->v4_udp = s; break; } s = s->next; } }
as->v4_addr_changed = 1;
changed = 1;
}
}
@@ -678,22 +837,28 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v6_udp = NULL; changed = 1;
} else if (has_v6 && sock && cur_type != v6_need_type) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP type changed: %s type %d→%d", ifname, cur_type, v6_need_type);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
create_iface_udp_socket(as, ifindex, ifname, AF_INET6, v6_need_type);
ifa->v6_type = v6_need_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6) { ifa->v6_udp = s; break; } s = s->next; } }
changed = 1;
} else if (has_v6 && sock) {
struct sockaddr_storage old6 = sock->interface_addr;
socket_monitor_update_if_addr(sock);
if (memcmp(&old6, &sock->interface_addr, sizeof(old6)) != 0) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP (%s) addr changed: %s → %s",
ifname, sockaddr_storage_to_str(&old6).str, sockaddr_storage_to_str(&sock->interface_addr).str);
etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED);
int addr_changed = (memcmp(&old6, &sock->interface_addr, sizeof(old6)) != 0);
int type_changed = (cur_type != v6_need_type);
if (addr_changed || type_changed) {
uint8_t new_type = type_changed ? v6_need_type : cur_type;
if (addr_changed && type_changed)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP (%s) addr+type changed: %s→%s type %d→%d", ifname,
sockaddr_storage_to_str(&old6).str, sockaddr_storage_to_str(&sock->interface_addr).str, cur_type, new_type);
else if (type_changed)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP (%s) type changed: %d→%d", ifname, cur_type, new_type);
else
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP (%s) addr changed: %s→%s", ifname,
sockaddr_storage_to_str(&old6).str, sockaddr_storage_to_str(&sock->interface_addr).str);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
create_iface_udp_socket(as, ifindex, ifname, AF_INET6, new_type);
ifa->v6_type = new_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6) { ifa->v6_udp = s; break; } s = s->next; } }
as->v6_addr_changed = 1;
changed = 1;
}
}
@@ -754,6 +919,7 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char
/* ─── Linux: netlink ─── */
#ifdef __linux__
/* При изменении IP-адреса (RTM_NEWADDR / RTM_DELADDR) — запускаем reconcile для интерфейса. */
static void handle_nl_addr(struct AUTO_SOCKET* as, struct ifaddrmsg* ifa, int msg_type) {
(void)msg_type;
char ifname[IFNAMSIZ];
@@ -761,6 +927,9 @@ static void handle_nl_addr(struct AUTO_SOCKET* as, struct ifaddrmsg* ifa, int ms
reconcile_iface(as, ifa->ifa_index, ifname);
}
/* При изменении состояния линка (RTM_NEWLINK / RTM_DELLINK):
* - DOWN или удаление → удаляем все сокеты интерфейса
* - UP (новый или поднялся) → запускаем reconcile */
static void handle_nl_link(struct AUTO_SOCKET* as, struct ifinfomsg* ifi, int msg_type) {
char ifname[IFNAMSIZ];
if (!if_indextoname(ifi->ifi_index, ifname)) return;
@@ -776,6 +945,8 @@ static void handle_nl_link(struct AUTO_SOCKET* as, struct ifinfomsg* ifi, int ms
}
}
/* Коллбэк uasync для netlink-сокета. Принимает все сообщения netlink,
* разбирает их и направляет в handle_nl_addr / handle_nl_link. */
static void auto_socket_netlink_cb(int fd, void* arg) {
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)arg;
char buf[8192];
@@ -822,12 +993,15 @@ static int auto_socket_init_monitor(struct AUTO_SOCKET* as) {
return 0;
}
/* Отключает netlink-мониторинг: убирает fd из uasync, закрывает сокет. */
static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) {
if (as->uasync_handle) { uasync_remove_socket(as->instance->ua, as->uasync_handle); as->uasync_handle = NULL; }
if (as->nl_sock >= 0) { close(as->nl_sock); as->nl_sock = -1; }
}
/* ─── BSD: route socket ─── */
/* ─── BSD: route socket ───
* На FreeBSD мониторим изменения через PF_ROUTE сокет. При любых изменениях
* адресов/интерфейсов запускаем полное пересканирование. */
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
static void auto_socket_bsd_cb(int fd, void* arg) {
@@ -859,7 +1033,9 @@ static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) {
if (as->route_sock >= 0) { close(as->route_sock); as->route_sock = -1; }
}
/* ─── Windows: IP Helper ─── */
/* ─── Windows: IP Helper ───
* Используем NotifyUnicastIpAddressChange + NotifyRouteChange2.
* Коллбэки приходят из системных потоков → uasync_post для обработки в главном потоке. */
#elif defined(_WIN32)
static void process_win_notify(void* arg) {
@@ -894,12 +1070,19 @@ static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) {
}
#else
static int auto_socket_init_monitor(struct AUTO_SOCKET* as) { (void)as; return 0; }
/* Платформа без встроенного мониторинга — только явные вызовы API (JNI и т.д.). */
static int auto_socket_init_monitor(struct AUTO_SOCKET* as) { (void)as; return 0; }
static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) { (void)as; }
#endif
/* ═══════════ Public API ═══════════ */
/*
* Инициализирует модуль auto_socket. Если auto_sockets=no в конфиге — сразу
* возвращает 0 без действий. Иначе создаёт таблицу портов в БД, запускает
* платформенный мониторинг и сканирует все текущие UP-интерфейсы.
* Состояние хранится в inst->auto_socket_state.
*/
int auto_socket_init(struct UTUN_INSTANCE* inst) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] instance is NULL"); return -1; }
if (!inst->config || !inst->config->global.auto_sockets) return 0;
@@ -930,6 +1113,9 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) {
return 0;
}
/*
* Завершает работу модуля: останавливает платформенный мониторинг, удаляет
* все сокеты и линки, освобождает память. Безопасно вызывать повторно. */
void auto_socket_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->auto_socket_state) return;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state;
+45 -20
View File
@@ -6,20 +6,32 @@ extern "C" {
#endif
/*
* auto_socket.h — автоматическое управление UDP-сокетами при изменении сетевых интерфейсов
* auto_socket.h — автоматическое управление сокетами при изменении сетевых интерфейсов
*
* При включённой опции auto_sockets=yes:
* - Ручные [server] сокеты пропускаются
* - Для каждого не-loopback интерфейса (UP, с IP-адресами) создаётся UDP-сокет (v4/v6)
* с bind на 0.0.0.0/[::] + случайный порт, привязанный к интерфейсу
* - Тип сокета: PUBLIC если есть публичный адрес, иначе NAT
* - Для каждого нового сокета добавляются линки ко всем активным NCD-соединениям
* - При пропадании интерфейса линки удаляются, сокет закрывается
* === Зачем нужен модуль ===
* При auto_sockets=yes не нужно вручную прописывать секции [server] в конфиге.
* Модуль сам создаёт UDP+TCP сокеты на всех не-loopback интерфейсах с IP-адресами,
* и сразу добавляет линки ко всем активным ETCP-соединениям. При пропадании
* интерфейса линки удаляются, сокеты закрываются.
*
* API разделён на два слоя:
* 1. auto_socket_init / auto_socket_destroy — жизненный цикл модуля
* 2. auto_socket_add_interface / auto_socket_remove_interface — platform-agnostic
* вызываются из платформенного мониторинга (netlink, Android JNI, etc.)
* === Что делает ===
* - Для каждого интерфейса (UP, с v4/v6 адресами) создаёт пару сокетов:
* UDP (случайный порт) + TCP (stcp_server на случайном порту)
* - Тип сокета: PUBLIC если на интерфейсе есть глобальный адрес, иначе NAT
* - Порт сохраняется в SQLite для переиспользования при перезапуске
* - Для каждого нового сокета: etcp_link_new ко всем соединениям (inst->connections),
* случайно выбирая один совместимый адрес пира (отдельно для v4 и v6)
* - При изменении адреса на интерфейсе — обновляет interface_addr и оповещает
* подписчиков (ETCP_SOCKET_EVENT_ADDR_CHANGED)
* - При смене типа (NAT↔PUBLIC) — пересоздаёт сокет с новым типом
* - При пропадании интерфейса — удаляет линки, закрывает сокет, чистит БД
*
* === Слои ===
* 1. Platform-agnostic API: auto_socket_init / auto_socket_destroy
* auto_socket_add_interface / auto_socket_remove_interface
* (можно вызывать из любого платформенного мониторинга: netlink, JNI и т.д.)
* 2. Platform monitoring: встроенные обработчики netlink (Linux),
* route socket (BSD), IP Helper (Windows) — работают автоматически
*/
#include <stdint.h>
@@ -27,33 +39,46 @@ extern "C" {
struct UTUN_INSTANCE;
struct ETCP_SOCKET;
/* Внутреннее состояние модуля — хранится в inst->auto_socket_state.
* Содержит связный список отслеживаемых интерфейсов (ifaces) и платформенные
* handle'ы для мониторинга (netlink fd, route socket fd, IP Helper handles). */
struct AUTO_SOCKET;
/* ─── Жизненный цикл ─── */
/* Инициализирует модуль: создаёт таблицу портов в БД, запускает платформенный
* мониторинг интерфейсов, сканирует все текущие UP-интерфейсы и создаёт сокеты.
* Если auto_sockets=no в конфиге — ничего не делает, возвращает 0. */
int auto_socket_init(struct UTUN_INSTANCE* inst);
/* Останавливает мониторинг, удаляет все созданные сокеты и линки, освобождает память. */
void auto_socket_destroy(struct UTUN_INSTANCE* inst);
/* Platform notification: call from any thread when network interfaces change.
* Posts auto_socket_scan_all() to uasync thread. Safe for JNI/ConnectivityManager callbacks. */
/* Вызвать при изменении сетевых интерфейсов из любого потока (JNI, ConnectivityManager).
* Откладывает полное сканирование (auto_socket_scan_all) в uasync-поток. */
void auto_socket_on_network_change(struct UTUN_INSTANCE* inst);
/* ─── Platform-agnostic API: управление сокетами на конкретном интерфейсе ─── */
/*
* Создать сокет(ы) для интерфейса и добавить линки ко всем активным соединениям.
* ifindex — индекс интерфейса (0 = без привязки, использовать только ifname)
* Создать UDP+TCP сокеты для интерфейса и добавить линки ко всем активным соединениям.
* ifindex — индекс интерфейса (0 = автоопределение по ifname)
* ifname — имя интерфейса для SO_BINDTODEVICE
* has_v4 — есть IPv4 адреса на интерфейсе
* has_v6 — есть IPv6 адреса на интерфейсе
* has_v4 — есть ли IPv4 адреса на интерфейсе
* has_v6 — есть ли IPv6 адреса на интерфейсе
* v4_type — CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
* v6_type — CFG_SERVER_TYPE_PUBLIC или CFG_SERVER_TYPE_NAT
* Возвращает количество созданных сокетов (0, 1 или 2)
* Возвращает количество созданных сокетов (0, 1, 2, 3 или 4 — до 4: v4 UDP+TCP, v6 UDP+TCP).
* Если сокеты для этого интерфейса уже есть — ничего не делает, возвращает 0.
*/
int auto_socket_add_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex,
const char* ifname, int has_v4, int has_v6,
uint8_t v4_type, uint8_t v6_type);
/*
* Удалить сокет(ы) для интерфейса: закрыть линки, удалить сокет.
* Удалить все сокеты и линки для интерфейса.
* Закрывает UDP/TCP сокеты, удаляет etcp-линки через ncd_remove_socket_links,
* чистит запись порта в БД.
* ifindex — индекс интерфейса, сокеты которого удаляем
*/
void auto_socket_remove_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex);
+13 -11
View File
@@ -453,19 +453,19 @@ restart_timer:
link->keepalive_timer = uasync_set_timeout(link->etcp->instance->ua, link->ka_period_ms * 10, link, keepalive_timer_cb, "link_keepalive");
}
static void sockaddr_to_key(struct sockaddr_storage* addr, uint8_t key[LINK_ADDR_KEY_SIZE]) {
static void sockaddr_to_key(struct sockaddr_storage* addr, uint8_t key[LINK_ADDR_KEY_SIZE], int is_tcp) {
memset(key, 0, LINK_ADDR_KEY_SIZE);
if (!addr) return;
if (addr->ss_family == AF_INET) {
struct sockaddr_in* sa = (struct sockaddr_in*)addr;
memcpy(key, &sa->sin_port, 2);
memcpy(key + 2, &sa->sin_addr.s_addr, 4);
key[18] = 4;
key[18] = 4 | (is_tcp ? 0x80 : 0);
} else {
struct sockaddr_in6* sa = (struct sockaddr_in6*)addr;
memcpy(key, &sa->sin6_port, 2);
memcpy(key + 2, &sa->sin6_addr, 16);
key[18] = 6;
key[18] = 6 | (is_tcp ? 0x80 : 0);
}
}
@@ -476,7 +476,7 @@ static int insert_link_queue(struct ETCP_SOCKET* e_sock, struct ETCP_LINK* link)
if (!e_sock || !link || !e_sock->links_queue) return -1;
uint8_t key[LINK_ADDR_KEY_SIZE];
sockaddr_to_key(&link->remote_addr, key);
sockaddr_to_key(&link->remote_addr, key, link->is_tcp);
struct ll_entry* dup_qe = queue_find_data_by_index(e_sock->links_queue, key);
if (dup_qe) {
struct link_queue_entry* dup_lqe = (struct link_queue_entry*)dup_qe->data;
@@ -529,11 +529,11 @@ static int sockaddr_equal(const struct sockaddr_storage* a, const struct sockadd
return 0;
}
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr) {
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr, int is_tcp) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
if (!e_sock || !addr || !e_sock->links_queue) return NULL;
uint8_t key[LINK_ADDR_KEY_SIZE];
sockaddr_to_key(addr, key);
sockaddr_to_key(addr, key, is_tcp);
struct ll_entry* e = queue_find_data_by_index(e_sock->links_queue, key);
if (!e) return NULL;
struct link_queue_entry* lqe = (struct link_queue_entry*)e->data;
@@ -1038,6 +1038,9 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
if (remote_addr) memcpy(&link->remote_addr, remote_addr, sizeof(struct sockaddr_storage));
if (conn && conn->interface_addr.ss_family) memcpy(&link->local_bound_addr, &conn->interface_addr, sizeof(struct sockaddr_storage));
else memset(&link->local_bound_addr, 0, sizeof(link->local_bound_addr));
link->last_recv_local_time = get_time_tb(); // Initialize to prevent immediate timeout
link->total_retransmissions = 0;
@@ -1151,6 +1154,7 @@ void etcp_link_close(struct ETCP_LINK* link) {
etcp_fire_link_status_cbk(link, link->link_state, link->link_status);
etcp_inflight_nullify_link(link->etcp->input_wait_ack, link);
etcp_inflight_nullify_link(link->etcp->input_send_q, link);
etcp_on_link_down(link->etcp, link);
u_free(link->bbr);
u_free(link);
}
@@ -1974,8 +1978,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
size_t pkt_len=0;
int errorcode=0;
struct ETCP_LINK* link=etcp_link_find_by_addr(e_sock, &addr);
struct ETCP_LINK* link=etcp_link_find_by_addr(e_sock, &addr, 0);
// Try normal decryption first if we have an established link with session keys
// This is the common case for data packets and responses
// if (link) {
@@ -2141,8 +2144,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
if (e) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; conn = ce->conn; }
}
if (!conn) {
struct ETCP_LINK* ol = etcp_link_find_by_addr(e_sock, &addr);
if (ol && ol->etcp && ol->etcp->conn_queue_entry) {
struct ETCP_LINK* ol = etcp_link_find_by_addr(e_sock, &addr, 0); if (ol && ol->etcp && ol->etcp->conn_queue_entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)ol->etcp->conn_queue_entry->data;
if (ce->peer_node_id == 0) {
conn = ol->etcp;
@@ -2201,7 +2203,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, req->link_id);
if (!existing_link) {
existing_link = etcp_link_find_by_addr(e_sock, &addr);
existing_link = etcp_link_find_by_addr(e_sock, &addr, 0);
if (existing_link && existing_link->etcp == conn) {
if (memcmp(conn->crypto_ctx.peer_public_key, sc.peer_public_key, SC_PUBKEY_SIZE)) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] link address match but pubkey mismatch, firing node_changed", conn->log_name);
+2 -1
View File
@@ -318,6 +318,7 @@ struct ETCP_LINK {
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx;
struct sockaddr_storage local_bound_addr; // локальный адрес к которому привязан линк (interface_addr на момент создания)
uint8_t is_tcp; // 1 = TCP транспорт (STCP)
struct stcp_link *tcp_link; // STCP линк (для TCP)
void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент)
@@ -355,7 +356,7 @@ void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr, int is_tcp);
// find free local_link_id for connection
// scans all links in connection, marks used ids in bit array
+124 -15
View File
@@ -157,7 +157,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
struct ETCP_SOCKET* use_sock = socks[rr++ % sock_count];
{
struct ETCP_LINK* stale = etcp_link_find_by_addr(use_sock, &sa);
struct ETCP_LINK* stale = etcp_link_find_by_addr(use_sock, &sa, 0);
if (stale && stale->etcp == conn) continue;
if (stale && stale->etcp != conn
&& memcmp(conn->crypto_ctx.peer_public_key, stale->etcp->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE))
@@ -209,7 +209,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
if ((a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80)) sin6.sin6_scope_id = use_sock->netif_index;
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
{
struct ETCP_LINK* stale = etcp_link_find_by_addr(use_sock, &sa);
struct ETCP_LINK* stale = etcp_link_find_by_addr(use_sock, &sa, 0);
if (stale && stale->etcp == conn) continue;
if (stale && stale->etcp != conn
&& memcmp(conn->crypto_ctx.peer_public_key, stale->etcp->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE))
@@ -965,23 +965,132 @@ struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h) {
/* ═══════════ Управление линками на конкретном сокете (для auto_socket) ═══════════ */
/* ncd_add_udp_link_random / ncd_add_tcp_link_random — собирают совместимые адреса пира,
* случайно выбирают один, проверяют что линк на (sock, addr, proto) ещё не существует,
* и создают новый. */
static int ncd_add_udp_link_random(struct ETCP_CONN* conn, struct TOPO_NODE* ni, struct ETCP_SOCKET* sock) {
int family = sock->local_addr.ss_family;
if (family == AF_INET) {
struct TOPO_ADDR4* compat[64]; int count = 0;
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
if (!a->port || !(a->protocol & TOPO_PROTO_UDP)) continue;
int zero = 1; for (int j = 0; j < 4; j++) if (a->addr[j]) { zero = 0; break; }
if (zero) continue;
if (count < 64) compat[count++] = (struct TOPO_ADDR4*)a;
}
if (count == 0) return 0;
uint32_t r; random_bytes((uint8_t*)&r, sizeof(r));
struct TOPO_ADDR4* a = compat[r % count];
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));
if (etcp_link_find_by_addr(sock, &sa, 0)) return 0;
if (etcp_link_new(conn, sock, &sa, 0)) return 1;
return 0;
}
if (family == AF_INET6) {
struct TOPO_ADDR6* compat[64]; int count = 0;
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
if (!a->port || !(a->protocol & TOPO_PROTO_UDP)) continue;
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j]) { zero = 0; break; }
if (zero) continue;
if (count < 64) compat[count++] = (struct TOPO_ADDR6*)a;
}
if (count == 0) return 0;
uint32_t r; random_bytes((uint8_t*)&r, sizeof(r));
struct TOPO_ADDR6* a = compat[r % count];
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);
if ((a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80)) sin6.sin6_scope_id = sock->netif_index;
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
if (etcp_link_find_by_addr(sock, &sa, 0)) return 0;
if (etcp_link_new(conn, sock, &sa, 0)) return 1;
return 0;
}
return 0;
}
static int ncd_add_tcp_link_random(struct ETCP_CONN* conn, struct TOPO_NODE* ni, struct ETCP_SOCKET* sock) {
int family = sock->local_addr.ss_family;
if (family == AF_INET) {
struct TOPO_ADDR4* compat[64]; int count = 0;
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
if (!a->port || !(a->protocol & TOPO_PROTO_TCP)) continue;
int zero = 1; for (int j = 0; j < 4; j++) if (a->addr[j]) { zero = 0; break; }
if (zero) continue;
if (count < 64) compat[count++] = (struct TOPO_ADDR4*)a;
}
if (count == 0) return 0;
uint32_t r; random_bytes((uint8_t*)&r, sizeof(r));
struct TOPO_ADDR4* a = compat[r % count];
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));
if (etcp_link_find_by_addr(sock, &sa, 1)) return 0;
struct ETCP_LINK* tlink = etcp_link_new(conn, sock, &sa, 0);
if (!tlink) return 0;
tlink->is_tcp = 1;
etcp_tcp_link_start_connect(tlink, &sa, a->port);
return 1;
}
if (family == AF_INET6) {
struct TOPO_ADDR6* compat[64]; int count = 0;
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
if (!a->port || !(a->protocol & TOPO_PROTO_TCP)) continue;
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j]) { zero = 0; break; }
if (zero) continue;
if (count < 64) compat[count++] = (struct TOPO_ADDR6*)a;
}
if (count == 0) return 0;
uint32_t r; random_bytes((uint8_t*)&r, sizeof(r));
struct TOPO_ADDR6* a = compat[r % count];
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);
if ((a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80)) sin6.sin6_scope_id = sock->netif_index;
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
if (etcp_link_find_by_addr(sock, &sa, 1)) return 0;
struct ETCP_LINK* tlink = etcp_link_new(conn, sock, &sa, 0);
if (!tlink) return 0;
tlink->is_tcp = 1;
etcp_tcp_link_start_connect(tlink, &sa, a->port);
return 1;
}
return 0;
}
int ncd_add_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock) {
if (!inst || !sock) return 0;
int added = 0;
struct ncd_entry* entry = (struct ncd_entry*)inst->ncd_registry;
while (entry) {
struct TOPO_NODE* ni = ncd_lookup_node(inst, entry->node_id);
if (ni) {
int n = ncd_create_links(entry, ni, sock);
if (n > 0) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] add_socket_links socket=%s node=0x%016llx links=%d",
sock->name, (unsigned long long)entry->node_id, n);
added += n;
}
topo_node_registry_unref(inst->topo_groups, ni->node_id);
int added = 0, total_conns = 0, skipped_closed = 0, skipped_no_addr = 0;
struct ll_entry* qe = inst->connections ? inst->connections->head : NULL;
while (qe) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)qe->data;
struct ETCP_CONN* conn = ce->conn;
qe = qe->next;
if (!conn) continue;
total_conns++;
if (conn->state == 2) { skipped_closed++; continue; }
struct TOPO_NODE* ni = ncd_lookup_node(inst, ce->peer_node_id);
if (!ni) { skipped_no_addr++; continue; }
int n;
if (sock->is_tcp) n = ncd_add_tcp_link_random(conn, ni, sock);
else n = ncd_add_udp_link_random(conn, ni, sock);
if (n > 0) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] add_socket_links sock=%s fd=%d conn=[%s] node=0x%016llx addr=%s is_tcp=%d",
sock->name, (int)sock->fd, conn->log_name,
(unsigned long long)ce->peer_node_id,
sockaddr_storage_to_str(&sock->local_addr).str, sock->is_tcp);
added += n;
}
entry = entry->next;
topo_node_registry_unref(inst->topo_groups, ni->node_id);
}
DEBUG_INFO(DEBUG_CATEGORY_NCD,
"[ncd] add_socket_links done: sock=%s is_tcp=%d total_conns=%d skipped(cl=%d,no_addr=%d) added=%d",
sock->name, sock->is_tcp, total_conns, skipped_closed, skipped_no_addr, added);
return added;
}