android: whitelist - working recheck backoff, fewer probe rounds
- scheduled rechecks are forced: the 15/30 s steps were below FRESH_MS, so the unforced recheck returned early and the backoff chain died - signal-strength-only capability changes are no longer network events (each one reset the backoff and launched a check) - tunnel state transitions force a new round at most every 15 s Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
514b15b1ca
commit
b0f353af90
1 file changed
+37
-4
+37
-4
@@ -44,6 +44,7 @@ import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.strongswan.android.logic.VpnStateService
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/** Whether the ISP currently blocks direct access to foreign hosts. */
|
||||
enum class WhiteListStatus
|
||||
@@ -177,6 +178,9 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
private val _state = MutableStateFlow(WhiteListState())
|
||||
val state: StateFlow<WhiteListState> = _state
|
||||
|
||||
/* last classification-relevant capabilities per network (see capabilityKey) */
|
||||
private val lastCapabilities = ConcurrentHashMap<Network, String>()
|
||||
|
||||
/* event trigger: the underlying network changed, the ISP may classify differently now */
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback()
|
||||
{
|
||||
@@ -186,12 +190,19 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities)
|
||||
{
|
||||
onNetworkEvent()
|
||||
{ /* fired on every signal-strength change too: only a change that
|
||||
* can alter the ISP's classification counts as an event (it also
|
||||
* resets the recheck backoff) */
|
||||
val key = capabilityKey(capabilities)
|
||||
if (lastCapabilities.put(network, key) != key)
|
||||
{
|
||||
onNetworkEvent()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLost(network: Network)
|
||||
{
|
||||
lastCapabilities.remove(network)
|
||||
onNetworkEvent()
|
||||
}
|
||||
}
|
||||
@@ -238,6 +249,7 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
recheckJob?.cancel()
|
||||
recheckJob = null
|
||||
unregisterNetworkCallback()
|
||||
lastCapabilities.clear()
|
||||
service?.unregisterListener(this)
|
||||
try
|
||||
{
|
||||
@@ -364,7 +376,10 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
return
|
||||
}
|
||||
backoffAttempt = 0
|
||||
scope.launch { runCheck(force = true) }
|
||||
/* a flapping tunnel (CONNECTING -> DISCONNECTING -> DISABLED -> ...)
|
||||
* must not trigger a fresh direct probe round on every transition */
|
||||
val force = SystemClock.elapsedRealtime() - lastCheckElapsed >= TRANSITION_MIN_GAP_MS
|
||||
scope.launch { runCheck(force = force) }
|
||||
}
|
||||
|
||||
private fun onNetworkEvent()
|
||||
@@ -453,7 +468,9 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
recheckJob?.cancel()
|
||||
recheckJob = scope.launch {
|
||||
delay(delayMs)
|
||||
runCheck(force = false)
|
||||
/* forced: the first backoff steps are shorter than FRESH_MS, an
|
||||
* unforced recheck returned early and ended the chain */
|
||||
runCheck(force = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +615,22 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt
|
||||
/** Cached results younger than this are not re-probed. */
|
||||
private const val FRESH_MS = 60_000L
|
||||
|
||||
/** State transitions force a new round only if the last one is at least this old. */
|
||||
private const val TRANSITION_MIN_GAP_MS = 15_000L
|
||||
|
||||
/** The parts of the capabilities that matter for the classification. */
|
||||
@JvmStatic
|
||||
internal fun capabilityKey(caps: NetworkCapabilities): String
|
||||
{
|
||||
val transports = intArrayOf(NetworkCapabilities.TRANSPORT_WIFI, NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||
NetworkCapabilities.TRANSPORT_ETHERNET, NetworkCapabilities.TRANSPORT_VPN)
|
||||
.filter { caps.hasTransport(it) }
|
||||
val capabilities = intArrayOf(NetworkCapabilities.NET_CAPABILITY_INTERNET,
|
||||
NetworkCapabilities.NET_CAPABILITY_VALIDATED, NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.filter { caps.hasCapability(it) }
|
||||
return "$transports/$capabilities"
|
||||
}
|
||||
|
||||
/**
|
||||
* A cached ACTIVE status stops suppressing reconnects once no recheck
|
||||
* has confirmed it for this long (fail open). Kept above the backoff
|
||||
|
||||
Reference in new issue
Block a user