fix: improve always on vpn reliability

#1289
This commit is contained in:
zaneschepke
2026-06-23 10:38:28 -04:00
parent e062fbb34d
commit 9ac7ae77b3
5 changed files with 49 additions and 21 deletions
@@ -51,6 +51,13 @@ class WireGuardAutoTunnel : Application(), KoinComponent {
private val backend: Backend by inject()
private val alwaysOnCallback =
object : VpnService.AlwaysOnCallback {
override fun alwaysOnTriggered() {
applicationScope.launch { tunnelCoordinator.startDefault() }
}
}
@OptIn(KoinViewModelScopeApi::class)
override fun onCreate() {
super.onCreate()
@@ -86,13 +93,7 @@ class WireGuardAutoTunnel : Application(), KoinComponent {
Timber.plant(ReleaseTree())
}
backend.setAlwaysOnCallback(
object : VpnService.AlwaysOnCallback {
override fun alwaysOnTriggered() {
applicationScope.launch { tunnelCoordinator.startDefault() }
}
}
)
backend.setAlwaysOnCallback(alwaysOnCallback)
val dispatcher = get<TunnelEventDispatcher>()
val coordinator = get<TunnelCoordinator>()
@@ -25,7 +25,6 @@ import com.zaneschepke.tunnel.util.RootShellException
import com.zaneschepke.tunnel.util.buildResolvedPeers
import com.zaneschepke.tunnel.util.toHostMap
import com.zaneschepke.wireguardautotunnel.parser.ActiveConfig
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -250,7 +249,7 @@ class TunnelBackend(
}
override fun setAlwaysOnCallback(alwaysOnCallback: VpnService.AlwaysOnCallback) {
ServiceHolder.alwaysOnCallback = WeakReference(alwaysOnCallback)
ServiceHolder.alwaysOnCallback = alwaysOnCallback
}
override suspend fun stop(id: Int): Result<Unit> = tunnelMutex.withLock {
@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent
import com.zaneschepke.tunnel.ProxyBackend
import com.zaneschepke.tunnel.util.BackendException
import java.lang.ref.WeakReference
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
@@ -90,7 +89,7 @@ internal class ServiceHolder(val context: Context) {
service.shutdown()
withTimeoutOrNull(1_500L.milliseconds) { tunnelServiceFlow.first { it == null } }
} finally {
clearVpnService()
clearTunnelService()
}
}
@@ -110,6 +109,6 @@ internal class ServiceHolder(val context: Context) {
const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30
const val DEFAULT_MTU = 1280
// for consumer to set AOVPN callback
var alwaysOnCallback: WeakReference<VpnService.AlwaysOnCallback>? = null
var alwaysOnCallback: VpnService.AlwaysOnCallback? = null
}
}
@@ -51,7 +51,7 @@ class TunnelService : LifecycleService() {
(intent.component!!.packageName != packageName)
) {
Timber.d("TunnelService started by system")
alwaysOnCallback?.get()?.alwaysOnTriggered()
alwaysOnCallback?.alwaysOnTriggered()
}
return START_STICKY
@@ -1,5 +1,6 @@
package com.zaneschepke.tunnel.service
import android.content.Context
import android.content.Intent
import android.net.TrafficStats
import android.os.Build
@@ -81,15 +82,28 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
bootKeepaliveService()
// Service restarted by system or Always-on VPN started
if (
intent == null ||
intent.component == null ||
(intent.component!!.packageName != packageName)
) {
Timber.d("VpnService started by system (Always-On trigger)")
alwaysOnCallback?.get()?.alwaysOnTriggered()
// system recovery restart
if (intent == null) {
return START_STICKY
}
val isUserLaunch = intent.getBooleanExtra(getUserLaunchExtraKey(this), false)
val platformSaysAlwaysOn =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
isAlwaysOn
} else {
false
}
val isAlwaysOnTrigger =
!isUserLaunch && (intent.action == SERVICE_INTERFACE || platformSaysAlwaysOn)
if (isAlwaysOnTrigger) {
Timber.d("VpnService started by system (Always-On trigger)")
alwaysOnCallback?.alwaysOnTriggered()
}
return START_STICKY
}
@@ -330,6 +344,21 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
}
companion object {
private fun getUserLaunchExtraKey(context: Context): String {
return "${context.packageName}.EXTRA_IS_USER_LAUNCH"
}
@JvmStatic
fun start(context: Context, serviceClass: Class<out VpnService>) {
val intent =
Intent(context, serviceClass).apply {
action = SERVICE_INTERFACE
putExtra(getUserLaunchExtraKey(context), true)
}
context.startService(intent)
}
private const val LOCKDOWN_SESSION_NAME = "Lockdown"
private const val LOCALHOST = "127.0.0.1"
private const val IPV4_INTERFACE_ADDRESS = "10.0.0.1"