1196 lines
52 KiB
C
1196 lines
52 KiB
C
// utun_instance.c - Root instance implementation
|
|
#include "utun_instance.h"
|
|
#include "stream/stream.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 "transport_layer/stun_nat.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_attachment.h"
|
|
#include "chat/chat_sync.h"
|
|
#include "chat/chat_headless_control.h"
|
|
#include "dm/dm_core.h"
|
|
#include "dm/dm_mailbox.h"
|
|
#include "dm/dm_media.h"
|
|
#include "member_files/member_files.h"
|
|
#include "call/call.h"
|
|
#include "call/call_headless.h"
|
|
#include "broadcast.h"
|
|
#include "radio/radio.h"
|
|
#include "radio/radio_headless.h"
|
|
#include "stcp_server.h"
|
|
#include "transport_layer/reality_relay.h"
|
|
#include "control_server.h"
|
|
#include "transport_layer/node_conn_direct.h"
|
|
#include "transport_layer/socket_monitor.h"
|
|
#include "transport_layer/auto_socket.h"
|
|
#ifdef UTUN_HAVE_STANDBY
|
|
#include "standby.h"
|
|
#endif
|
|
|
|
#include "../lib/u_async.h"
|
|
#include "../lib/debug_config.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include "../lib/platform_compat.h"
|
|
#include "../lib/mem.h"
|
|
#include "../lib/ll_queue.h"
|
|
#include <openssl/sha.h>
|
|
|
|
|
|
|
|
// Forward declarations
|
|
static uint32_t get_dest_ip(const uint8_t *packet, size_t len);
|
|
static int mkdir_recursive(const char* path);
|
|
static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config);
|
|
|
|
// 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_requested = instance->client_activity;
|
|
|
|
// 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");
|
|
if (etcp_padding_start(&instance->padding, ua, instance->name) < 0) return -1;
|
|
|
|
// 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);
|
|
return -1;
|
|
}
|
|
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");
|
|
if (!instance->ack_pool || !instance->data_pool || !instance->pkt_pool) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core memory pool initialization failed"); return -1;
|
|
}
|
|
|
|
// 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");
|
|
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;
|
|
}
|
|
if (config->global.db_path[0]) mkdir_recursive(config->global.db_path);
|
|
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");
|
|
return -1;
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
|
|
|
|
}
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group initialization disabled, skipping BGP module");
|
|
instance->topo_groups = NULL;
|
|
}
|
|
|
|
if (socket_monitor_init(instance) < 0 || auto_socket_init(instance) < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Socket monitoring/automatic socket initialization failed"); return -1;
|
|
}
|
|
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_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load firewall rules"); return -1;
|
|
} 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;
|
|
}
|
|
|
|
/* NAT управляет пробами через router; binding создаётся после его инициализации. */
|
|
if (instance->topo_groups) {
|
|
instance->nat_det = nat_detection_create(instance);
|
|
if (!instance->nat_det) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT detection creation failed"); return -1; }
|
|
}
|
|
|
|
// Bind CONN_MGR handler AFTER etcp_router_init (init затирает router_bindings)
|
|
if (g_topo_group_enabled && instance->topo_groups)
|
|
etcp_router_bind(instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler);
|
|
|
|
// 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) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core creation failed; releasing all initialized resources");
|
|
instance_destroy(instance, 1);
|
|
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) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core creation failed; releasing resources and preserving caller config");
|
|
instance_destroy(instance, 0);
|
|
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
|
|
static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config) {
|
|
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;
|
|
stun_nat_destroy(instance);
|
|
stun_nat_forget_ports(instance);
|
|
/* Оба детектора остановить до callbacks закрываемых сервисов и транспорта. */
|
|
if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; }
|
|
etcp_padding_stop(&instance->padding);
|
|
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");
|
|
|
|
chat_service_stop(instance);
|
|
utun_service_stop(instance);
|
|
|
|
/* 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);
|
|
radio_destroy_instance(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] F done — BGP + broadcast + radio");
|
|
|
|
/* Phase F2: NAT transport — отвязываем и закрываем nat_tun ДО разборки
|
|
сокетов/соединений, чтобы nat_transport_etcp_recv_cb не был вызван с
|
|
уже освобождённым буфером пакета (use-after-free при teardown). */
|
|
if (instance->nat_tr.initialized) nat_transport_destroy(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] F2 done — NAT transport");
|
|
|
|
/* 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 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);
|
|
reality_owner_cleanup(&instance->reality_owner); /* активные reality-релеи откладывают u_free через call_soon */
|
|
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 ? instance->connections->head : NULL;
|
|
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->user_data); 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 */
|
|
if (instance->tun) {
|
|
if (instance->tun_routes) tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, &instance->tun_routes);
|
|
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;
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] M done — TUN/proxy/routing");
|
|
|
|
/* Phase N: etcp_router + nat_det + firewall */
|
|
etcp_router_destroy(instance);
|
|
/* etcp_router_destroy откладывает queue_entry_free(rconn->ll) через uasync_call_soon
|
|
(router_close_finalize) — выкачать их, пока ua ещё жив. */
|
|
uasync_drain_immediate(instance->ua);
|
|
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 && owns_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);
|
|
}
|
|
|
|
// Cleanup activity callback chain
|
|
while (instance->activity_cbks) {
|
|
struct utun_activity_cbk_entry* r = instance->activity_cbks;
|
|
instance->activity_cbks = r->next; u_free(r);
|
|
}
|
|
|
|
// Cleanup peer-sleep callback chain
|
|
while (instance->peer_sleep_cbks) {
|
|
struct peer_sleep_cbk_entry* r = instance->peer_sleep_cbks;
|
|
instance->peer_sleep_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");
|
|
}
|
|
|
|
void utun_instance_destroy(struct UTUN_INSTANCE* instance) {
|
|
instance_destroy(instance, 1);
|
|
}
|
|
|
|
// 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) {
|
|
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_core_start(struct UTUN_INSTANCE *instance) {
|
|
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "core start: NULL instance"); return -1; }
|
|
if (instance->core_started) return 0;
|
|
|
|
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;
|
|
}
|
|
|
|
// Generic replication is a core service; channel instances belong to chat.
|
|
if (!instance->db_sync && db_sync_init(instance) < 0) return -1;
|
|
|
|
// Initialize connections
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "core start: initializing transports 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) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control server allocation failed"); return -1;
|
|
}
|
|
if (control_server_init(instance->control_srv, instance->ua, instance,
|
|
&instance->config->global.control_sock, 8) != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Configured control socket initialization failed");
|
|
control_server_shutdown(instance->control_srv);
|
|
u_free(instance->control_srv); instance->control_srv = NULL;
|
|
return -1;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Control server initialized successfully");
|
|
}
|
|
|
|
// 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);
|
|
radio_init_instance(instance);
|
|
|
|
instance->core_started = 1;
|
|
if (stun_nat_init(instance) < 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "STUN diagnostics could not start; core remains operational");
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "core started");
|
|
return 0;
|
|
}
|
|
|
|
/* Service lifecycle runs on the owning uasync thread, outside module callbacks. */
|
|
int utun_service_start(struct UTUN_INSTANCE* instance) {
|
|
if (!instance || !instance->core_started || !instance->topo_groups) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "UTUN start requires a running core"); return -1;
|
|
}
|
|
if (instance->utun_started) return 0;
|
|
struct utun_config* config = instance->config;
|
|
struct UASYNC* ua = instance->ua;
|
|
struct TOPO_GROUP* group = topo_groups_create_group(instance->topo_groups, TOPO_GROUP_UTUN, TOPO_GROUP_TYPE_UTUN, NULL);
|
|
if (!group) return -1;
|
|
instance->conn_mgr = group->conn_mgr;
|
|
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");
|
|
goto fail;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname);
|
|
if (config->route_subnets) {
|
|
if (tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets, &instance->tun_routes) < 0)
|
|
goto fail;
|
|
}
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup");
|
|
instance->tun = NULL;
|
|
}
|
|
|
|
if (routing_bind(instance) < 0) goto fail;
|
|
if (instance->tun) routing_set_tun(instance);
|
|
if (config->global.nat_enabled && nat_transport_init(instance) < 0) goto fail;
|
|
if (init_client_connections(instance, config->clients) < 0) goto fail;
|
|
instance->utun_started = 1;
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "UTUN service started"); return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "UTUN service start failed; rolling back");
|
|
utun_service_stop(instance); return -1;
|
|
}
|
|
|
|
void utun_service_stop(struct UTUN_INSTANCE* instance) {
|
|
if (!instance) return;
|
|
instance->utun_started = 0;
|
|
if (instance->nat_tr.initialized) nat_transport_destroy(instance);
|
|
if (instance->router_bindings.callbacks[ETCP_RT_ID_DATA]) etcp_router_unbind(instance, ETCP_RT_ID_DATA);
|
|
if (instance->tun) {
|
|
if (instance->tun_routes) tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, &instance->tun_routes);
|
|
tun_close(instance->tun); instance->tun = NULL;
|
|
}
|
|
while (instance->config_conn_handles) {
|
|
struct CONFIG_CONN_HANDLE* owner = instance->config_conn_handles;
|
|
instance->config_conn_handles = owner->next;
|
|
topo_group_peer_close(owner->request); u_free(owner);
|
|
}
|
|
if (topo_groups_get_default(instance->topo_groups)) topo_groups_remove_group(instance->topo_groups, TOPO_GROUP_UTUN);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "UTUN service stopped");
|
|
}
|
|
|
|
int chat_service_start(struct UTUN_INSTANCE* instance) {
|
|
if (!instance || !instance->core_started || !instance->topo_sqlite_db) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat start requires a running core with SQLite"); return -1;
|
|
}
|
|
if (instance->chat_started) return 0;
|
|
char db_file[512];
|
|
snprintf(db_file, sizeof(db_file), "%s/chats.db", instance->config->global.db_path);
|
|
if (db_sync_enable(instance) < 0) return -1;
|
|
topo_group_invite_init(instance);
|
|
if (chat_core_init(instance, db_file) < 0 || chat_sync_init(instance) < 0 || stream_service_init(instance) < 0) goto fail;
|
|
instance->media_async = media_async_create();
|
|
if (!instance->media_async || media_delivery_init(instance) < 0) goto fail;
|
|
if (chat_attachment_init(instance) < 0) goto fail;
|
|
if (member_files_init(instance) < 0 || dm_mailbox_init(instance) < 0 || dm_core_init(instance) < 0 ||
|
|
dm_media_init(instance) < 0 || call_init(instance) < 0) goto fail;
|
|
chat_media_backfill(instance);
|
|
chat_media_startup_backfill_init(instance);
|
|
// 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)");
|
|
}
|
|
|
|
// Initialize headless call audio socket if configured in [chatserver]
|
|
if (instance->config->global.call_audio_bind[0] != '\0') {
|
|
char ip_str[64] = "127.0.0.1"; int port = 10001;
|
|
const char* colon = strrchr(instance->config->global.call_audio_bind, ':');
|
|
if (colon) {
|
|
size_t ip_len = (size_t)(colon - instance->config->global.call_audio_bind);
|
|
if (ip_len < sizeof(ip_str)) { memcpy(ip_str, instance->config->global.call_audio_bind, ip_len); ip_str[ip_len] = '\0'; }
|
|
port = atoi(colon + 1);
|
|
if (port <= 0 || port > 65535) port = 10001;
|
|
}
|
|
if (call_headless_init(instance->ua, instance, ip_str, port) != 0)
|
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to init headless call audio socket (non-fatal)");
|
|
}
|
|
|
|
// Initialize headless radio audio socket if configured in [chatserver]
|
|
if (instance->config->global.radio_audio_bind[0] != '\0') {
|
|
char ip_str[64] = "127.0.0.1"; int port = 10002;
|
|
const char* colon = strrchr(instance->config->global.radio_audio_bind, ':');
|
|
if (colon) {
|
|
size_t ip_len = (size_t)(colon - instance->config->global.radio_audio_bind);
|
|
if (ip_len < sizeof(ip_str)) { memcpy(ip_str, instance->config->global.radio_audio_bind, ip_len); ip_str[ip_len] = '\0'; }
|
|
port = atoi(colon + 1);
|
|
if (port <= 0 || port > 65535) port = 10002;
|
|
}
|
|
if (radio_headless_init(instance->ua, instance, ip_str, port) != 0)
|
|
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to init headless radio audio socket (non-fatal)");
|
|
}
|
|
|
|
|
|
instance->chat_started = 1;
|
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "chat service started"); return 0;
|
|
fail:
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat service start failed; rolling back");
|
|
chat_service_stop(instance); return -1;
|
|
}
|
|
|
|
void chat_service_stop(struct UTUN_INSTANCE* instance) {
|
|
if (!instance) return;
|
|
instance->chat_started = 0;
|
|
chat_headless_control_destroy(instance);
|
|
call_headless_destroy(instance); radio_headless_destroy(instance);
|
|
stream_service_destroy(instance);
|
|
chat_media_startup_backfill_destroy(instance);
|
|
dm_media_quiesce(instance);
|
|
chat_attachment_quiesce(instance);
|
|
member_files_quiesce(instance);
|
|
media_async_destroy(instance->media_async); instance->media_async = NULL;
|
|
member_files_destroy(instance);
|
|
media_delivery_destroy(instance);
|
|
call_destroy(instance); dm_media_destroy(instance); dm_core_destroy(instance); dm_mailbox_destroy(instance);
|
|
etcp_unbind(instance, ETCP_RT_ID_GROUP_INVITE);
|
|
topo_group_invite_cancel_all(instance);
|
|
chat_sync_destroy(instance);
|
|
struct ll_entry* entry = instance->topo_groups ? instance->topo_groups->group_list->head : NULL;
|
|
while (entry) {
|
|
struct TOPO_GROUP* group = (struct TOPO_GROUP*)entry; entry = entry->next;
|
|
if (group->group_type != TOPO_GROUP_TYPE_CHAT) continue;
|
|
struct DB_SYNC_INSTANCE* sync = db_sync_instance_find(instance, group->group_id);
|
|
if (sync) db_sync_instance_remove(sync);
|
|
topo_groups_remove_group(instance->topo_groups, group->group_id);
|
|
}
|
|
chat_core_destroy(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "chat service stopped");
|
|
}
|
|
|
|
/* Application convenience entry point; embedders may start each service explicitly. */
|
|
int utun_instance_init(struct UTUN_INSTANCE* instance) {
|
|
if (utun_core_start(instance) < 0) return -1;
|
|
if (instance->topo_groups && utun_service_start(instance) < 0) return -1;
|
|
if (instance->config->global.chatserver_enabled && chat_service_start(instance) < 0) return -1;
|
|
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;
|
|
}
|
|
int client_result = instance->utun_started ? init_client_connections(instance, new_config->clients) : 0;
|
|
if (client_result < 0)
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reload: failed to replace client handles; previous connections retained");
|
|
// 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
|
|
stun_nat_destroy(instance);
|
|
if (instance->config) free_config(instance->config);
|
|
instance->config = new_config;
|
|
if (instance->core_started && stun_nat_init(instance) < 0) DEBUG_WARN(DEBUG_CATEGORY_NAT, "STUN restart failed after config reload");
|
|
if (client_result < 0) DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reload completed with client errors; previous client handles retained");
|
|
else 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;
|
|
}
|
|
}
|
|
|
|
void utun_add_peer_sleep_cbk(struct UTUN_INSTANCE* instance, peer_sleep_cbk_fn fn, void* arg) {
|
|
if (!instance || !fn) return;
|
|
struct peer_sleep_cbk_entry* e = u_malloc(sizeof(*e));
|
|
if (!e) return;
|
|
e->fn = fn; e->arg = arg; e->next = instance->peer_sleep_cbks;
|
|
instance->peer_sleep_cbks = e;
|
|
}
|
|
|
|
void utun_remove_peer_sleep_cbk(struct UTUN_INSTANCE* instance, peer_sleep_cbk_fn fn, void* arg) {
|
|
if (!instance || !fn) return;
|
|
struct peer_sleep_cbk_entry** p = &instance->peer_sleep_cbks;
|
|
while (*p) {
|
|
if ((*p)->fn == fn && (*p)->arg == arg) {
|
|
struct peer_sleep_cbk_entry* rm = *p;
|
|
*p = rm->next; u_free(rm); return;
|
|
}
|
|
p = &(*p)->next;
|
|
}
|
|
}
|
|
|
|
void utun_fire_peer_sleep_cbk(struct UTUN_INSTANCE* instance, uint64_t peer_node_id, int sleeping) {
|
|
struct peer_sleep_cbk_entry* e = instance ? instance->peer_sleep_cbks : NULL;
|
|
while (e) {
|
|
struct peer_sleep_cbk_entry* n = e->next;
|
|
e->fn(instance, peer_node_id, sleeping, e->arg);
|
|
e = n;
|
|
}
|
|
}
|
|
|
|
/* Явная активность UI/звонка не истекает по таймеру: владелец сообщает завершение сам. */
|
|
int utun_set_client_activity(struct UTUN_INSTANCE* instance, int active) {
|
|
if (!instance) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "client_activity: missing instance active=%d", active);
|
|
return -1;
|
|
}
|
|
if (instance->client_type == CLIENT_TYPE_SERVER) return 0;
|
|
int requested = active ? CLIENT_ACTIVITY_ACTIVE : CLIENT_ACTIVITY_STANDBY;
|
|
int held = stream_service_needs_activity(instance);
|
|
int state = held ? CLIENT_ACTIVITY_ACTIVE : requested;
|
|
int previous = instance->client_activity;
|
|
#ifdef UTUN_HAVE_STANDBY
|
|
int previous_requested = instance->client_activity_requested;
|
|
#endif
|
|
instance->client_activity_requested = requested;
|
|
instance->client_activity = state;
|
|
#ifdef UTUN_HAVE_STANDBY
|
|
if (instance->client_type == CLIENT_TYPE_MOBILE && standby_set_enabled(instance, state != CLIENT_ACTIVITY_ACTIVE) < 0) {
|
|
instance->client_activity = previous;
|
|
instance->client_activity_requested = previous_requested;
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "client_activity: standby transition failed node=%016llx requested=%d previous=%d",
|
|
(unsigned long long)instance->node_id, state, previous);
|
|
return -1;
|
|
}
|
|
#endif
|
|
if (previous == state) return 0;
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "client_activity effective=%s requested=%s stream_hold=%d node=%016llx type=%u",
|
|
state ? "ACTIVE" : "STANDBY", requested ? "ACTIVE" : "STANDBY", held,
|
|
(unsigned long long)instance->node_id, instance->client_type);
|
|
utun_fire_activity_cbk(instance, state);
|
|
return 0;
|
|
}
|