Browse Source

auto_socket: модуль авто-управления сокетами при изменении сетевых интерфейсов + fix pre-existing ASAN-багов

- auto_socket.c/h: создание/удаление UDP сокетов автоматически при изменении интерфейсов через netlink/route socket
  При auto_sockets=yes пропускает ручные [server] сокеты
  Тип сокета: PUBLIC если публичный IP, иначе NAT
  Для новых сокетов добавляются линки ко всем активным NCD-соединениям
  При удалении интерфейса линки убираются, сокет закрывается
  Platform-agnostic API для использования из Android/chatgui

- node_conn_direct.h/c: ncd_add_socket_links / ncd_remove_socket_links — управление линками на конкретном сокете

- socket_monitor.h/c: экспорт socket_monitor_update_if_addr для auto_socket

- config_parser.h/c: опция auto_sockets в [global]

- init_sockets(): пропуск ручных сокетов при auto_sockets=yes

- utun_instance: интеграция auto_socket_init/destroy

- media_delivery: fix stack-buffer-overflow ack[3] → ack[9]

- topo_node: fix heap-use-after-free в topo_group_update_my_nodeinfo — сохранение node_id/ver перед registry_store, использование return value
topo_upd
evgeny 2 months ago
parent
commit
ef637d33a6
  1. 2
      src/Makefile.am
  2. 10
      src/config_parser.c
  3. 1
      src/config_parser.h
  4. 2
      src/media_delivery/media_delivery.c
  5. 7
      src/routing_layer/topo_node.c
  6. 615
      src/transport_layer/auto_socket.c
  7. 60
      src/transport_layer/auto_socket.h
  8. 8
      src/transport_layer/etcp_connections.c
  9. 53
      src/transport_layer/node_conn_direct.c
  10. 22
      src/transport_layer/node_conn_direct.h
  11. 2
      src/transport_layer/socket_monitor.c
  12. 5
      src/transport_layer/socket_monitor.h
  13. 3
      src/utun_instance.c
  14. 2
      src/utun_instance.h
  15. 1
      tools/chatgui/transport/node_config.cpp

2
src/Makefile.am

@ -45,6 +45,7 @@ utun_CORE_SOURCES = \
transport_layer/etcp_connect.c \
transport_layer/node_conn_direct.c \
transport_layer/socket_monitor.c \
transport_layer/auto_socket.c \
control_server.c \
firewall.c \
eim_nat.c \
@ -120,6 +121,7 @@ libutun_a_SOURCES = \
transport_layer/etcp_connect.c \
transport_layer/node_conn_direct.c \
transport_layer/socket_monitor.c \
transport_layer/auto_socket.c \
control_server.c \
firewall.c \
eim_nat.c \

10
src/config_parser.c

