Select frontend network callbacks for auto_sockets=android instead of opening a denied netlink monitor. Preserve monitor descriptor ownership, keep native monitoring failures fatal, and release caller config on failed Android core creation and restart. Register ConnectivityManager before requesting startup, report readiness after SERVICE_STARTED, and refresh addresses after initialization with serialized updates and generation checks. Add regression coverage for denied netlink, socket failures, address updates, and fd=0 preservation. Record the separate cancellation issue for startup before readiness. Validation: full check.sh passed, including proxy/burst/load integrations. Clean Android build installed successfully on SM-A525F and Q8; logcat confirms both cores initialized and entered the poll loop with external monitoring and post-start address refresh.
728 lines
30 KiB
C
728 lines
30 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 → 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 "stream_bridge.h"
|
|
#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>
|
|
#include <openssl/evp.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 ──
|
|
*
|
|
* 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;
|
|
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);
|
|
|
|
udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port);
|
|
udp_log_attach(g_ua);
|
|
|
|
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");
|
|
free_config(config);
|
|
standby_deinit();
|
|
udp_log_detach(g_ua);
|
|
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 || android_stream_attach(g_inst) < 0) {
|
|
IL_LOGE("core/chat start failed");
|
|
utun_instance_destroy(g_inst);
|
|
g_inst = NULL;
|
|
standby_deinit();
|
|
udp_log_detach(g_ua);
|
|
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;
|
|
android_stream_detach(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);
|
|
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 — всегда мобильный узел */
|
|
udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port);
|
|
udp_log_attach(g_ua);
|
|
|
|
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"); free_config(config); 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 || android_stream_attach(g_inst) < 0) {
|
|
IL_LOGE("poll exit: core/chat/stream bridge 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;
|
|
android_stream_detach(my_inst);
|
|
utun_instance_destroy(my_inst);
|
|
my_inst = NULL;
|
|
}
|
|
if (my_ua) {
|
|
standby_deinit();
|
|
udp_log_detach(my_ua);
|
|
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) { android_stream_detach(my_inst); utun_instance_destroy(my_inst); my_inst = NULL; }
|
|
if (my_ua) { udp_log_detach(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_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);
|
|
}
|
|
}
|