You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
11 KiB
333 lines
11 KiB
/* |
|
* 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 "u_async.h" |
|
#include "sqlite3.h" |
|
#include "mem.h" |
|
#include "debug_config.h" |
|
#include "../jni_bridge/android_udp_log.h" |
|
#include <pthread.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <unistd.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 ── */ |
|
|
|
static struct UASYNC* g_ua = NULL; |
|
static struct UTUN_INSTANCE* g_inst = NULL; |
|
static pthread_t g_thread; |
|
static volatile int g_running = 0; |
|
static volatile int g_stop = 0; |
|
static char g_db_path[512]; |
|
static instance_lite_event_fn g_event_handler = NULL; |
|
static char g_generated_pub[65]; |
|
static char g_generated_priv[65]; |
|
|
|
/* ── 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); |
|
} |
|
|
|
/* ── Thread function ── */ |
|
|
|
static void* instance_thread(void* arg) { |
|
char* config_text = (char*)arg; |
|
|
|
/* Parse config from text */ |
|
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"); |
|
g_running = 0; |
|
return NULL; |
|
} |
|
|
|
/* Create uasync */ |
|
g_ua = uasync_create(); |
|
if (!g_ua) { |
|
IL_LOGE("uasync_create failed"); |
|
free_config(config); |
|
g_running = 0; |
|
return NULL; |
|
} |
|
|
|
/* Start UDP log early before any DEBUG messages */ |
|
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"); |
|
} |
|
|
|
/* Set event handler before ensure_keys so KEYS_GENERATED event is delivered */ |
|
chat_event_set_handler(chat_event_forward); |
|
|
|
/* Generate keys if missing (before creating instance) */ |
|
ensure_keys(config); |
|
|
|
/* Create UTUN_INSTANCE from parsed 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(); |
|
g_running = 0; |
|
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(); |
|
g_running = 0; |
|
return NULL; |
|
} |
|
|
|
/* Bind chat sync via etcp_router */ |
|
etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL); |
|
|
|
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); |
|
} |
|
|
|
g_running = 1; |
|
chat_event_post(CHAT_EVT_SERVICE_STARTED, NULL, 0); |
|
|
|
while (!g_stop) { |
|
uasync_poll(g_ua, 100); |
|
} |
|
|
|
IL_LOGI("poll loop exit, cleaning up"); |
|
|
|
if (g_inst) { |
|
utun_instance_destroy(g_inst); |
|
g_inst = NULL; |
|
} |
|
if (g_ua) { |
|
uasync_destroy(g_ua, 0); |
|
g_ua = NULL; |
|
} |
|
u_report_unfreed_blocks(); |
|
g_running = 0; |
|
chat_event_post(CHAT_EVT_SERVICE_STOPPED, NULL, 0); |
|
g_event_handler = NULL; |
|
pthread_detach(pthread_self()); |
|
IL_LOGI("cleanup complete"); |
|
return NULL; |
|
} |
|
|
|
/* ── Public API ── */ |
|
|
|
int instance_lite_start(const char* config_text) { |
|
if (!config_text || g_running) return -1; |
|
|
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_INFO); |
|
debug_enable_function_name(0); |
|
|
|
/* Extract db_path for later SQLite open */ |
|
cfg_get_val(config_text, "db_path", g_db_path, sizeof(g_db_path)); |
|
|
|
/* Copy config text — the thread takes ownership */ |
|
char* config_copy = u_strdup(config_text); |
|
if (!config_copy) return -1; |
|
|
|
g_stop = 0; |
|
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; |
|
} |
|
|
|
/* Wait for init to complete (or fail) */ |
|
for (int i = 0; i < 300 && g_running == 0 && g_inst == NULL; i++) |
|
usleep(10000); |
|
|
|
if (!g_inst) { |
|
IL_LOGE("init timed out or failed"); |
|
g_stop = 1; |
|
pthread_join(g_thread, NULL); |
|
return -1; |
|
} |
|
|
|
IL_LOGI("started OK"); |
|
return 0; |
|
} |
|
|
|
void instance_lite_stop(void) { |
|
if (!g_inst && !g_running) return; |
|
IL_LOGI("stopping..."); |
|
g_stop = 1; |
|
if (g_ua) uasync_wakeup(g_ua); |
|
} |
|
|
|
int instance_lite_is_running(void) { |
|
return g_running ? 1 : 0; |
|
} |
|
|
|
struct UASYNC* instance_lite_get_uasync(void) { |
|
return g_ua; |
|
} |
|
|
|
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) { |
|
if (!g_ua || !g_inst) return; |
|
uasync_post(g_ua, regenerate_trampoline, NULL); |
|
} |
|
|
|
void instance_lite_set_event_handler(instance_lite_event_fn handler) { |
|
g_event_handler = handler; |
|
if (g_running) chat_event_set_handler(chat_event_forward); |
|
}
|
|
|