@ -394,6 +394,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->bbr_max_cwnd = atoi(value);
return 0;
}
if (strcmp(key, "auto_sockets") == 0) {
global->auto_sockets = strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0;
return 0;
}
if (strcmp(key, "debug_level") == 0) {
return assign_string(global->debug_level, sizeof(global->debug_level), value);
}
@ -407,6 +411,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->db_sync_enabled = atoi(value);
return 0;
}
if (strcmp(key, "chatserver_enabled") == 0) {
global->chatserver_enabled = atoi(value);
return 0;
}
if (strcmp(key, "db_sync_ttl") == 0) {
global->db_sync_ttl = (uint32_t)atoi(value);
return 0;
@ -837,6 +845,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
cfg->global.auto_sockets = 0; // Default: disabled, use manual [server] sockets
cfg->global.tcp_recv_buf = 0; // 0 = OS default
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
@ -1121,6 +1130,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
cfg->global.keepalive_interval = 200;
cfg->global.keepalive_adaptive = 1;
cfg->global.bbr_max_cwnd = 1048576;
cfg->global.auto_sockets = 0;
cfg->global.tcp_recv_buf = 0;
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;

1
src/config_parser.h

@ -188,6 +188,7 @@ struct global_config {
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
int auto_sockets; // 1 = auto-create sockets from interfaces, skip [server]
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
// NTP configuration ([ntp] section)

2
src/media_delivery/media_delivery.c

@ -439,7 +439,7 @@ static void md_handle_serve_reg(struct media_delivery_ctx* md, uint64_t from_nod
queue_data_put_with_index(md->served_nodes, qe);
}
uint8_t ack[3] = { MEDIA_SUBCMD_SERVE_ACK, 0, 0 };
uint8_t ack[9] = { MEDIA_SUBCMD_SERVE_ACK, 0, 0 };
memcpy(ack + 1, &pkt->group_id, 8);
(void)from_node;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: SERVE_REG node=0x%016llx group=%016llx",

7
src/routing_layer/topo_node.c

@ -682,9 +682,10 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ed25519 self-sign FAILED for my node=%016llx", (unsigned long long)ni->node_id);
if (name_len) { ni->node_name = u_malloc(name_len + 1); if (ni->node_name) { memcpy(ni->node_name, instance->name, name_len); ni->node_name[name_len] = 0; } }
topo_node_registry_store(instance->topo_groups, ni);
lq->node_id = ni->node_id;
lq->last_ver = ni->ver;
{ uint64_t saved_node_id = ni->node_id; uint32_t saved_ver = ni->ver;
ni = topo_node_registry_store(instance->topo_groups, ni);
lq->node_id = saved_node_id;
lq->last_ver = saved_ver; }
e_sock = instance->etcp_sockets;
while (e_sock) {

615
src/transport_layer/auto_socket.c

@ -0,0 +1,615 @@
/*
* auto_socket.c — автоматическое управление UDP-сокетами при изменении сетевых интерфейсов
*
* При auto_sockets=yes:
* - Для каждого не-loopback интерфейса (UP, с IP-адресами) создаётся UDP-сокет
* с bind на 0.0.0.0/[::] + случайный порт, привязанный к интерфейсу (SO_BINDTODEVICE)
* - Тип: PUBLIC если есть публичный адрес, иначе NAT
* - При добавлении сокета — линки ко всем активным NCD-соединениям
* - При удалении интерфейса — линки удаляются, сокет закрывается
*
* Стратегия reconcile:
* При любом изменении адресов/линков сканируем фактические IP интерфейса,
* сравниваем с состоянием сокетов и устраняем расхождения:
* - появился адрес нужного family → создать сокет + линки
* - исчезли все адреса family → удалить сокет + линки
* - изменился тип (NAT↔PUBLIC) → пересоздать сокет с новым типом + линки
* - без изменений → только обновить interface_addr
* - интерфейс стал DOWN → удалить все сокеты
*
* Слои:
* Platform-agnostic: auto_socket_add_interface / auto_socket_remove_interface
* Platform monitoring: netlink (Linux), route socket (BSD), IP Helper (Windows)
*/
#include "auto_socket.h"
#include "etcp_connections.h"
#include "etcp_api.h"
#include "node_conn_direct.h"
#include "socket_monitor.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include "../lib/platform_compat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if !defined(_WIN32)
#include <unistd.h>
#include <net/if.h>
#include <ifaddrs.h>
#endif
#ifdef __linux__
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#include <net/route.h>
#include <net/if_dl.h>
#include <fcntl.h>
#elif defined(_WIN32)
#include <iphlpapi.h>
#include <netioapi.h>
#include <ws2ipdef.h>
#endif
#define DEBUG_CATEGORY_AS DEBUG_CATEGORY_SOCKET
/* ─── IPv4-адрес: публичный (интернет) или приватный/NAT ─── */
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 */
if ((a & 0xFFF00000u) == 0xAC100000u) return 0; /* 172.16.0.0/12 */
if ((a & 0xFFFF0000u) == 0xC0A80000u) return 0; /* 192.168.0.0/16 */
if ((a & 0xFF000000u) == 0x7F000000u) return 0; /* 127.0.0.0/8 */
if ((a & 0xFFFF0000u) == 0xA9FE0000u) return 0; /* 169.254.0.0/16 */
if ((a & 0xFF000000u) == 0x00000000u) return 0; /* 0.0.0.0/8 */
if ((a & 0xF0000000u) == 0xE0000000u) return 0; /* 224.0.0.0/4 */
if ((a & 0xF0000000u) == 0xF0000000u) return 0; /* 240.0.0.0/4 */
if ((a & 0xFFC00000u) == 0x64400000u) return 0; /* 100.64.0.0/10 (CGNAT) */
return 1;
}
/* ─── IPv6-адрес: глобальный или локальный (аналог cm_classify_v6_addr) ─── */
#define AS_V6_LL 0
#define AS_V6_LOC 1
#define AS_V6_DIR 2
#define AS_V6_OTH 3
static int v6_classify(const uint8_t addr[16]) {
if (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80) return AS_V6_LL;
if (addr[0] == 0xfc || addr[0] == 0xfd) return AS_V6_LOC;
if (addr[0] == 0xff) return AS_V6_OTH;
{ static const uint8_t z[16]; if (!memcmp(addr, z, 16)) return AS_V6_OTH; }
{ static const uint8_t lb[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; if (!memcmp(addr, lb, 16)) return AS_V6_OTH; }
return AS_V6_DIR;
}
/* ─── Внутреннее состояние модуля ─── */
struct auto_sock_iface {
struct auto_sock_iface* next;
uint32_t netif_index;
struct ETCP_SOCKET* v4_sock;
struct ETCP_SOCKET* v6_sock;
uint8_t v4_type; /* текущий тип v4 сокета: CFG_SERVER_TYPE_PUBLIC / CFG_SERVER_TYPE_NAT */
uint8_t v6_type; /* текущий тип v6 сокета */
};
struct AUTO_SOCKET {
struct UTUN_INSTANCE* instance;
struct auto_sock_iface* ifaces;
#ifdef __linux__
socket_t nl_sock;
void* uasync_handle;
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
socket_t route_sock;
void* uasync_handle;
#elif defined(_WIN32)
HANDLE addr_notify_handle;
HANDLE route_notify_handle;
#endif
};
/* ─── forward declarations ─── */
static int create_iface_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname, int family, uint8_t type);
static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex);
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);
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname);
static void auto_socket_scan_all(struct AUTO_SOCKET* as);
/* ═══════════ Platform-agnostic core ═══════════ */
static uint16_t random_port(void) {
uint32_t r;
random_bytes((uint8_t*)&r, sizeof(r));
return (uint16_t)((r % (65535 - 1024)) + 1024);
}
static int create_iface_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;
memset(&server, 0, sizeof(server));
snprintf(server.name, sizeof(server.name), "as_%s_%s", ifname, family == AF_INET6 ? "v6" : "v4");
server.type = type;
server.netif_index = ifindex;
server.transport = 0;
server.mtu = inst->config ? inst->config->global.mtu : 1500;
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&server.ip;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&server.ip;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
}
for (int attempt = 0; attempt < 10; attempt++) {
uint16_t port = random_port();
if (family == AF_INET)
((struct sockaddr_in*)&server.ip)->sin_port = htons(port);
else
((struct sockaddr_in6*)&server.ip)->sin6_port = htons(port);
struct ETCP_SOCKET* es = etcp_socket_add(inst, &server);
if (!es) {
DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] socket create failed if=%s fam=%d port=%u att=%d err=%d",
ifname, family, port, attempt + 1, socket_get_error());
if (socket_get_error() != EADDRINUSE) return -1;
continue;
}
socket_monitor_update_if_addr(es);
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] socket created: name=%s fd=%d if=%s(%u) type=%d addr=%s",
es->name, (int)es->fd, ifname, ifindex, type,
sockaddr_storage_to_str(&es->local_addr).str);
int links = ncd_add_socket_links(inst, es);
if (links > 0)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] added %d links to socket %s", links, es->name);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] failed to create socket after 10 attempts if=%s fam=%d", ifname, family);
return -1;
}
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) {
if (!inst || !inst->auto_socket_state || !ifname) return 0;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state;
#if !defined(_WIN32)
if (ifindex == 0) ifindex = if_nametoindex(ifname);
#endif
if (ifindex == 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] ifindex=0 for %s", ifname); return 0; }
struct auto_sock_iface* ifa = as->ifaces;
while (ifa) { if (ifa->netif_index == ifindex) return 0; ifa = ifa->next; }
ifa = u_calloc(1, sizeof(*ifa));
if (!ifa) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return 0; }
ifa->netif_index = ifindex;
int added = 0;
if (has_v4) {
if (create_iface_socket(as, ifindex, ifname, AF_INET, v4_type) == 0) {
added++; ifa->v4_type = v4_type;
struct ETCP_SOCKET* s = inst->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET) { ifa->v4_sock = s; break; } s = s->next; }
}
}
if (has_v6) {
if (create_iface_socket(as, ifindex, ifname, AF_INET6, v6_type) == 0) {
added++; ifa->v6_type = v6_type;
struct ETCP_SOCKET* s = inst->etcp_sockets;
while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6) { ifa->v6_sock = s; break; } s = s->next; }
}
}
if (added > 0) {
ifa->next = as->ifaces; as->ifaces = ifa;
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] added iface %s idx=%u socks=%d v4=%d v6=%d v4t=%d v6t=%d",
ifname, ifindex, added, has_v4, has_v6, v4_type, v6_type);
} else {
u_free(ifa);
}
return added;
}
void auto_socket_remove_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex) {
if (!inst || !inst->auto_socket_state || ifindex == 0) return;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state;
remove_iface_sockets(as, ifindex);
}
static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex) {
struct auto_sock_iface** pp = &as->ifaces;
while (*pp) {
if ((*pp)->netif_index == ifindex) {
struct auto_sock_iface* ifa = *pp;
*pp = ifa->next;
if (ifa->v4_sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v4 socket %s ifidx=%u", ifa->v4_sock->name, ifindex);
ncd_remove_socket_links(as->instance, ifa->v4_sock);
etcp_socket_remove(ifa->v4_sock);
}
if (ifa->v6_sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v6 socket %s ifidx=%u", ifa->v6_sock->name, ifindex);
ncd_remove_socket_links(as->instance, ifa->v6_sock);
etcp_socket_remove(ifa->v6_sock);
}
u_free(ifa);
return;
}
pp = &(*pp)->next;
}
}
/* ─── Сканирование адресов интерфейса и определение типа ─── */
#if !defined(_WIN32)
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) {
(void)ifindex;
*out_has_v4 = 0; *out_has_v6 = 0;
*out_v4_type = CFG_SERVER_TYPE_NAT;
*out_v6_type = CFG_SERVER_TYPE_NAT;
struct ifaddrs* ifa_list = NULL;
if (getifaddrs(&ifa_list) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] getifaddrs: %s", strerror(errno)); return -1; }
int is_up = 0;
for (struct ifaddrs* ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr) continue;
if (strcmp(ifa->ifa_name, ifname) != 0) continue;
if (ifa->ifa_flags & IFF_UP) is_up = 1;
if (!(ifa->ifa_flags & IFF_UP)) continue;
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)ifa->ifa_addr;
if (sin->sin_addr.s_addr == INADDR_ANY) continue;
*out_has_v4 = 1;
if (is_ipv4_public(sin->sin_addr.s_addr))
*out_v4_type = CFG_SERVER_TYPE_PUBLIC;
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) continue;
if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) continue;
*out_has_v6 = 1;
if (v6_classify(sin6->sin6_addr.s6_addr) == AS_V6_DIR)
*out_v6_type = CFG_SERVER_TYPE_PUBLIC;
}
}
freeifaddrs(ifa_list);
/* интерфейс не UP — адреса не релевантны */
if (!is_up) { *out_has_v4 = 0; *out_has_v6 = 0; }
return 0;
}
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; }
int up_count = 0;
char up_names[64][IFNAMSIZ];
for (struct ifaddrs* ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr) continue;
if (!(ifa->ifa_flags & IFF_UP)) continue;
if (ifa->ifa_flags & IFF_LOOPBACK) continue;
int found = 0;
for (int i = 0; i < up_count; i++) { if (strcmp(up_names[i], ifa->ifa_name) == 0) { found = 1; break; } }
if (found) continue;
if (up_count >= 64) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] too many interfaces, cap at 64"); break; }
strncpy(up_names[up_count], ifa->ifa_name, IFNAMSIZ - 1);
up_names[up_count][IFNAMSIZ - 1] = 0;
up_count++;
}
freeifaddrs(ifa_list);
for (int i = 0; i < up_count; i++)
reconcile_iface(as, if_nametoindex(up_names[i]), up_names[i]);
}
#else /* _WIN32 */
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) {
(void)ifindex; (void)ifname;
*out_has_v4 = 0; *out_has_v6 = 0;
*out_v4_type = CFG_SERVER_TYPE_NAT;
*out_v6_type = CFG_SERVER_TYPE_NAT;
return 0;
}
static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
(void)as;
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] Windows: auto_socket_scan_all not yet implemented");
}
#endif /* _WIN32 */
/* ═══════════ reconcile: сверка и исправление состояния сокетов ═══════════ */
static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) {
if (ifindex == 0) return;
int has_v4 = 0, has_v6 = 0;
uint8_t v4_need_type = CFG_SERVER_TYPE_NAT, v6_need_type = CFG_SERVER_TYPE_NAT;
if (scan_and_classify_iface(ifindex, ifname, &has_v4, &has_v6, &v4_need_type, &v6_need_type) < 0) return;
struct auto_sock_iface* ifa = as->ifaces;
while (ifa && ifa->netif_index != ifindex) ifa = ifa->next;
/* ── IPv4 ── */
{
struct ETCP_SOCKET* sock = ifa ? ifa->v4_sock : NULL;
uint8_t cur_type = ifa ? ifa->v4_type : CFG_SERVER_TYPE_NAT;
if (has_v4 && !sock) {
create_iface_socket(as, ifindex, ifname, AF_INET, v4_need_type);
if (!ifa) { ifa = u_calloc(1, sizeof(*ifa)); ifa->netif_index = ifindex; ifa->next = as->ifaces; as->ifaces = ifa; }
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_sock = s; break; } s = s->next; } }
} else if (!has_v4 && sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 removed: %s ifidx=%u", ifname, ifindex);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v4_sock = NULL;
} else if (has_v4 && sock && cur_type != v4_need_type) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 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_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_sock = s; break; } s = s->next; } }
} else if (has_v4 && sock) {
socket_monitor_update_if_addr(sock);
etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED);
}
}
/* ── IPv6 ── */
{
struct ETCP_SOCKET* sock = ifa ? ifa->v6_sock : NULL;
uint8_t cur_type = ifa ? ifa->v6_type : CFG_SERVER_TYPE_NAT;
if (has_v6 && !sock) {
create_iface_socket(as, ifindex, ifname, AF_INET6, v6_need_type);
if (!ifa) { ifa = u_calloc(1, sizeof(*ifa)); ifa->netif_index = ifindex; ifa->next = as->ifaces; as->ifaces = ifa; }
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_sock = s; break; } s = s->next; } }
} else if (!has_v6 && sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 removed: %s ifidx=%u", ifname, ifindex);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v6_sock = NULL;
} else if (has_v6 && sock && cur_type != v6_need_type) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 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_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_sock = s; break; } s = s->next; } }
} else if (has_v6 && sock) {
socket_monitor_update_if_addr(sock);
etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED);
}
}
/* ── удалить запись если оба family пропали ── */
if (ifa && !ifa->v4_sock && !ifa->v6_sock) {
struct auto_sock_iface** pp = &as->ifaces;
while (*pp && *pp != ifa) pp = &(*pp)->next;
if (*pp) *pp = ifa->next;
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] iface record removed: %s idx=%u", ifname, ifindex);
u_free(ifa);
}
}
/* ═══════════ Platform monitoring ═══════════ */
/* ─── Linux: netlink ─── */
#ifdef __linux__
static void handle_nl_addr(struct AUTO_SOCKET* as, struct ifaddrmsg* ifa, int msg_type) {
(void)msg_type;
char ifname[IFNAMSIZ];
if (!if_indextoname(ifa->ifa_index, ifname)) return;
reconcile_iface(as, ifa->ifa_index, ifname);
}
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;
if (ifi->ifi_flags & IFF_LOOPBACK) return;
int is_up = (ifi->ifi_flags & IFF_UP) != 0;
if (msg_type == RTM_DELLINK || !is_up) {
remove_iface_sockets(as, ifi->ifi_index);
} else if (msg_type == RTM_NEWLINK && is_up) {
/* интерфейс появился или стал UP — сверяем (адреса могут уже быть) */
reconcile_iface(as, ifi->ifi_index, ifname);
}
}
static void auto_socket_netlink_cb(int fd, void* arg) {
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)arg;
char buf[8192];
struct sockaddr_nl nladdr;
socklen_t nladdr_len = sizeof(nladdr);
DEBUG_DEBUG(DEBUG_CATEGORY_AS, "[as] netlink cb fd=%d", fd);
for (;;) {
ssize_t n = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&nladdr, &nladdr_len);
if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink recv: %s", strerror(errno)); break; }
if (n == 0) break;
size_t remaining = (size_t)n;
struct nlmsghdr* nlh = (struct nlmsghdr*)buf;
while (NLMSG_OK(nlh, remaining)) {
DEBUG_DEBUG(DEBUG_CATEGORY_AS, "[as] netlink msg type=%u len=%u", nlh->nlmsg_type, nlh->nlmsg_len);
if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR)
handle_nl_addr(as, (struct ifaddrmsg*)NLMSG_DATA(nlh), nlh->nlmsg_type);
else if (nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK)
handle_nl_link(as, (struct ifinfomsg*)NLMSG_DATA(nlh), nlh->nlmsg_type);
if (nlh->nlmsg_type == NLMSG_DONE) break;
nlh = NLMSG_NEXT(nlh, remaining);
}
}
}
static int auto_socket_init_monitor(struct AUTO_SOCKET* as) {
as->nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (as->nl_sock < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink socket: %s", strerror(errno)); return -1; }
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
if (bind(as->nl_sock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink bind: %s", strerror(errno)); close(as->nl_sock); return -1;
}
socket_set_nonblocking(as->nl_sock);
as->uasync_handle = uasync_add_socket(as->instance->ua, as->nl_sock, auto_socket_netlink_cb, NULL, NULL, as);
if (!as->uasync_handle) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] uasync_add_socket failed"); close(as->nl_sock); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink monitor started: fd=%d", (int)as->nl_sock);
return 0;
}
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 ─── */
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
static void auto_socket_bsd_cb(int fd, void* arg) {
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)arg;
char buf[8192];
for (;;) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] route sock read: %s", strerror(errno)); break; }
if (n < (ssize_t)sizeof(struct rt_msghdr)) break;
struct rt_msghdr* rtm = (struct rt_msghdr*)buf;
if (rtm->rtm_version != RTM_VERSION) continue;
if (rtm->rtm_type == RTM_IFINFO || rtm->rtm_type == RTM_IFANNOUNCE || rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_DELADDR)
auto_socket_scan_all(as);
}
}
static int auto_socket_init_monitor(struct AUTO_SOCKET* as) {
as->route_sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
if (as->route_sock < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] route socket: %s", strerror(errno)); return -1; }
fcntl(as->route_sock, F_SETFL, O_NONBLOCK);
as->uasync_handle = uasync_add_socket(as->instance->ua, as->route_sock, auto_socket_bsd_cb, NULL, NULL, as);
if (!as->uasync_handle) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] uasync_add_socket failed"); close(as->route_sock); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] route socket monitor started: fd=%d", (int)as->route_sock);
return 0;
}
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->route_sock >= 0) { close(as->route_sock); as->route_sock = -1; }
}
/* ─── Windows: IP Helper ─── */
#elif defined(_WIN32)
static void process_win_notify(void* arg) {
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)arg;
auto_socket_scan_all(as);
}
static void WINAPI win_addr_change_cb(PVOID ctx, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE type) {
(void)row; (void)type;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)ctx;
uasync_post(as->instance->ua, process_win_notify, as);
}
static void WINAPI win_route_change_cb(PVOID ctx, PMIB_IPFORWARD_ROW2 row, MIB_NOTIFICATION_TYPE type) {
(void)row; (void)type;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)ctx;
uasync_post(as->instance->ua, process_win_notify, as);
}
static int auto_socket_init_monitor(struct AUTO_SOCKET* as) {
DWORD ret = NotifyUnicastIpAddressChange(AF_UNSPEC, win_addr_change_cb, as, FALSE, &as->addr_notify_handle);
if (ret != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] NotifyUnicastIpAddressChange: %lu", (unsigned long)ret); return -1; }
ret = NotifyRouteChange2(AF_UNSPEC, win_route_change_cb, as, FALSE, &as->route_notify_handle);
if (ret != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] NotifyRouteChange2: %lu", (unsigned long)ret); CancelMibChangeNotify2(as->addr_notify_handle); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] Windows IP Helper monitor started");
return 0;
}
static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) {
if (as->addr_notify_handle) { CancelMibChangeNotify2(as->addr_notify_handle); as->addr_notify_handle = NULL; }
if (as->route_notify_handle) { CancelMibChangeNotify2(as->route_notify_handle); as->route_notify_handle = NULL; }
}
#else
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 ═══════════ */
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;
if (inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] already initialized"); return -1; }
struct AUTO_SOCKET* as = u_calloc(1, sizeof(*as));
if (!as) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return -1; }
as->instance = inst;
inst->auto_socket_state = as;
if (auto_socket_init_monitor(as) < 0) { u_free(as); inst->auto_socket_state = NULL; return -1; }
auto_socket_scan_all(as);
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized");
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;
inst->auto_socket_state = NULL;
auto_socket_destroy_monitor(as);
/* удаляем только записи ifaces, сокеты освобождает основной цикл очистки utun_instance_destroy */
while (as->ifaces) {
struct auto_sock_iface* ifa = as->ifaces;
as->ifaces = ifa->next;
ncd_remove_socket_links(as->instance, ifa->v4_sock);
ncd_remove_socket_links(as->instance, ifa->v6_sock);
u_free(ifa);
}
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] destroyed");
u_free(as);
}

