From a96d5926c7f23c2a68141ad51f234be2f36c6c75 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 16:01:26 +0200 Subject: [PATCH] Fix Android external network monitoring and startup readiness 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. --- doc/service_lifecycle.md | 6 + doc/tasks.md | 7 + src/transport_layer/auto_socket.c | 21 ++- src/transport_layer/socket_monitor.c | 4 + src/transport_layer/socket_monitor.h | 1 + tests/Makefile.am | 3 +- tests/test_tun_route.c | 49 ++++++- .../java/com/utun/chat/ChatApplication.kt | 1 + .../utun/chat/data/NetworkChangeMonitor.kt | 126 +++++++++--------- .../com/utun/chat/headless/HeadlessService.kt | 20 ++- .../libutun_lite/instance_lite.c | 3 +- 11 files changed, 165 insertions(+), 76 deletions(-) diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 60ce8866..619a3d8f 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -35,6 +35,12 @@ listen-сокетов, мониторинга, TUN и всех `route_subnet` п Маршрут, на добавлении которого ядро вернуло ошибку, не становится собственностью UTUN и при откате не удаляется. Ошибка одного удаления не прекращает очистку остальных. +`auto_sockets=android` выбирает внешнее управление сетью: frontend регистрирует +ConnectivityManager до запроса запуска и передаёт конфигурацию сокетов через JNI. +Оба нативных монитора ОС в этом режиме не создаются. Рабочие listen-сокеты по-прежнему +должны открыться полностью. Android подтверждает запуск по `SERVICE_STARTED`, а не +по успешному созданию потока; после события передаётся свежий снимок адресов. + | Владелец | Ресурсы | |---|---| | Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг | diff --git a/doc/tasks.md b/doc/tasks.md index 9dfdc82b..406f680d 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,6 +4,13 @@ ## Текущие задачи +[ ] **Android: отмена асинхронного запуска до готовности ядра** — при проверке + мониторинга 12.10.2026 обнаружено: `NativeLib.stop()` пропускает native stop, + если `isReady()==false`, а `instance_lite_stop()` возвращается при `g_inst==NULL`. + Поэтому запрос stop до завершения конструктора может не отменить запускаемый + поток. Согласовать отмену с состоянием потока и единым сигналом завершения; + проверить stop до SERVICE_STARTED, отказ конструктора и последующий start. + [ ] **Android: повторные отказы persisted node snapshots** — замечено 12.10.2026 на устройстве D2AR10P3600820 при проверке live media. В сохранённой БД ряд узлов регулярно получает `invalid persisted snapshot` в `topo_node_sqlite_snapshot_load`, diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index 3175c07e..7053ec33 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -1187,8 +1187,7 @@ static void auto_socket_netlink_cb(int fd, void* arg) { static int auto_socket_init_monitor(struct AUTO_SOCKET* as) { as->nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (as->nl_sock < 0) { - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink socket: %s — relying on JNI/scan_all for updates", strerror(errno)); - return 0; + DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink socket failed: %s", strerror(errno)); return -1; } struct sockaddr_nl nladdr; @@ -1196,12 +1195,15 @@ static int auto_socket_init_monitor(struct AUTO_SOCKET* as) { nladdr.nl_family = AF_NETLINK; nladdr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE; if (bind(as->nl_sock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) { - DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink bind: %s — relying on JNI/scan_all for updates", strerror(errno)); - close(as->nl_sock); as->nl_sock = -1; return 0; + DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink bind failed: %s", strerror(errno)); + close(as->nl_sock); as->nl_sock = -1; return -1; + } + if (socket_set_nonblocking(as->nl_sock) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] netlink nonblocking mode failed: %s", strerror(errno)); + close(as->nl_sock); as->nl_sock = -1; return -1; } - socket_set_nonblocking(as->nl_sock); as->uasync_handle = uasync_add_socket(as->instance->ua, as->nl_sock, auto_socket_netlink_cb, NULL, NULL, "netlink_autosock", as); - if (!as->uasync_handle) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] uasync_add_socket failed"); close(as->nl_sock); as->nl_sock = -1; return 0; } + if (!as->uasync_handle) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] uasync_add_socket failed"); close(as->nl_sock); as->nl_sock = -1; return -1; } DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] netlink monitor started: fd=%d", (int)as->nl_sock); return 0; } @@ -1303,6 +1305,11 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) { struct AUTO_SOCKET* as = u_calloc(1, sizeof(*as)); if (!as) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return -1; } +#ifdef __linux__ + as->nl_sock = -1; +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + as->route_sock = -1; +#endif as->instance = inst; as->mode = (uint8_t)inst->config->global.auto_sockets; inst->auto_socket_state = as; @@ -1343,7 +1350,7 @@ void auto_socket_destroy(struct UTUN_INSTANCE* inst) { struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; inst->auto_socket_state = NULL; - auto_socket_destroy_monitor(as); + if (as->mode != CFG_AUTO_SOCKETS_ANDROID) auto_socket_destroy_monitor(as); while (as->ifaces) { struct auto_sock_iface* ifa = as->ifaces; diff --git a/src/transport_layer/socket_monitor.c b/src/transport_layer/socket_monitor.c index 5260ccd8..8f4c72e4 100644 --- a/src/transport_layer/socket_monitor.c +++ b/src/transport_layer/socket_monitor.c @@ -413,6 +413,10 @@ static void WINAPI route_change_cb(PVOID ctx, PMIB_IPFORWARD_ROW2 row, MIB_NOTIF int socket_monitor_init(struct UTUN_INSTANCE* instance) { if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "instance is NULL"); return -1; } if (instance->socket_monitor) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket monitor already initialized"); return -1; } + if (instance->config && instance->config->global.auto_sockets == CFG_AUTO_SOCKETS_ANDROID) { + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "Socket monitoring delegated to external network callbacks (auto_sockets=android)"); + return 0; + } DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "initializing socket monitor"); #ifdef __linux__ diff --git a/src/transport_layer/socket_monitor.h b/src/transport_layer/socket_monitor.h index cb357566..43280f44 100644 --- a/src/transport_layer/socket_monitor.h +++ b/src/transport_layer/socket_monitor.h @@ -12,6 +12,7 @@ extern "C" { struct UTUN_INSTANCE; struct ETCP_SOCKET; +/* В режиме auto_sockets=android адресами управляет frontend; нативный монитор не создаётся. */ int socket_monitor_init(struct UTUN_INSTANCE* instance); void socket_monitor_destroy(struct UTUN_INSTANCE* instance); diff --git a/tests/Makefile.am b/tests/Makefile.am index 4f142a1a..0987f8ff 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -832,7 +832,8 @@ test_services_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) test_tun_route_SOURCES = test_tun_route.c test_tun_route_LDADD = $(LIBUTUN) $(COMMON_LIBS) -test_tun_route_LDFLAGS = -Wl,--wrap=sendto -Wl,--wrap=recvfrom -Wl,--wrap=poll -Wl,--wrap=tun_platform_init +test_tun_route_LDFLAGS = -Wl,--wrap=sendto -Wl,--wrap=recvfrom -Wl,--wrap=poll -Wl,--wrap=tun_platform_init \ + -Wl,--wrap=socket -Wl,--wrap=bind test_member_files_SOURCES = test_member_files.c test_member_files_CFLAGS = $(test_services_CFLAGS) diff --git a/tests/test_tun_route.c b/tests/test_tun_route.c index 9621a615..fc8d6471 100644 --- a/tests/test_tun_route.c +++ b/tests/test_tun_route.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -12,6 +14,8 @@ #include "tun_route.h" #include "tun_if.h" #include "chat/chat_core.h" +#include "transport_layer/auto_socket.h" +#include "transport_layer/etcp_connections.h" #include "../lib/debug_config.h" #include "../lib/mem.h" @@ -20,6 +24,19 @@ static uint32_t added[8], removed[8], request_seq; static uint16_t request_cmd; static int ack_error; static int fail_tun; +static int deny_netlink, netlink_calls; + +int __real_socket(int, int, int); +int __wrap_socket(int domain, int type, int protocol) { + if (domain == AF_NETLINK) netlink_calls++; + return __real_socket(domain, type, protocol); +} + +int __real_bind(int, const struct sockaddr*, socklen_t); +int __wrap_bind(int fd, const struct sockaddr* addr, socklen_t len) { + if (deny_netlink && addr->sa_family == AF_NETLINK) { errno = EACCES; return -1; } + return __real_bind(fd, addr, len); +} int __real_tun_platform_init(struct tun_if*, const char*, const char*, int); int __wrap_tun_platform_init(struct tun_if* tun, const char* name, const char* ip, int mtu) { @@ -135,6 +152,7 @@ static void check_mock(void) { /* Ошибка второго listen-сокета отклоняет весь запуск, включая начальный auto_sockets=android. */ static void check_socket_failure(int automatic) { + int stdin_flags = fcntl(STDIN_FILENO, F_GETFD); assert(stdin_flags >= 0); int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0); struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) }; assert(bind(blocker, (struct sockaddr*)&address, sizeof(address)) == 0 && listen(blocker, 1) == 0); @@ -147,9 +165,38 @@ static void check_socket_failure(int automatic) { struct UASYNC* ua = uasync_create(); assert(ua); assert(!utun_instance_create_from_str(ua, config)); uasync_drain_immediate(ua); uasync_destroy(ua, 0); close(blocker); + assert(fcntl(STDIN_FILENO, F_GETFD) == stdin_flags); DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second socket failure rejects startup and cleans opened sockets automatic=%d", automatic); } +/* Android/JNI backend запускается без разрешений netlink и принимает свежие адреса без рестарта. */ +static void check_external_monitor(void) { + const char* config = "[global]\ntun_enabled=no\nauto_sockets=android\n" + "[server:external]\naddr=127.0.0.1:0\ntransport=udp\ntype=private\n" + "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"; + int stdin_flags = fcntl(STDIN_FILENO, F_GETFD); assert(stdin_flags >= 0); + struct UASYNC* ua = uasync_create(); assert(ua); + deny_netlink = 1; netlink_calls = 0; + struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); + assert(inst && !inst->socket_monitor && inst->auto_socket_state && !netlink_calls); + inst->config->global.db_sync_enabled = 0; + assert(utun_core_start(inst) == 0 && chat_service_start(inst) == 0); + sqlite3* db = inst->topo_sqlite_db; void* chat = inst->chat_core; + struct utun_config* updated = parse_config_from_buf(config, strlen(config), "external-update"); assert(updated); + ((struct sockaddr_in*)&updated->servers->ip)->sin_addr.s_addr = htonl(0x7f000002); + auto_socket_reload_config(inst, updated); + assert(inst->core_started && inst->chat_core == chat && inst->topo_sqlite_db == db); + assert(((struct sockaddr_in*)&inst->etcp_sockets->local_addr)->sin_addr.s_addr == htonl(0x7f000002)); + assert(!netlink_calls); + destroy(inst, ua); assert(fcntl(STDIN_FILENO, F_GETFD) == stdin_flags); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS external monitoring: no netlink, core/chat ready, address update preserves DB and fd=0"); + const char* native = "[global]\ntun_enabled=no\n[server:local]\naddr=127.0.0.1:0\ntype=private\n"; + ua = uasync_create(); assert(ua); + assert(!utun_instance_create_from_str(ua, native) && netlink_calls > 0); + uasync_drain_immediate(ua); uasync_destroy(ua, 0); deny_netlink = 0; + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS native monitoring failure remains fatal"); +} + static void check_control_failure(void) { int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0); struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) }; @@ -189,6 +236,6 @@ int main(int argc, char** argv) { utun_instance_set_tun_init_enabled(1); size_t baseline = u_get_allocated_count(); if (argc == 2 && !strcmp(argv[1], "--real")) check_real(); - else { check_mock(); check_socket_failure(0); check_socket_failure(1); check_control_failure(); } + else { check_mock(); check_socket_failure(0); check_socket_failure(1); check_control_failure(); check_external_monitor(); } assert(u_get_allocated_count() == baseline); return 0; } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt index 68bf3e93..112a5aba 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt @@ -31,6 +31,7 @@ object AppEventHandler : ChatEventCallback { val events: SharedFlow> = _events override fun onEvent(type: Int, data: ByteArray?) { + if (type == 12) mainHandler.post { com.utun.chat.headless.HeadlessService.onCoreStarted() } if (type == 13 || type in 49..50) mainHandler.post { ChatApplication.instance.memberFiles.onEvent(type, data) } if (type in listOf(4, 8, 9, 12, 13, 23, 24, 25, 31) || type in 49..53) mainHandler.post { ChatApplication.instance.avatars.onEvent(type, data) } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt index 9943e6e8..5834d93a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt @@ -7,93 +7,99 @@ import android.os.Handler import android.os.Looper import com.utun.chat.ChatApplication -/** - * Слушатель смены сети (ConnectivityManager.NetworkCallback). - * - * На Android нативный netlink-мониторинг сокетов недоступен (Permission denied), - * поэтому о смене сети сообщает ConnectivityManager. При смене default-сети - * (mobile↔wifi, смена IP) пересобираем конфиг сокетов и перезапускаем C-ядро, - * чтобы оно забиндилось на актуальный интерфейс. - * - * Регистрируется в HeadlessService (foreground) — работает в фоне. - */ +/** ConnectivityManager передаёт актуальный конфиг сокетов в uasync без рестарта ядра. + * Регистрация живёт в HeadlessService; состояние очереди обновлений — на main thread. */ object NetworkChangeMonitor { private var started = false - - /* Пропуск первого onAvailable: он приходит сразу при регистрации с текущим - * состоянием сети, а не как реальное событие смены. */ - private var primed = false - - /* Рестарт в процессе — защита от наложения (C-ядро тоже имеет CAS-гарды). */ - private var restarting = false - - /* Последний известный набор интерфейсов (ifName=ip) для лога «было → стало». */ + private var generation = 0 + private var updating = false + private var pending = false private var lastInterfaces: List>? = null - private const val DEBOUNCE_MS = 500L private val handler = Handler(Looper.getMainLooper()) - private val restartRunnable = Runnable { doRestart() } + private val updateRunnable = Runnable { updateSockets() } private val callback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - if (!primed) { primed = true; return } - schedule() - } - - override fun onLost(network: Network) { - if (!primed) return - schedule() - } - - override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { - if (!primed) return - schedule() - } + override fun onAvailable(network: Network) = schedule() + override fun onLost(network: Network) = schedule() + override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) = schedule() } private fun schedule() { - handler.removeCallbacks(restartRunnable) - handler.postDelayed(restartRunnable, DEBOUNCE_MS) + handler.post { + if (!started) return@post + handler.removeCallbacks(updateRunnable) + handler.postDelayed(updateRunnable, DEBOUNCE_MS) + } } - private fun doRestart() { - if (restarting || !NativeLib.isReady()) return - restarting = true + private fun updateSockets() { + if (!started || !NativeLib.isReady()) return + if (updating) { pending = true; return } + updating = true + val token = generation Thread { + var interfaces: List>? = null + var config: String? = null try { val provider = ChatApplication.instance.configProvider - val now = provider.getAvailableInterfaces() - val prev = lastInterfaces - lastInterfaces = now - val prevStr = prev?.joinToString(", ") { (n, ip) -> "$n=$ip" } ?: "n/a" - val nowStr = now.joinToString(", ") { (n, ip) -> "$n=$ip" } - LogManager.addLog("INFO", "NET", "network change: [$prevStr] -> [$nowStr]") - NativeLib.notifyNetworkActive() - NativeLib.updateSockets(provider.buildConfigText()) + interfaces = provider.getAvailableInterfaces() + config = provider.buildConfigText() + } catch (e: Exception) { + LogManager.addLog("ERROR", "NET", "network config update failed: ${e.message}") } finally { - restarting = false + val current = interfaces + val text = config + handler.post { + if (!started || token != generation) return@post + updating = false + if (text != null && current != null && NativeLib.isReady()) { + val previous = lastInterfaces?.joinToString(", ") { (n, ip) -> "$n=$ip" } ?: "n/a" + val next = current.joinToString(", ") { (n, ip) -> "$n=$ip" } + lastInterfaces = current + LogManager.addLog("INFO", "NET", "network change: [$previous] -> [$next]") + NativeLib.notifyNetworkActive() + if (!NativeLib.updateSockets(text)) LogManager.addLog("ERROR", "NET", "socket update rejected") + } + if (pending) { pending = false; schedule() } + } } }.start() } - fun start() { - if (started) return - val ctx = ChatApplication.instance.applicationContext - val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return - /* Если сети ещё нет — первый onAvailable будет реальным событием. */ - primed = cm.activeNetwork == null - cm.registerDefaultNetworkCallback(callback) + // Повторный снимок после SERVICE_STARTED покрывает изменения сети во время инициализации. + fun onCoreStarted() = schedule() + + fun start(): Boolean { + if (started) return true + val cm = ChatApplication.instance.getSystemService(ConnectivityManager::class.java) + if (cm == null) { LogManager.addLog("ERROR", "NET", "ConnectivityManager unavailable"); return false } + try { + cm.registerDefaultNetworkCallback(callback) + } catch (e: Exception) { + LogManager.addLog("ERROR", "NET", "network listener registration failed: ${e.message}") + return false + } + generation++ started = true LogManager.addLog("INFO", "NET", "network change listener started") + return true } fun stop() { if (!started) return - val ctx = ChatApplication.instance.applicationContext - val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return - cm.unregisterNetworkCallback(callback) - handler.removeCallbacks(restartRunnable) started = false + generation++ + pending = false + updating = false + lastInterfaces = null + handler.removeCallbacks(updateRunnable) + val cm = ChatApplication.instance.getSystemService(ConnectivityManager::class.java) + try { + cm?.unregisterNetworkCallback(callback) + } catch (e: Exception) { + LogManager.addLog("WARN", "NET", "network listener removal failed: ${e.message}") + } LogManager.addLog("INFO", "NET", "network change listener stopped") } } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt index 3468072c..cd57d895 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt @@ -22,6 +22,13 @@ class HeadlessService : Service() { companion object { private var currentService: HeadlessService? = null + fun onCoreStarted() { + if (currentService == null || !NativeLib.isReady()) return + LogManager.addLog("INFO", "SRV", "uTun core started") + NetworkActivityMonitor.start() + NetworkChangeMonitor.onCoreStarted() + } + fun refreshLanguage() { currentService?.let { service -> service.createNotificationChannel() @@ -45,18 +52,19 @@ class HeadlessService : Service() { val dbDir = provider.getString("db.path") ?: filesDir.absolutePath val listenPort = provider.getInt("node.listen_port") - val configText = provider.buildConfigText() - LogManager.addLog("INFO", "SRV", "uTun service starting, port=$listenPort db=$dbDir") NativeLib.init(dbDir, 9999) - val ok = NativeLib.start(configText) + if (!NetworkChangeMonitor.start()) { + LogManager.addLog("ERROR", "SRV", "uTun startup rejected: network monitoring unavailable") + return + } + val ok = NativeLib.start(provider.buildConfigText()) if (ok) { - LogManager.addLog("INFO", "SRV", "uTun core started") - NetworkActivityMonitor.start() - NetworkChangeMonitor.start() + LogManager.addLog("INFO", "SRV", "uTun startup requested") } else { + NetworkChangeMonitor.stop() LogManager.addLog("ERROR", "SRV", "uTun start failed") } } diff --git a/tools/chatgui-android/libutun_lite/instance_lite.c b/tools/chatgui-android/libutun_lite/instance_lite.c index 58204761..6c50ce30 100644 --- a/tools/chatgui-android/libutun_lite/instance_lite.c +++ b/tools/chatgui-android/libutun_lite/instance_lite.c @@ -284,6 +284,7 @@ static void* instance_thread(void* arg) { 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); @@ -384,7 +385,7 @@ static void* instance_thread(void* arg) { g_inst = utun_instance_create_from_config(g_ua, config); my_inst = g_inst; - if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; } + 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. */