Files
utun2/tools/chatgui-android/libutun_lite/instance_lite.c
T

792 lines
36 KiB
C

/*
* instance_lite.c — lightweight uTun instance lifecycle for Android
*
* Kotlin sends INI config text → parse_config_from_buf →
* utun_instance_create_from_config → utun_core_start → chat_service_start →
* pthread with uasync_poll loop.
*
* UTUN выключен по умолчанию; [gui] utun_enabled=yes явно включает сервис при старте и рестарте.
*/
#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 "file_io.h"
#include "secure_channel.h"
#include "topo_node_sqlite.h"
#include "chat/chat_core.h"
#include "chat/chat_sync.h"
#include "chat/chat_join_port.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 "stream_bridge.h"
#include "call/call_video_port.h"
#ifdef UTUN_WITH_VOICE
#include "call/call_audio.h"
#endif
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <unwind.h>
#include <dlfcn.h>
#include <time.h>
#include <errno.h>
#ifdef __ANDROID__
#include <android/log.h>
#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 ──
*
* g_control_mutex сериализует start/stop/restart и владение joinable pthread.
* Worker единолично создаёт/освобождает instance/ua. g_stop_mutex защищает
* публикацию ua для wakeup, подтверждение готовности и условие завершения.
* Сырые getters не удерживают ресурсы; их callers по-прежнему исключают teardown.
*/
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 pthread_mutex_t join_port_lock = PTHREAD_MUTEX_INITIALIZER;
static struct chat_join_port* join_port;
static void bind_join_port(struct UTUN_INSTANCE* inst) {
struct chat_join_port* next = inst ? chat_sync_join_port(inst) : NULL;
pthread_mutex_lock(&join_port_lock);
struct chat_join_port* old = join_port; join_port = next;
pthread_mutex_unlock(&join_port_lock);
chat_join_port_release(old);
}
int instance_lite_join_link(uint64_t request, const char* link, size_t len) {
pthread_mutex_lock(&join_port_lock);
int result = chat_join_port_submit(join_port, request, link, len);
pthread_mutex_unlock(&join_port_lock); return result;
}
static volatile int g_stop = 0; /* Kotlin-write, C-read (poll loop) */
struct restart_request {
char* config_text; /* Копия INI, принадлежит запросу до завершения подготовки. */
int regenerate_keys; /* Новая пара сохраняется до уничтожения прежнего ядра. */
};
static struct restart_request* g_restart_config; /* Kotlin atomic-xchg → worker atomic-xchg (ownership transfer) */
static volatile int g_running = 0; /* C-write, Kotlin-read */
static int g_thread_joinable; /* Только controller под g_control_mutex, до успешного join. */
static int g_thread_exited; /* Worker/controller под g_stop_mutex, после освобождения ресурсов. */
static instance_lite_event_fn g_event_handler = NULL; /* Kotlin-write, C-read+call */
static pthread_mutex_t g_control_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t g_stop_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_stop_cond = PTHREAD_COND_INITIALIZER;
/* Генерируем отдельно от config: не публикуем в памяти ключи, которые не удалось сохранить. */
int instance_lite_generate_keys(struct utun_config* config) {
if (!config || !config->global.db_path[0]) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity generation requires a database directory"); return -1;
}
struct SC_MYKEYS keys;
if (sc_generate_keypair(&keys) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "Android identity key generation failed"); return -1;
}
uint64_t node_id = sc_derive_node_id_from_pubkey(keys.public_key);
if (!node_id) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "Android identity derived zero node_id"); return -1; }
char pub[65], priv[65];
bytes_to_hex(keys.public_key, sizeof(keys.public_key), pub, sizeof(pub));
bytes_to_hex(keys.private_key, sizeof(keys.private_key), priv, sizeof(priv));
char filename[sizeof(config->global.db_path) + sizeof("/keys.conf")];
snprintf(filename, sizeof(filename), "%s/keys.conf", config->global.db_path);
struct file_io_temp output = {0};
const char* phase = "open";
if (file_io_temp_open(&output, filename, FILE_IO_DURABLE)) goto failed;
phase = "write";
if (fprintf(output.file, "public_key=%s\nprivate_key=%s\n", pub, priv) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity write failed path=%s errno=%d: %s", filename, errno, strerror(errno));
goto failed;
}
phase = "finish";
if (file_io_temp_finish(&output)) goto failed;
phase = "publish";
if (file_io_temp_publish(&output, output.temporary, filename)) goto failed;
file_io_temp_discard(&output);
memcpy(config->global.my_public_key_hex, pub, sizeof(pub));
memcpy(config->global.my_private_key_hex, priv, sizeof(priv));
config->global.my_node_id = node_id;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Android identity saved path=%s node_id=%016llx", filename, (unsigned long long)node_id);
return 0;
failed:
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity save failed path=%s phase=%s published=%d",
filename, phase, !strcmp(phase, "publish") && !output.temporary[0]);
file_io_temp_discard(&output);
return -1;
}
/* ── Ensure valid keys in config, generate if missing ── */
static int ensure_keys(struct utun_config* config) {
char filename[sizeof(config->global.db_path) + sizeof("/keys.conf")];
snprintf(filename, sizeof(filename), "%s/keys.conf", config->global.db_path);
struct file_io_info info;
int status = config->global.db_path[0] ? file_io_inspect(filename, 0, &info) : 1;
if (status < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity inspection failed path=%s", filename); return -1; }
if (!status) {
if (info.type != FILE_IO_REGULAR) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity is not a regular file path=%s type=%d", filename, info.type); return -1;
}
FILE* file = file_io_open_read(filename);
if (!file) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity read failed path=%s", filename); return -1; }
char public_line[sizeof("public_key=") + SC_PUBKEY_SIZE * 2 + 1] = {0};
char private_line[sizeof("private_key=") + SC_PRIVKEY_SIZE * 2 + 1] = {0};
int complete = fgets(public_line, sizeof(public_line), file) && fgets(private_line, sizeof(private_line), file) && fgetc(file) == EOF;
int read_error = ferror(file), closed = fclose(file);
if (!complete || read_error || closed || strlen(public_line) != sizeof(public_line) - 1 ||
strlen(private_line) != sizeof(private_line) - 1 || public_line[sizeof(public_line) - 2] != '\n' ||
private_line[sizeof(private_line) - 2] != '\n' ||
strncmp(public_line, "public_key=", sizeof("public_key=") - 1) ||
strncmp(private_line, "private_key=", sizeof("private_key=") - 1)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity read/format failed path=%s complete=%d read_error=%d close_error=%d",
filename, complete, read_error, closed); return -1;
}
public_line[sizeof(public_line) - 2] = private_line[sizeof(private_line) - 2] = '\0';
memcpy(config->global.my_public_key_hex, public_line + sizeof("public_key=") - 1, SC_PUBKEY_SIZE * 2 + 1);
memcpy(config->global.my_private_key_hex, private_line + sizeof("private_key=") - 1, SC_PRIVKEY_SIZE * 2 + 1);
}
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");
return instance_lite_generate_keys(config);
}
struct SC_MYKEYS keys;
uint8_t public_key[SC_PUBKEY_SIZE];
if (sc_init_local_keys(&keys, config->global.my_public_key_hex, config->global.my_private_key_hex) != SC_OK ||
sc_compute_public_key_from_private(keys.private_key, public_key) != SC_OK ||
memcmp(public_key, keys.public_key, sizeof(public_key))) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity key pair is invalid path=%s persisted=%d", filename, !status); return -1;
}
config->global.my_node_id = sc_derive_node_id_from_pubkey(public_key);
if (!config->global.my_node_id) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Android identity derived zero node_id"); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Android identity ready path=%s persisted=%d node_id=%016llx",
filename, !status, (unsigned long long)config->global.my_node_id);
return 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;
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 log_active_sockets(struct UTUN_INSTANCE* inst, const char* label);
/* Только uasync; частичный attach всегда закрывается общим detach до уничтожения ядра. */
static int attach_media(struct UTUN_INSTANCE* inst) {
if (android_stream_attach(inst) < 0 || call_video_port_attach(inst) < 0) return -1;
#ifdef UTUN_WITH_VOICE
if (call_audio_init(inst) < 0) return -1;
#endif
return 0;
}
static void detach_media(struct UTUN_INSTANCE* inst) {
#ifdef UTUN_WITH_VOICE
call_audio_destroy(inst);
#endif
call_video_port_detach(inst);
android_stream_detach(inst);
}
static void* instance_thread(void* arg) {
char* config_text = (char*)arg;
struct UASYNC* my_ua = NULL;
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"); goto cleanup; }
config->global.client_type = CLIENT_TYPE_MOBILE; /* Android — всегда мобильный узел (keepalive standby/normal) */
install_crash_handlers();
if (ensure_keys(config)) {
IL_LOGE("startup refused: Android identity is not saved");
free_config(config);
goto cleanup;
}
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { free_config(config); goto cleanup; }
my_ua = uasync_create();
if (!my_ua) { IL_LOGE("uasync_create failed"); free_config(config); goto cleanup; }
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_ua, my_ua, __ATOMIC_RELEASE);
pthread_mutex_unlock(&g_stop_mutex);
standby_init(g_ua);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { free_config(config); goto cleanup; }
udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port);
udp_log_attach(g_ua);
my_inst = utun_instance_create_from_config(g_ua, config);
__atomic_store_n(&g_inst, my_inst, __ATOMIC_RELEASE);
if (!my_inst) {
IL_LOGE("utun_instance_create_from_config failed");
free_config(config);
goto cleanup;
}
chat_event_set_handler(g_inst, chat_event_forward);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) goto cleanup;
IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id);
IL_LOGI("service policy: utun_enabled=%d tun_enabled=%d", config->global.gui_utun_enabled, config->global.tun_enabled);
if (utun_core_start(g_inst) != 0 || (config->global.gui_utun_enabled && utun_service_start(g_inst) != 0) ||
chat_service_start(g_inst) != 0 || attach_media(g_inst) < 0) {
IL_LOGE("core/optional UTUN/chat start failed");
goto cleanup;
}
bind_join_port(g_inst);
/* sockets are now created — sync my addresses to DB */
chat_core_sync_my_addresses(g_inst);
fire_local_sockets_event();
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");
pthread_mutex_lock(&g_stop_mutex);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { pthread_mutex_unlock(&g_stop_mutex); goto cleanup; }
/* Notify GUI of current public key (initial generation or existing). Stop не может пересечь публикацию ready. */
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);
pthread_mutex_unlock(&g_stop_mutex);
poll_current:
while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE) &&
!__atomic_load_n(&g_restart_config, __ATOMIC_ACQUIRE) && g_inst->running) {
uasync_poll(g_ua, -1);
}
if (!g_inst->running) IL_LOGE("poll exit: core requested stop; cleanup runs outside callbacks");
struct restart_request* request;
while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE) &&
(request = __atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE))) {
int regenerate_keys = request->regenerate_keys;
struct utun_config* config = parse_config_from_buf(request->config_text, strlen(request->config_text), "android-restart");
u_free(request->config_text);
u_free(request);
if (!config) { IL_LOGE("restart refused: invalid config, current core retained"); goto poll_current; }
if (regenerate_keys ? instance_lite_generate_keys(config) : ensure_keys(config)) {
IL_LOGE("restart refused: Android identity is not saved, current core retained");
free_config(config);
goto poll_current;
}
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { free_config(config); break; }
config->global.client_type = CLIENT_TYPE_MOBILE;
log_active_sockets(g_inst, "restart OLD sockets");
bind_join_port(NULL);
IL_LOGI("poll exit: restart requested, destroying old instance");
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);
chat_event_post(g_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0);
struct UTUN_INSTANCE* old_inst = g_inst;
__atomic_store_n(&g_inst, NULL, __ATOMIC_RELEASE);
detach_media(old_inst);
utun_instance_destroy(old_inst);
my_inst = NULL;
uasync_print_resources(g_ua, "AFTER_DESTROY");
u_report_unfreed_blocks();
standby_deinit();
udp_log_detach(g_ua);
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_ua, NULL, __ATOMIC_RELEASE);
pthread_mutex_unlock(&g_stop_mutex);
uasync_destroy(my_ua, 0);
my_ua = NULL;
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { free_config(config); break; }
my_ua = uasync_create();
if (!my_ua) { IL_LOGE("poll exit: uasync_create failed"); free_config(config); break; }
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_ua, my_ua, __ATOMIC_RELEASE);
pthread_mutex_unlock(&g_stop_mutex);
standby_init(g_ua);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { free_config(config); break; }
IL_LOGI("poll exit: creating new instance from restart config");
udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port);
udp_log_attach(g_ua);
my_inst = utun_instance_create_from_config(g_ua, config);
__atomic_store_n(&g_inst, my_inst, __ATOMIC_RELEASE);
if (!my_inst) { IL_LOGE("poll exit: create_from_config failed"); free_config(config); break; }
chat_event_set_handler(g_inst, chat_event_forward);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) break;
IL_LOGI("restart service policy: utun_enabled=%d tun_enabled=%d", config->global.gui_utun_enabled, config->global.tun_enabled);
if (utun_core_start(g_inst) != 0 || (config->global.gui_utun_enabled && utun_service_start(g_inst) != 0) ||
chat_service_start(g_inst) != 0 || attach_media(g_inst) < 0) {
IL_LOGE("poll exit: core/chat/stream bridge start failed"); break;
}
bind_join_port(g_inst);
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);
utun_add_nodeinfo_cbk(g_inst, nodeinfo_event_cb, NULL);
etcp_add_link_status_cbk(g_inst, on_link_status_changed, NULL);
pthread_mutex_lock(&g_stop_mutex);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { pthread_mutex_unlock(&g_stop_mutex); break; }
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);
pthread_mutex_unlock(&g_stop_mutex);
IL_LOGI("poll exit: restart done, entering new poll loop");
while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE) &&
!__atomic_load_n(&g_restart_config, __ATOMIC_ACQUIRE) && g_inst->running) uasync_poll(g_ua, -1);
if (!g_inst->running) IL_LOGE("poll exit: restarted core requested stop; cleanup runs outside callbacks");
}
cleanup:
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android worker cleanup: instance=%p loop=%p", (void*)my_inst, (void*)my_ua);
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_stop, 1, __ATOMIC_RELEASE);
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
pthread_mutex_unlock(&g_stop_mutex);
bind_join_port(NULL);
if (my_inst) {
chat_event_post(my_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0);
__atomic_store_n(&g_inst, NULL, __ATOMIC_RELEASE);
detach_media(my_inst);
utun_instance_destroy(my_inst);
}
if (my_ua) {
standby_deinit();
udp_log_detach(my_ua);
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_ua, NULL, __ATOMIC_RELEASE);
pthread_mutex_unlock(&g_stop_mutex);
uasync_destroy(my_ua, 0);
}
struct restart_request* stale = __atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE);
if (stale) { u_free(stale->config_text); u_free(stale); }
u_report_unfreed_blocks();
pthread_mutex_lock(&g_stop_mutex);
g_thread_exited = 1;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android worker finished: all native resources released");
pthread_cond_signal(&g_stop_cond);
pthread_mutex_unlock(&g_stop_mutex);
return NULL;
}
/* ── Public API ── */
/* g_control_mutex удерживается caller: завершившийся pthread надо собрать до следующего create. */
static int start_locked(const char* config_text) {
if (!config_text) { IL_LOGE("start refused: config is NULL"); return -1; }
if (g_thread_joinable) {
pthread_mutex_lock(&g_stop_mutex);
int exited = g_thread_exited;
pthread_mutex_unlock(&g_stop_mutex);
if (!exited) {
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) { IL_LOGE("start refused: previous worker is stopping"); return -1; }
return 0;
}
int result = pthread_join(g_thread, NULL);
if (result) { IL_LOGE("start refused: previous worker join failed rc=%d", result); return -1; }
g_thread_joinable = 0;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android previous worker joined before start");
}
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_ETCP, DEBUG_LEVEL_INFO);
debug_set_category_level(DEBUG_CATEGORY_DUMP, DEBUG_LEVEL_WARN);
debug_enable_function_name(0);
char* config_copy = u_strdup(config_text);
if (!config_copy) { IL_LOGE("start refused: config allocation failed"); return -1; }
pthread_mutex_lock(&g_stop_mutex);
g_thread_exited = 0;
pthread_mutex_unlock(&g_stop_mutex);
__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);
IL_LOGE("pthread_create failed rc=%d", rc);
return -1;
}
g_thread_joinable = 1;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android worker created");
return 0;
}
int instance_lite_start(const char* config_text) {
pthread_mutex_lock(&g_control_mutex);
int result = start_locked(config_text);
pthread_mutex_unlock(&g_control_mutex);
return result;
}
int instance_lite_stop(void) {
pthread_mutex_lock(&g_control_mutex);
if (!g_thread_joinable) { pthread_mutex_unlock(&g_control_mutex); return 0; }
if (pthread_equal(pthread_self(), g_thread)) {
IL_LOGE("stop refused: must run outside the native worker");
pthread_mutex_unlock(&g_control_mutex);
return -1;
}
bind_join_port(NULL);
pthread_mutex_lock(&g_stop_mutex);
__atomic_store_n(&g_stop, 1, __ATOMIC_RELEASE);
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE);
if (ua) uasync_wakeup(ua);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android worker stop requested: exited=%d loop=%p", g_thread_exited, (void*)ua);
while (!g_thread_exited) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 2;
int rc = pthread_cond_timedwait(&g_stop_cond, &g_stop_mutex, &ts);
if (rc == ETIMEDOUT) {
DEBUG_WARN(DEBUG_CATEGORY_SYS, "Android worker still stopping after 2s; resources retained until join");
} else if (rc) {
IL_LOGE("worker condition wait failed rc=%d; waiting with pthread_join", rc);
break;
}
}
pthread_mutex_unlock(&g_stop_mutex);
int result = pthread_join(g_thread, NULL);
if (result) IL_LOGE("worker join failed rc=%d", result);
else {
g_thread_joinable = 0;
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Android worker joined; stop completed");
}
pthread_mutex_unlock(&g_control_mutex);
return result ? -1 : 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)");
}
static int request_restart(const char* new_config_text, int regenerate_keys) {
if (!new_config_text) { IL_LOGE("restart refused: config is NULL"); return -1; }
pthread_mutex_lock(&g_control_mutex);
pthread_mutex_lock(&g_stop_mutex);
int alive = g_thread_joinable && !g_thread_exited;
pthread_mutex_unlock(&g_stop_mutex);
int running = __atomic_load_n(&g_running, __ATOMIC_ACQUIRE);
log_config_servers(new_config_text, "restart NEW config");
if (regenerate_keys && !running) {
IL_LOGE("key regeneration refused: core is not ready"); pthread_mutex_unlock(&g_control_mutex); return -1;
}
if (!alive) {
int result = start_locked(new_config_text);
pthread_mutex_unlock(&g_control_mutex);
return result;
}
struct restart_request* next = u_calloc(1, sizeof(*next));
if (!next) { IL_LOGE("restart refused: request allocation failed"); pthread_mutex_unlock(&g_control_mutex); return -1; }
next->config_text = u_strdup(new_config_text);
if (!next->config_text) {
IL_LOGE("restart refused: config allocation failed"); u_free(next); pthread_mutex_unlock(&g_control_mutex); return -1;
}
next->regenerate_keys = regenerate_keys;
pthread_mutex_lock(&g_stop_mutex);
if (__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE) || g_thread_exited ||
(regenerate_keys && !__atomic_load_n(&g_running, __ATOMIC_ACQUIRE))) {
pthread_mutex_unlock(&g_stop_mutex);
IL_LOGE("restart refused: worker is stopping or not ready");
u_free(next->config_text); u_free(next);
pthread_mutex_unlock(&g_control_mutex);
return -1;
}
struct restart_request* old = __atomic_exchange_n(&g_restart_config, next, __ATOMIC_ACQ_REL);
if (old) { IL_LOGI("restart: replacing pending request regenerate_keys=%d", old->regenerate_keys); u_free(old->config_text); u_free(old); }
IL_LOGI("restart: request queued regenerate_keys=%d", regenerate_keys);
struct UASYNC* ua = __atomic_load_n(&g_ua, __ATOMIC_ACQUIRE);
if (ua) uasync_wakeup(ua);
pthread_mutex_unlock(&g_stop_mutex);
pthread_mutex_unlock(&g_control_mutex);
return 0;
}
void instance_lite_restart(const char* new_config_text) {
request_restart(new_config_text, 0);
}
/* ── Лёгкое обновление сокетов (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);
}
int instance_lite_regenerate_keys(const char* config_text) {
return request_restart(config_text, 1);
}
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);
}
}