60
src/transport_layer/auto_socket.h

@ -0,0 +1,60 @@
#ifndef AUTO_SOCKET_H
#define AUTO_SOCKET_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* auto_socket.h — автоматическое управление UDP-сокетами при изменении сетевых интерфейсов
*
* При включённой опции auto_sockets=yes:
* - Ручные [server] сокеты пропускаются
* - Для каждого не-loopback интерфейса (UP, с IP-адресами) создаётся UDP-сокет (v4/v6)
* с bind на 0.0.0.0/[::] + случайный порт, привязанный к интерфейсу
* - Тип сокета: PUBLIC если есть публичный адрес, иначе NAT
* - Для каждого нового сокета добавляются линки ко всем активным NCD-соединениям
* - При пропадании интерфейса линки удаляются, сокет закрывается
*
* API разделён на два слоя:
* 1. auto_socket_init / auto_socket_destroy — жизненный цикл модуля
* 2. auto_socket_add_interface / auto_socket_remove_interface — platform-agnostic
* вызываются из платформенного мониторинга (netlink, Android JNI, etc.)
*/
#include <stdint.h>
struct UTUN_INSTANCE;
struct ETCP_SOCKET;
/* ─── Жизненный цикл ─── */
int auto_socket_init(struct UTUN_INSTANCE* inst);
void auto_socket_destroy(struct UTUN_INSTANCE* inst);
/* ─── Platform-agnostic API: управление сокетами на конкретном интерфейсе ─── */
/*
* Создать сокет(ы) для интерфейса и добавить линки ко всем активным соединениям.
* ifindex — индекс интерфейса (0 = без привязки, использовать только ifname)
* ifname — имя интерфейса для SO_BINDTODEVICE
* 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)
*/
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);
/*
* Удалить сокет(ы) для интерфейса: закрыть линки, удалить сокет.
* ifindex — индекс интерфейса, сокеты которого удаляем
*/
void auto_socket_remove_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex);
#ifdef __cplusplus
}
#endif
#endif

