/* * 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 "stcp_link.h" #include "utun_instance.h" #include "config_parser.h" #include "../chat/chat_event.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 #include #include #include "../../lib/sqlite3.h" #if !defined(_WIN32) #include #include #include #endif #ifdef __linux__ #include #include #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #include #include #include #elif defined(_WIN32) #include #include #include #endif #define DEBUG_CATEGORY_AS DEBUG_CATEGORY_SOCKET #define AS_PROTO_UDP 0 #define AS_PROTO_TCP 1 /* ─── 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_udp; struct ETCP_SOCKET* v6_udp; struct stcp_server* v4_tcp; struct stcp_server* v6_tcp; uint8_t v4_type; uint8_t v6_type; }; 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_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); 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); /* ─── DB helpers (port persistence) ─── */ 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); } static void auto_socket_post_sockets_changed(struct AUTO_SOCKET* as) { chat_event_post(CHAT_EVT_LOCAL_SOCKETS, NULL, 0); (void)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_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 : 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; } 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; } static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname, int family, uint8_t type) { struct UTUN_INSTANCE* inst = as->instance; 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.ua = inst->ua; scfg.my_keys = &inst->my_keys; 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 stcp_server* tsrv = stcp_server_listen(&scfg, port, tcp_server_on_link, inst); 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()); if (socket_get_error() != EADDRINUSE) return NULL; continue; } stcp_server_list_add(inst, tsrv); 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 TCP_SOCKET* ts = tcp_socket_add(inst, &server); { const struct sockaddr_storage* ia = (ts && ts->interface_addr.ss_family) ? &ts->interface_addr : &ts->local_addr; if (!ts || (ia->ss_family == AF_INET && ((struct sockaddr_in*)ia)->sin_addr.s_addr == INADDR_ANY) || (ia->ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)ia)->sin6_addr))) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] TCP socket %s has null addr — removing", server.name); if (ts) tcp_socket_remove(ts); stcp_server_list_remove(inst, tsrv); stcp_link_server_destroy(tsrv); continue; } } save_port_to_db(as, ifname, family, AS_PROTO_TCP, port); 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; 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->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); 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->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); 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); } 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; char ifname[IFNAMSIZ] = ""; if (!if_indextoname(ifindex, ifname)) snprintf(ifname, sizeof(ifname), "idx%u", ifindex); 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); { uint16_t v4_port = load_port_from_db(as, ifname, AF_INET, AS_PROTO_TCP); struct TCP_SOCKET** tsp = &as->instance->tcp_sockets; while (*tsp) { uint16_t tsp_port = 0; const struct sockaddr_storage* addr = (*tsp)->interface_addr.ss_family ? &(*tsp)->interface_addr : &(*tsp)->local_addr; if (addr->ss_family == AF_INET) tsp_port = ntohs(((struct sockaddr_in*)addr)->sin_port); if (tsp_port == v4_port && v4_port > 0) { struct TCP_SOCKET* rm = *tsp; *tsp = rm->next; u_free(rm); break; } tsp = &(*tsp)->next; } } stcp_server_list_remove(as->instance, ifa->v4_tcp); stcp_link_server_destroy(ifa->v4_tcp); 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); { uint16_t v6_port = load_port_from_db(as, ifname, AF_INET6, AS_PROTO_TCP); struct TCP_SOCKET** tsp = &as->instance->tcp_sockets; while (*tsp) { uint16_t tsp_port = 0; const struct sockaddr_storage* addr = (*tsp)->interface_addr.ss_family ? &(*tsp)->interface_addr : &(*tsp)->local_addr; if (addr->ss_family == AF_INET6) tsp_port = ntohs(((struct sockaddr_in6*)addr)->sin6_port); if (tsp_port == v6_port && v6_port > 0) { struct TCP_SOCKET* rm = *tsp; *tsp = rm->next; u_free(rm); break; } tsp = &(*tsp)->next; } } stcp_server_list_remove(as->instance, ifa->v6_tcp); stcp_link_server_destroy(ifa->v6_tcp); delete_port_from_db(as, ifname, AF_INET6, AS_PROTO_TCP); } u_free(ifa); auto_socket_post_sockets_changed(as); return; } pp = &(*pp)->next; } } /* ─── Сканирование адресов интерфейса и определение типа ─── */ #if !defined(_WIN32) #include /* /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; } 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; } 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 (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; 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; } 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: сверка и исправление состояния сокетов ═══════════ */ /* helper: ensure ifa record exists, allocate if needed */ static struct auto_sock_iface* reconcile_get_ifa(struct AUTO_SOCKET* as, uint32_t ifindex) { struct auto_sock_iface* ifa = as->ifaces; while (ifa) { if (ifa->netif_index == ifindex) return ifa; ifa = ifa->next; } ifa = u_calloc(1, sizeof(*ifa)); if (!ifa) return NULL; ifa->netif_index = ifindex; ifa->next = as->ifaces; as->ifaces = ifa; return ifa; } /* helper: remove ifa record if all 4 sockets gone */ 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); } static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) { if (ifindex == 0) return; 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; 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); 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->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 && cur_type != v4_need_type) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP type changed: %s type %d→%d", ifname, cur_type, v4_need_type); ncd_remove_socket_links(as->instance, sock); etcp_socket_remove(sock); create_iface_udp_socket(as, ifindex, ifname, AF_INET, v4_need_type); ifa->v4_type = v4_need_type; { struct ETCP_SOCKET* s = as->instance->etcp_sockets; while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET) { ifa->v4_udp = s; break; } s = s->next; } } changed = 1; } else if (has_v4 && sock) { struct sockaddr_storage old4 = sock->interface_addr; socket_monitor_update_if_addr(sock); if (memcmp(&old4, &sock->interface_addr, sizeof(old4)) != 0) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v4 UDP interface_addr changed: %s -> %s", sockaddr_storage_to_str(&old4).str, sockaddr_storage_to_str(&sock->interface_addr).str); etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED); 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); 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->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 && cur_type != v6_need_type) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP type changed: %s type %d→%d", ifname, cur_type, v6_need_type); ncd_remove_socket_links(as->instance, sock); etcp_socket_remove(sock); create_iface_udp_socket(as, ifindex, ifname, AF_INET6, v6_need_type); ifa->v6_type = v6_need_type; { struct ETCP_SOCKET* s = as->instance->etcp_sockets; while (s) { if (s->netif_index == ifindex && s->local_addr.ss_family == AF_INET6) { ifa->v6_udp = s; break; } s = s->next; } } changed = 1; } else if (has_v6 && sock) { struct sockaddr_storage old6 = sock->interface_addr; socket_monitor_update_if_addr(sock); if (memcmp(&old6, &sock->interface_addr, sizeof(old6)) != 0) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] v6 UDP interface_addr changed: %s -> %s", sockaddr_storage_to_str(&old6).str, sockaddr_storage_to_str(&sock->interface_addr).str); etcp_socket_cbk_fire(sock, ETCP_SOCKET_EVENT_ADDR_CHANGED); 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); if (ifa) { struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET, v4_need_type); 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); { char buf[IFNAMSIZ]; const char* nm = if_indextoname(ifindex, buf) ? buf : ifname; uint16_t p = load_port_from_db(as, nm, AF_INET, AS_PROTO_TCP); struct TCP_SOCKET** tsp = &as->instance->tcp_sockets; while (*tsp) { uint16_t tp = 0; const struct sockaddr_storage* a = (*tsp)->interface_addr.ss_family ? &(*tsp)->interface_addr : &(*tsp)->local_addr; if (a->ss_family == AF_INET) tp = ntohs(((struct sockaddr_in*)a)->sin_port); if (tp == p && p > 0) { struct TCP_SOCKET* rm = *tsp; *tsp = rm->next; u_free(rm); break; } tsp = &(*tsp)->next; } } stcp_server_list_remove(as->instance, srv); stcp_link_server_destroy(srv); 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); if (ifa) { struct stcp_server* tsrv = create_iface_tcp_socket(as, ifindex, ifname, AF_INET6, v6_need_type); 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); { char buf[IFNAMSIZ]; const char* nm = if_indextoname(ifindex, buf) ? buf : ifname; uint16_t p = load_port_from_db(as, nm, AF_INET6, AS_PROTO_TCP); struct TCP_SOCKET** tsp = &as->instance->tcp_sockets; while (*tsp) { uint16_t tp = 0; const struct sockaddr_storage* a = (*tsp)->interface_addr.ss_family ? &(*tsp)->interface_addr : &(*tsp)->local_addr; if (a->ss_family == AF_INET6) tp = ntohs(((struct sockaddr_in6*)a)->sin6_port); if (tp == p && p > 0) { struct TCP_SOCKET* rm = *tsp; *tsp = rm->next; u_free(rm); break; } tsp = &(*tsp)->next; } } stcp_server_list_remove(as->instance, srv); stcp_link_server_destroy(srv); delete_port_from_db(as, ifname, AF_INET6, AS_PROTO_TCP); ifa->v6_tcp = NULL; changed = 1; } } reconcile_prune_ifa(as, ifa, ifindex, ifname); if (changed) auto_socket_post_sockets_changed(as); } /* ═══════════ 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_INFO(DEBUG_CATEGORY_AS, "[as] netlink socket: %s — relying on JNI/scan_all for updates", strerror(errno)); return 0; } 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_INFO(DEBUG_CATEGORY_AS, "[as] netlink bind: %s — relying on JNI/scan_all for updates", strerror(errno)); close(as->nl_sock); as->nl_sock = -1; return 0; } 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); as->nl_sock = -1; return 0; } 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 (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; } 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); 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; uasync_post(as->instance->ua, auto_socket_scan_all_trampoline, as); }