fix(network): make non-VPN network selection robust + show failure reason
This commit is contained in:
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.util.Log
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
@@ -14,6 +15,10 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
private val channelName = "ru.komet.app/vpn_bypass"
|
||||
|
||||
private companion object {
|
||||
const val LOG_TAG = "VpnBypass"
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(
|
||||
@@ -79,19 +84,28 @@ class MainActivity : FlutterActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
private data class Candidate(
|
||||
val network: Network,
|
||||
val iface: String?,
|
||||
val transport: String,
|
||||
val score: Int,
|
||||
)
|
||||
|
||||
// Привязывает процесс к не-VPN сети: Wi-Fi → Ethernet → моб.
|
||||
// Жёсткий фильтр — только исключение VPN-транспорта; INTERNET/NOT_VPN/
|
||||
// VALIDATED лишь повышают приоритет (физическая сеть под активным VPN
|
||||
// часто теряет эти capability, но через неё всё равно можно ходить).
|
||||
private fun bindToNonVpnNetwork(): Map<String, Any?> {
|
||||
val cm = connectivityManager()
|
||||
var best: Network? = null
|
||||
var bestIface: String? = null
|
||||
var bestTransport: String? = null
|
||||
var bestScore = -1
|
||||
val networks = cm.allNetworks
|
||||
val candidates = ArrayList<Candidate>()
|
||||
|
||||
for (network in cm.allNetworks) {
|
||||
val caps = cm.getNetworkCapabilities(network) ?: continue
|
||||
if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) continue
|
||||
for (network in networks) {
|
||||
val caps = cm.getNetworkCapabilities(network)
|
||||
val iface = cm.getLinkProperties(network)?.interfaceName
|
||||
Log.i(LOG_TAG, "net=$network iface=$iface caps=$caps")
|
||||
if (caps == null) continue
|
||||
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) continue
|
||||
if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) continue
|
||||
|
||||
val baseScore = when {
|
||||
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> 3
|
||||
@@ -104,27 +118,42 @@ class MainActivity : FlutterActivity() {
|
||||
2 -> "ethernet"
|
||||
else -> "cellular"
|
||||
}
|
||||
val internet =
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
val notVpn =
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
val validated =
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
val score = baseScore * 2 + if (validated) 1 else 0
|
||||
if (score > bestScore) {
|
||||
bestScore = score
|
||||
best = network
|
||||
bestIface = cm.getLinkProperties(network)?.interfaceName
|
||||
bestTransport = transport
|
||||
}
|
||||
val score = baseScore * 8 +
|
||||
(if (internet) 4 else 0) +
|
||||
(if (notVpn) 2 else 0) +
|
||||
(if (validated) 1 else 0)
|
||||
candidates.add(Candidate(network, iface, transport, score))
|
||||
}
|
||||
|
||||
val chosen = best
|
||||
?: return mapOf("bound" to false, "reason" to "no_non_vpn_network")
|
||||
candidates.sortByDescending { it.score }
|
||||
Log.i(LOG_TAG, "candidates=${candidates.map { "${it.iface}:${it.score}" }}")
|
||||
|
||||
val ok = cm.bindProcessToNetwork(chosen)
|
||||
return mapOf(
|
||||
"bound" to ok,
|
||||
"interface" to bestIface,
|
||||
"transport" to bestTransport,
|
||||
"reason" to if (ok) null else "bind_failed",
|
||||
)
|
||||
if (candidates.isEmpty()) {
|
||||
return mapOf(
|
||||
"bound" to false,
|
||||
"reason" to "no_non_vpn_network(scanned=${networks.size})",
|
||||
)
|
||||
}
|
||||
|
||||
for (c in candidates) {
|
||||
if (cm.bindProcessToNetwork(c.network)) {
|
||||
Log.i(LOG_TAG, "bound to ${c.iface} (${c.transport})")
|
||||
return mapOf(
|
||||
"bound" to true,
|
||||
"interface" to c.iface,
|
||||
"transport" to c.transport,
|
||||
"reason" to null,
|
||||
)
|
||||
}
|
||||
Log.w(LOG_TAG, "bindProcessToNetwork failed for ${c.iface}")
|
||||
}
|
||||
return mapOf("bound" to false, "reason" to "bind_failed")
|
||||
}
|
||||
|
||||
private fun unbindNetwork(): Map<String, Any?> {
|
||||
|
||||
Reference in New Issue
Block a user