diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/connectivity/WhiteListManager.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/connectivity/WhiteListManager.kt index 32ceaad52..2bc90c348 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/connectivity/WhiteListManager.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/connectivity/WhiteListManager.kt @@ -38,11 +38,14 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withTimeoutOrNull import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import org.strongswan.android.logic.DiagLog import org.strongswan.android.logic.VpnStateService import org.strongswan.android.logic.events.Events import java.util.concurrent.ConcurrentHashMap @@ -351,6 +354,23 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt scope.launch { runCheck(force = true) } } + /** + * Runs a probe round and waits for its result: the state of a round that + * finished after this call, or null when none did within [timeoutMs] + * (checks disabled, or the round is slower). A round already in flight + * counts, so a busy cycle does not cost the whole timeout for nothing. + */ + suspend fun checkFresh(timeoutMs: Long): WhiteListState? + { + if (!running || !config.enabled) + { + return null + } + val before = _state.value.lastCheckMs + checkNow() + return withTimeoutOrNull(timeoutMs) { _state.first { it.lastCheckMs != before } } + } + override fun stateChanged() { handler.post { onStateChanged() } @@ -434,8 +454,11 @@ class WhiteListManager private constructor(private val context: Context) : VpnSt val foreignLog = round.foreignOks.mapIndexed { index, ok -> "${targets.getOrNull(index)?.host ?: index}=${ok ?: "?"}/${round.foreignRttMs.getOrNull(index) ?: "-"}ms" } - Log.i(TAG, "Direct round: ${foreignLog.joinToString(" ")}, " + - "domestic=${round.domesticOk}/${round.domesticRttMs ?: "-"}ms -> $candidate") + val roundLog = "Direct round: ${foreignLog.joinToString(" ")}, " + + "domestic=${round.domesticOk}/${round.domesticRttMs ?: "-"}ms -> $candidate" + Log.i(TAG, roundLog) + /* also in the report: tells a stale verdict from a real failure */ + DiagLog.i(TAG, roundLog) val before = machine machine = before.advance(candidate) publish(round) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ReportSheet.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ReportSheet.kt index b4e36a6b6..eef3c5e9b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ReportSheet.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ReportSheet.kt @@ -128,8 +128,8 @@ class ReportViewModel(app: Application) : AndroidViewModel(app) val note = comment val diag = attachDiag job = viewModelScope.launch { - /* the same checks as the Diagnostics screen (~3 s); a hang must not block the report */ - val checks = if (!diag) null else withTimeoutOrNull(10_000) { + /* the same checks as the Diagnostics screen (1-8 s); a hang must not block the report */ + val checks = if (!diag) null else withTimeoutOrNull(15_000) { runCatching { runDiagnostics(app) }.getOrNull()?.steps?.map { ReportContent.Check(it.title, it.value, it.ok) } } val result = withContext(Dispatchers.IO) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/DiagnosticsScreen.kt b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/DiagnosticsScreen.kt index 9a4564c52..b882fa3c2 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/DiagnosticsScreen.kt +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/compose/DiagnosticsScreen.kt @@ -97,13 +97,14 @@ internal suspend fun runDiagnostics(context: Context): DiagReport = coroutineSco val vpnUp = vpn?.state == VpnStateService.State.CONNECTED val gateway = (vpn?.profile)?.gateway val whiteList = WhiteListManager.getInstance(context) - whiteList.checkNow() val server = async { if (gateway != null) ConnectivityProbe.probe(ProbeTarget.SERVER, 3000, context, gateway) else null } val foreign = async { ConnectivityProbe.probe(ProbeTarget.GOOGLE, 3000, context, null) } val network = withContext(Dispatchers.Default) { networkLabel(context) } - /* the whitelist check runs its own direct round; give it a moment */ - delay(2500) - val wl = whiteList.state.value + /* the whitelist check runs its own direct round: wait for that very round, a + * verdict left from an earlier one (say, a network outage) is not shown. + * 8 s covers a round whose foreign probes time out and are retried. */ + val fresh = whiteList.checkFresh(8000) + val wl = fresh ?: whiteList.state.value.copy(domesticDirectOk = null, domesticDirectRttMs = null) val serverResult = server.await() val foreignResult = foreign.await() val steps = listOf(