8
src/transport_layer/etcp_connections.c

@ -2154,6 +2154,14 @@ int init_sockets(struct UTUN_INSTANCE* instance) {
}
struct utun_config* config = instance->config;
/* автосокеты — ручные [server] сокеты пропускаются, create_sockets вызовется отдельно */
if (config->global.auto_sockets) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "auto_sockets=enabled, skipping manual server sockets");
instance->auto_socket_enabled = 1;
return 0;
}
int success_count = 0;
int fail_count = 0;

53
src/transport_layer/node_conn_direct.c

@ -928,6 +928,12 @@ void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb,
h->cb_arg = cb_arg;
}
void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb, void* new_cb_arg) {
if (!h || !h->entry) return;
h->cb = new_cb;
h->cb_arg = new_cb_arg;
}
/*
* Прямой доступ к ETCP_CONN из handle.
* Нужен для отправки данных (etcp_send), проверки статуса, и т.д.
@ -936,3 +942,50 @@ struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h) {
if (!h || !h->entry) return NULL;
return h->entry->conn;
}
/* ═══════════ Управление линками на конкретном сокете (для auto_socket) ═══════════ */
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);
}
entry = entry->next;
}
return added;
}
int ncd_remove_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock) {
if (!inst || !sock) return 0;
int removed = 0;
struct ncd_entry* entry = (struct ncd_entry*)inst->ncd_registry;
while (entry) {
struct ETCP_CONN* conn = entry->conn;
if (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (link->conn == sock) {
etcp_link_close(link);
removed++;
}
link = next;
}
}
entry = entry->next;
}
if (removed > 0) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] remove_socket_links socket=%s closed=%d", sock->name, removed);
}
return removed;
}

