@@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.net
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.net.LinkProperties
|
||||
import android.net.RouteInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
@@ -13,9 +14,7 @@ import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
||||
import be.mygod.vpnhotspot.room.AppDatabase
|
||||
import be.mygod.vpnhotspot.root.RootManager
|
||||
import be.mygod.vpnhotspot.root.RoutingCommands
|
||||
import be.mygod.vpnhotspot.util.RootSession
|
||||
import be.mygod.vpnhotspot.util.if_nametoindex
|
||||
import be.mygod.vpnhotspot.util.parseNumericAddress
|
||||
import be.mygod.vpnhotspot.util.*
|
||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import timber.log.Timber
|
||||
@@ -180,52 +179,64 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
|
||||
}
|
||||
}
|
||||
|
||||
var subrouting: Subrouting? = null
|
||||
var dns: List<InetAddress> = emptyList()
|
||||
var subrouting = mutableMapOf<String, Subrouting>()
|
||||
var dns = emptyList<Pair<InetAddress, String?>>()
|
||||
|
||||
override fun onAvailable(ifname: String, properties: LinkProperties) = synchronized(this@Routing) {
|
||||
override fun onAvailable(properties: LinkProperties?) = synchronized(this@Routing) {
|
||||
if (stopped) return
|
||||
val subrouting = subrouting
|
||||
when {
|
||||
subrouting != null -> check(subrouting.upstream == ifname) { "${subrouting.upstream} != $ifname" }
|
||||
!upstreams.add(ifname) -> return
|
||||
else -> this.subrouting = try {
|
||||
Subrouting(priority, ifname)
|
||||
val toRemove = subrouting.keys.toMutableSet()
|
||||
for (ifname in properties?.allInterfaceNames ?: emptyList()) {
|
||||
if (toRemove.remove(ifname) || !upstreams.add(ifname)) continue
|
||||
try {
|
||||
subrouting[ifname] = Subrouting(priority, ifname)
|
||||
} catch (e: Exception) {
|
||||
SmartSnackbar.make(e).show()
|
||||
if (e !is CancellationException) Timber.w(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
dns = properties.dnsServers
|
||||
updateDnsRoute()
|
||||
for (ifname in toRemove) {
|
||||
subrouting.remove(ifname)?.transaction?.revert()
|
||||
check(upstreams.remove(ifname))
|
||||
}
|
||||
|
||||
override fun onLost() = synchronized(this@Routing) {
|
||||
if (stopped) return
|
||||
val subrouting = subrouting ?: return
|
||||
// we could be removing fallback subrouting which no collision could ever happen, check before removing
|
||||
subrouting.upstream?.let { check(upstreams.remove(it)) }
|
||||
subrouting.transaction.revert()
|
||||
this.subrouting = null
|
||||
dns = emptyList()
|
||||
val routes = properties?.allRoutes
|
||||
dns = properties?.dnsServers?.map { dest ->
|
||||
// based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java;l=88;drc=master
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/libs/net/common/framework/android/net/util/NetUtils.java;l=44;drc=de5905fe0407a1f5e115423d56c948ee2400683d
|
||||
val size = dest.address.size
|
||||
var bestRoute: RouteInfo? = null
|
||||
for (route in routes!!) {
|
||||
if (route.destination.rawAddress.size == size &&
|
||||
(bestRoute == null ||
|
||||
bestRoute.destination.prefixLength < route.destination.prefixLength) &&
|
||||
route.matches(dest)) {
|
||||
bestRoute = route
|
||||
}
|
||||
}
|
||||
dest to bestRoute?.`interface`
|
||||
} ?: emptyList()
|
||||
updateDnsRoute()
|
||||
}
|
||||
}
|
||||
private val fallbackUpstream = object : Upstream(RULE_PRIORITY_UPSTREAM_FALLBACK) {
|
||||
var fallbackInactive = true
|
||||
|
||||
override fun onAvailable(properties: LinkProperties?) {
|
||||
check(fallbackInactive)
|
||||
super.onAvailable(properties)
|
||||
}
|
||||
|
||||
override fun onFallback() = synchronized(this@Routing) {
|
||||
if (stopped) return
|
||||
fallbackInactive = false
|
||||
check(subrouting == null)
|
||||
subrouting = try {
|
||||
Subrouting(priority)
|
||||
check(subrouting.isEmpty() && upstreams.add(""))
|
||||
try {
|
||||
subrouting[""] = Subrouting(priority)
|
||||
} catch (e: Exception) {
|
||||
SmartSnackbar.make(e).show()
|
||||
if (e !is CancellationException) Timber.w(e)
|
||||
null
|
||||
}
|
||||
dns = listOf(parseNumericAddress("8.8.8.8"))
|
||||
dns = listOf(parseNumericAddress("8.8.8.8") to null)
|
||||
updateDnsRoute()
|
||||
}
|
||||
}
|
||||
@@ -334,8 +345,9 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
|
||||
private var currentDns: DnsRoute? = null
|
||||
private fun updateDnsRoute() {
|
||||
val selected = sequenceOf(upstream, fallbackUpstream).flatMap { upstream ->
|
||||
val ifindex = upstream.subrouting?.ifindex ?: 0
|
||||
if (ifindex == 0) emptySequence() else upstream.dns.asSequence().map { ifindex to it }
|
||||
upstream.dns.asSequence().map { (server, iface) ->
|
||||
((if (iface != null) upstream.subrouting[iface]?.ifindex else null) ?: 0) to server
|
||||
}
|
||||
}.firstOrNull { it.second is Inet4Address }
|
||||
val ifindex = selected?.first ?: 0
|
||||
var dns = selected?.second?.hostAddress
|
||||
@@ -378,8 +390,8 @@ class Routing(private val caller: Any, private val downstream: String) : IpNeigh
|
||||
synchronized(this) { clients.values.forEach { it.close() } }
|
||||
currentDns?.transaction?.revert()
|
||||
disableSystem?.revert()
|
||||
fallbackUpstream.subrouting?.transaction?.revert()
|
||||
upstream.subrouting?.transaction?.revert()
|
||||
fallbackUpstream.subrouting.values.forEach { it.transaction.revert() }
|
||||
upstream.subrouting.values.forEach { it.transaction.revert() }
|
||||
transaction.revert()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ import android.os.Build
|
||||
import be.mygod.vpnhotspot.util.Services
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
object DefaultNetworkMonitor : UpstreamMonitor() {
|
||||
private var registered = false
|
||||
private var currentNetwork: Network? = null
|
||||
override var currentLinkProperties: LinkProperties? = null
|
||||
private set
|
||||
/**
|
||||
@@ -27,62 +25,30 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
val properties = Services.connectivity.getLinkProperties(network)
|
||||
val ifname = properties?.interfaceName ?: return
|
||||
var switching = false
|
||||
synchronized(this@DefaultNetworkMonitor) {
|
||||
val oldProperties = currentLinkProperties
|
||||
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
||||
switching = true // we are using the other default network now
|
||||
currentNetwork = network
|
||||
}
|
||||
currentLinkProperties = properties
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
if (switching) it.onLost()
|
||||
it.onAvailable(ifname, properties)
|
||||
}
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||
var losing = true
|
||||
var ifname: String?
|
||||
synchronized(this@DefaultNetworkMonitor) {
|
||||
if (currentNetwork == null) {
|
||||
onAvailable(network)
|
||||
return
|
||||
}
|
||||
if (currentNetwork != network) return
|
||||
val oldProperties = currentLinkProperties!!
|
||||
currentLinkProperties = properties
|
||||
ifname = properties.interfaceName
|
||||
when (ifname) {
|
||||
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
||||
oldProperties.interfaceName -> losing = false
|
||||
else -> Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties"))
|
||||
}
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
if (losing) {
|
||||
if (ifname == null) return onLost(network)
|
||||
it.onLost()
|
||||
}
|
||||
ifname?.let { ifname -> it.onAvailable(ifname, properties) }
|
||||
}
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) = synchronized(this@DefaultNetworkMonitor) {
|
||||
if (currentNetwork != network) return
|
||||
currentNetwork = null
|
||||
currentLinkProperties = null
|
||||
callbacks.toList()
|
||||
}.forEach { it.onLost() }
|
||||
}.forEach { it.onAvailable() }
|
||||
}
|
||||
|
||||
override fun registerCallbackLocked(callback: Callback) {
|
||||
if (registered) {
|
||||
val currentLinkProperties = currentLinkProperties
|
||||
if (currentLinkProperties != null) GlobalScope.launch {
|
||||
callback.onAvailable(currentLinkProperties.interfaceName!!, currentLinkProperties)
|
||||
callback.onAvailable(currentLinkProperties)
|
||||
}
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
|
||||
@@ -103,7 +69,6 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
||||
if (!registered) return
|
||||
Services.connectivity.unregisterNetworkCallback(networkCallback)
|
||||
registered = false
|
||||
currentNetwork = null
|
||||
currentLinkProperties = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ abstract class FallbackUpstreamMonitor private constructor() : UpstreamMonitor()
|
||||
}
|
||||
val new = generateMonitor()
|
||||
monitor = new
|
||||
for (callback in callbacks) {
|
||||
callback.onLost()
|
||||
new.registerCallback(callback)
|
||||
}
|
||||
for (callback in callbacks) new.registerCallback(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,85 @@
|
||||
package be.mygod.vpnhotspot.net.monitor
|
||||
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.LinkProperties
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import be.mygod.vpnhotspot.util.Services
|
||||
import be.mygod.vpnhotspot.util.allInterfaceNames
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class InterfaceMonitor(val iface: String) : UpstreamMonitor() {
|
||||
private fun setPresent(present: Boolean) {
|
||||
var available: Pair<String, LinkProperties>? = null
|
||||
synchronized(this) {
|
||||
val old = currentIface != null
|
||||
if (present == old) return
|
||||
currentIface = if (present) iface else null
|
||||
if (present) available = iface to (currentLinkProperties ?: return)
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val available = available
|
||||
if (available != null) {
|
||||
val (iface, lp) = available
|
||||
it.onAvailable(iface, lp)
|
||||
} else it.onLost()
|
||||
}
|
||||
}
|
||||
|
||||
class InterfaceMonitor(ifaceRegex: String) : UpstreamMonitor() {
|
||||
private val iface = ifaceRegex.toRegex()
|
||||
private val request = networkRequestBuilder().apply {
|
||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
}.build()
|
||||
private var registered = false
|
||||
override var currentIface: String? = null
|
||||
private set
|
||||
override val currentLinkProperties get() = Services.connectivity.allNetworks
|
||||
.map { Services.connectivity.getLinkProperties(it) }
|
||||
.singleOrNull { it?.allInterfaceNames?.contains(iface) == true }
|
||||
|
||||
private val available = HashMap<Network, LinkProperties?>()
|
||||
private var currentNetwork: Network? = null
|
||||
override val currentLinkProperties: LinkProperties? get() = currentNetwork?.let { available[it] }
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
val properties = Services.connectivity.getLinkProperties(network)
|
||||
if (properties?.allInterfaceNames?.any(iface::matches) != true) return
|
||||
synchronized(this@InterfaceMonitor) {
|
||||
available[network] = properties
|
||||
currentNetwork = network
|
||||
callbacks.toList()
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||
val matched = properties.allInterfaceNames.any(iface::matches)
|
||||
synchronized(this@InterfaceMonitor) {
|
||||
if (!matched) {
|
||||
if (currentNetwork == network) currentNetwork = null
|
||||
available.remove(network)
|
||||
return
|
||||
}
|
||||
available[network] = properties
|
||||
if (currentNetwork == null) currentNetwork = network
|
||||
else if (currentNetwork != network) return
|
||||
callbacks.toList()
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
var properties: LinkProperties? = null
|
||||
synchronized(this@InterfaceMonitor) {
|
||||
if (available.remove(network) == null || currentNetwork != network) return
|
||||
if (available.isNotEmpty()) {
|
||||
val next = available.entries.first()
|
||||
currentNetwork = next.key
|
||||
Timber.d("Switching to ${next.value} for $iface")
|
||||
properties = next.value
|
||||
} else currentNetwork = null
|
||||
callbacks.toList()
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerCallbackLocked(callback: Callback) {
|
||||
if (!registered) {
|
||||
IpLinkMonitor.registerCallback(this, iface, this::setPresent)
|
||||
if (registered) {
|
||||
val currentLinkProperties = currentLinkProperties
|
||||
if (currentLinkProperties != null) GlobalScope.launch {
|
||||
callback.onAvailable(currentLinkProperties)
|
||||
}
|
||||
} else {
|
||||
Services.connectivity.registerNetworkCallback(request, networkCallback)
|
||||
registered = true
|
||||
} else if (currentIface != null) GlobalScope.launch {
|
||||
callback.onAvailable(iface, currentLinkProperties ?: return@launch)
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroyLocked() {
|
||||
IpLinkMonitor.unregisterCallback(this)
|
||||
currentIface = null
|
||||
if (!registered) return
|
||||
Services.connectivity.unregisterNetworkCallback(networkCallback)
|
||||
registered = false
|
||||
available.clear()
|
||||
currentNetwork = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package be.mygod.vpnhotspot.net.monitor
|
||||
|
||||
class IpLinkMonitor private constructor() : IpMonitor() {
|
||||
companion object {
|
||||
/**
|
||||
* Based on: https://android.googlesource.com/platform/external/iproute2/+/70556c1/ip/ipaddress.c#1053
|
||||
*/
|
||||
private val parser = "^(Deleted )?-?\\d+: ([^:@]+)".toRegex()
|
||||
|
||||
private val callbacks = HashMap<Any, Pair<String, (Boolean) -> Unit>>()
|
||||
private var instance: IpLinkMonitor? = null
|
||||
|
||||
fun registerCallback(owner: Any, iface: String, callback: (Boolean) -> Unit) = synchronized(this) {
|
||||
check(callbacks.put(owner, Pair(iface, callback)) == null)
|
||||
var monitor = instance
|
||||
if (monitor == null) {
|
||||
monitor = IpLinkMonitor()
|
||||
instance = monitor
|
||||
} else monitor.flushAsync()
|
||||
}
|
||||
fun unregisterCallback(owner: Any) = synchronized(this) {
|
||||
if (callbacks.remove(owner) == null || callbacks.isNotEmpty()) return@synchronized
|
||||
instance?.destroy()
|
||||
instance = null
|
||||
}
|
||||
}
|
||||
|
||||
override val monitoredObject: String get() = "link"
|
||||
|
||||
override fun processLine(line: String) {
|
||||
val match = parser.find(line) ?: return
|
||||
val iface = match.groupValues[2]
|
||||
val present = match.groupValues[1].isEmpty()
|
||||
synchronized(IpLinkMonitor) {
|
||||
for ((target, callback) in callbacks.values) if (target == iface) callback(present)
|
||||
}
|
||||
}
|
||||
|
||||
override fun processLines(lines: Sequence<String>) {
|
||||
val present = HashSet<String>()
|
||||
for (it in lines) present.add((parser.find(it) ?: continue).groupValues[2])
|
||||
synchronized(IpLinkMonitor) { for ((iface, callback) in callbacks.values) callback(present.contains(iface)) }
|
||||
}
|
||||
}
|
||||
@@ -37,18 +37,15 @@ abstract class UpstreamMonitor {
|
||||
if (key == KEY) GlobalScope.launch { // prevent callback called in main
|
||||
synchronized(this) {
|
||||
val old = monitor
|
||||
val (active, callbacks) = synchronized(old) {
|
||||
(old.currentIface != null) to old.callbacks.toList().also {
|
||||
val callbacks = synchronized(old) {
|
||||
old.callbacks.toList().also {
|
||||
old.callbacks.clear()
|
||||
old.destroyLocked()
|
||||
}
|
||||
}
|
||||
val new = generateMonitor()
|
||||
monitor = new
|
||||
for (callback in callbacks) {
|
||||
if (active) callback.onLost()
|
||||
new.registerCallback(callback)
|
||||
}
|
||||
for (callback in callbacks) new.registerCallback(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,14 +53,9 @@ abstract class UpstreamMonitor {
|
||||
|
||||
interface Callback {
|
||||
/**
|
||||
* Called if some interface is available. This might be called on different ifname without having called onLost.
|
||||
* This might also be called on the same ifname but with updated link properties.
|
||||
* Called if some possibly stacked interface is available
|
||||
*/
|
||||
fun onAvailable(ifname: String, properties: LinkProperties)
|
||||
/**
|
||||
* Called if no interface is available.
|
||||
*/
|
||||
fun onLost()
|
||||
fun onAvailable(properties: LinkProperties? = null)
|
||||
/**
|
||||
* Called on API 23- from DefaultNetworkMonitor. This indicates that there isn't a good way of telling the
|
||||
* default network (see DefaultNetworkMonitor) and we are using rules at priority 22000
|
||||
@@ -77,7 +69,6 @@ abstract class UpstreamMonitor {
|
||||
|
||||
val callbacks = mutableSetOf<Callback>()
|
||||
protected abstract val currentLinkProperties: LinkProperties?
|
||||
open val currentIface: String? get() = currentLinkProperties?.interfaceName
|
||||
protected abstract fun registerCallbackLocked(callback: Callback)
|
||||
abstract fun destroyLocked()
|
||||
|
||||
|
||||
@@ -16,71 +16,40 @@ object VpnMonitor : UpstreamMonitor() {
|
||||
.build()
|
||||
private var registered = false
|
||||
|
||||
private val available = HashMap<Network, LinkProperties>()
|
||||
private val available = HashMap<Network, LinkProperties?>()
|
||||
private var currentNetwork: Network? = null
|
||||
override val currentLinkProperties: LinkProperties? get() {
|
||||
val currentNetwork = currentNetwork
|
||||
return if (currentNetwork == null) null else available[currentNetwork]
|
||||
}
|
||||
override val currentLinkProperties: LinkProperties? get() = currentNetwork?.let { available[it] }
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
val properties = Services.connectivity.getLinkProperties(network)
|
||||
val ifname = properties?.interfaceName ?: return
|
||||
var switching = false
|
||||
synchronized(this@VpnMonitor) {
|
||||
val oldProperties = available.put(network, properties)
|
||||
if (currentNetwork != network || ifname != oldProperties?.interfaceName) {
|
||||
if (currentNetwork != null) switching = true
|
||||
available[network] = properties
|
||||
currentNetwork = network
|
||||
}
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
if (switching) it.onLost()
|
||||
it.onAvailable(ifname, properties)
|
||||
}
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||
var losing = true
|
||||
var ifname: String?
|
||||
synchronized(this@VpnMonitor) {
|
||||
if (currentNetwork == null) {
|
||||
onAvailable(network)
|
||||
return
|
||||
}
|
||||
if (currentNetwork != network) return
|
||||
val oldProperties = available.put(network, properties)!!
|
||||
ifname = properties.interfaceName
|
||||
when (ifname) {
|
||||
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
||||
oldProperties.interfaceName -> losing = false
|
||||
else -> Timber.w("interfaceName changed: $oldProperties -> $properties")
|
||||
}
|
||||
available[network] = properties
|
||||
if (currentNetwork == null) currentNetwork = network
|
||||
else if (currentNetwork != network) return
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
if (losing) {
|
||||
if (ifname == null) return onLost(network)
|
||||
it.onLost()
|
||||
}
|
||||
ifname?.let { ifname -> it.onAvailable(ifname, properties) }
|
||||
}
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
var newProperties: LinkProperties? = null
|
||||
var properties: LinkProperties? = null
|
||||
synchronized(this@VpnMonitor) {
|
||||
if (available.remove(network) == null || currentNetwork != network) return
|
||||
if (available.isNotEmpty()) {
|
||||
val next = available.entries.first()
|
||||
currentNetwork = next.key
|
||||
Timber.d("Switching to ${next.value.interfaceName} as VPN interface")
|
||||
newProperties = next.value
|
||||
Timber.d("Switching to ${next.value} as VPN interface")
|
||||
properties = next.value
|
||||
} else currentNetwork = null
|
||||
callbacks.toList()
|
||||
}.forEach {
|
||||
it.onLost()
|
||||
newProperties?.let { prop -> it.onAvailable(prop.interfaceName!!, prop) }
|
||||
}
|
||||
}.forEach { it.onAvailable(properties) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +57,7 @@ object VpnMonitor : UpstreamMonitor() {
|
||||
if (registered) {
|
||||
val currentLinkProperties = currentLinkProperties
|
||||
if (currentLinkProperties != null) GlobalScope.launch {
|
||||
callback.onAvailable(currentLinkProperties.interfaceName!!, currentLinkProperties)
|
||||
callback.onAvailable(currentLinkProperties)
|
||||
}
|
||||
} else {
|
||||
Services.connectivity.registerNetworkCallback(request, networkCallback)
|
||||
|
||||
@@ -21,32 +21,31 @@ import timber.log.Timber
|
||||
class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs),
|
||||
DefaultLifecycleObserver {
|
||||
companion object {
|
||||
private val internetAddress = parseNumericAddress("8.8.8.8")
|
||||
private val internetV4Address = parseNumericAddress("8.8.8.8")
|
||||
private val internetV6Address = parseNumericAddress("2001:4860:4860::8888")
|
||||
}
|
||||
|
||||
private data class Interface(val ifname: String, val internet: Boolean = true)
|
||||
private open inner class Monitor : UpstreamMonitor.Callback {
|
||||
protected var currentInterface: Interface? = null
|
||||
val charSequence get() = currentInterface?.run {
|
||||
protected var currentInterfaces = emptyMap<String, Boolean>()
|
||||
val charSequence get() = currentInterfaces.map { (ifname, internet) ->
|
||||
if (internet) SpannableStringBuilder(ifname).apply {
|
||||
setSpan(StyleSpan(Typeface.BOLD), 0, length, 0)
|
||||
} else ifname
|
||||
} ?: "∅"
|
||||
}.joinTo(SpannableStringBuilder()).let { if (it.isEmpty()) "∅" else it }
|
||||
|
||||
override fun onAvailable(ifname: String, properties: LinkProperties) {
|
||||
currentInterface = Interface(ifname, properties.allRoutes.any {
|
||||
try {
|
||||
it.matches(internetAddress)
|
||||
override fun onAvailable(properties: LinkProperties?) {
|
||||
val result = mutableMapOf<String, Boolean>()
|
||||
for (route in properties?.allRoutes ?: emptyList()) {
|
||||
result.compute(route.`interface` ?: continue) { _, internet ->
|
||||
internet == true || try {
|
||||
route.matches(internetV4Address) || route.matches(internetV6Address)
|
||||
} catch (e: RuntimeException) {
|
||||
Timber.w(e)
|
||||
false
|
||||
}
|
||||
})
|
||||
onUpdate()
|
||||
}
|
||||
|
||||
override fun onLost() {
|
||||
currentInterface = null
|
||||
}
|
||||
currentInterfaces = result
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
@@ -54,7 +53,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co
|
||||
private val primary = Monitor()
|
||||
private val fallback: Monitor = object : Monitor() {
|
||||
override fun onFallback() {
|
||||
currentInterface = Interface("<default>")
|
||||
currentInterfaces = mapOf("<default>" to true)
|
||||
onUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user