11package org.getlantern.lantern.service
22
3+ import android.content.Context
34import android.content.Intent
5+ import android.content.pm.PackageInfo
6+ import android.content.pm.PackageManager
47import android.net.VpnService
58import android.os.Build
69import android.os.ParcelFileDescriptor
@@ -11,6 +14,7 @@ import kotlinx.coroutines.SupervisorJob
1114import kotlinx.coroutines.TimeoutCancellationException
1215import kotlinx.coroutines.async
1316import kotlinx.coroutines.cancel
17+ import kotlinx.coroutines.delay
1418import kotlinx.coroutines.launch
1519import kotlinx.coroutines.runBlocking
1620import 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