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