/* * instance_lite.c — lightweight uTun instance lifecycle for Android * * Kotlin sends INI config text → parse_config_from_buf → * utun_instance_create_from_config → sqlite3_open → utun_instance_init → * pthread with uasync_poll loop. * * Mirrors UtunNode::runLoop() from tools/chatgui/transport/utun_node.cpp:221-372. */ #define _GNU_SOURCE 1 /* dladdr (Linux); no-op on Android/bionic */ #include "instance_lite.h" #include "utun_instance.h" #include "config_parser.h" #include "config_updater.h" #include "secure_channel.h" #include "etcp_router.h" #include "topo_node_sqlite.h" #include "chat/chat_core.h" #include "chat/chat_sync.h" #include "chat/chat_event.h" #include "chat/db_sync.h" #include "u_async.h" #include "sqlite3.h" #include "mem.h" #include "debug_config.h" #include "transport_layer/etcp_connections.h" #include "transport_layer/etcp.h" #include "transport_layer/auto_socket.h" #include "../jni_bridge/android_udp_log.h" #include "../jni_bridge/android_jni_bridge.h" #include "standby.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef __ANDROID__ #include #define IL_LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, "utun-inst", fmt, ##__VA_ARGS__) #define IL_LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "utun-inst", fmt, ##__VA_ARGS__) #define IL_LOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "utun-inst", fmt, ##__VA_ARGS__) #else #define IL_LOGV(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) #define IL_LOGI(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) #define IL_LOGE(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) #endif /* ── Static state ── * * Thread model: one Kotlin thread, one C worker thread. * Each variable has exactly ONE writer — no data races by construction. * * Kotlin-writes (signals/commands to C): * g_stop, g_do_restart, g_restart_config, g_event_handler, g_db_path * C-writes (state to Kotlin): * g_ua, g_inst, g_running, g_thread_exited, g_thread_running */ static struct UASYNC* g_ua = NULL; /* C-write, Kotlin-read via __atomic */ static struct UTUN_INSTANCE* g_inst = NULL; /* C-write, Kotlin-read via __atomic */ static pthread_t g_thread; static volatile int g_stop = 0; /* Kotlin-write, C-read (poll loop) */ static volatile int g_do_restart = 0; /* Kotlin-write, C atomic-xchg→0 */ static volatile char* g_restart_config = NULL; /* Kotlin-write, C atomic-xchg→NULL (ownership transfer) */ static volatile int g_running = 0; /* C-write, Kotlin-read */ static volatile int g_thread_running = 0; /* C-write + Kotlin CAS-guard for start() */ static volatile int g_thread_exited = 0; /* C-write (under mutex), Kotlin-read */ static volatile int g_generation = 0; /* Kotlin-write, C-read */ static char g_db_path[512]; /* Kotlin-write (start), C-read (init) */ static instance_lite_event_fn g_event_handler = NULL; /* Kotlin-write, C-read+call */ static char g_generated_pub[65]; static char g_generated_priv[65]; static pthread_mutex_t g_stop_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_stop_cond = PTHREAD_COND_INITIALIZER; /* ── Generate X25519 key pair, hex-encode to out buffers ── */ static int generate_x25519_keys(char pub_hex[65], char priv_hex[65]) { EVP_PKEY* pkey = NULL; EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); if (!ctx) return -1; if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_generate(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); return -1; } EVP_PKEY_CTX_free(ctx); size_t pkLen = 32, skLen = 32; uint8_t pk[32], sk[32]; EVP_PKEY_get_raw_public_key(pkey, pk, &pkLen); EVP_PKEY_get_raw_private_key(pkey, sk, &skLen); EVP_PKEY_free(pkey); for (int i = 0; i < 32; i++) snprintf(pub_hex + i * 2, 3, "%02x", pk[i]); for (int i = 0; i < 32; i++) snprintf(priv_hex + i * 2, 3, "%02x", sk[i]); pub_hex[64] = '\0'; priv_hex[64] = '\0'; return 0; } static void save_keys_to_file(const char* pub, const char* priv) { if (!g_db_path[0]) { IL_LOGE("save_keys_to_file: g_db_path is empty, keys not saved to disk"); return; } char fname[512]; snprintf(fname, sizeof(fname), "%s/keys.conf", g_db_path); FILE* f = fopen(fname, "w"); if (!f) { IL_LOGE("cannot write %s: %s", fname, strerror(errno)); return; } fchmod(fileno(f), 0600); fprintf(f, "public_key=%s\nprivate_key=%s\n", pub, priv); fclose(f); IL_LOGI("keys saved to %s", fname); } /* ── Ensure valid keys in config, generate if missing ── */ static void ensure_keys(struct utun_config* config) { if (strlen(config->global.my_private_key_hex) != 64 || strlen(config->global.my_public_key_hex) != 64) { IL_LOGI("config keys missing or invalid, generating new X25519 keypair"); if (generate_x25519_keys(g_generated_pub, g_generated_priv) == 0) { memcpy(config->global.my_public_key_hex, g_generated_pub, 65); memcpy(config->global.my_private_key_hex, g_generated_priv, 65); IL_LOGI("new keys generated pub=%s", g_generated_pub); save_keys_to_file(g_generated_pub, g_generated_priv); } else { IL_LOGE("key generation failed"); } } } /* ── Helper: extract single config value by key ── */ static const char* cfg_get(const char* text, const char* key) { size_t klen = strlen(key); const char* p = text; while (*p) { if (strncmp(p, key, klen) == 0 && p[klen] == '=') { return p + klen + 1; } while (*p && *p != '\n') p++; if (*p == '\n') p++; } return NULL; } static void cfg_get_val(const char* text, const char* key, char* out, size_t out_sz) { const char* v = cfg_get(text, key); if (!v) { out[0] = '\0'; return; } size_t i = 0; while (*v && *v != '\n' && *v != '\r' && i < out_sz - 1) out[i++] = *v++; out[i] = '\0'; } /* ── Chat event forward → Kotlin callback ── */ static void chat_event_forward(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) { (void)inst; if (g_event_handler) { g_event_handler(type, data, len); return; } IL_LOGE("[EVT_DIAG] chat_event_forward: g_event_handler is NULL! type=%d len=%d", type, len); } static void nodeinfo_event_cb(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, void* arg) { (void)group; (void)arg; if (!nq || !g_event_handler) return; uint16_t best_rtt = topo_get_chain_rtt(nq); uint8_t hc = 0; topo_node_best_hop_list(nq, &hc, NULL); uint16_t bgp_nodes = (hc > 0) ? (uint16_t)(hc - 1) : 0; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "nodeinfo_cb: nid=%016llx pres=%02x up=%02x rtt=%u(0x%04X) hc=%u bgp_nodes=%u paths=%zu", (unsigned long long)nq->node_id, nq->conn_presence, nq->conn_up, best_rtt, best_rtt, hc, bgp_nodes, nq->paths ? queue_entry_count(nq->paths) : 0); uint8_t data[16]; memcpy(data, &nq->node_id, 8); data[8] = nq->conn_presence; data[9] = nq->conn_up; memcpy(data + 10, &best_rtt, 2); memcpy(data + 12, &best_rtt, 2); memcpy(data + 14, &bgp_nodes, 2); g_event_handler(CHAT_EVT_NODEINFO_UPDATED, data, sizeof(data)); } static void fire_local_sockets_event(void) { if (!g_event_handler) return; char* json = utun_bridge_get_local_sockets_json(); if (!json) return; g_event_handler(CHAT_EVT_LOCAL_SOCKETS, (const uint8_t*)json, (int)strlen(json)); u_free(json); } static void on_link_status_changed(struct ETCP_CONN* conn, struct ETCP_LINK* link, int old_state, int old_status, void* arg) { (void)old_state; (void)old_status; (void)arg; if (!g_event_handler || !conn || conn->peer_node_id == 0) return; uint8_t data[13]; memcpy(data, &conn->peer_node_id, 8); data[8] = link->local_link_id; data[9] = link->link_state; data[10] = link->link_status; data[11] = conn->links_up; data[12] = conn->initialized ? 1 : 0; g_event_handler(CHAT_EVT_LINK_STATUS_CHANGED, data, sizeof(data)); fire_local_sockets_event(); } /* ── Crash handler ── */ static volatile int g_signal_caught = 0; static volatile int g_signal_num = 0; struct crash_bt_state { void** frames; int count; int max; }; static _Unwind_Reason_Code crash_bt_cb(struct _Unwind_Context* ctx, void* arg) { struct crash_bt_state* st = (struct crash_bt_state*)arg; if (st->count < st->max) { void* pc = (void*)_Unwind_GetIP(ctx); if (pc) st->frames[st->count++] = pc; return _URC_NO_REASON; } return _URC_END_OF_STACK; } static void crash_signal_handler(int sig, siginfo_t* info, void* ctx) { (void)ctx; g_signal_caught = 1; g_signal_num = sig; DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "CRASH signal=%d code=%d addr=%p thread still alive", sig, info->si_code, info->si_addr); void* frames[64]; struct crash_bt_state st = { frames, 0, 64 }; _Unwind_Backtrace(crash_bt_cb, &st); IL_LOGE("CRASH backtrace %d frames:", st.count); for (int i = 0; i < st.count; i++) { Dl_info dli; if (dladdr(frames[i], &dli) && dli.dli_sname) { IL_LOGE(" #%02d %p %s+%p (%s)", i, frames[i], dli.dli_sname, (void*)((char*)frames[i] - (char*)dli.dli_saddr), dli.dli_fname ? dli.dli_fname : "?"); } else { IL_LOGE(" #%02d %p (no symbol)", i, frames[i]); } } signal(sig, SIG_DFL); raise(sig); } static void install_crash_handlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_signal_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGBUS, &sa, NULL); } /* ── Thread function ── */ static void* instance_thread(void* arg) { char* config_text = (char*)arg; int my_gen = g_generation; struct UASYNC* my_ua = NULL; /* поток-локальная копия — для cleanup stale-потока */ struct UTUN_INSTANCE* my_inst = NULL; struct utun_config* config = parse_config_from_buf(config_text, strlen(config_text), "android"); u_free(config_text); if (!config) { IL_LOGE("parse_config_from_buf failed"); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; } config->global.client_type = CLIENT_TYPE_MOBILE; /* Android — всегда мобильный узел (keepalive standby/normal) */ install_crash_handlers(); g_ua = uasync_create(); my_ua = g_ua; if (!g_ua) { IL_LOGE("uasync_create failed"); free_config(config); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; } standby_init(g_ua); if (config->global.log_udp_ip[0] && config->global.log_udp_port > 0) { udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port); } IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler); ensure_keys(config); g_inst = utun_instance_create_from_config(g_ua, config); my_inst = g_inst; if (!g_inst) { IL_LOGE("utun_instance_create_from_config failed"); standby_deinit(); uasync_destroy(g_ua, 0); g_ua = NULL; u_report_unfreed_blocks(); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; } chat_event_set_handler(g_inst, chat_event_forward); IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id); /* Core owns SQLite and transport; this frontend starts only the chat service. */ if (utun_core_start(g_inst) != 0 || chat_service_start(g_inst) != 0) { IL_LOGE("core/chat start failed"); utun_instance_destroy(g_inst); g_inst = NULL; standby_deinit(); uasync_destroy(g_ua, 0); g_ua = NULL; u_report_unfreed_blocks(); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; } /* sockets are now created — sync my addresses to DB */ chat_core_sync_my_addresses(g_inst); fire_local_sockets_event(); /* Bind chat sync via etcp_router */ etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL); utun_add_nodeinfo_cbk(g_inst, nodeinfo_event_cb, NULL); etcp_add_link_status_cbk(g_inst, on_link_status_changed, NULL); /* Set my_name from config */ if (g_inst->config->global.name[0]) { chat_core_update_my_name(g_inst, g_inst->config->global.name); IL_LOGI("my_name set to '%s'", g_inst->config->global.name); } IL_LOGI("all init done, entering poll loop"); /* Notify GUI of current public key (initial generation or existing) */ if (strlen(config->global.my_public_key_hex) == 64) { chat_event_post(g_inst, CHAT_EVT_KEYS_GENERATED, (const uint8_t*)config->global.my_public_key_hex, 64); } __atomic_store_n(&g_running, 1, __ATOMIC_RELEASE); uasync_mark_running(g_ua); chat_event_post(g_inst, CHAT_EVT_SERVICE_STARTED, NULL, 0); while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { uasync_poll(g_ua, -1); } while (__atomic_load_n(&g_do_restart, __ATOMIC_ACQUIRE)) { IL_LOGI("poll exit: restart requested, destroying old instance"); __atomic_store_n(&g_do_restart, 0, __ATOMIC_RELEASE); IL_LOGI("pre-destroy: g_inst=%p node_id=0x%016llx ua=%p running=%d sockets=%p conns=%p", g_inst, g_inst ? (unsigned long long)g_inst->node_id : 0ULL, g_ua, g_running, g_inst ? g_inst->etcp_sockets : NULL, g_inst ? g_inst->connections : NULL); uasync_mark_stopped(g_ua); __atomic_store_n(&g_running, 0, __ATOMIC_RELEASE); struct UTUN_INSTANCE* old_inst = g_inst; g_inst = NULL; utun_instance_destroy(old_inst); my_inst = NULL; uasync_print_resources(g_ua, "AFTER_DESTROY"); u_report_unfreed_blocks(); standby_deinit(); uasync_destroy(g_ua, 0); my_ua = NULL; g_ua = uasync_create(); my_ua = g_ua; if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; } standby_init(g_ua); char* cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE); if (!cfg) { IL_LOGE("poll exit: restart_config is NULL"); break; } __atomic_store_n(&g_stop, 0, __ATOMIC_RELEASE); IL_LOGI("poll exit: creating new instance from restart config"); struct utun_config* config = parse_config_from_buf(cfg, strlen(cfg), "android"); u_free(cfg); if (!config) { IL_LOGE("poll exit: parse_config failed"); break; } config->global.client_type = CLIENT_TYPE_MOBILE; /* Android — всегда мобильный узел */ g_inst = utun_instance_create_from_config(g_ua, config); my_inst = g_inst; if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; } chat_event_set_handler(g_inst, chat_event_forward); /* Recreate core and chat independently of UTUN. */ if (utun_core_start(g_inst) != 0 || chat_service_start(g_inst) != 0) { IL_LOGE("poll exit: core/chat start failed"); break; } chat_core_sync_my_addresses(g_inst); fire_local_sockets_event(); if (g_inst->config->global.name[0]) chat_core_update_my_name(g_inst, g_inst->config->global.name); etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL); utun_add_nodeinfo_cbk(g_inst, nodeinfo_event_cb, NULL); etcp_add_link_status_cbk(g_inst, on_link_status_changed, NULL); chat_event_post(g_inst, CHAT_EVT_KEYS_GENERATED, (const uint8_t*)config->global.my_public_key_hex, 64); __atomic_store_n(&g_running, 1, __ATOMIC_RELEASE); uasync_mark_running(g_ua); chat_event_post(g_inst, CHAT_EVT_SERVICE_STARTED, NULL, 0); IL_LOGI("poll exit: restart done, entering new poll loop"); while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) uasync_poll(g_ua, -1); } IL_LOGI("poll exit: final cleanup"); if (my_gen == g_generation) { __atomic_store_n(&g_running, 0, __ATOMIC_RELEASE); if (my_inst) { chat_event_post(my_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0); g_inst = NULL; utun_instance_destroy(my_inst); my_inst = NULL; } if (my_ua) { standby_deinit(); uasync_destroy(my_ua, 0); g_ua = NULL; my_ua = NULL; } char* stale_cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE); u_free(stale_cfg); u_report_unfreed_blocks(); IL_LOGI("cleanup complete (gen=%d)", my_gen); } else { /* stale-поток: глобальные g_inst/g_ua уже принадлежат новому потоку, но собственные объекты этого потока надо закрыть, иначе течёт fd. */ IL_LOGI("cleanup (stale thread gen=%d vs %d): destroying own inst=%p ua=%p to avoid fd leak", my_gen, g_generation, (void*)my_inst, (void*)my_ua); if (my_inst) { utun_instance_destroy(my_inst); my_inst = NULL; } if (my_ua) { uasync_destroy(my_ua, 0); my_ua = NULL; } } __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_mutex_lock(&g_stop_mutex); g_thread_exited = 1; pthread_cond_signal(&g_stop_cond); pthread_mutex_unlock(&g_stop_mutex); IL_LOGI("thread exit signalled"); return NULL; } /* ── Public API ── */ int instance_lite_start(const char* config_text) { if (!config_text) return -1; /* CAS: prevent double-start race between multiple Kotlin restart calls */ int expected = 0; if (!__atomic_compare_exchange_n(&g_thread_running, &expected, 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) return 0; if (__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE)) { __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); return 0; } debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_MEMBER_SYNC, DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_INFO); debug_set_category_level(DEBUG_CATEGORY_DUMP, DEBUG_LEVEL_WARN); debug_enable_function_name(0); cfg_get_val(config_text, "db_path", g_db_path, sizeof(g_db_path)); char* config_copy = u_strdup(config_text); if (!config_copy) { __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); return -1; } g_generation++; g_thread_exited = 0; __atomic_store_n(&g_stop, 0, __ATOMIC_RELEASE); int rc = pthread_create(&g_thread, NULL, instance_thread, config_copy); if (rc != 0) { u_free(config_copy); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); IL_LOGE("pthread_create failed rc=%d", rc); return -1; } IL_LOGI("started OK"); return 0; } void instance_lite_stop(void) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!inst) return; IL_LOGI("stopping..."); __atomic_store_n(&g_stop, 1, __ATOMIC_RELEASE); struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); if (ua) uasync_wakeup(ua); struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; int timedout = 0; pthread_mutex_lock(&g_stop_mutex); while (!g_thread_exited) { int rc = pthread_cond_timedwait(&g_stop_cond, &g_stop_mutex, &ts); if (rc == ETIMEDOUT) { IL_LOGE("stop timeout (2s), detaching thread and bumping generation to prevent stale writes"); pthread_detach(g_thread); g_generation++; timedout = 1; break; } } pthread_mutex_unlock(&g_stop_mutex); if (!timedout) { int join_rc = pthread_join(g_thread, NULL); if (join_rc != 0) IL_LOGE("pthread_join failed rc=%d errno=%d", join_rc, errno); IL_LOGI("thread joined"); } /* C-thread cleanup already set g_inst=NULL, g_ua=NULL, g_running=0. Reset thread sync state only. */ g_thread_exited = 0; } /* Логирует текущие активные сокеты (имя=ip:port(протокол)) — состояние ДО изменения. */ static void log_active_sockets(struct UTUN_INSTANCE* inst, const char* label) { if (!inst) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "%s: (no instance)", label); return; } char buf[768] = ""; int off = 0; for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) { const struct sockaddr_storage* a = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr; const char* addr = sockaddr_storage_to_str(a).str; if (off < (int)sizeof(buf) - 96) off += snprintf(buf + off, sizeof(buf) - (size_t)off, " %s=%s(%s)", s->name, addr, s->is_tcp ? "TCP" : "UDP"); } DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "%s:%s", label, buf[0] ? buf : " (none)"); } /* Логирует [server:...]/addr= секции нового конфига — состояние ПОСЛЕ изменения. */ static void log_config_servers(const char* cfg, const char* label) { char buf[768] = ""; int off = 0; char srvname[64] = ""; const char* p = cfg; while (p && *p) { if (strncmp(p, "[server:", 8) == 0) { const char* end = strchr(p, ']'); if (end && end > p + 8) { int n = (int)(end - p) - 8; if (n > 63) n = 63; memcpy(srvname, p + 8, (size_t)n); srvname[n] = '\0'; } else srvname[0] = '\0'; } else if (strncmp(p, "addr=", 5) == 0 && srvname[0]) { const char* eol = p; while (*eol && *eol != '\n' && *eol != '\r') eol++; int alen = (int)(eol - p) - 5; char addr[128]; if (alen < 1) alen = 0; if (alen > 127) alen = 127; memcpy(addr, p + 5, (size_t)alen); addr[alen] = '\0'; if (off < (int)sizeof(buf) - 96) off += snprintf(buf + off, sizeof(buf) - (size_t)off, " %s=%s", srvname, addr); srvname[0] = '\0'; } while (*p && *p != '\n') p++; if (*p == '\n') p++; } DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "%s:%s", label, buf[0] ? buf : " (none)"); } void instance_lite_restart(const char* new_config_text) { if (!new_config_text) return; struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); int running = __atomic_load_n(&g_running, __ATOMIC_ACQUIRE); /* Диагностика смены сети: какие сокеты/адреса были → какие будут */ log_active_sockets(inst, "restart OLD sockets"); log_config_servers(new_config_text, "restart NEW config"); if (!ua || !inst || !running) { IL_LOGI("restart: instance dead, doing hard restart via stop+start"); if (inst) instance_lite_stop(); instance_lite_start(new_config_text); return; } IL_LOGI("restart: signaling poll loop to exit"); char* copy = u_strdup(new_config_text); if (!copy) return; char* old_cfg = (char*)__atomic_exchange_n(&g_restart_config, copy, __ATOMIC_RELEASE); u_free(old_cfg); __atomic_store_n(&g_do_restart, 1, __ATOMIC_RELEASE); __atomic_store_n(&g_stop, 1, __ATOMIC_RELEASE); uasync_wakeup(ua); } /* ── Лёгкое обновление сокетов (auto_sockets=android) ── */ static void update_sockets_trampoline(void* arg) { char* cfg_text = (char*)arg; struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!inst) { IL_LOGE("update_sockets: instance gone in trampoline — dropping update"); u_free(cfg_text); return; } struct utun_config* cfg = parse_config_from_buf(cfg_text, strlen(cfg_text), "android-update"); u_free(cfg_text); if (!cfg) { IL_LOGE("update_sockets: parse_config_from_buf failed"); return; } auto_socket_reload_config(inst, cfg); /* cfg владеет auto_socket: servers переносятся, остальное освобождается */ } void instance_lite_update_sockets(const char* config_text) { if (!config_text) return; struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); int running = __atomic_load_n(&g_running, __ATOMIC_ACQUIRE); /* Диагностика смены сети: какие сокеты/адреса были → какие будут (без полного рестарта). */ log_active_sockets(inst, "update OLD sockets"); log_config_servers(config_text, "update NEW config"); if (!ua || !inst || !running) { IL_LOGE("update_sockets: instance not running (ua=%p inst=%p running=%d) — update dropped (no fallback restart)", (void*)ua, (void*)inst, running); return; } char* copy = u_strdup(config_text); if (!copy) { IL_LOGE("update_sockets: u_strdup failed"); return; } uasync_post(ua, update_sockets_trampoline, copy); } /* ── Health check ping ── */ static volatile int g_ping_id = 0; static volatile int g_pong_id = 0; static void ping_trampoline(void* arg) { (void)arg; g_pong_id = g_ping_id; } void instance_lite_ping(void) { struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!ua || !inst) return; g_ping_id++; uasync_post(ua, ping_trampoline, NULL); } int instance_lite_is_responsive(void) { struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!ua || !inst || !__atomic_load_n(&g_running, __ATOMIC_ACQUIRE)) return 0; if (g_ping_id == 0) return 0; return (g_pong_id == g_ping_id) ? 1 : 0; } void instance_lite_collect_conn_list(void) { struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!ua || !inst) return; uasync_post(ua, chat_core_collect_conn_list_trampoline, inst); } void instance_lite_collect_conn_metrics(uint64_t peer_node_id) { struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!ua || !inst) return; struct chat_node_arg* arg = (struct chat_node_arg*)u_malloc(sizeof(struct chat_node_arg)); if (!arg) return; arg->inst = inst; arg->node_id = peer_node_id; uasync_post(ua, chat_core_collect_conn_metrics_trampoline, arg); } int instance_lite_is_running(void) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); return (inst != NULL && __atomic_load_n(&g_running, __ATOMIC_ACQUIRE)) ? 1 : 0; } struct UASYNC* instance_lite_get_uasync(void) { return (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); } struct UTUN_INSTANCE* instance_lite_get_instance(void) { return (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); } static void regenerate_trampoline(void* arg) { (void)arg; if (!g_inst || !g_inst->config) { IL_LOGE("regenerate_keys: instance not running"); return; } struct utun_config* config = g_inst->config; IL_LOGI("regenerating keys..."); config->global.my_public_key_hex[0] = '\0'; config->global.my_private_key_hex[0] = '\0'; ensure_keys(config); if (g_generated_pub[0]) { uint8_t pk_bin[32]; for (int i = 0; i < 32; i++) { unsigned int b; sscanf(g_generated_pub + i * 2, "%2x", &b); pk_bin[i] = (uint8_t)b; } g_inst->node_id = sc_derive_node_id_from_pubkey(pk_bin); sc_init_local_keys(&g_inst->my_keys, g_generated_pub, g_generated_priv); IL_LOGI("keys regenerated, node_id=0x%016llx", (unsigned long long)g_inst->node_id); chat_event_post(g_inst, CHAT_EVT_KEYS_GENERATED, (const uint8_t*)g_generated_pub, 64); } } void instance_lite_regenerate_keys(void) { struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE); struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (!ua || !inst) return; uasync_post(ua, regenerate_trampoline, NULL); } void instance_lite_set_event_handler(instance_lite_event_fn handler) { IL_LOGI("[EVT_DIAG] instance_lite_set_event_handler handler=%p g_running=%d", (void*)handler, g_running); g_event_handler = handler; if (__atomic_load_n(&g_running, __ATOMIC_ACQUIRE)) { struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)__atomic_load_n(&g_inst, __ATOMIC_ACQUIRE); if (inst) chat_event_set_handler(inst, chat_event_forward); } }