// utun_instance.c - Root instance implementation #include "utun_instance.h" #include "etcp_router.h" #include "config_parser.h" #include "config_updater.h" #include "tun_if.h" #include "tun_route.h" #include "topo_node.h" #include "route_lib.h" #include "routing.h" #include "topo_group.h" #include "topo_group_invite.h" #include "nat_detection.h" #include "etcp_connections.h" #include "etcp.h" #include "conn_mgr.h" #include "chat/db_sync.h" #include "chat/chat_core.h" #include "chat/chat_sync.h" #include "chat/chat_headless_control.h" #include "dm/dm_core.h" #include "dm/dm_mailbox.h" #include "broadcast.h" #include "stcp_server.h" #include "control_server.h" #include "transport_layer/node_conn_direct.h" #include "transport_layer/socket_monitor.h" #include "transport_layer/auto_socket.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include #include #include #include #ifndef _WIN32 #include #endif #include #include #include "../lib/platform_compat.h" #include "../lib/mem.h" #include "../lib/ll_queue.h" #include // Forward declarations static uint32_t get_dest_ip(const uint8_t *packet, size_t len); // Global instance for signal handlers static struct UTUN_INSTANCE *g_instance = NULL; // Global flag to control TUN initialization (disabled by default) static int g_tun_init_enabled = 0; // Function to control TUN initialization void utun_instance_set_tun_init_enabled(int enabled) { g_tun_init_enabled = enabled ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization %s", enabled ? "enabled" : "disabled"); } // Global flag to control topo_group initialization (enabled by default) static int g_topo_group_enabled = 1; void utun_instance_set_topo_group_enabled(int enabled) { g_topo_group_enabled = enabled ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group initialization %s", enabled ? "enabled" : "disabled"); } static int local_sockaddr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b) { if (!a || !b || a->ss_family != b->ss_family) return 0; if (a->ss_family == AF_INET) { const struct sockaddr_in *ia = (const struct sockaddr_in *)a; const struct sockaddr_in *ib = (const struct sockaddr_in *)b; return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port; } if (a->ss_family == AF_INET6) { const struct sockaddr_in6 *ia = (const struct sockaddr_in6 *)a; const struct sockaddr_in6 *ib = (const struct sockaddr_in6 *)b; return memcmp(&ia->sin6_addr, &ib->sin6_addr, 16) == 0 && ia->sin6_port == ib->sin6_port; } return 0; } // Common initialization function (called by both create functions) // Returns 0 on success, -1 on error (instance is NOT freed on error - caller must handle) static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* ua, struct utun_config* config) { // Initialize basic fields instance->running = 0; instance->ua = ua; instance->config = config; instance->ntp.reachable = 1; /* оптимистично: до первого NTP-цикла считаем, что интернет есть */ // Set name from config if (config->global.name[0] != '\0') { strncpy(instance->name, config->global.name, sizeof(instance->name) - 1); instance->name[sizeof(instance->name) - 1] = '\0'; } else { instance->name[0] = '\0'; } // Set node_id from config instance->node_id = config->global.my_node_id; // Set client type instance->client_type = config->global.client_type; instance->keepalive_interval = (uint16_t)(config->global.keepalive_interval > 0 ? config->global.keepalive_interval : 200); instance->client_activity = (instance->client_type == CLIENT_TYPE_MOBILE) ? CLIENT_ACTIVITY_STANDBY : CLIENT_ACTIVITY_ACTIVE; instance->client_activity_timer = NULL; // Set my keys if (sc_init_local_keys(&instance->my_keys, config->global.my_public_key_hex, config->global.my_private_key_hex) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to initialize local keys"); return -1; } /* Always verify node_id matches pubkey, fix if mismatched */ { uint8_t pub_bin[SC_PUBKEY_SIZE]; sc_compute_public_key_from_private(instance->my_keys.private_key, pub_bin); uint64_t derived = sc_derive_node_id_from_pubkey(pub_bin); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "node_id check: config=%016llx derived=%016llx match=%d", (unsigned long long)instance->node_id, (unsigned long long)derived, (instance->node_id == derived)); if (!instance->node_id || instance->node_id != derived) { if (instance->node_id) DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "node_id mismatch: config=%016llx derived=%016llx — fixing", (unsigned long long)instance->node_id, (unsigned long long)derived); instance->node_id = derived; config->global.my_node_id = derived; } } if (sc_derive_ed25519_pubkey(instance->my_keys.private_key, instance->my_ed25519_pubkey) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "Failed to derive Ed25519 pubkey"); return -1; } { uint8_t ed_priv_hash[SHA512_DIGEST_LENGTH]; SHA512(instance->my_keys.private_key, SC_PRIVKEY_SIZE, ed_priv_hash); memcpy(instance->my_ed25519_privkey, ed_priv_hash, SC_PRIVKEY_SIZE); } DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "Ed25519 pubkey derived successfully"); // Initialize networks queue (indexed by 56-bit network id) instance->networks = queue_new(ua, 16, offsetof(struct NETWORK_ENTRY, id), 7, "networks"); if (!instance->networks) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to create networks queue"); return -1; } // Initialize connection queue instance->connections = queue_new(ua, 256, 0, 8, "connections"); if (!instance->connections) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create connections queue"); return -1; } instance->tcp_connections = queue_new(ua, sizeof(struct tcp_conn_entry), sizeof(uint64_t), 8, "tcpconn"); if (!instance->tcp_connections) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create tcp_connections queue"); return -1; } struct CFG_NETWORK *net = config->networks; while (net) { struct ll_entry *entry = queue_entry_new(sizeof(struct NETWORK_ENTRY)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate network entry for %s", net->name); net = net->next; continue; } struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)entry->data; ne->id = net->id & 0x00FFFFFFFFFFFFFFULL; for (int i = 0; i < SC_PUBKEY_SIZE; i++) { unsigned int b; sscanf(net->pubkey_hex + i * 2, "%2x", &b); ne->pubkey[i] = (uint8_t)b; } for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { unsigned int b; sscanf(net->signing_key_hex + i * 2, "%2x", &b); ne->signing_key[i] = (uint8_t)b; } strncpy(ne->name, net->name, sizeof(ne->name) - 1); ne->name[sizeof(ne->name) - 1] = '\0'; queue_data_put_with_index(instance->networks, entry); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Network added: [%s] id=%012llx", ne->name, (unsigned long long)ne->id); net = net->next; } // Create memory pools instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool"); instance->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool"); instance->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool"); // Create routing module if (routing_create(instance) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to create routing module"); return -1; } DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created"); if (g_tun_init_enabled && config->global.tun_enabled) { instance->tun = tun_init(ua, config); if (!instance->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to initialize TUN device"); return -1; } DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname); if (config->route_subnets) { int added = tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets); DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added); instance->route_subnets = config->route_subnets; } } else { DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup"); instance->tun = NULL; } int socket_result = init_sockets(instance); instance->socket_init_status = socket_result; if (socket_result != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Socket initialization failed: result=%d", socket_result); return -1; } instance->nat_det = nat_detection_create(instance); if (!instance->nat_det) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection creation failed"); } if (g_topo_group_enabled) { instance->topo_groups = topo_groups_init(instance); if (!instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module"); } else { DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); etcp_router_bind(instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); topo_group_invite_init(instance); struct TOPO_GROUP* g = topo_groups_get_default(instance->topo_groups); if (instance->rt && g && g->local_node) { topo_group_update_my_nodeinfo(instance, g); } } } else { DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group initialization disabled, skipping BGP module"); instance->topo_groups = NULL; } socket_monitor_init(instance); auto_socket_init(instance); if (g_topo_group_enabled && instance->topo_groups) topo_node_update_my_addresses(instance); // conn_mgr initialized inside topo_group_create (via topo_groups_init) // Initialize firewall fw_init(&instance->fw); if (fw_load_rules(&instance->fw, &config->global) != 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Failed to load firewall rules"); } else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall initialized: %d rules, bypass_all=%d", instance->fw.count, instance->fw.bypass_all); } // etcp_router — сервисная маршрутизация (после BGP, до TCP proxy/NAT/DATA) if (etcp_router_init(instance) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize etcp_router"); return -1; } // Bind DATA handler via etcp_router (after etcp_router_init) if (routing_bind(instance) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to bind DATA via etcp_router"); return -1; } // TCP proxy server (exit node, optional) — must be before tcp_proxy_client so // tcp_proxy_client_create can overwrite the handler if it has remote mappings #ifndef _WIN32 if (tcp_proxy_server_init(instance) != 0) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize tcp_proxy_server (non-fatal)"); } #endif // TCP proxy client (from [tcp_proxy] config section) #ifndef _WIN32 if (config->global.tcp_proxy_client_enabled || config->global.tcp_proxy_client_socks_enabled || config->global.tcp_proxy_client_http_proxy_enabled) { const char* tun_name = config->global.tcp_proxy_client_tun_name; const char* tun_ip = config->global.tcp_proxy_client_tun_ip; int mtu = config->global.tcp_proxy_client_mtu; instance->tcp_proxy_client = tcp_proxy_client_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1, config->global.tcp_proxy_client_mappings, config->global.tcp_proxy_client_mapping_count, config->global.tcp_proxy_client_via_node_id, config->global.tcp_proxy_client_socks_enabled, config->global.tcp_proxy_client_socks_addr, config->global.tcp_proxy_client_http_proxy_enabled, config->global.tcp_proxy_client_http_proxy_addr); if (instance->tcp_proxy_client) { DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d socks=%s http=%s mappings=%d", tun_name, tun_ip, mtu, config->global.tcp_proxy_client_socks_enabled ? config->global.tcp_proxy_client_socks_addr : "off", config->global.tcp_proxy_client_http_proxy_enabled ? config->global.tcp_proxy_client_http_proxy_addr : "off", config->global.tcp_proxy_client_mapping_count); } else { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy client"); return -1; } } else { instance->tcp_proxy_client = NULL; } #endif /* _WIN32 */ return 0; } // Create and initialize root instance from config file struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config_file) { // Ensure keys and node_id exist in config (generates them if missing) if (config_ensure_keys_and_node_id(config_file) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to ensure keys and node_id in config: %s", config_file); return NULL; } // Load configuration struct utun_config* config = parse_config(config_file); if (!config) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load config from %s", config_file); return NULL; } // Open log file only if not using global debug system output if (config->global.log_file[0]) { debug_set_output_file(config->global.log_file); } // Apply debug level from config if (config->global.debug_level[0]) { debug_apply_global_level(config->global.debug_level); } // Apply per-category debug levels from [debug] section 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) { free_config(config); return NULL; } instance->etcp_connect_timeout_tb = 20000; // Initialize using common function if (instance_init_common(instance, ua, config) != 0) { // Cleanup on error if (instance->config) free_config(instance->config); u_free(instance); return NULL; } // Log instance info if (instance->name[0] != '\0') { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "uTun instance '%s' created, node_id=0x%llx", instance->name, (unsigned long long)instance->node_id); } else { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "uTun instance created, node_id=0x%llx", (unsigned long long)instance->node_id); } return instance; } // Create instance from existing config structure (config ownership transfers to instance) struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct utun_config* config) { if (!config) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "NULL config"); 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) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate UTUN_INSTANCE"); return NULL; } instance->etcp_connect_timeout_tb = 20000; // Initialize using common function (config ownership transferred to instance) if (instance_init_common(instance, ua, config) != 0) { // Cleanup on error - caller still owns config since we failed u_free(instance); return NULL; } return instance; } // Create instance from config text string (writes to temp file, parses, cleans up) struct UTUN_INSTANCE* utun_instance_create_from_str(struct UASYNC* ua, const char* config_text) { if (!config_text) return NULL; char tmp_path[] = "/tmp/utun_cfg_XXXXXX"; int fd = mkstemp(tmp_path); if (fd < 0) return NULL; size_t len = strlen(config_text); if (write(fd, config_text, len) != (ssize_t)len) { close(fd); unlink(tmp_path); return NULL; } close(fd); struct UTUN_INSTANCE* inst = utun_instance_create(ua, tmp_path); unlink(tmp_path); return inst; } // Destroy instance and cleanup resources void utun_instance_destroy(struct UTUN_INSTANCE *instance) { if (!instance) return; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Starting cleanup for instance %p node=0x%016llx ua=%p", instance, (unsigned long long)instance->node_id, instance->ua); /* Phase A: diagnose */ utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP"); if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_BEFORE"); instance->running = 0; if (instance->client_activity_timer) { uasync_cancel_timeout(instance->ua, instance->client_activity_timer); instance->client_activity_timer = NULL; } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] A done — diagnose complete"); /* Phase B: NTP */ ntp_time_destroy(instance); ntp_node_time_destroy(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] B done — NTP"); /* Phase C: control server */ if (instance->control_srv) { control_server_shutdown(instance->control_srv); u_free(instance->control_srv); instance->control_srv = NULL; } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] C done — control_srv"); /* Phase D: config conn handles */ { struct CONFIG_CONN_HANDLE* ch = instance->config_conn_handles; int ch_count = 0; while (ch) { struct CONFIG_CONN_HANDLE* next = ch->next; node_conn_direct_close(ch->handle); u_free(ch); ch_count++; ch = next; } if (ch_count) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] D: closed %d config handles", ch_count); } instance->config_conn_handles = NULL; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] D done — config_conn"); /* Phase E: socket_monitor + auto_socket */ socket_monitor_destroy(instance); auto_socket_destroy(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] E done — socket_monitor+auto_socket"); /* Phase F: BGP */ if (instance->topo_groups) { etcp_router_unbind(instance, ETCP_RT_ID_CONN_MGR); etcp_unbind(instance, ETCP_RT_ID_GROUP_INVITE); topo_group_invite_cancel_all(instance); topo_groups_destroy(instance); } broadcast_destroy_instance(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] F done — BGP + broadcast"); /* Phase G: ETCP sockets */ { struct ETCP_SOCKET* sock = instance->etcp_sockets; int sc = 0; while (sock) { struct ETCP_SOCKET* next = sock->next; if (sock->instance && sock->fd > 0) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] G: removing socket %p fd=%d inst=%p", sock, sock->fd, sock->instance); else if (sock->fd > 0) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DESTROY] G: socket %p fd=%d has NULL instance! skip remove", sock, sock->fd); sock = next; continue; } etcp_socket_remove(sock); sc++; sock = next; } if (sc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] G: removed %d ETCP sockets", sc); } instance->etcp_sockets = NULL; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] G done — ETCP sockets"); /* Phase H: chat */ chat_headless_control_destroy(instance); chat_media_startup_backfill_destroy(instance); dm_mailbox_destroy(instance); dm_core_destroy(instance); chat_sync_destroy(instance); chat_core_destroy(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] H done — chat"); /* Phase I: db_sync */ db_sync_destroy(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] I done — db_sync"); /* Phase J: TCP connections + sockets — MUST be before ETCP connections */ stcp_server_list_destroy_all(instance); if (instance->tcp_connections) { struct ll_entry* entry = instance->tcp_connections->head; int tc = 0; while (entry) { struct ll_entry* next = entry->next; struct tcp_conn_entry* te = (struct tcp_conn_entry*)entry->data; if (te && te->link) { stcp_link_close(te->link); tc++; } entry = next; } if (tc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] J: closed %d TCP links", tc); } queue_free(instance->tcp_connections); instance->tcp_connections = NULL; /* TCP sockets already cleaned in Phase G via etcp_sockets */ uasync_drain_immediate(instance->ua); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] J done — TCP"); /* Phase K: ETCP connections */ { struct ll_entry* entry = instance->connections->head; int cc = 0; while (entry) { struct ll_entry* next = entry->next; struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; if (ce && ce->conn) { etcp_connection_close(ce->conn); cc++; } entry = next; } if (cc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] K: closed %d ETCP connections", cc); } queue_free(instance->connections); instance->connections = NULL; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] K done — connections"); uasync_drain_immediate(instance->ua); /* Phase L: pings */ { struct PING_CONTEXT* p = instance->pending_pings; int pc = 0; while (p) { struct PING_CONTEXT* next = p->next; if (p->timeout_timer) uasync_cancel_timeout(instance->ua, p->timeout_timer); u_free(p); pc++; p = next; } instance->pending_pings = NULL; if (pc) DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] L: freed %d pending pings", pc); } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] L done — deferred+pings"); /* Phase M: TUN + proxy + routing + NAT */ if (instance->tun) { if (instance->tun->ifindex && instance->route_subnets) tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, instance->route_subnets); tun_close(instance->tun); instance->tun = NULL; } #ifndef _WIN32 if (instance->tcp_proxy_client) { tcp_proxy_client_destroy(instance->tcp_proxy_client); instance->tcp_proxy_client = NULL; } tcp_proxy_server_destroy(instance); #endif routing_destroy(instance); if (instance->md.initialized) media_delivery_destroy(instance); media_async_destroy(instance->media_async); instance->media_async = NULL; if (instance->nat_tr.initialized) nat_transport_destroy(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] M done — TUN/proxy/routing/NAT"); /* Phase N: etcp_router + nat_det + firewall */ etcp_router_destroy(instance); if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; } fw_free(&instance->fw); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] N done — router/nat_det/fw"); /* Phase O: networks queue */ if (instance->networks) { struct ll_entry *entry; while ((entry = queue_data_get(instance->networks)) != NULL) queue_entry_free(entry); queue_free(instance->networks); instance->networks = NULL; } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] O done — networks"); /* Phase P: config + pools */ if (instance->config) { free_config(instance->config); instance->config = NULL; } // Cleanup packet pool (ensure no leak if stop wasn't called) if (instance->pkt_pool) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Destroying packet pool"); memory_pool_destroy(instance->pkt_pool); instance->pkt_pool = NULL; } // Cleanup packet pool (ensure no leak if stop wasn't called) if (instance->ack_pool) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Destroying ack pool"); memory_pool_destroy(instance->ack_pool); instance->ack_pool = NULL; } // Cleanup data pool if (instance->data_pool) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Destroying data pool"); memory_pool_destroy(instance->data_pool); instance->data_pool = NULL; } if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_AFTER"); // Note: uasync is NOT destroyed here - caller must destroy it separately // This allows sharing uasync between multiple instances instance->ua = NULL; // Clear global instance if (g_instance == instance) { g_instance = NULL; } // Free the instance memory DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Freeing instance memory"); // Cleanup nodeinfo callback chain while (instance->nodeinfo_cbks) { struct nodeinfo_cbk_entry* r = instance->nodeinfo_cbks; instance->nodeinfo_cbks = r->next; u_free(r); } // Shared SQLite DB закрываем в самом конце: routing/chat/media/db_sync // используют его во время всего teardown (см. topo_groups_destroy). if (instance->topo_sqlite_db) { sqlite3_close(instance->topo_sqlite_db); instance->topo_sqlite_db = NULL; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] shared sqlite closed"); } u_free(instance); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Instance destroyed completely"); } // Stop instance void utun_instance_stop(struct UTUN_INSTANCE *instance) { if (!instance) return; instance->running = 0; // Wakeup main loop using built-in uasync wakeup if (instance->ua) { memory_pool_destroy(instance->pkt_pool); uasync_wakeup(instance->ua); } } static int mkdir_recursive(const char *path) { char tmp[512]; int len = snprintf(tmp, sizeof(tmp), "%s", path); if (len <= 0 || (size_t)len >= sizeof(tmp)) return -1; for (char *p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; utun_mkdir(tmp, 0755); *p = '/'; } } return utun_mkdir(tmp, 0755); } int utun_instance_init(struct UTUN_INSTANCE *instance) { if (!instance) return -1; struct utun_config* config = instance->config; if (config) { instance->client_type = config->global.client_type; uint16_t ka = (uint16_t)config->global.keepalive_interval; if (ka == 0) ka = 200; if (ka < 100) { DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "keepalive_interval %u < 100ms, clamped to 100ms", (unsigned)ka); ka = 100; } instance->keepalive_interval = ka; } // db_sync — распределённая таблица с репликацией db_sync_init(instance); // chat — сообщения, каналы, P2P-синхронизация if (instance->config->global.chatserver_enabled) { if (!instance->config->global.db_path[0]) snprintf(instance->config->global.db_path, sizeof(instance->config->global.db_path), "/var/lib/utun"); mkdir_recursive(instance->config->global.db_path); char db_file[512]; snprintf(db_file, sizeof(db_file), "%s/chats.db", instance->config->global.db_path); if (chat_core_init(instance, db_file) == 0) { chat_sync_init(instance); chat_media_backfill(instance); chat_media_startup_backfill_init(instance); /* dm_mailbox до dm_core: dm_core_init ставит deliver-cb в mailbox */ dm_mailbox_init(instance); dm_core_init(instance); } } // Set TUN interface in routing module if (instance->tun) { routing_set_tun(instance); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "TUN interface registered in routing module"); } // Initialize NAT (after routing_set_tun, before connections) if (instance->config->global.nat_enabled) { int nat_ret = nat_transport_init(instance); if (nat_ret != 0) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT transport init failed (non-fatal)"); } } // Initialize media delivery if (media_delivery_init(instance) != 0) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "media_delivery init failed (non-fatal)"); } // Initialize media async engine instance->media_async = media_async_create(); if (!instance->media_async) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "media_async create failed"); } // Note: TUN socket is already registered in tun_init() // Initialize connections DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "utun_instance_init() calling init_connections() for instance %p", instance); int conn_result = init_connections(instance); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_connections() returned: %d", conn_result); if (conn_result < 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize connections, error=%d", conn_result); return -1; } DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Connections initialized successfully, count=%d", queue_entry_count(instance->connections)); // Initialize control server if configured if (instance->config->global.control_sock.ss_family != 0) { instance->control_srv = (struct control_server*)u_calloc(1, sizeof(struct control_server)); if (instance->control_srv) { if (control_server_init(instance->control_srv, instance->ua, instance, &instance->config->global.control_sock, 8) != 0) { DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Failed to initialize control server, continuing without monitoring"); u_free(instance->control_srv); instance->control_srv = NULL; } else { DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Control server initialized successfully"); } } } // Initialize headless chat control if configured in [chatserver] if (instance->config->global.headless_control_bind[0] != '\0') { char ip_str[64] = "127.0.0.1"; int port = 9999; const char* colon = strrchr(instance->config->global.headless_control_bind, ':'); if (colon) { size_t ip_len = (size_t)(colon - instance->config->global.headless_control_bind); if (ip_len < sizeof(ip_str)) { memcpy(ip_str, instance->config->global.headless_control_bind, ip_len); ip_str[ip_len] = '\0'; } port = atoi(colon + 1); if (port <= 0 || port > 65535) port = 9999; } if (chat_headless_control_init(instance->ua, instance, ip_str, port) != 0) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to init headless chat control (non-fatal)"); } // Start the main loop instance->running = 1; // Initialize NTP time sync (non-fatal if fails) if (ntp_time_init(instance) != 0) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP time sync init failed (non-fatal)"); } // Initialize NTP node time exchange (non-fatal) if (ntp_node_time_init(instance) != 0) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP node time exchange init failed (non-fatal)"); } broadcast_init_instance(instance); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Connections initialized successfully"); return 0; } // Диагностическая функция для анализа утечек void utun_instance_diagnose_leaks(struct UTUN_INSTANCE *instance, const char *phase) { if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DIAGNOSE] NULL instance for phase: %s", phase); return; } struct { int etcp_sockets_count; int etcp_connections_count; int etcp_links_count; } report = {0}; /* Check etcp_sockets linked list for corruption */ struct ETCP_SOCKET *sock = instance->etcp_sockets; int sock_visited = 0; while (sock) { /* Validate pointer looks like heap (not node_id or stack garbage) */ if ((uintptr_t)sock < 0x1000) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DIAGNOSE] CORRUPT socket ptr=%p (low addr) at index %d", sock, sock_visited); break; } if (sock_visited++ > 10000) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DIAGNOSE] socket list cycle or too long (%d)", sock_visited); break; } report.etcp_sockets_count++; if (sock->links_queue) report.etcp_links_count += queue_entry_count(sock->links_queue); sock = sock->next; } /* Check connections linked list for corruption */ if (instance->connections) { struct ll_entry* entry = instance->connections->head; int conn_visited = 0; while (entry) { if ((uintptr_t)entry < 0x1000) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DIAGNOSE] CORRUPT conn entry ptr=%p at index %d", entry, conn_visited); break; } if (conn_visited++ > 10000) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[DIAGNOSE] conn list cycle or too long (%d)", conn_visited); break; } struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; if (ce && ce->conn) { report.etcp_connections_count++; struct ETCP_LINK *link = ce->conn->links; while (link) { report.etcp_links_count++; link = link->next; } } entry = entry->next; } } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[LEAK DIAGNOSIS] Phase: %s", phase); DEBUG_INFO(DEBUG_CATEGORY_SYS, "Instance: %p, Node ID: %llu, UA: %p, Running: %d", instance, (unsigned long long)instance->node_id, instance->ua, instance->running); DEBUG_INFO(DEBUG_CATEGORY_SYS, "STRUCTURE COUNTS - ETCP Sockets: %d, ETCP Connections: %d, ETCP Links: %d", report.etcp_sockets_count, report.etcp_connections_count, report.etcp_links_count); DEBUG_INFO(DEBUG_CATEGORY_SYS, "RESOURCE STATUS - Memory Pool: %s, TUN: %s, TUN FD: %d", instance->pkt_pool ? "ALLOCATED" : "NULL", instance->tun ? instance->tun->ifname : "NULL", instance->tun ? instance->tun->fd : -1); if (instance->pkt_pool) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "POTENTIAL LEAK: Memory Pool not freed"); } if (instance->tun) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "POTENTIAL LEAK: TUN interface not closed"); } if (report.etcp_sockets_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "POTENTIAL LEAK: %d ETCP sockets still allocated", report.etcp_sockets_count); } if (report.etcp_connections_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "POTENTIAL LEAK: %d ETCP connections still allocated", report.etcp_connections_count); } if (report.etcp_links_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "POTENTIAL LEAK: %d ETCP links still allocated", report.etcp_links_count); } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[LEAK DIAGNOSIS] Recommendations: pkt_pool=%d, tun=%d, sockets=%d, connections=%d", instance->pkt_pool ? 1 : 0, instance->tun ? 1 : 0, report.etcp_sockets_count, report.etcp_connections_count); } /* Variant B: selective reload. Compares settings for [server:], [client:], link=. Unchanged untouched (no close, no timers, no queues). Changed/deleted: target only that object. New: add. Routing/fw always. BGP self-restarts. */ struct UTUN_INSTANCE *utun_instance_reload(struct UTUN_INSTANCE *instance, struct UASYNC *ua, const char *config_file) { if (!instance || !ua || !config_file) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "invalid arguments"); return NULL; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "SIGHUP selective reload from %s", config_file); debug_reopen_log(); struct utun_config *new_config = parse_config(config_file); if (!new_config) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to parse new config"); return instance; } // Reset + apply debug categories from [debug] section (always, for partial and full paths). // Mirrors utun_instance_create exactly. Reset ensures removed categories fall back to global. for (int i = 0; i < DEBUG_CATEGORY_COUNT; i++) { g_debug_config.category_levels[i] = DEBUG_LEVEL_NONE; } if (new_config->global.debug_level[0]) { debug_apply_global_level(new_config->global.debug_level); } for (int i = 0; i < new_config->global.debug_levels.count; i++) { debug_apply_category_config( new_config->global.debug_levels.category[i], new_config->global.debug_levels.level[i] ); } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Applied [debug] section (%d categories)", new_config->global.debug_levels.count); int needs_full = 0; if (strcmp(instance->config->global.my_public_key_hex, new_config->global.my_public_key_hex) != 0 || strcmp(instance->config->global.my_private_key_hex, new_config->global.my_private_key_hex) != 0 || instance->config->global.my_node_id != new_config->global.my_node_id || strcmp(instance->config->global.tun_ifname, new_config->global.tun_ifname) != 0) { needs_full = 1; } if (needs_full) { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Incompatible global - full reload"); utun_instance_destroy(instance); struct UTUN_INSTANCE *new_instance = utun_instance_create(ua, config_file); if (!new_instance || utun_instance_init(new_instance) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Full reload failed"); if (new_instance) utun_instance_destroy(new_instance); free_config(new_config); return NULL; } new_instance->running = 1; free_config(new_config); DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Full reload completed"); return new_instance; } // Selective Variant B (settings compare, only affected updated) // Servers [server:] for (struct CFG_SERVER *ns = new_config->servers; ns; ns = ns->next) { int matched = 0; for (struct ETCP_SOCKET *os = instance->etcp_sockets; os; os = os->next) { if (strcmp(ns->name, os->name) == 0 && local_sockaddr_equal(&ns->ip, &os->local_addr)) { matched = 1; if (ns->mtu != os->mtu) { os->mtu = ns->mtu; DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Updated socket settings for %s", ns->name); } break; } } if (!matched) { etcp_socket_add(instance, ns); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Added new socket %s", ns->name); } } struct ETCP_SOCKET *os = instance->etcp_sockets; while (os) { struct ETCP_SOCKET *next = os->next; int found = 0; for (struct CFG_SERVER *ns = new_config->servers; ns; ns = ns->next) { if (strcmp(ns->name, os->name) == 0) { found = 1; break; } } if (!found) { etcp_socket_remove(os); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Removed deleted socket %s", os->name); } os = next; } // Clients [client:] — close old config handles, rebuild from new config struct CONFIG_CONN_HANDLE* ch = instance->config_conn_handles; while (ch) { struct CONFIG_CONN_HANDLE* next = ch->next; node_conn_direct_close(ch->handle); u_free(ch); ch = next; } instance->config_conn_handles = NULL; for (struct CFG_CLIENT *nc = new_config->clients; nc; nc = nc->next) { if (strlen(nc->peer_public_key_hex) == 0) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "client %s no peer pubkey", nc->name); continue; } uint8_t pubkey_bin[SC_PUBKEY_SIZE]; if (sc_hex_to_binary(nc->peer_public_key_hex, pubkey_bin, SC_PUBKEY_SIZE) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "invalid pubkey hex client %s", nc->name); continue; } uint64_t node_id = sc_derive_node_id_from_pubkey(pubkey_bin); if (nc->reality_enabled) { struct NODE_CONN_DIRECT* rhandle = etcp_config_client_reality(instance, nc, pubkey_bin, node_id); if (rhandle) { ch = u_calloc(1, sizeof(*ch)); if (ch) { ch->node_id = node_id; strncpy(ch->name, nc->name, MAX_CONN_NAME_LEN - 1); ch->handle = rhandle; ch->next = instance->config_conn_handles; instance->config_conn_handles = ch; } } continue; } struct NODE_CONN_DIRECT* handle = NULL; struct ETCP_CONN* conn = NULL; for (struct CFG_CLIENT_LINK *nl = nc->links; nl; nl = nl->next) { struct ETCP_SOCKET* sock = NULL; for (struct ETCP_SOCKET* s = instance->etcp_sockets; s; s = s->next) if (nl->local_srv && strcmp(nl->local_srv->name, s->name) == 0) { sock = s; break; } if (!sock) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no socket for server '%s' client %s", nl->local_srv ? nl->local_srv->name : "?", nc->name); continue; } if (!handle) { struct TOPO_ADDR4 v4_addr; struct TOPO_ADDR6 v6_addr; struct TOPO_NODE ni_tmp; memset(&ni_tmp, 0, sizeof(ni_tmp)); ni_tmp.node_id = node_id; ni_tmp.node_name = nc->name; memcpy(ni_tmp.public_key, pubkey_bin, SC_PUBKEY_SIZE); if (nl->remote_addr.ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)&nl->remote_addr; v4_addr = (struct TOPO_ADDR4){0}; memcpy(v4_addr.addr, &sin->sin_addr.s_addr, 4); v4_addr.port = ntohs(sin->sin_port); v4_addr.type = TOPO_ADDR_INTERFACE; v4_addr.protocol = TOPO_PROTO_UDP; ni_tmp.v4_addrs = &v4_addr; } else if (nl->remote_addr.ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&nl->remote_addr; v6_addr = (struct TOPO_ADDR6){0}; memcpy(v6_addr.addr, &sin6->sin6_addr, 16); v6_addr.port = ntohs(sin6->sin6_port); v6_addr.type = TOPO_ADDR_INTERFACE; v6_addr.protocol = TOPO_PROTO_UDP; ni_tmp.v6_addrs = &v6_addr; } int rc = node_conn_direct_open_node(instance, node_id, NULL, NULL, &handle, &ni_tmp, sock); if (rc == NCD_ERR || !handle) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ncd reload open failed client %s", nc->name); break; } conn = node_conn_direct_get_conn(handle); DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "reload: client %s ncd %s node=0x%016llx", nc->name, rc == NCD_NEW ? "NEW" : "REUSED", (unsigned long long)node_id); } else { etcp_link_new(conn, sock, &nl->remote_addr, 0); } } if (handle) { ch = u_calloc(1, sizeof(*ch)); if (ch) { ch->node_id = node_id; strncpy(ch->name, nc->name, MAX_CONN_NAME_LEN - 1); ch->handle = handle; ch->next = instance->config_conn_handles; instance->config_conn_handles = ch; } } } // Reload networks: clear and repopulate if (instance->networks) { struct ll_entry *entry; while ((entry = queue_data_get(instance->networks)) != NULL) { queue_entry_free(entry); } struct CFG_NETWORK *net = new_config->networks; while (net) { struct ll_entry *new_entry = queue_entry_new(sizeof(struct NETWORK_ENTRY)); if (!new_entry) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate network entry for reload: %s", net->name); net = net->next; continue; } struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)new_entry->data; ne->id = net->id & 0x00FFFFFFFFFFFFFFULL; for (int i = 0; i < SC_PUBKEY_SIZE; i++) { unsigned int b; sscanf(net->pubkey_hex + i * 2, "%2x", &b); ne->pubkey[i] = (uint8_t)b; } for (int i = 0; i < SC_PRIVKEY_SIZE; i++) { unsigned int b; sscanf(net->signing_key_hex + i * 2, "%2x", &b); ne->signing_key[i] = (uint8_t)b; } strncpy(ne->name, net->name, sizeof(ne->name) - 1); ne->name[sizeof(ne->name) - 1] = '\0'; queue_data_put_with_index(instance->networks, new_entry); net = net->next; } DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks reloaded"); } fw_free(&instance->fw); fw_init(&instance->fw); fw_load_rules(&instance->fw, &new_config->global); // routing_destroy(instance); // commented per request - BGP self-restarts on conn events // routing_create(instance); // commented per request if (instance->config) free_config(instance->config); instance->config = new_config; DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Selective partial reload completed (only changed updated, unchanged untouched, [debug] applied)"); return instance; } void utun_add_activity_cbk(struct UTUN_INSTANCE* instance, utun_activity_cbk_fn fn, void* arg) { if (!instance || !fn) return; struct utun_activity_cbk_entry* e = u_malloc(sizeof(struct utun_activity_cbk_entry)); if (!e) return; e->fn = fn; e->arg = arg; e->next = instance->activity_cbks; instance->activity_cbks = e; } void utun_remove_activity_cbk(struct UTUN_INSTANCE* instance, utun_activity_cbk_fn fn, void* arg) { if (!instance || !fn) return; struct utun_activity_cbk_entry** p = &instance->activity_cbks; while (*p) { if ((*p)->fn == fn && (*p)->arg == arg) { struct utun_activity_cbk_entry* rm = *p; *p = rm->next; u_free(rm); return; } p = &(*p)->next; } } static void utun_fire_activity_cbk(struct UTUN_INSTANCE* instance, int active) { struct utun_activity_cbk_entry* e = instance->activity_cbks; while (e) { struct utun_activity_cbk_entry* n = e->next; e->fn(instance, active, e->arg); e = n; } } static void client_activity_timeout_cb(void* arg) { struct UTUN_INSTANCE* instance = (struct UTUN_INSTANCE*)arg; DEBUG_INFO(DEBUG_CATEGORY_BGP, "client_activity_timer fired: node=%016llx type=%u -> standby", (unsigned long long)instance->node_id, instance->client_type); instance->client_activity_timer = NULL; if (instance->client_type == CLIENT_TYPE_SERVER) return; instance->client_activity = CLIENT_ACTIVITY_STANDBY; utun_fire_activity_cbk(instance, CLIENT_ACTIVITY_STANDBY); } void utun_set_client_activity(struct UTUN_INSTANCE* instance, int active) { if (!instance) return; if (instance->client_type == CLIENT_TYPE_SERVER) return; if (instance->client_activity_timer) { uasync_cancel_timeout(instance->ua, instance->client_activity_timer); instance->client_activity_timer = NULL; } if (active) { instance->client_activity = CLIENT_ACTIVITY_ACTIVE; instance->client_activity_timer = uasync_set_timeout(instance->ua, 600000, instance, client_activity_timeout_cb, "activity_timeout"); DEBUG_INFO(DEBUG_CATEGORY_BGP, "client_activity set ACTIVE, timer restarted 60s node=%016llx type=%u", (unsigned long long)instance->node_id, instance->client_type); } else { instance->client_activity = CLIENT_ACTIVITY_STANDBY; DEBUG_INFO(DEBUG_CATEGORY_BGP, "client_activity set STANDBY node=%016llx type=%u", (unsigned long long)instance->node_id, instance->client_type); } utun_fire_activity_cbk(instance, active); }