22
src/transport_layer/node_conn_direct.h

@ -76,6 +76,11 @@ void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h);
* Не влияет на refcounting и состояние conn. */
void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb, void* cb_arg);
/* Передать владение handle новому владельцу (смена callback + arg).
* После вызова handle принадлежит new_cb_arg, прежний владелец
* должен забыть указатель. Соединение не трогается. */
void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb, void* new_cb_arg);
struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h);
/* ─── Протокол CLOSE / KEEP_ALIVE (ETCP_RT_ID_NCD_CONTROL = 0x12) ─── */
@ -93,4 +98,21 @@ struct ncd_control_msg {
#define NCD_CONTROL_MSG_SIZE sizeof(struct ncd_control_msg)
/* ─── Управление линками на конкретном сокете ─── */
/*
* ncd_add_socket_links — итерирует все активные NCD-соединения (ncd_registry),
* для каждого загружает TOPO_NODE с адресами пира и создаёт линки (etcp_link_new)
* на переданном сокете (только совместимые family: v4/v6). Аналог ncd_create_links
* но вызывается извне (auto_socket) при добавлении нового интерфейса.
* Возвращает количество созданных линков (0 если нечего добавить или ошибка).
*/
int ncd_add_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock);
/*
* ncd_remove_socket_links — итерирует все активные NCD-соединения,
* для каждого закрывает линки (etcp_link_close) привязанные к переданному сокету.
* Вызывается при удалении сокета (интерфейс пропал). Возвращает количество закрытых линков.
*/
int ncd_remove_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock);
#endif

