From eda65ece52edb44ab771b357af8b2697c22bf90f Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 25 Oct 2026 16:23:41 +0100 Subject: [PATCH] Generate Android autosockets per selected protocol and retain ports --- .../java/com/utun/chat/data/ConfigProvider.kt | 50 +++++++++++-------- .../java/com/utun/chat/data/ServerEntry.kt | 22 +++++++- 2 files changed, 50 insertions(+), 22 deletions(-) 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 75575dce..aa7e784d 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 @@ -21,7 +21,6 @@ import java.net.NetworkInterface import java.io.File import java.io.IOException import java.util.UUID -import java.util.concurrent.ThreadLocalRandom private val Context.dataStore by preferencesDataStore("utun_config") @@ -82,6 +81,7 @@ class ConfigProvider(private val context: Context) { private val chatLocalBroadcastsEnabled = intPreferencesKey("chat_local_broadcasts_enabled") private val firstLaunchDone = booleanPreferencesKey("first_launch_done") private val connectionModeKey = stringPreferencesKey("connection_mode") + private val autoSocketProtocolsKey = stringPreferencesKey("auto_socket_protocols") private val serviceEnabledKey = booleanPreferencesKey("service_enabled") private val startOnBootKey = booleanPreferencesKey("start_on_boot") private val soundEnabledKey = booleanPreferencesKey("sound_enabled") @@ -430,6 +430,19 @@ class ConfigProvider(private val context: Context) { context.dataStore.edit { prefs -> prefs[connectionModeKey] = mode } } + fun getAutoSocketProtocols(): AutoSocketProtocols = runBlocking { + val value = context.dataStore.data.first()[autoSocketProtocolsKey] ?: return@runBlocking AutoSocketProtocols.BOTH + AutoSocketProtocols.values().firstOrNull { it.configValue == value } ?: run { + LogManager.addLog("WARN", "CFG", "invalid auto_socket_protocols='$value', using tcp+udp") + AutoSocketProtocols.BOTH + } + } + + suspend fun setAutoSocketProtocols(protocols: AutoSocketProtocols) { + context.dataStore.edit { prefs -> prefs[autoSocketProtocolsKey] = protocols.configValue } + LogManager.addLog("INFO", "CFG", "auto_socket_protocols=${protocols.configValue}") + } + /* ── Background service enabled/disabled ── */ val serviceEnabledFlow: Flow = context.dataStore.data.map { it[serviceEnabledKey] ?: true } @@ -584,6 +597,7 @@ class ConfigProvider(private val context: Context) { suspend fun detectInterfaces(saved: List = emptyList()): List { val servers = mutableListOf() val seen = mutableSetOf() + val protocols = getAutoSocketProtocols() try { val nis = NetworkInterface.getNetworkInterfaces() while (nis.hasMoreElements()) { @@ -599,16 +613,7 @@ class ConfigProvider(private val context: Context) { val isV6 = ip.contains(":") val key = "${ni.name}/${if (isV6) "v6" else "v4"}" if (!seen.add(key)) continue - val savedPort = saved.firstOrNull { - it.ifName == ni.name && it.bindIp.contains(":") == isV6 - }?.port - servers.add(ServerEntry( - id = UUID.randomUUID().toString(), - ifName = ni.name, - bindIp = ip, - protocol = "tcp", - port = savedPort ?: ThreadLocalRandom.current().nextInt(2000, 10000) - )) + servers.addAll(protocols.createServers(ni.name, ip, saved)) } } } catch (e: Exception) { @@ -636,12 +641,13 @@ class ConfigProvider(private val context: Context) { val trimmed = line.trim() val commented = trimmed.startsWith("#") val body = if (commented) trimmed.removePrefix("#").trim() else trimmed - if (body.startsWith("[server:") && body.endsWith("]")) { + if (body.startsWith("[") && body.endsWith("]")) { if (sectionOpen && ifName.isNotEmpty() && addrPort > 0 && (!sectionCommented || includeCommented)) raw.add(RawSrv(ifName, family, addrPort, proto)) sectionOpen = false; sectionCommented = commented; addrPort = 0; proto = "tcp"; ifName = ""; family = "" + if (!body.startsWith("[server:")) continue if (commented && !includeCommented) continue - val section = body.removeSurrounding("[", "]").removePrefix("server:") + val section = body.removeSurrounding("[", "]").removePrefix("server:").removeSuffix("_tcp").removeSuffix("_udp") val lastUnderscore = section.lastIndexOf('_') if (lastUnderscore > 0) { ifName = section.substring(0, lastUnderscore) @@ -658,7 +664,10 @@ class ConfigProvider(private val context: Context) { } if (sectionOpen && ifName.isNotEmpty() && addrPort > 0 && (!sectionCommented || includeCommented)) raw.add(RawSrv(ifName, family, addrPort, proto)) - } catch (_: Exception) { return emptyList() } + } catch (e: Exception) { + LogManager.addLog("ERROR", "CFG", "loadServersFromConfig failed path=${file.path}: ${e.message}") + return emptyList() + } val liveIps = getAvailableInterfaces() return raw.map { rs -> @@ -694,6 +703,7 @@ class ConfigProvider(private val context: Context) { appendLine("db_sync_enabled=1") appendLine("chatserver_enabled=1") appendLine("auto_sockets=android") + appendLine("auto_socket_protocols=${getAutoSocketProtocols().configValue}") appendLine() for (srv in servers) appendServerSection(this, srv, false) for (srv in commented) appendServerSection(this, srv, true) @@ -777,7 +787,7 @@ class ConfigProvider(private val context: Context) { val family = if (srv.bindIp.contains(":")) "v6" else "v4" val p = if (comment) "#" else "" val addr = if (family == "v6") "[::]" else srv.bindIp.ifBlank { "0.0.0.0" } - sb.appendLine("${p}[server:${srv.ifName}_$family]") + sb.appendLine("${p}[server:${srv.sectionName}]") sb.appendLine("${p}addr=$addr:${srv.port}") sb.appendLine("${p}type=public") sb.appendLine("${p}transport=${srv.protocol}") @@ -814,13 +824,13 @@ class ConfigProvider(private val context: Context) { val cfgFile = File(configFilePath()) val saved = if (cfgFile.exists()) loadServersFromConfig(includeCommented = true) else emptyList() val detected = runBlocking { detectInterfaces(saved) } - LogManager.addLog("INFO", "CFG", "buildConfigText: auto mode, detected ${detected.size} interfaces" + + LogManager.addLog("INFO", "CFG", "buildConfigText: auto mode protocols=${getAutoSocketProtocols().configValue}, ${detected.size} sockets: " + detected.joinToString(", ") { "${it.ifName}=${it.protocol}:${it.port}" }) - val upKeys = detected.map { it.ifName to it.bindIp.contains(":") }.toSet() - val down = saved.filter { (it.ifName to it.bindIp.contains(":")) !in upKeys } + val upKeys = detected.map { it.socketKey }.toSet() + val down = saved.filter { it.socketKey !in upKeys }.distinctBy { it.socketKey } if (down.isNotEmpty()) { - LogManager.addLog("INFO", "CFG", "buildConfigText: ${down.size} interfaces down, port preserved" + - down.joinToString(", ") { "${it.ifName}=${it.port}" }) + LogManager.addLog("INFO", "CFG", "buildConfigText: ${down.size} sockets inactive, ports preserved: " + + down.joinToString(", ") { "${it.ifName}=${it.protocol}:${it.port}" }) } saveServersToConfig(detected, down) return buildConfigFromServers(detected) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ServerEntry.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ServerEntry.kt index 3a4e56a6..a137ba60 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ServerEntry.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ServerEntry.kt @@ -1,9 +1,27 @@ package com.utun.chat.data +import java.util.UUID + +/** Протоколы автосокетов. Порты сохраняются отдельно для интерфейса, семейства IP и протокола. */ +enum class AutoSocketProtocols(val configValue: String, val protocols: List) { + TCP("tcp", listOf("tcp")), + UDP("udp", listOf("udp")), + BOTH("tcp+udp", listOf("tcp", "udp")); + + fun createServers(ifName: String, bindIp: String, saved: List): List = protocols.map { protocol -> + val key = Triple(ifName, bindIp.contains(":"), protocol) + val port = saved.firstOrNull { it.socketKey == key }?.port ?: (2000..9999).random() + ServerEntry(UUID.randomUUID().toString(), ifName, bindIp, protocol, port) + } +} + data class ServerEntry( val id: String, // UUID val ifName: String, // wlan0, rmnet0, ... - val bindIp: String, // IPv4 address + val bindIp: String, // IPv4 или IPv6 адрес val protocol: String, // "tcp" or "udp" val port: Int -) +) { + val socketKey: Triple get() = Triple(ifName, bindIp.contains(":"), protocol) + val sectionName: String get() = "${ifName}_${if (bindIp.contains(":")) "v6" else "v4"}_$protocol" +}