Browse Source

android: мониторинг смены сети — NetworkChangeMonitor, перезапуск ядра при смене интерфейса

v2
evgeny 2 months ago
parent
commit
7dee49ce63
  1. 1
      tools/chatgui-android/app/src/main/AndroidManifest.xml
  2. 101
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt
  3. 3
      tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt
  4. 48
      tools/chatgui-android/libutun_lite/instance_lite.c

1
tools/chatgui-android/app/src/main/AndroidManifest.xml

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

101
tools/chatgui-android/app/src/main/java/com/utun/chat/data/NetworkChangeMonitor.kt

@ -0,0 +1,101 @@
package com.utun.chat.data
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
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) — работает в фоне.
*/
object NetworkChangeMonitor {
private var started = false
/* Пропуск первого onAvailable: он приходит сразу при регистрации с текущим
* состоянием сети, а не как реальное событие смены. */
private var primed = false
/* Рестарт в процессе — защита от наложения (C-ядро тоже имеет CAS-гарды). */
private var restarting = false
/* Последний известный набор интерфейсов (ifName=ip) для лога «было → стало». */
private var lastInterfaces: List<Pair<String, String>>? = null
private const val DEBOUNCE_MS = 500L
private val handler = Handler(Looper.getMainLooper())
private val restartRunnable = Runnable { doRestart() }
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()
}
}
private fun schedule() {
handler.removeCallbacks(restartRunnable)
handler.postDelayed(restartRunnable, DEBOUNCE_MS)
}
private fun doRestart() {
if (restarting || !NativeLib.isReady()) return
restarting = true
Thread {
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()
val path = provider.getString("db.path") ?: ""
val port = provider.getInt("control.port")
NativeLib.restart(path, port, provider.buildConfigText())
} finally {
restarting = false
}
}.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)
started = true
LogManager.addLog("INFO", "NET", "network change listener started")
}
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
LogManager.addLog("INFO", "NET", "network change listener stopped")
}
}

3
tools/chatgui-android/app/src/main/java/com/utun/chat/headless/HeadlessService.kt

@ -13,6 +13,7 @@ import com.utun.chat.MainActivity
import com.utun.chat.data.LogManager
import com.utun.chat.data.NativeLib
import com.utun.chat.data.NetworkActivityMonitor
import com.utun.chat.data.NetworkChangeMonitor
class HeadlessService : Service() {
override fun onCreate() {
@ -47,6 +48,7 @@ class HeadlessService : Service() {
if (ok) {
LogManager.addLog("INFO", "SRV", "uTun core started")
NetworkActivityMonitor.start()
NetworkChangeMonitor.start()
} else {
LogManager.addLog("ERROR", "SRV", "uTun start failed")
}
@ -54,6 +56,7 @@ class HeadlessService : Service() {
override fun onDestroy() {
LogManager.addLog("INFO", "SRV", "uTun service stopping")
NetworkChangeMonitor.stop()
NetworkActivityMonitor.stop()
NativeLib.stop()
super.onDestroy()

48
tools/chatgui-android/libutun_lite/instance_lite.c

@ -499,11 +499,59 @@ void instance_lite_stop(void) {
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, "restart: %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, "restart: %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, "restart: %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, "OLD sockets");
log_config_servers(new_config_text, "NEW config");
if (!ua || !inst || !running) {
IL_LOGI("restart: instance dead, doing hard restart via stop+start");
if (inst) instance_lite_stop();

Loading…
Cancel
Save