- Добавлен device_type (1 байт, CLIENT_TYPE_*) в INIT_REQUEST и INIT_RESPONSE - Добавлен keepalive_interval (2 байта) в INIT_RESPONSE - STCP handshake расширен с 43 до 46 байт (device_type+keepalive) - negotiate_keepalive(): оба desktop/server → min, иначе (есть mobile) → max - keepalive_interval вынесен в UTUN_INSTANCE для использования в handshake - stcp_client_connect() принимает device_type и keepalive_interval - peer_device_type/peer_keepalive_interval передаются через stcp_link в ETCP_LINK
606 lines
24 KiB
C
606 lines
24 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.
|
|
*/
|
|
#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 "../jni_bridge/android_udp_log.h"
|
|
#include "../jni_bridge/android_jni_bridge.h"
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <signal.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(int type, const uint8_t* data, int len) {
|
|
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);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "nodeinfo_cb: nid=%016llx pres=%02x up=%02x rtt=%u(0x%04X) paths=%zu",
|
|
(unsigned long long)nq->node_id, nq->conn_presence, nq->conn_up,
|
|
best_rtt, best_rtt, nq->paths ? queue_entry_count(nq->paths) : 0);
|
|
uint8_t data[12];
|
|
memcpy(data, &nq->node_id, 8);
|
|
data[8] = nq->conn_presence;
|
|
data[9] = nq->conn_up;
|
|
memcpy(data + 10, &best_rtt, 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;
|
|
|
|
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);
|
|
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);
|
|
}
|
|
|
|
static void heartbeat_cb(void* arg) {
|
|
(void)arg;
|
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "heartbeat alive");
|
|
uasync_set_timeout(g_ua, 100000, NULL, heartbeat_cb, "hb");
|
|
}
|
|
|
|
/* ── Thread function ── */
|
|
|
|
static void* instance_thread(void* arg) {
|
|
char* config_text = (char*)arg;
|
|
int my_gen = g_generation;
|
|
|
|
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; }
|
|
|
|
install_crash_handlers();
|
|
|
|
g_ua = uasync_create();
|
|
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; }
|
|
|
|
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);
|
|
uasync_set_timeout(g_ua, 2000, NULL, udp_trampoline, "udp_log_flush");
|
|
}
|
|
|
|
IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler);
|
|
chat_event_set_handler(chat_event_forward);
|
|
ensure_keys(config);
|
|
|
|
g_inst = utun_instance_create_from_config(g_ua, config);
|
|
if (!g_inst) {
|
|
IL_LOGE("utun_instance_create_from_config failed");
|
|
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;
|
|
}
|
|
|
|
IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id);
|
|
|
|
/* Open shared SQLite DB */
|
|
if (g_db_path[0] && !g_inst->topo_sqlite_db) {
|
|
char db_file[512];
|
|
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
|
|
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
|
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
|
|
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA wal_autocheckpoint=10000", NULL, NULL, NULL);
|
|
topo_node_sqlite_init(g_inst->topo_sqlite_db);
|
|
IL_LOGI("SQLite opened %s", db_file);
|
|
} else {
|
|
IL_LOGE("SQLite open failed rc=%d", rc);
|
|
if (g_inst->topo_sqlite_db) { sqlite3_close(g_inst->topo_sqlite_db); g_inst->topo_sqlite_db = NULL; }
|
|
}
|
|
}
|
|
|
|
/* Full init: db_sync, chat_core, chat_sync, init_connections */
|
|
if (utun_instance_init(g_inst) != 0) {
|
|
IL_LOGE("utun_instance_init failed");
|
|
utun_instance_destroy(g_inst);
|
|
g_inst = NULL;
|
|
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();
|
|
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->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(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(CHAT_EVT_SERVICE_STARTED, NULL, 0);
|
|
|
|
uasync_set_timeout(g_ua, 100000, NULL, heartbeat_cb, "hb");
|
|
|
|
while (!__atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) {
|
|
uasync_poll(g_ua, 100);
|
|
}
|
|
|
|
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);
|
|
utun_instance_destroy(g_inst);
|
|
g_inst = NULL;
|
|
uasync_print_resources(g_ua, "AFTER_DESTROY");
|
|
u_report_unfreed_blocks();
|
|
uasync_destroy(g_ua, 0);
|
|
g_ua = uasync_create();
|
|
if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; }
|
|
|
|
chat_event_set_handler(chat_event_forward);
|
|
|
|
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; }
|
|
|
|
g_inst = utun_instance_create_from_config(g_ua, config);
|
|
if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; }
|
|
|
|
char db_file[512];
|
|
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
|
|
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
|
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
|
|
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
|
|
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
|
|
topo_node_sqlite_init(g_inst->topo_sqlite_db);
|
|
} else { IL_LOGE("poll exit: SQLite open failed rc=%d", rc); break; }
|
|
|
|
if (utun_instance_init(g_inst) != 0) { IL_LOGE("poll exit: utun_instance_init failed"); break; }
|
|
chat_core_sync_my_addresses();
|
|
fire_local_sockets_event();
|
|
if (g_inst->config->global.name[0]) chat_core_update_my_name(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(CHAT_EVT_KEYS_GENERATED, (const uint8_t*)config->global.my_public_key_hex, 64);
|
|
|
|
uasync_set_timeout(g_ua, 100000, NULL, heartbeat_cb, "hb");
|
|
__atomic_store_n(&g_running, 1, __ATOMIC_RELEASE);
|
|
uasync_mark_running(g_ua);
|
|
chat_event_post(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, 100);
|
|
}
|
|
|
|
IL_LOGI("poll exit: final cleanup");
|
|
|
|
if (my_gen == g_generation) {
|
|
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
|
|
if (g_inst) {
|
|
utun_instance_destroy(g_inst);
|
|
g_inst = NULL;
|
|
}
|
|
if (g_ua) {
|
|
uasync_destroy(g_ua, 0);
|
|
g_ua = NULL;
|
|
}
|
|
char* stale_cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE);
|
|
u_free(stale_cfg);
|
|
u_report_unfreed_blocks();
|
|
chat_event_post(CHAT_EVT_SERVICE_STOPPED, NULL, 0);
|
|
IL_LOGI("cleanup complete (gen=%d)", my_gen);
|
|
} else {
|
|
IL_LOGI("cleanup skipped — thread gen=%d but current gen=%d (stale thread)", my_gen, g_generation);
|
|
}
|
|
__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_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;
|
|
}
|
|
|
|
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);
|
|
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);
|
|
}
|
|
|
|
/* ── 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);
|
|
if (!ua) return;
|
|
uasync_post(ua, chat_core_collect_conn_list_trampoline, NULL);
|
|
}
|
|
|
|
void instance_lite_collect_conn_metrics(uint64_t peer_node_id) {
|
|
struct UASYNC* ua = (struct UASYNC*)__atomic_load_n(&g_ua, __ATOMIC_ACQUIRE);
|
|
if (!ua) return;
|
|
uint64_t* arg = (uint64_t*)u_malloc(sizeof(uint64_t));
|
|
if (!arg) return;
|
|
*arg = 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);
|
|
}
|
|
|
|
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(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)) chat_event_set_handler(chat_event_forward);
|
|
}
|