@ -27,8 +27,17 @@
# include "etcp_api.h"
# include "node_conn_direct.h"
# include "socket_monitor.h"
# include "stcp_link.h"
/* struct stcp_server layout from stcp_link.c — opaque in public header,
need this to safely remove from inst - > stcp_servers linked list */
struct stcp_link_server_local {
struct stcp_link_server_local * next ;
struct stcp_server * srv ;
} ;
# 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"
@ -37,6 +46,7 @@
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "../../lib/sqlite3.h"
# if !defined(_WIN32)
# include <unistd.h>
@ -59,6 +69,9 @@
# 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 ) ;
@ -94,10 +107,12 @@ static int v6_classify(const uint8_t addr[16]) {
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 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 {
@ -116,12 +131,70 @@ struct AUTO_SOCKET {
} ;
/* ─── forward declarations ─── */
static int create_iface_socket ( struct AUTO_SOCKET * as , uint32_t ifindex , const char * ifname , int family , uint8_t type ) ;
static int create_iface_udp_socket ( struct AUTO_SOCKET * as , uint32_t ifindex , const char * ifname , int family , uint8_t type ) ;
static int 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 ) {
@ -130,11 +203,11 @@ static uint16_t random_port(void) {
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 ) {
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 " , ifname , family = = AF_INET6 ? " v6 " : " v4 " ) ;
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 ;
@ -150,8 +223,11 @@ static int create_iface_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const c
sin6 - > sin6_addr = in6addr_any ;
}
for ( int attempt = 0 ; attempt < 10 ; attempt + + ) {
uint16_t port = random_port ( ) ;
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
@ -159,25 +235,109 @@ static int create_iface_socket(struct AUTO_SOCKET* as, uint32_t ifindex, const c
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 ( 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 ) ;
DEBUG_INFO ( DEBUG_CATEGORY_AS , " [as] socket created: name=%s fd=%d if=%s(%u) type=%d addr=%s " ,
{ 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 socket %s " , links , es - > name ) ;
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 socket after 10 attempts if=%s fam=%d " , ifname , family ) ;
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 int 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 - 1 ;
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 ) ;
{ struct stcp_link_server_local * * pp = ( struct stcp_link_server_local * * ) & inst - > stcp_servers ; while ( * pp ) { if ( ( struct stcp_server * ) * pp = = tsrv ) { * pp = ( * pp ) - > next ; break ; } pp = & ( * pp ) - > next ; } }
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 0 ;
}
DEBUG_ERROR ( DEBUG_CATEGORY_AS , " [as] failed to create TCP socket after %d attempts if=%s fam=%d " , max_att , ifname , family ) ;
return - 1 ;
}
@ -200,17 +360,23 @@ int auto_socket_add_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex,
int added = 0 ;
if ( has_v4 ) {
if ( create_iface_socket ( as , ifindex , ifname , AF_INET , v4_type ) = = 0 ) {
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_sock = s ; break ; } s = s - > next ; }
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 ; }
}
if ( create_iface_tcp_socket ( as , ifindex , ifname , AF_INET , v4_type ) = = 0 ) {
added + + ;
}
}
if ( has_v6 ) {
if ( create_iface_socket ( as , ifindex , ifname , AF_INET6 , v6_type ) = = 0 ) {
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_sock = s ; break ; } s = s - > next ; }
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 ; }
}
if ( create_iface_tcp_socket ( as , ifindex , ifname , AF_INET6 , v6_type ) = = 0 ) {
added + + ;
}
}
@ -218,6 +384,7 @@ int auto_socket_add_interface(struct UTUN_INSTANCE* inst, uint32_t ifindex,
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 ) ;
}
@ -237,19 +404,54 @@ static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex) {
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 ) ;
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_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 ) ;
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_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_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 ;
@ -260,6 +462,47 @@ static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex) {
# if !defined(_WIN32)
# include <stdio.h>
/* /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 ) {
@ -268,6 +511,8 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname,
* 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 ; }
@ -288,6 +533,7 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname,
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 ;
@ -349,8 +595,32 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) {
/* ═══════════ 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 ;
@ -359,84 +629,130 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char
struct auto_sock_iface * ifa = as - > ifaces ;
while ( ifa & & ifa - > netif_index ! = ifindex ) ifa = ifa - > next ;
/* ── IPv4 ── */
/* ── IPv4 UDP ── */
{
struct ETCP_SOCKET * sock = ifa ? ifa - > v4_sock : NULL ;
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 ) {
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 ; } }
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 removed: %s ifidx=%u " , ifname , ifindex ) ;
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_sock = NULL ;
ifa - > v4_udp = NULL ; changed = 1 ;
} 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 ) ;
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_socket ( as , ifindex , ifname , AF_INET , v4_need_type ) ;
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_sock = s ; break ; } s = s - > next ; } }
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 interface_addr changed: %s -> %s " ,
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 ── */
/* ── IPv6 UDP ── */
{
struct ETCP_SOCKET * sock = ifa ? ifa - > v6_sock : NULL ;
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 ) {
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 ; } }
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 removed: %s ifidx=%u " , ifname , ifindex ) ;
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_sock = NULL ;
ifa - > v6_udp = NULL ; changed = 1 ;
} 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 ) ;
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_socket ( as , ifindex , ifname , AF_INET6 , v6_need_type ) ;
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_sock = s ; break ; } s = s - > next ; } }
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 interface_addr changed: %s -> %s " ,
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 ;
}
}
}
/* ── удалить запись если оба 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 ) ;
/* ── 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 ) { create_iface_tcp_socket ( as , ifindex , ifname , AF_INET , v4_need_type ) ; ifa - > v4_tcp = NULL ; /* find below */ 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_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 ) { create_iface_tcp_socket ( as , ifindex , ifname , AF_INET6 , v6_need_type ) ; ifa - > v6_tcp = NULL ; 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_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 ═══════════ */
@ -492,18 +808,22 @@ static void auto_socket_netlink_cb(int fd, void* arg) {
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 ; }
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_ERROR ( DEBUG_CATEGORY_AS , " [as] netlink bind: %s " , strerror ( errno ) ) ; close ( as - > nl_sock ) ; return - 1 ;
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 ) ; return - 1 ; }
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 ;
}
@ -596,6 +916,18 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) {
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 ) ;
@ -611,15 +943,24 @@ void auto_socket_destroy(struct UTUN_INSTANCE* inst) {
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 ) ;
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 ) ;
}