Skip to content

Commit 59dfa70

Browse files
committed
Fix Android VPN state after APK upgrade
1 parent 8623d3f commit 59dfa70

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

android/app/src/main/kotlin/org/getlantern/lantern/service/LanternVpnService.kt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.getlantern.lantern.service
22

3+
import android.content.Context
34
import android.content.Intent
5+
import android.content.pm.PackageInfo
6+
import android.content.pm.PackageManager
47
import android.net.VpnService
58
import android.os.Build
69
import android.os.ParcelFileDescriptor
@@ -11,6 +14,7 @@ import kotlinx.coroutines.SupervisorJob
1114
import kotlinx.coroutines.TimeoutCancellationException
1215
import kotlinx.coroutines.async
1316
import kotlinx.coroutines.cancel
17+
import kotlinx.coroutines.delay
1418
import kotlinx.coroutines.launch
1519
import kotlinx.coroutines.runBlocking
1620
import kotlinx.coroutines.withContext
@@ -53,6 +57,10 @@ class LanternVpnService :
5357
const val ACTION_STOP_VPN = "org.getlantern.START_STOP"
5458
const val ACTION_TILE_START = "org.getlantern.TILE_START"
5559

60+
private const val UPGRADE_RESET_PREFS = "vpn_upgrade_reset"
61+
private const val LAST_RESET_APK_UPDATE_TIME = "last_reset_apk_update_time"
62+
private const val UPGRADE_RESET_SETTLE_MS = 250L
63+
5664
// Hard ceiling on how long Mobile.startVPN / connectToServer can block.
5765
// Radiance + sing-box + TUN establish usually finishes in under 15 s;
5866
// anything above 60 s means the Go side has deadlocked (see Freshdesk
@@ -219,6 +227,10 @@ class LanternVpnService :
219227

220228
override fun openTun(tunOptions: TunOptions): Int {
221229
val vpnBuilder = createVPNBuilder(tunOptions)
230+
// Android normally tears the previous TUN down when a new VPN is
231+
// established, but explicitly dropping our fd first prevents a stale
232+
// descriptor from surviving restarts or app-upgrade recovery.
233+
closeTunInterface()
222234
val pfd =
223235
vpnBuilder.establish()
224236
?: error("android: the application is not prepared or is revoked")
@@ -345,6 +357,7 @@ class LanternVpnService :
345357
Mobile.startIPCServer(this@LanternVpnService, opts())
346358
Mobile.setupRadiance(opts(), flutterEventListener)
347359
}
360+
resetVpnAfterAppUpgradeIfNeeded()
348361
DefaultNetworkMonitor.setNetworkChangeCallback { updateUnderlyingNetworks() }
349362
DefaultNetworkMonitor.start()
350363
// Tell Android which physical network underlies our VPN so that
@@ -417,6 +430,72 @@ class LanternVpnService :
417430
}
418431
}
419432

433+
private suspend fun resetVpnAfterAppUpgradeIfNeeded() {
434+
if (!consumeUpgradeResetMarker()) return
435+
436+
AppLogger.i(TAG, "App APK updated; resetting VPN state before first tunnel start")
437+
closeTunInterface()
438+
runCatching {
439+
if (Mobile.isRadianceConnected()) {
440+
Mobile.stopVPN()
441+
AppLogger.d(TAG, "stopVPN completed for app-upgrade reset")
442+
} else {
443+
AppLogger.d(TAG, "Skipping app-upgrade stopVPN - Radiance IPC not running")
444+
}
445+
}.onFailure { e ->
446+
AppLogger.e(TAG, "stopVPN failed during app-upgrade reset", e)
447+
}
448+
449+
runCatching { DefaultNetworkMonitor.stop() }
450+
.onFailure { e -> AppLogger.e(TAG, "DefaultNetworkMonitor.stop() failed during app-upgrade reset", e) }
451+
452+
VpnStatusManager.postVPNStatus(VPNStatus.Disconnected)
453+
delay(UPGRADE_RESET_SETTLE_MS)
454+
}
455+
456+
private fun consumeUpgradeResetMarker(): Boolean {
457+
val packageInfo = currentPackageInfo()
458+
val currentApkUpdateTime = packageInfo.lastUpdateTime
459+
val prefs = getSharedPreferences(UPGRADE_RESET_PREFS, Context.MODE_PRIVATE)
460+
val lastResetApkUpdateTime = prefs.getLong(LAST_RESET_APK_UPDATE_TIME, Long.MIN_VALUE)
461+
if (lastResetApkUpdateTime == currentApkUpdateTime) {
462+
return false
463+
}
464+
465+
prefs.edit().putLong(LAST_RESET_APK_UPDATE_TIME, currentApkUpdateTime).apply()
466+
if (packageInfo.lastUpdateTime <= packageInfo.firstInstallTime) {
467+
AppLogger.d(TAG, "Recording initial APK install for VPN upgrade reset")
468+
return false
469+
}
470+
471+
AppLogger.i(
472+
TAG,
473+
"VPN upgrade reset needed: lastResetApkUpdateTime=$lastResetApkUpdateTime currentApkUpdateTime=$currentApkUpdateTime versionCode=${packageVersionCode(packageInfo)}",
474+
)
475+
return true
476+
}
477+
478+
private fun currentPackageInfo(): PackageInfo {
479+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
480+
packageManager.getPackageInfo(
481+
packageName,
482+
PackageManager.PackageInfoFlags.of(0),
483+
)
484+
} else {
485+
@Suppress("DEPRECATION")
486+
packageManager.getPackageInfo(packageName, 0)
487+
}
488+
}
489+
490+
private fun packageVersionCode(info: PackageInfo): Long {
491+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
492+
info.longVersionCode
493+
} else {
494+
@Suppress("DEPRECATION")
495+
info.versionCode.toLong()
496+
}
497+
}
498+
420499
fun doStopVPN() {
421500
AppLogger.d(TAG, "doStopVPN")
422501
serviceScope.launch {

0 commit comments

Comments
 (0)