Files
utun2/src/transport_layer/auto_socket.c
T

1502 lines
75 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* auto_socket.c — автоматическое управление сокетами (UDP+TCP) при изменении сетевых интерфейсов
*
* === Зачем ===
* При auto_sockets=yes модуль сам создаёт UDP и TCP сокеты на всех не-loopback
* интерфейсах с 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, оповестить подписчиков
* - интерфейс стал DOWN → удалить все сокеты
*
* === Линки ===
* Каждый созданный сокет получает линки ко всем активным соединениям через
* 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"
#include "etcp_connections.h"
#include "etcp_api.h"
#include "node_conn_direct.h"
#include "socket_monitor.h"
#include "stcp_link.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../chat/chat_event.h"
#include "../chat/chat_sync.h"
#include "../routing_layer/topo_group.h"
#include "../routing_layer/conn_mgr.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>
#include "../../lib/sqlite3.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 <sys/sysctl.h>
#include <fcntl.h>
#elif defined(_WIN32)
#include <iphlpapi.h>
#include <netioapi.h>
#include <ws2ipdef.h>
#endif
#define DEBUG_CATEGORY_AS DEBUG_CATEGORY_SOCKET
#define AS_PROTO_UDP 0
#define AS_PROTO_TCP 1
/* Классифицирует 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
#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;
}
/* Один отслеживаемый интерфейс: хранит указатели на созданные сокеты (UDP — ETCP_SOCKET,
* TCP — stcp_server) и их актуальный тип (NAT/PUBLIC). По типу определяется когда
* нужно пересоздать сокет при изменении адресов. */
struct auto_sock_iface {
struct auto_sock_iface* next;
uint32_t netif_index; // индекс интерфейса (if_nametoindex)
char ifname[IF_NAMESIZE]; // нужен и после удаления интерфейса из ОС
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
struct ETCP_SOCKET* v4_tcp_sock; // модель сокета принадлежит тому же интерфейсу
struct ETCP_SOCKET* v6_tcp_sock;
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; // родительский utun instance
struct auto_sock_iface* ifaces; // связный список интерфейсов
uint8_t mode; // CFG_AUTO_SOCKETS_SYSTEM или CFG_AUTO_SOCKETS_ANDROID
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; // netlink socket fd для RTMGRP_LINK/IPV4/IPV6
void* uasync_handle; // handle в uasync для netlink fd
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
socket_t route_sock; // route socket fd (PF_ROUTE)
void* uasync_handle; // handle в uasync для route fd
#elif defined(_WIN32)
HANDLE addr_notify_handle; // NotifyUnicastIpAddressChange handle
HANDLE route_notify_handle; // NotifyRouteChange2 handle
#endif
};
/* ─── forward declarations ─── */
static int create_iface_udp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname, int family, uint8_t type);
static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname,
int family, uint8_t type, struct ETCP_SOCKET** out_sock);
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 int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname);
static int auto_socket_scan_all(struct AUTO_SOCKET* as);
static int auto_socket_reconcile_config(struct AUTO_SOCKET* as);
/* ─── 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;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"SELECT port FROM auto_socket_ports WHERE if_name=? AND family=? AND protocol=?",
-1, &stmt, NULL) != SQLITE_OK) return 0;
sqlite3_bind_text(stmt, 1, ifname, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, family);
sqlite3_bind_int(stmt, 3, protocol);
uint16_t port = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) port = (uint16_t)sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
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;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT OR REPLACE INTO auto_socket_ports(if_name, family, protocol, port, updated_at) VALUES(?,?,?,?,unixepoch())",
-1, &stmt, NULL) != SQLITE_OK) return;
sqlite3_bind_text(stmt, 1, ifname, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, family);
sqlite3_bind_int(stmt, 3, protocol);
sqlite3_bind_int(stmt, 4, port);
sqlite3_exec(db, "SAVEPOINT asp_save", NULL, NULL, NULL);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE)
sqlite3_exec(db, "RELEASE asp_save", NULL, NULL, NULL);
else
sqlite3_exec(db, "ROLLBACK TO asp_save", NULL, NULL, NULL);
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;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"DELETE FROM auto_socket_ports WHERE if_name=? AND family=? AND protocol=?",
-1, &stmt, NULL) != SQLITE_OK) return;
sqlite3_bind_text(stmt, 1, ifname, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, family);
sqlite3_bind_int(stmt, 3, protocol);
sqlite3_step(stmt);
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;
struct TOPO_GROUPS* groups = as->instance->topo_groups;
for (struct ll_entry* e = groups && groups->group_list ? groups->group_list->head : NULL; e; e = e->next) {
struct TOPO_GROUP* group = (struct TOPO_GROUP*)e;
if (!group->stopping) conn_mgr_network_changed(group->conn_mgr);
}
chat_event_post(as->instance, CHAT_EVT_LOCAL_SOCKETS, NULL, 0);
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;
memset(&server, 0, sizeof(server));
snprintf(server.name, sizeof(server.name), "as_%s_%s_udp", ifname, family == AF_INET6 ? "v6" : "v4");
server.type = type;
server.netif_index = ifindex;
server.transport = 0;
server.mtu = inst->config ? inst->config->global.mtu : 1280;
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;
}
uint16_t saved_port = load_port_from_db(as, ifname, family, AS_PROTO_UDP);
int max_att = saved_port ? 11 : 10;
for (int attempt = 0; attempt < max_att; attempt++) {
uint16_t port = (attempt == 0 && saved_port) ? saved_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) {
if (attempt == 0 && saved_port)
DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP reuse port failed if=%s fam=%d port=%u — trying random", ifname, family, port);
else
DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] UDP 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);
{ const struct sockaddr_storage* ia = es->interface_addr.ss_family ? &es->interface_addr : &es->local_addr;
if (ia->ss_family == AF_INET && ((struct sockaddr_in*)ia)->sin_addr.s_addr == INADDR_ANY) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] UDP socket %s has INADDR_ANY — removing", es->name);
ncd_remove_socket_links(inst, es); etcp_socket_remove(es); continue;
}
if (ia->ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)ia)->sin6_addr)) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] UDP socket %s has :: — removing", es->name);
ncd_remove_socket_links(inst, es); etcp_socket_remove(es); continue;
}
}
save_port_to_db(as, ifname, family, AS_PROTO_UDP, port);
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] UDP 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 UDP socket %s", links, es->name);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] failed to create UDP socket after %d attempts if=%s fam=%d", max_att, ifname, family);
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 ETCP_SOCKET** out_sock) {
struct UTUN_INSTANCE* inst = as->instance;
*out_sock = NULL;
uint16_t saved_port = load_port_from_db(as, ifname, family, AS_PROTO_TCP);
int max_att = saved_port ? 11 : 10;
struct stcp_link_config scfg;
memset(&scfg, 0, sizeof(scfg));
scfg.inst = inst;
scfg.listen_family = family;
for (int attempt = 0; attempt < max_att; attempt++) {
uint16_t port = (attempt == 0 && saved_port) ? saved_port : random_port();
struct CFG_SERVER server;
memset(&server, 0, sizeof(server));
snprintf(server.name, sizeof(server.name), "as_%s_%s_tcp", ifname, family == AF_INET6 ? "v6" : "v4");
server.type = type;
server.netif_index = ifindex;
server.transport = 1;
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; sin->sin_port = htons(port);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&server.ip;
sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; sin6->sin6_port = htons(port);
}
struct ETCP_SOCKET* ts = tcp_socket_add(inst, &server);
if (!ts) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP tcp_socket_add failed if=%s fam=%d port=%u", ifname, family, port); continue; }
{ const struct sockaddr_storage* ia = (ts->interface_addr.ss_family) ? &ts->interface_addr : &ts->local_addr;
if ((ia->ss_family == AF_INET && ((const struct sockaddr_in*)ia)->sin_addr.s_addr == INADDR_ANY) ||
(ia->ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&((const struct sockaddr_in6*)ia)->sin6_addr))) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] TCP socket %s has null addr — removing", server.name);
tcp_socket_remove(ts); continue;
}
}
struct stcp_server* tsrv = stcp_server_listen(&scfg, port, tcp_server_on_link, ts);
if (!tsrv) {
if (attempt == 0 && saved_port)
DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP reuse port failed if=%s fam=%d port=%u — trying random", ifname, family, port);
else
DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] TCP socket create failed if=%s fam=%d port=%u att=%d err=%d",
ifname, family, port, attempt + 1, socket_get_error());
tcp_socket_remove(ts);
if (socket_get_error() != EADDRINUSE) return NULL;
continue;
}
stcp_server_list_add(inst, tsrv);
*out_sock = ts;
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;
}
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] failed to create TCP socket after %d attempts if=%s fam=%d", max_att, ifname, family);
return NULL;
}
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;
snprintf(ifa->ifname, sizeof(ifa->ifname), "%s", ifname);
int added = 0;
if (has_v4) {
if (create_iface_udp_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->is_tcp && s->netif_index == ifindex && s->local_addr.ss_family == AF_INET && !ifa->v4_udp) { ifa->v4_udp = s; break; } s = s->next; }
}
{ struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET, v4_type, &ifa->v4_tcp_sock);
if (tsrv) { added++; ifa->v4_tcp = tsrv; } }
}
if (has_v6) {
if (create_iface_udp_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->is_tcp && s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6 && !ifa->v6_udp) { ifa->v6_udp = s; break; } s = s->next; }
}
{ struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET6, v6_type, &ifa->v6_tcp_sock);
if (tsrv) { added++; ifa->v6_tcp = tsrv; } }
}
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);
auto_socket_post_sockets_changed(as);
} 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);
}
/*
* Удаляет все сокеты и линки для указанного интерфейса.
* Для каждого из 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_tcp(struct AUTO_SOCKET* as, struct stcp_server** server, struct ETCP_SOCKET** socket) {
/* Сначала отменяем все callbacks/линки, затем освобождаем их socket context. */
if (*server) {
stcp_server_list_remove(as->instance, *server);
stcp_link_server_destroy(*server);
*server = NULL;
}
if (*socket) { tcp_socket_remove(*socket); *socket = NULL; }
}
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;
const char* ifname = ifa->ifname;
if (ifa->v4_udp) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v4 UDP socket %s ifidx=%u", ifa->v4_udp->name, ifindex);
ncd_remove_socket_links(as->instance, ifa->v4_udp);
etcp_socket_remove(ifa->v4_udp);
delete_port_from_db(as, ifname, AF_INET, AS_PROTO_UDP);
}
if (ifa->v6_udp) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v6 UDP socket %s ifidx=%u", ifa->v6_udp->name, ifindex);
ncd_remove_socket_links(as->instance, ifa->v6_udp);
etcp_socket_remove(ifa->v6_udp);
delete_port_from_db(as, ifname, AF_INET6, AS_PROTO_UDP);
}
if (ifa->v4_tcp) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v4 TCP socket ifidx=%u", ifindex);
remove_iface_tcp(as, &ifa->v4_tcp, &ifa->v4_tcp_sock);
delete_port_from_db(as, ifname, AF_INET, AS_PROTO_TCP);
}
if (ifa->v6_tcp) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing v6 TCP socket ifidx=%u", ifindex);
remove_iface_tcp(as, &ifa->v6_tcp, &ifa->v6_tcp_sock);
delete_port_from_db(as, ifname, AF_INET6, AS_PROTO_TCP);
}
u_free(ifa);
auto_socket_post_sockets_changed(as);
return;
}
pp = &(*pp)->next;
}
}
/* ─── Сканирование адресов интерфейса и определение типа ───
*
* Эти функции анализируют реальное состояние интерфейса: есть ли 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) {
FILE* f = fopen("/proc/net/if_inet6", "r");
if (!f) return 0;
char line[256];
int found = 0;
while (fgets(line, sizeof(line), f)) {
char addr[33], name[IFNAMSIZ];
unsigned int flags;
if (sscanf(line, "%32s %*x %*x %*x %x %15s", addr, &flags, name) == 3) {
if (strcmp(name, ifname) != 0) continue;
if (strncmp(addr, "fe80", 4) == 0) continue; /* link-local */
if (!(flags & 0x20)) { found = 1; break; }
}
}
fclose(f);
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]);
FILE* f = fopen("/proc/net/if_inet6", "r");
if (!f) return 0;
char line[256];
int tmp = 0;
while (fgets(line, sizeof(line), f)) {
char a[33], name[IFNAMSIZ];
unsigned int flags;
if (sscanf(line, "%32s %*x %*x %*x %x %15s", a, &flags, name) == 3) {
if (strcmp(name, ifname) != 0) continue;
if (strcmp(a, hex) == 0) { tmp = (flags & 0x20) != 0; break; }
}
}
fclose(f);
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) {
(void)ifindex;
*out_has_v4 = 0; *out_has_v6 = 0;
*out_v4_type = CFG_SERVER_TYPE_NAT;
*out_v6_type = CFG_SERVER_TYPE_NAT;
int has_perm_v6 = iface_has_permanent_v6(ifname);
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 (ip_is_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;
if (has_perm_v6 && is_v6_addr_temporary(sin6->sin6_addr.s6_addr, ifname)) 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;
}
/*
* Полное сканирование: обходит все UP-интерфейсы (не loopback) и для каждого
* вызывает reconcile_iface. Используется при старте и при ручном запросе
* пересканирования (auto_socket_on_network_change). */
static int 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 -1; }
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_ERROR(DEBUG_CATEGORY_AS, "[as] too many interfaces, cap at 64"); freeifaddrs(ifa_list); return -1; }
strncpy(up_names[up_count], ifa->ifa_name, IFNAMSIZ - 1);
up_names[up_count][IFNAMSIZ - 1] = 0;
up_count++;
}
freeifaddrs(ifa_list);
int result = 0;
for (int i = 0; i < up_count; i++)
if (reconcile_iface(as, if_nametoindex(up_names[i]), up_names[i]) < 0) result = -1;
return result;
}
#else /* _WIN32 */
/* Собирает IfIndex всех UP не-loopback адаптеров. Возвращает число найденных (до max_count). */
static int win_collect_up_ifaces(uint32_t* out_ifindex, int max_count) {
PIP_ADAPTER_ADDRESSES adapters = NULL;
ULONG size = 0;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
DWORD rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &size);
if (rv == ERROR_NO_DATA) return 0;
if (rv != ERROR_BUFFER_OVERFLOW) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] GetAdaptersAddresses size failed: %lu", rv); return -1; }
adapters = (PIP_ADAPTER_ADDRESSES)u_malloc(size);
if (!adapters) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] adapter list allocation failed"); return -1; }
rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
if (rv != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] GetAdaptersAddresses failed: %lu", rv); u_free(adapters); return -1; }
int count = 0;
for (PIP_ADAPTER_ADDRESSES a = adapters; a && count < max_count; a = a->Next) {
if (a->OperStatus != IfOperStatusUp) continue;
if (a->IfType == IF_TYPE_SOFTWARE_LOOPBACK) continue;
out_ifindex[count++] = a->IfIndex;
}
u_free(adapters);
return count;
}
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)ifname;
*out_has_v4 = 0; *out_has_v6 = 0;
*out_v4_type = CFG_SERVER_TYPE_NAT;
*out_v6_type = CFG_SERVER_TYPE_NAT;
PIP_ADAPTER_ADDRESSES adapters = NULL;
ULONG size = 0;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
DWORD rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &size);
if (rv != ERROR_BUFFER_OVERFLOW) return -1;
adapters = (PIP_ADAPTER_ADDRESSES)u_malloc(size);
if (!adapters) return -1;
rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
if (rv != NO_ERROR) { u_free(adapters); return -1; }
PIP_ADAPTER_ADDRESSES a = adapters;
while (a && a->IfIndex != ifindex) a = a->Next;
if (!a || a->OperStatus != IfOperStatusUp || a->IfType == IF_TYPE_SOFTWARE_LOOPBACK) {
u_free(adapters);
return 0;
}
for (PIP_ADAPTER_UNICAST_ADDRESS u = a->FirstUnicastAddress; u; u = u->Next) {
if (!u->Address.lpSockaddr) continue;
if (u->Address.lpSockaddr->sa_family == AF_INET) {
const struct sockaddr_in* sin = (const struct sockaddr_in*)u->Address.lpSockaddr;
if (sin->sin_addr.s_addr == INADDR_ANY) continue;
*out_has_v4 = 1;
if (ip_is_public(sin->sin_addr.s_addr)) *out_v4_type = CFG_SERVER_TYPE_PUBLIC;
} else if (u->Address.lpSockaddr->sa_family == AF_INET6) {
const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)u->Address.lpSockaddr;
int cls = v6_classify(sin6->sin6_addr.s6_addr);
if (cls == AS_V6_OTH || cls == AS_V6_LL) continue;
*out_has_v6 = 1;
if (cls == AS_V6_DIR) *out_v6_type = CFG_SERVER_TYPE_PUBLIC;
}
}
u_free(adapters);
return 0;
}
static int auto_socket_scan_all(struct AUTO_SOCKET* as) {
uint32_t up[64];
int up_count = win_collect_up_ifaces(up, 64);
if (up_count < 0) return -1;
int result = 0;
for (int i = 0; i < up_count; i++) {
char ifname[IF_NAMESIZE];
if (!if_indextoname(up[i], ifname)) snprintf(ifname, sizeof(ifname), "if%u", up[i]);
if (reconcile_iface(as, up[i], ifname) < 0) result = -1;
}
/* prune: удаляем сокеты адаптеров, которых больше нет в UP-списке (или они DOWN) */
struct auto_sock_iface** pp = &as->ifaces;
while (*pp) {
struct auto_sock_iface* ifa = *pp;
int found = 0;
for (int i = 0; i < up_count; i++) if (up[i] == ifa->netif_index) { found = 1; break; }
if (!found) { remove_iface_sockets(as, ifa->netif_index); continue; }
pp = &ifa->next;
}
return result;
}
#endif /* _WIN32 */
/* ═══════════ default-маршрут: проверка возможности выхода в интернет ═══════════
*
* Фильтр для авто-режима: интерфейс без default-маршрута (шлюза) не может
* достучаться до публичных пиров и даёт только sendto ENETUNREACH (например
* VirtualBox host-only, изолированные VPN). Такие интерфейсы пропускаем.
* Возвращают 1 если default-маршрут есть, 0 если нет. При невозможности
* определить — 1 (не пропускаем, чтобы не сломать существующие сетапы). */
#if defined(__linux__)
static int iface_has_default_v4(uint32_t ifindex, const char* ifname) {
(void)ifindex;
FILE* f = fopen("/proc/net/route", "r");
if (!f) return 1;
char line[512];
int found = 0;
if (fgets(line, sizeof(line), f)) {
while (fgets(line, sizeof(line), f)) {
char iface[IFNAMSIZ];
unsigned long dest;
if (sscanf(line, "%15s %lx %*x %*x", iface, &dest) == 2) {
if (dest == 0 && strcmp(iface, ifname) == 0) { found = 1; break; }
}
}
}
fclose(f);
return found;
}
static int iface_has_default_v6(uint32_t ifindex, const char* ifname) {
(void)ifindex;
FILE* f = fopen("/proc/net/ipv6_route", "r");
if (!f) return 1;
char line[512];
int found = 0;
while (fgets(line, sizeof(line), f)) {
char dest[33], iface[IFNAMSIZ];
unsigned int prefixlen;
if (sscanf(line, "%32s %2x %*32s %*2x %*32s %*8x %*8x %*8x %*8x %15s", dest, &prefixlen, iface) == 3) {
if (prefixlen == 0 && strcmp(iface, ifname) == 0) { found = 1; break; }
}
}
fclose(f);
return found;
}
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
/* Вытаскивает sockaddr по индексу RTAX_* из rt_msghdr (вместо SA_SIZE — универсально). */
static struct sockaddr* rta_get(struct rt_msghdr* rtm, int rtax) {
if (!(rtm->rtm_addrs & (1 << rtax))) return NULL;
char* p = (char*)(rtm + 1);
for (int i = 0; i < rtax; i++) {
if (rtm->rtm_addrs & (1 << i)) {
struct sockaddr* sa = (struct sockaddr*)p;
p += 1 + ((((size_t)sa->sa_len - 1) | (sizeof(uint32_t) - 1)));
}
}
return (struct sockaddr*)p;
}
static int freebsd_iface_has_default(uint32_t ifindex, int family) {
int mib[6] = { CTL_NET, PF_ROUTE, 0, family, NET_RT_DUMP, 0 };
size_t need = 0;
if (sysctl(mib, 6, NULL, &need, NULL, 0) < 0 || need == 0) return 1;
char* buf = u_malloc(need);
if (!buf) return 1;
if (sysctl(mib, 6, buf, &need, NULL, 0) < 0) { u_free(buf); return 1; }
int found = 0;
char* p = buf;
char* end = buf + need;
while (p + sizeof(struct rt_msghdr) <= end) {
struct rt_msghdr* rtm = (struct rt_msghdr*)p;
if (rtm->rtm_msglen < sizeof(struct rt_msghdr) || rtm->rtm_msglen > (size_t)(end - p)) break;
struct sockaddr* mask = rta_get(rtm, RTAX_NETMASK);
struct sockaddr_dl* ifp = (struct sockaddr_dl*)rta_get(rtm, RTAX_IFP);
int is_default = 0;
if (mask && ifp && ifp->sdl_family == AF_LINK) {
if (family == AF_INET && mask->sa_family == AF_INET)
is_default = (((struct sockaddr_in*)mask)->sin_addr.s_addr == 0);
else if (family == AF_INET6 && mask->sa_family == AF_INET6)
is_default = IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)mask)->sin6_addr);
}
if (is_default && ifp->sdl_index == ifindex) { found = 1; break; }
p += rtm->rtm_msglen;
}
u_free(buf);
return found;
}
static int iface_has_default_v4(uint32_t ifindex, const char* ifname) {
(void)ifname;
return freebsd_iface_has_default(ifindex, AF_INET);
}
static int iface_has_default_v6(uint32_t ifindex, const char* ifname) {
(void)ifname;
return freebsd_iface_has_default(ifindex, AF_INET6);
}
#elif defined(_WIN32)
static int iface_has_default_v4(uint32_t ifindex, const char* ifname) {
(void)ifname;
MIB_IPFORWARD_TABLE2* table = NULL;
if (GetIpForwardTable2(AF_INET, &table) != NO_ERROR || !table) return 1;
int found = 0;
for (ULONG i = 0; i < table->NumEntries; i++) {
if (table->Table[i].InterfaceIndex == ifindex &&
table->Table[i].DestinationPrefix.PrefixLength == 0) { found = 1; break; }
}
FreeMibTable(table);
return found;
}
static int iface_has_default_v6(uint32_t ifindex, const char* ifname) {
(void)ifname;
MIB_IPFORWARD_TABLE2* table = NULL;
if (GetIpForwardTable2(AF_INET6, &table) != NO_ERROR || !table) return 1;
int found = 0;
for (ULONG i = 0; i < table->NumEntries; i++) {
if (table->Table[i].InterfaceIndex == ifindex &&
table->Table[i].DestinationPrefix.PrefixLength == 0) { found = 1; break; }
}
FreeMibTable(table);
return found;
}
#else
static int iface_has_default_v4(uint32_t ifindex, const char* ifname) { (void)ifindex; (void)ifname; return 1; }
static int iface_has_default_v6(uint32_t ifindex, const char* ifname) { (void)ifindex; (void)ifname; return 1; }
#endif
/* ═══════════ reconcile: сверка и исправление состояния сокетов ═══════════
*
* reconcile — ключевой механизм модуля. При любом изменении сети сравнивает
* фактическое состояние интерфейса (IP-адреса, их тип) с текущим набором сокетов
* и устраняет расхождения: создаёт недостающие сокеты, удаляет лишние,
* пересоздаёт при смене типа (NAT↔PUBLIC), обновляет interface_addr. */
/* Находит или создаёт запись 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, const char* ifname) {
struct auto_sock_iface* ifa = as->ifaces;
while (ifa) { if (ifa->netif_index == ifindex) return ifa; ifa = ifa->next; }
ifa = u_calloc(1, sizeof(*ifa));
if (!ifa) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] interface record allocation failed: %s", ifname); return NULL; }
ifa->netif_index = ifindex;
snprintf(ifa->ifname, sizeof(ifa->ifname), "%s", ifname);
ifa->next = as->ifaces;
as->ifaces = ifa;
return ifa;
}
/* Удаляет запись 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;
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);
}
/*
* Сверяет состояние интерфейса с набором сокетов и устраняет расхождения.
* Это основная логика модуля — вызывается при ЛЮБОМ изменении сети.
*
* Для каждого из 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 int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) {
if (ifindex == 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] invalid interface index: %s", ifname); return -1; }
int changed = 0;
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 -1;
/* Фильтр авто-режима: интерфейс без default-маршрута не может достать до
* публичных пиров — не создаём на нём сокеты (иначе только sendto ENETUNREACH). */
if (as->instance->config->global.auto_socket_skip_no_default_route) {
if (has_v4 && !iface_has_default_v4(ifindex, ifname)) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] %s has no default v4 route — skip v4", ifname);
has_v4 = 0;
}
if (has_v6 && !iface_has_default_v6(ifindex, ifname)) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] %s has no default v6 route — skip v6", ifname);
has_v6 = 0;
}
}
struct auto_sock_iface* ifa = as->ifaces;
while (ifa && ifa->netif_index != ifindex) ifa = ifa->next;
/* ── IPv4 UDP ── */
{
struct ETCP_SOCKET* sock = ifa ? ifa->v4_udp : NULL;
uint8_t cur_type = ifa ? ifa->v4_type : CFG_SERVER_TYPE_NAT;
if (has_v4 && !sock) {
if (!ifa) ifa = reconcile_get_ifa(as, ifindex, ifname);
if (ifa) {
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->is_tcp && s->netif_index == ifindex && s->local_addr.ss_family == AF_INET && !ifa->v4_udp) { ifa->v4_udp = s; break; } s = s->next; } }
changed = 1;
}
} else if (!has_v4 && sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP removed: %s ifidx=%u", ifname, ifindex);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v4_udp = NULL; changed = 1;
} else if (has_v4 && sock) {
struct sockaddr_storage old4 = sock->interface_addr;
socket_monitor_update_if_addr(sock);
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);
create_iface_udp_socket(as, ifindex, ifname, AF_INET, new_type);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v4_type = new_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (!s->is_tcp && 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;
}
}
}
/* ── IPv6 UDP ── */
{
struct ETCP_SOCKET* sock = ifa ? ifa->v6_udp : NULL;
uint8_t cur_type = ifa ? ifa->v6_type : CFG_SERVER_TYPE_NAT;
if (has_v6 && !sock) {
if (!ifa) ifa = reconcile_get_ifa(as, ifindex, ifname);
if (ifa) {
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->is_tcp && s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6 && !ifa->v6_udp) { ifa->v6_udp = s; break; } s = s->next; } }
changed = 1;
}
} else if (!has_v6 && sock) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP removed: %s ifidx=%u", ifname, ifindex);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v6_udp = NULL; changed = 1;
} else if (has_v6 && sock) {
struct sockaddr_storage old6 = sock->interface_addr;
socket_monitor_update_if_addr(sock);
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);
create_iface_udp_socket(as, ifindex, ifname, AF_INET6, new_type);
ncd_remove_socket_links(as->instance, sock);
etcp_socket_remove(sock);
ifa->v6_type = new_type;
{ struct ETCP_SOCKET* s = as->instance->etcp_sockets;
while (s) { if (!s->is_tcp && 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;
}
}
}
/* ── IPv4 TCP ── */
{
int has = has_v4 && 1; /* always create both TCP and UDP if v4 present */
struct stcp_server* srv = ifa ? ifa->v4_tcp : NULL;
if (has && !srv) {
if (!ifa) ifa = reconcile_get_ifa(as, ifindex, ifname);
if (ifa) { struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET, v4_need_type, &ifa->v4_tcp_sock); ifa->v4_tcp = tsrv; if (tsrv) changed = 1; }
} else if (!has && srv) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 TCP removed: %s ifidx=%u", ifname, ifindex);
remove_iface_tcp(as, &ifa->v4_tcp, &ifa->v4_tcp_sock);
delete_port_from_db(as, ifname, AF_INET, AS_PROTO_TCP);
ifa->v4_tcp = NULL; changed = 1;
}
}
/* ── IPv6 TCP ── */
{
int has = has_v6 && 1;
struct stcp_server* srv = ifa ? ifa->v6_tcp : NULL;
if (has && !srv) {
if (!ifa) ifa = reconcile_get_ifa(as, ifindex, ifname);
if (ifa) { struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET6, v6_need_type, &ifa->v6_tcp_sock); ifa->v6_tcp = tsrv; if (tsrv) changed = 1; }
} else if (!has && srv) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 TCP removed: %s ifidx=%u", ifname, ifindex);
remove_iface_tcp(as, &ifa->v6_tcp, &ifa->v6_tcp_sock);
delete_port_from_db(as, ifname, AF_INET6, AS_PROTO_TCP);
ifa->v6_tcp = NULL; changed = 1;
}
}
int complete = (!has_v4 || (ifa && ifa->v4_udp && ifa->v4_tcp)) && (!has_v6 || (ifa && ifa->v6_udp && ifa->v6_tcp));
if (!complete) DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] incomplete socket initialization iface=%s index=%u v4=%d v6=%d",
ifname, ifindex, has_v4, has_v6);
reconcile_prune_ifa(as, ifa, ifindex, ifname);
if (changed) auto_socket_post_sockets_changed(as);
return complete ? 0 : -1;
}
/* ═══════════ Platform monitoring ═══════════ */
/* ─── 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];
if (!if_indextoname(ifa->ifa_index, ifname)) return;
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)) {
DEBUG_DEBUG(DEBUG_CATEGORY_AS, "[as] handle_nl_link: if_indextoname(%u) failed, msg=%d — removing by index",
ifi->ifi_index, msg_type);
remove_iface_sockets(as, ifi->ifi_index);
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);
}
}
/* При изменении маршрута (RTM_NEWROUTE / RTM_DELROUTE): default-маршрут
* (dst_len==0) мог появиться/исчезнуть на интерфейсе — пересканируем все. */
static void handle_nl_route(struct AUTO_SOCKET* as, struct rtmsg* rtm) {
if (rtm->rtm_dst_len == 0) auto_socket_scan_all(as);
}
/* Коллбэк 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];
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);
else if (nlh->nlmsg_type == RTM_NEWROUTE || nlh->nlmsg_type == RTM_DELROUTE)
handle_nl_route(as, (struct rtmsg*)NLMSG_DATA(nlh));
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 failed: %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 | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
if (bind(as->nl_sock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink bind failed: %s", strerror(errno));
close(as->nl_sock); as->nl_sock = -1; return -1;
}
if (socket_set_nonblocking(as->nl_sock) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink nonblocking mode failed: %s", strerror(errno));
close(as->nl_sock); as->nl_sock = -1; return -1;
}
as->uasync_handle = uasync_add_socket(as->instance->ua, as->nl_sock, auto_socket_netlink_cb, NULL, NULL, "netlink_autosock", as);
if (!as->uasync_handle) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] uasync_add_socket failed"); close(as->nl_sock); as->nl_sock = -1; return -1; }
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink monitor started: fd=%d", (int)as->nl_sock);
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 ───
* На FreeBSD мониторим изменения через PF_ROUTE сокет. При любых изменениях
* адресов/интерфейсов запускаем полное пересканирование. */
#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, "route_autosock", 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 ───
* Используем NotifyUnicastIpAddressChange + NotifyRouteChange2.
* Коллбэки приходят из системных потоков → uasync_post для обработки в главном потоке. */
#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
/* Платформа без встроенного мониторинга — только явные вызовы 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 == CFG_AUTO_SOCKETS_OFF) 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; }
#ifdef __linux__
as->nl_sock = -1;
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
as->route_sock = -1;
#endif
as->instance = inst;
as->mode = (uint8_t)inst->config->global.auto_sockets;
inst->auto_socket_state = as;
/* Android-режим: источник сокетов — [server] секции конфига (генерирует Kotlin).
* Системный мониторинг и таблица портов в БД не нужны. */
if (as->mode == CFG_AUTO_SOCKETS_ANDROID) {
if (auto_socket_reconcile_config(as) < 0) { auto_socket_destroy(inst); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized (android mode)");
return 0;
}
if (inst->topo_sqlite_db) {
sqlite3_exec(inst->topo_sqlite_db,
"CREATE TABLE IF NOT EXISTS auto_socket_ports ("
" if_name TEXT NOT NULL,"
" family INTEGER NOT NULL,"
" protocol INTEGER NOT NULL,"
" port INTEGER NOT NULL,"
" updated_at INTEGER DEFAULT (unixepoch()),"
" UNIQUE(if_name, family, protocol)"
");", NULL, NULL, NULL);
}
if (auto_socket_init_monitor(as) < 0) { u_free(as); inst->auto_socket_state = NULL; return -1; }
if (auto_socket_scan_all(as) < 0) { auto_socket_destroy(inst); return -1; }
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;
if (as->mode != CFG_AUTO_SOCKETS_ANDROID) auto_socket_destroy_monitor(as);
while (as->ifaces) {
struct auto_sock_iface* ifa = as->ifaces;
as->ifaces = ifa->next;
ncd_remove_socket_links(as->instance, ifa->v4_udp);
ncd_remove_socket_links(as->instance, ifa->v6_udp);
u_free(ifa);
}
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] destroyed");
u_free(as);
}
static void auto_socket_scan_all_trampoline(void* arg) {
auto_socket_scan_all((struct AUTO_SOCKET*)arg);
}
void auto_socket_on_network_change(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->auto_socket_state) return;
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state;
/* Android-режим: обновление идёт только через auto_socket_reload_config (Kotlin → JNI). */
if (as->mode == CFG_AUTO_SOCKETS_ANDROID) return;
uasync_post(as->instance->ua, auto_socket_scan_all_trampoline, as);
}
/* ═══════════ Android-режим: reconcile сокетов из [server] секций конфига ═══════════ */
/* Сравнивает CFG_SERVER с существующим ETCP_SOCKET: имя, адрес+порт, тип, транспорт. */
static int server_matches_socket(const struct CFG_SERVER* server, const struct ETCP_SOCKET* sock) {
if (!server || !sock) return 0;
if (strcmp(server->name, sock->name) != 0) return 0;
if (server->transport != (uint8_t)sock->is_tcp) return 0;
if (server->type != sock->type) return 0;
const struct sockaddr_storage* a = &server->ip;
const struct sockaddr_storage* b = sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr;
if (a->ss_family != b->ss_family) return 0;
if (a->ss_family == AF_INET) {
const struct sockaddr_in* ia = (const struct sockaddr_in*)a;
const struct sockaddr_in* ib = (const struct sockaddr_in*)b;
return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port;
}
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6* ia = (const struct sockaddr_in6*)a;
const struct sockaddr_in6* ib = (const struct sockaddr_in6*)b;
return memcmp(&ia->sin6_addr, &ib->sin6_addr, 16) == 0 && ia->sin6_port == ib->sin6_port;
}
return 0;
}
/* Удаляет один сокет (UDP или TCP) и все его линки. */
static void remove_config_socket(struct AUTO_SOCKET* as, struct ETCP_SOCKET* sock) {
if (!sock) return;
ncd_remove_socket_links(as->instance, sock);
if (sock->is_tcp) {
struct stcp_server* tsrv = stcp_server_list_find_by_sock(as->instance, sock);
if (tsrv) {
stcp_server_list_remove(as->instance, tsrv);
stcp_link_server_destroy(tsrv);
}
tcp_socket_remove(sock);
} else {
etcp_socket_remove(sock);
}
}
/* Сверяет inst->config->servers с inst->etcp_sockets и устраняет расхождения:
* удаляет сокеты, которых нет/которые изменились (проход 1), создаёт отсутствующие (проход 2). */
static int auto_socket_reconcile_config(struct AUTO_SOCKET* as) {
struct UTUN_INSTANCE* inst = as->instance;
if (!inst || !inst->config) return -1;
int result = 0;
/* Тип сокета определяем по фактическому адресу (не доверяем type из конфига):
* публичный адрес → PUBLIC, приватный/link-local → NAT. */
for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) {
uint8_t by_addr = CFG_SERVER_TYPE_NAT;
if (s->ip.ss_family == AF_INET) {
if (ip_is_public(((const struct sockaddr_in*)&s->ip)->sin_addr.s_addr))
by_addr = CFG_SERVER_TYPE_PUBLIC;
} else if (s->ip.ss_family == AF_INET6) {
if (v6_classify(((const struct sockaddr_in6*)&s->ip)->sin6_addr.s6_addr) == AS_V6_DIR)
by_addr = CFG_SERVER_TYPE_PUBLIC;
}
if (s->type != by_addr)
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] server %s type %d → %d (by address)", s->name, s->type, by_addr);
s->type = by_addr;
}
/* Проход 1: удалить сокеты, которых нет в новом списке, либо которые изменились. */
struct ETCP_SOCKET* sock = inst->etcp_sockets;
while (sock) {
struct ETCP_SOCKET* next = sock->next;
if (sock->probe_only) { sock = next; continue; }
const struct CFG_SERVER* srv = NULL;
for (const struct CFG_SERVER* s = inst->config->servers; s; s = s->next)
if (strcmp(s->name, sock->name) == 0) { srv = s; break; }
ip_str_t old_addr = sockaddr_storage_to_str(sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr);
if (!srv) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing socket %s (%s) addr=%s (no longer in config)",
sock->name, sock->is_tcp ? "tcp" : "udp", old_addr.str);
remove_config_socket(as, sock);
} else if (!server_matches_socket(srv, sock)) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] socket %s changed: %s → %s — rebuild",
sock->name, old_addr.str, sockaddr_storage_to_str(&srv->ip).str);
remove_config_socket(as, sock);
}
sock = next;
}
/* Проход 2: создать сокеты, которых ещё нет. */
for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) {
int exists = 0;
for (struct ETCP_SOCKET* es = inst->etcp_sockets; es; es = es->next)
if (strcmp(es->name, s->name) == 0) { exists = 1; break; }
if (exists) continue;
struct ETCP_SOCKET* created = NULL;
if (etcp_create_server_socket(inst, s, &created) == 0 && created) {
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] created socket %s (%s) addr=%s",
s->name, s->transport ? "tcp" : "udp", sockaddr_storage_to_str(&s->ip).str);
ncd_add_socket_links(inst, created);
} else result = -1;
}
auto_socket_post_sockets_changed(as);
return result;
}
void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg) {
if (!new_cfg) return;
if (!inst || !inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] reload_config: invalid args"); free_config(new_cfg); return; }
struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state;
if (as->mode != CFG_AUTO_SOCKETS_ANDROID) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] reload_config: not in android mode"); free_config(new_cfg); return; }
/* Сводка до/после: сколько серверов было → стало. */
int old_count = 0; for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) old_count++;
int new_count = 0; for (struct CFG_SERVER* s = new_cfg->servers; s; s = s->next) new_count++;
/* Переносим новый список серверов в inst->config, старый освобождаем. */
struct CFG_SERVER* old = inst->config->servers;
inst->config->servers = new_cfg->servers;
new_cfg->servers = NULL;
while (old) { struct CFG_SERVER* n = old->next; u_free(old); old = n; }
free_config(new_cfg);
DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] reload_config: %d → %d servers", old_count, new_count);
auto_socket_reconcile_config(as);
}