Generate Android autosockets per selected protocol and retain ports

This commit is contained in:
evgeny committed 2026-10-25 16:35:54 +01:00
1 parent ae85423e39
commit eda65ece52
2 files changed
+50 -22

No files matched your search

@@ -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<Boolean> = context.dataStore.data.map { it[serviceEnabledKey] ?: true }
@@ -584,6 +597,7 @@ class ConfigProvider(private val context: Context) {
suspend fun detectInterfaces(saved: List<ServerEntry> = emptyList()): List<ServerEntry> {
val servers = mutableListOf<ServerEntry>()
val seen = mutableSetOf<String>()
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)
@@ -1,9 +1,27 @@
package com.utun.chat.data
import java.util.UUID
/** Протоколы автосокетов. Порты сохраняются отдельно для интерфейса, семейства IP и протокола. */
enum class AutoSocketProtocols(val configValue: String, val protocols: List<String>) {
TCP("tcp", listOf("tcp")),
UDP("udp", listOf("udp")),
BOTH("tcp+udp", listOf("tcp", "udp"));
fun createServers(ifName: String, bindIp: String, saved: List<ServerEntry>): List<ServerEntry> = 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<String, Boolean, String> get() = Triple(ifName, bindIp.contains(":"), protocol)
val sectionName: String get() = "${ifName}_${if (bindIp.contains(":")) "v6" else "v4"}_$protocol"
}