2
src/transport_layer/socket_monitor.c

@ -46,7 +46,7 @@ struct SOCKET_MONITOR {
/* ===== Common functions (all platforms) ===== */
static void socket_monitor_update_if_addr(struct ETCP_SOCKET* es) {
void socket_monitor_update_if_addr(struct ETCP_SOCKET* es) {
if (es->local_addr.ss_family == AF_INET) {
if (es->netif_index > 0) {
uint32_t if_ip = get_interface_ip_by_index(es->netif_index);

5
src/transport_layer/socket_monitor.h

@ -6,10 +6,15 @@ extern "C" {
#endif
struct UTUN_INSTANCE;
struct ETCP_SOCKET;
int socket_monitor_init(struct UTUN_INSTANCE* instance);
void socket_monitor_destroy(struct UTUN_INSTANCE* instance);
/* Обновляет interface_addr сокета (IP интерфейса / default route).
* Экспортирована для auto_socket — вызывается когда интерфейс получил новый адрес. */
void socket_monitor_update_if_addr(struct ETCP_SOCKET* es);
#ifdef __cplusplus
}
#endif

3
src/utun_instance.c

@ -20,6 +20,7 @@
#include "control_server.h"
#include "transport_layer/node_conn_direct.h"
#include "transport_layer/socket_monitor.h"
#include "transport_layer/auto_socket.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
@ -219,6 +220,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
}
socket_monitor_init(instance);
auto_socket_init(instance);
// conn_mgr initialized inside topo_group_create (via topo_groups_init)
@ -434,6 +436,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
instance->config_conn_handles = NULL;
socket_monitor_destroy(instance);
auto_socket_destroy(instance);
// Cleanup BGP module BEFORE sockets (needs live conn_mgr for recovery cleanup)
if (instance->topo_groups) {

2
src/utun_instance.h

@ -140,6 +140,8 @@ struct UTUN_INSTANCE {
struct TCP_SOCKET* tcp_sockets; // linked-list [TCP-only]
struct stcp_server *stcp_servers; // TCP servers linked list (via stcp_link.c)
void* socket_monitor; // SOCKET_MONITOR* (opaque, transport_layer/socket_monitor.c)
void* auto_socket_state; // AUTO_SOCKET* (opaque, transport_layer/auto_socket.c)
uint8_t auto_socket_enabled; // 1 = auto_sockets enabled from config
// Pending one-shot pings (for callback on PONG or timeout)
struct PING_CONTEXT* pending_pings;

1
tools/chatgui/transport/node_config.cpp

@ -19,6 +19,7 @@ static const QSet<QString> GLOBAL_KEYS = {
"my_node_name", "my_private_key", "my_public_key", "my_node_id",
"tun_enabled", "tun_ifname", "tun_ip", "mtu",
"keepalive_timeout", "keepalive_interval", "keepalive_adaptive", "bbr_max_cwnd",
"auto_sockets",
"debug_level", "log_file", "db_path", "db_sync_enabled", "db_sync_ttl",
"enable_timestamp", "enable_function_names", "enable_file_lines", "enable_colors",
"tun_test_mode"

Loading…
Cancel
Save