diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 8537a7d1..8f670cf6 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -88,7 +88,7 @@ void si_register(struct DB_SYNC_INSTANCE* si, const char* ch_id) { /* ─── жизненный цикл ─── */ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { - if (!inst || !db_path) return -1; + if (!inst || !db_path) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid args inst=%p db_path=%p", CC_ID, (void*)inst, (void*)db_path); return -1; } memset(&g_cc, 0, sizeof(g_cc)); snprintf(g_cc.db_path, sizeof(g_cc.db_path), "%s", db_path); diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index d568c65e..475540ed 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -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 #include #include +#include "../../lib/sqlite3.h" #if !defined(_WIN32) #include @@ -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 + +/* /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); +} diff --git a/src/utun_instance.c b/src/utun_instance.c index 155a47fe..7581a1c5 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -368,6 +368,11 @@ struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct return NULL; } + if (config->global.debug_level[0]) + debug_apply_global_level(config->global.debug_level); + for (int i = 0; i < config->global.debug_levels.count; i++) + debug_apply_category_config(config->global.debug_levels.category[i], + config->global.debug_levels.level[i]); // Allocate instance struct UTUN_INSTANCE *instance = u_calloc(1, sizeof(struct UTUN_INSTANCE)); if (!instance) {