From 5b31a765c2d200a0e4dcf9eecf8fb657ecf78f0c Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 9 Sep 2026 17:34:46 +0300 Subject: [PATCH] =?UTF-8?q?chatgui-android:=20auto=5Fsockets=3Dandroid=20?= =?UTF-8?q?=E2=80=94=20=D1=81=D0=BE=D0=BA=D0=B5=D1=82=D1=8B=20=D0=B8=D0=B7?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D0=B0,=20=D0=BB=D1=91?= =?UTF-8?q?=D0=B3=D0=BA=D0=BE=D0=B5=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B5=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auto_sockets три-режимный: off/system/android; android берёт сокеты из [server] конфига - auto_socket_reload_config: reconcile сокетов (remove→create) по имени+addr:port, сохранение портов - etcp_create_server_socket вынесен из init_sockets; stcp_server_list_find_by_sock - instance_lite_update_sockets + JNI nativeUpdateSockets + NativeLib.updateSockets - NetworkChangeMonitor: updateSockets вместо restart; порты сохраняются при смене сети --- src/config_parser.c | 4 +- src/config_parser.h | 8 +- src/transport_layer/auto_socket.c | 120 +++++++++++++++++- src/transport_layer/auto_socket.h | 6 + src/transport_layer/etcp_connections.c | 76 ++++++----- src/transport_layer/etcp_connections.h | 3 + src/transport_layer/stcp_link.c | 7 + src/transport_layer/stcp_link.h | 1 + .../java/com/utun/chat/data/ConfigProvider.kt | 1 + .../main/java/com/utun/chat/data/NativeLib.kt | 6 + .../utun/chat/data/NetworkChangeMonitor.kt | 4 +- .../jni_bridge/android_jni_bridge.c | 16 +++ .../jni_bridge/android_jni_bridge.h | 4 + .../libutun_lite/instance_lite.c | 51 +++++++- .../libutun_lite/instance_lite.h | 5 + 15 files changed, 266 insertions(+), 46 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 06246007..25567af8 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -407,7 +407,9 @@ static int parse_global(const char *key, const char *value, struct global_config return 0; } if (strcmp(key, "auto_sockets") == 0) { - global->auto_sockets = strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0; + if (strcasecmp(value, "android") == 0) global->auto_sockets = CFG_AUTO_SOCKETS_ANDROID; + else if (strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0) global->auto_sockets = CFG_AUTO_SOCKETS_SYSTEM; + else global->auto_sockets = CFG_AUTO_SOCKETS_OFF; return 0; } if (strcmp(key, "auto_socket_skip_no_default_route") == 0) { diff --git a/src/config_parser.h b/src/config_parser.h index 0f99b73d..c03edec8 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -29,6 +29,12 @@ struct IP { #define CFG_SERVER_TYPE_PRIVATE 3 #define CFG_SERVER_TYPE_LOCAL 4 +/* Режимы auto_sockets: OFF — ручные [server]; SYSTEM — авто-сокеты из системных + * интерфейсов; ANDROID — авто-сокеты из [server] секций конфига (генерирует Kotlin). */ +#define CFG_AUTO_SOCKETS_OFF 0 +#define CFG_AUTO_SOCKETS_SYSTEM 1 +#define CFG_AUTO_SOCKETS_ANDROID 2 + #define CFG_IPV6_MODE_NONE 0 // normal explicit address #define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface #define CFG_IPV6_MODE_PERMANENT 2 // permanent IPv6 from interface @@ -189,7 +195,7 @@ struct global_config { // TCP proxy server (exit node) configuration ([tcp_proxy_server] section) int tcp_proxy_server_enabled; - int auto_sockets; // 1 = auto-create sockets from interfaces, skip [server] + int auto_sockets; // CFG_AUTO_SOCKETS_*: OFF/SYSTEM/ANDROID (см. константы ниже) int auto_socket_skip_no_default_route; // 1 = в авто-режиме не создавать сокеты на интерфейсах без default-маршрута int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС) int tcp_proxy_server_fib; // FreeBSD SO_SETFIB, 0=не менять diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index c19d37d2..9c244a5c 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -144,6 +144,7 @@ struct auto_sock_iface { struct AUTO_SOCKET { struct UTUN_INSTANCE* instance; // родительский utun instance struct auto_sock_iface* ifaces; // связный список интерфейсов + uint8_t mode; // CFG_AUTO_SOCKETS_SYSTEM или CFG_AUTO_SOCKETS_ANDROID uint8_t v4_usable; // есть ли сейчас рабочий v4-сокет с реальным IP uint8_t v6_usable; // есть ли сейчас рабочий v6-сокет с реальным IP uint8_t v4_addr_changed; // v4-адрес или тип изменился с прошлой проверки @@ -167,6 +168,7 @@ static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex); static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, int* out_has_v4, int* out_has_v6, uint8_t* out_v4_type, uint8_t* out_v6_type); static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname); static void auto_socket_scan_all(struct AUTO_SOCKET* as); +static void auto_socket_reconcile_config(struct AUTO_SOCKET* as); /* ─── DB helpers (port persistence) ─── * Сохраняют порт сокета в SQLite чтобы переиспользовать его при перезапуске utun. @@ -1318,14 +1320,23 @@ static void auto_socket_destroy_monitor(struct AUTO_SOCKET* as) { (void)as; } */ int auto_socket_init(struct UTUN_INSTANCE* inst) { if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] instance is NULL"); return -1; } - if (!inst->config || !inst->config->global.auto_sockets) return 0; + if (!inst->config || inst->config->global.auto_sockets == CFG_AUTO_SOCKETS_OFF) return 0; if (inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] already initialized"); return -1; } struct AUTO_SOCKET* as = u_calloc(1, sizeof(*as)); if (!as) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] alloc failed"); return -1; } as->instance = inst; + as->mode = (uint8_t)inst->config->global.auto_sockets; inst->auto_socket_state = as; + /* Android-режим: источник сокетов — [server] секции конфига (генерирует Kotlin). + * Системный мониторинг и таблица портов в БД не нужны. */ + if (as->mode == CFG_AUTO_SOCKETS_ANDROID) { + auto_socket_reconcile_config(as); + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized (android mode)"); + return 0; + } + if (inst->topo_sqlite_db) { sqlite3_exec(inst->topo_sqlite_db, "CREATE TABLE IF NOT EXISTS auto_socket_ports (" @@ -1375,5 +1386,112 @@ static void auto_socket_scan_all_trampoline(void* arg) { void auto_socket_on_network_change(struct UTUN_INSTANCE* inst) { if (!inst || !inst->auto_socket_state) return; struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; + /* Android-режим: обновление идёт только через auto_socket_reload_config (Kotlin → JNI). */ + if (as->mode == CFG_AUTO_SOCKETS_ANDROID) return; uasync_post(as->instance->ua, auto_socket_scan_all_trampoline, as); } + +/* ═══════════ Android-режим: reconcile сокетов из [server] секций конфига ═══════════ */ + +/* Сравнивает CFG_SERVER с существующим ETCP_SOCKET: имя, адрес+порт, тип, транспорт. */ +static int server_matches_socket(const struct CFG_SERVER* server, const struct ETCP_SOCKET* sock) { + if (!server || !sock) return 0; + if (strcmp(server->name, sock->name) != 0) return 0; + if (server->transport != (uint8_t)sock->is_tcp) return 0; + if (server->type != sock->type) return 0; + const struct sockaddr_storage* a = &server->ip; + const struct sockaddr_storage* b = sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr; + if (a->ss_family != b->ss_family) return 0; + if (a->ss_family == AF_INET) { + const struct sockaddr_in* ia = (const struct sockaddr_in*)a; + const struct sockaddr_in* ib = (const struct sockaddr_in*)b; + return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port; + } + if (a->ss_family == AF_INET6) { + const struct sockaddr_in6* ia = (const struct sockaddr_in6*)a; + const struct sockaddr_in6* ib = (const struct sockaddr_in6*)b; + return memcmp(&ia->sin6_addr, &ib->sin6_addr, 16) == 0 && ia->sin6_port == ib->sin6_port; + } + return 0; +} + +/* Удаляет один сокет (UDP или TCP) и все его линки. */ +static void remove_config_socket(struct AUTO_SOCKET* as, struct ETCP_SOCKET* sock) { + if (!sock) return; + ncd_remove_socket_links(as->instance, sock); + if (sock->is_tcp) { + struct stcp_server* tsrv = stcp_server_list_find_by_sock(as->instance, sock); + if (tsrv) { + stcp_server_list_remove(as->instance, tsrv); + stcp_link_server_destroy(tsrv); + } + tcp_socket_remove(sock); + } else { + etcp_socket_remove(sock); + } +} + +/* Сверяет inst->config->servers с inst->etcp_sockets и устраняет расхождения: + * удаляет сокеты, которых нет/которые изменились (проход 1), создаёт отсутствующие (проход 2). */ +static void auto_socket_reconcile_config(struct AUTO_SOCKET* as) { + struct UTUN_INSTANCE* inst = as->instance; + if (!inst || !inst->config) return; + + /* Проход 1: удалить сокеты, которых нет в новом списке, либо которые изменились. */ + struct ETCP_SOCKET* sock = inst->etcp_sockets; + while (sock) { + struct ETCP_SOCKET* next = sock->next; + const struct CFG_SERVER* srv = NULL; + for (const struct CFG_SERVER* s = inst->config->servers; s; s = s->next) + if (strcmp(s->name, sock->name) == 0) { srv = s; break; } + ip_str_t old_addr = sockaddr_storage_to_str(sock->local_addr.ss_family ? &sock->local_addr : &sock->interface_addr); + if (!srv) { + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] removing socket %s (%s) addr=%s (no longer in config)", + sock->name, sock->is_tcp ? "tcp" : "udp", old_addr.str); + remove_config_socket(as, sock); + } else if (!server_matches_socket(srv, sock)) { + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] socket %s changed: %s → %s — rebuild", + sock->name, old_addr.str, sockaddr_storage_to_str(&srv->ip).str); + remove_config_socket(as, sock); + } + sock = next; + } + + /* Проход 2: создать сокеты, которых ещё нет. */ + for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) { + int exists = 0; + for (struct ETCP_SOCKET* es = inst->etcp_sockets; es; es = es->next) + if (strcmp(es->name, s->name) == 0) { exists = 1; break; } + if (exists) continue; + struct ETCP_SOCKET* created = NULL; + if (etcp_create_server_socket(inst, s, &created) == 0 && created) { + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] created socket %s (%s) addr=%s", + s->name, s->transport ? "tcp" : "udp", sockaddr_storage_to_str(&s->ip).str); + ncd_add_socket_links(inst, created); + } + } + + auto_socket_post_sockets_changed(as); +} + +void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg) { + if (!new_cfg) return; + if (!inst || !inst->auto_socket_state) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] reload_config: invalid args"); free_config(new_cfg); return; } + struct AUTO_SOCKET* as = (struct AUTO_SOCKET*)inst->auto_socket_state; + if (as->mode != CFG_AUTO_SOCKETS_ANDROID) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] reload_config: not in android mode"); free_config(new_cfg); return; } + + /* Сводка до/после: сколько серверов было → стало. */ + int old_count = 0; for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) old_count++; + int new_count = 0; for (struct CFG_SERVER* s = new_cfg->servers; s; s = s->next) new_count++; + + /* Переносим новый список серверов в inst->config, старый освобождаем. */ + struct CFG_SERVER* old = inst->config->servers; + inst->config->servers = new_cfg->servers; + new_cfg->servers = NULL; + while (old) { struct CFG_SERVER* n = old->next; u_free(old); old = n; } + free_config(new_cfg); + + DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] reload_config: %d → %d servers", old_count, new_count); + + auto_socket_reconcile_config(as); +} diff --git a/src/transport_layer/auto_socket.h b/src/transport_layer/auto_socket.h index cc8c60ff..d97d4171 100644 --- a/src/transport_layer/auto_socket.h +++ b/src/transport_layer/auto_socket.h @@ -38,6 +38,7 @@ extern "C" { struct UTUN_INSTANCE; struct ETCP_SOCKET; +struct utun_config; /* Внутреннее состояние модуля — хранится в inst->auto_socket_state. * Содержит связный список отслеживаемых интерфейсов (ifaces) и платформенные @@ -58,6 +59,11 @@ void auto_socket_destroy(struct UTUN_INSTANCE* inst); * Откладывает полное сканирование (auto_socket_scan_all) в uasync-поток. */ void auto_socket_on_network_change(struct UTUN_INSTANCE* inst); +/* Android-режим (auto_sockets=android): обновить сокеты из [server] секций нового + * конфига. new_cfg — уже распарсенный конфиг; его список servers переносится в + * inst->config, остальное освобождается. Должен вызываться в uasync-потоке. */ +void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg); + /* ─── Platform-agnostic API: управление сокетами на конкретном интерфейсе ─── */ /* diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 0e41fcbf..6baeab43 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -2333,6 +2333,41 @@ int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, // Initialize only sockets (servers for incoming connections) // Called before topo_group_init() to populate etcp_sockets for nodeinfo // Returns: 0 = all OK, 1 = partial success (some sockets failed), -1 = fatal error (no sockets) +/* Создаёт один серверный сокет из CFG_SERVER: UDP (etcp_socket_add) или TCP + * (tcp_socket_add + stcp_server_listen + stcp_server_list_add). + * Линки к соединениям не добавляет — это забота вызывающего. + * Возвращает 0 при успехе и *out_sock = созданный сокет, -1 при ошибке. */ +int etcp_create_server_socket(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server, struct ETCP_SOCKET** out_sock) { + if (!instance || !server || !out_sock) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_create_server_socket: invalid args"); return -1; } + *out_sock = NULL; + + if (server->transport) { + uint16_t port = 0; + if (server->ip.ss_family == AF_INET) port = ntohs(((struct sockaddr_in*)&server->ip)->sin_port); + else if (server->ip.ss_family == AF_INET6) port = ntohs(((struct sockaddr_in6*)&server->ip)->sin6_port); + + struct ETCP_SOCKET* ts = tcp_socket_add(instance, server); + if (!ts) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "tcp_socket_add failed for %s", server->name); return -1; } + + struct stcp_link_config scfg = {.inst = instance, .listen_family = server->ip.ss_family}; + struct stcp_server* tsrv = stcp_server_listen(&scfg, port, tcp_server_on_link, ts); + if (!tsrv) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP server for %s", server->name); + tcp_socket_remove(ts); + return -1; + } + stcp_server_list_add(instance, tsrv); + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server %s on port %u", server->name, port); + *out_sock = ts; + return 0; + } + + struct ETCP_SOCKET* e_sock = etcp_socket_add(instance, server); + if (!e_sock) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create socket for server %s", server->name); return -1; } + *out_sock = e_sock; + return 0; +} + int init_sockets(struct UTUN_INSTANCE* instance) { DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, ""); if (!instance || !instance->config) return -1; @@ -2401,52 +2436,27 @@ int init_sockets(struct UTUN_INSTANCE* instance) { } } - // TCP transport: create stcp_server instead of UDP socket - if (server->transport) { - uint16_t port = 0; - if (server->ip.ss_family == AF_INET) port = ntohs(((struct sockaddr_in*)&server->ip)->sin_port); - else if (server->ip.ss_family == AF_INET6) port = ntohs(((struct sockaddr_in6*)&server->ip)->sin6_port); - - struct ETCP_SOCKET* ts = tcp_socket_add(instance, server); - if (!ts) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "tcp_socket_add failed for %s", server->name); fail_count++; server = server->next; continue; } - - struct stcp_link_config scfg = {.inst = instance, .listen_family = server->ip.ss_family}; - struct stcp_server *tsrv = stcp_server_listen(&scfg, port, tcp_server_on_link, ts); - if (!tsrv) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP server for %s", server->name); - tcp_socket_remove(ts); - fail_count++; - } else { - stcp_server_list_add(instance, tsrv); - success_count++; - DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server %s on port %u", server->name, port); - } + struct ETCP_SOCKET* sock = NULL; + if (etcp_create_server_socket(instance, server, &sock) != 0) { + fail_count++; server = server->next; continue; } - - struct ETCP_SOCKET* e_sock = etcp_socket_add(instance, server); - if (e_sock && default_ip != 0) { - e_sock->local_defaultroute_ip = default_ip; + if (sock && !server->transport && default_ip != 0) { + sock->local_defaultroute_ip = default_ip; if (server->ip.ss_family == AF_INET) { struct in_addr addr; addr.s_addr = default_ip; DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Server %s type=%s ip=%s", server->name, server_type_str(server->type), ip_to_str(&addr, AF_INET).str); } } - if (e_sock && have_default_ip6) { - memcpy(e_sock->local_defaultroute_ip6, default_ip6, 16); - } - if (!e_sock) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create socket for server %s", server->name); - fail_count++; - server = server->next; - continue; + if (sock && !server->transport && have_default_ip6) { + memcpy(sock->local_defaultroute_ip6, default_ip6, 16); } success_count++; DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Initialized server %s on %s (links: %d)", - server->name, sockaddr_storage_to_str(&server->ip).str, queue_entry_count(e_sock->links_queue)); + server->name, sockaddr_storage_to_str(&server->ip).str, queue_entry_count(sock->links_queue)); server = server->next; } diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h index 7e10a515..a1b98246 100644 --- a/src/transport_layer/etcp_connections.h +++ b/src/transport_layer/etcp_connections.h @@ -348,6 +348,9 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn); struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server); void tcp_socket_remove(struct ETCP_SOCKET* sock); +// Создаёт один серверный сокет (UDP или TCP) из CFG_SERVER. Линки не добавляет. +int etcp_create_server_socket(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server, struct ETCP_SOCKET** out_sock); + struct stcp_link; void tcp_server_on_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock); diff --git a/src/transport_layer/stcp_link.c b/src/transport_layer/stcp_link.c index fe8eb74d..81643c21 100644 --- a/src/transport_layer/stcp_link.c +++ b/src/transport_layer/stcp_link.c @@ -420,3 +420,10 @@ int stcp_server_list_count(struct UTUN_INSTANCE *inst) { for (struct stcp_server *s = inst->stcp_servers; s; s = s->next) n++; return n; } + +struct stcp_server *stcp_server_list_find_by_sock(struct UTUN_INSTANCE *inst, const struct ETCP_SOCKET *sock) { + if (!inst || !sock) return NULL; + for (struct stcp_server *s = inst->stcp_servers; s; s = s->next) + if (s->tcp_sock == sock) return s; + return NULL; +} diff --git a/src/transport_layer/stcp_link.h b/src/transport_layer/stcp_link.h index 27c0bbf4..c456d164 100644 --- a/src/transport_layer/stcp_link.h +++ b/src/transport_layer/stcp_link.h @@ -39,6 +39,7 @@ void stcp_server_list_add(struct UTUN_INSTANCE *inst, struct stcp_server *srv); void stcp_server_list_remove(struct UTUN_INSTANCE *inst, struct stcp_server *srv); void stcp_server_list_destroy_all(struct UTUN_INSTANCE *inst); int stcp_server_list_count(struct UTUN_INSTANCE *inst); +struct stcp_server *stcp_server_list_find_by_sock(struct UTUN_INSTANCE *inst, const struct ETCP_SOCKET *sock); // ====== TCP client link ====== diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index e5a07c19..136c2ec1 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -333,6 +333,7 @@ class ConfigProvider(private val context: Context) { appendLine("db_path=$dbDir") appendLine("db_sync_enabled=1") appendLine("chatserver_enabled=1") + appendLine("auto_sockets=android") appendLine() for (srv in servers) appendServerSection(this, srv, false) for (srv in commented) appendServerSection(this, srv, true) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt index da8b4370..3f3ed32b 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt @@ -44,6 +44,11 @@ object NativeLib { return nativeRestart(configText) } + /** Лёгкое обновление сокетов (auto_sockets=android) без полного рестарта ядра */ + fun updateSockets(configText: String): Boolean { + return nativeUpdateSockets(configText) + } + fun isReady(): Boolean = nativeIsRunning() fun getMyNodeId(): Long = nativeGetMyNodeId() @@ -156,6 +161,7 @@ object NativeLib { private external fun nativeRequestInviteCandidates(channelId: String) private external fun nativeRequestInviteLink(channelId: String, targetNodeId: Long) private external fun nativeRestart(configText: String): Boolean + private external fun nativeUpdateSockets(configText: String): Boolean private external fun nativePing() private external fun nativeIsResponsive(): Boolean private external fun nativeIsRunning(): Boolean 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 0a37e999..9943e6e8 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 @@ -69,9 +69,7 @@ object NetworkChangeMonitor { val nowStr = now.joinToString(", ") { (n, ip) -> "$n=$ip" } LogManager.addLog("INFO", "NET", "network change: [$prevStr] -> [$nowStr]") NativeLib.notifyNetworkActive() - val path = provider.getString("db.path") ?: "" - val port = provider.getInt("control.port") - NativeLib.restart(path, port, provider.buildConfigText()) + NativeLib.updateSockets(provider.buildConfigText()) } finally { restarting = false } diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index feff8c00..684d6c83 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -1014,6 +1014,11 @@ void utun_bridge_restart(const char* config_text) { bridge_log(BLEV_ERROR, "bridge restart: instance not ready after 5s"); } +void utun_bridge_update_sockets(const char* config_text) { + bridge_log(BLEV_INFO, "bridge update_sockets: posting to instance"); + instance_lite_update_sockets(config_text); +} + void utun_bridge_ping(void) { instance_lite_ping(); } @@ -1519,6 +1524,17 @@ JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeRestart( return JNI_TRUE; } +JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeUpdateSockets( + JNIEnv* env, jobject thiz, jstring configText) { + (void)thiz; + if (!configText) return JNI_FALSE; + const char* cfg = (*env)->GetStringUTFChars(env, configText, NULL); + if (!cfg) return JNI_FALSE; + utun_bridge_update_sockets(cfg); + (*env)->ReleaseStringUTFChars(env, configText, cfg); + return JNI_TRUE; +} + JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_NativeLib_nativeIsResponsive( JNIEnv* env, jobject thiz) { (void)env; (void)thiz; diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.h b/tools/chatgui-android/jni_bridge/android_jni_bridge.h index bcb6df67..a7dde845 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.h +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.h @@ -70,6 +70,10 @@ uint64_t utun_bridge_get_my_node_id(void); void utun_bridge_restart(const char* config_text); +/* ── Лёгкое обновление сокетов (auto_sockets=android) ── */ + +void utun_bridge_update_sockets(const char* config_text); + /* ── Health check ── */ void utun_bridge_ping(void); diff --git a/tools/chatgui-android/libutun_lite/instance_lite.c b/tools/chatgui-android/libutun_lite/instance_lite.c index 72292ab4..1bd60ec9 100644 --- a/tools/chatgui-android/libutun_lite/instance_lite.c +++ b/tools/chatgui-android/libutun_lite/instance_lite.c @@ -24,6 +24,7 @@ #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" @@ -499,9 +500,9 @@ void instance_lite_stop(void) { g_thread_exited = 0; } -/* Логирует текущие активные сокеты (имя=ip:port(протокол)) — состояние ДО рестарта. */ +/* Логирует текущие активные сокеты (имя=ip:port(протокол)) — состояние ДО изменения. */ static void log_active_sockets(struct UTUN_INSTANCE* inst, const char* label) { - if (!inst) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "restart: %s: (no instance)", label); return; } + 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) { @@ -511,10 +512,10 @@ static void log_active_sockets(struct UTUN_INSTANCE* inst, const char* label) { 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, "restart: %s:%s", label, buf[0] ? buf : " (none)"); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "%s:%s", label, buf[0] ? buf : " (none)"); } -/* Логирует [server:...]/addr= секции нового конфига — состояние ПОСЛЕ рестарта. */ +/* Логирует [server:...]/addr= секции нового конфига — состояние ПОСЛЕ изменения. */ static void log_config_servers(const char* cfg, const char* label) { char buf[768] = ""; int off = 0; @@ -539,7 +540,7 @@ static void log_config_servers(const char* cfg, const char* label) { while (*p && *p != '\n') p++; if (*p == '\n') p++; } - DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "restart: %s:%s", label, buf[0] ? buf : " (none)"); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "%s:%s", label, buf[0] ? buf : " (none)"); } void instance_lite_restart(const char* new_config_text) { @@ -549,8 +550,8 @@ void instance_lite_restart(const char* new_config_text) { int running = __atomic_load_n(&g_running, __ATOMIC_ACQUIRE); /* Диагностика смены сети: какие сокеты/адреса были → какие будут */ - log_active_sockets(inst, "OLD sockets"); - log_config_servers(new_config_text, "NEW config"); + 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"); @@ -568,6 +569,42 @@ void instance_lite_restart(const char* new_config_text) { 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; diff --git a/tools/chatgui-android/libutun_lite/instance_lite.h b/tools/chatgui-android/libutun_lite/instance_lite.h index 8f3d611f..c3c16b1a 100644 --- a/tools/chatgui-android/libutun_lite/instance_lite.h +++ b/tools/chatgui-android/libutun_lite/instance_lite.h @@ -45,6 +45,11 @@ void instance_lite_regenerate_keys(void); * Posts to uasync thread — restart happens asynchronously. */ void instance_lite_restart(const char* new_config_text); +/* Лёгкое обновление сокетов (auto_sockets=android): перечитывает [server] секции + * из нового конфига и сверяет сокеты без полного рестарта. Posts to uasync thread. + * Если ядро не запущено — только логирует ошибку и дропает вызов. */ +void instance_lite_update_sockets(const char* config_text); + /* Health check: posts a ping to uasync, returns responsiveness after delay */ void instance_lite_ping(void); int instance_lite_is_responsive(void);