fix: auto tunneling failure with rapid network changes
Fixes issue where rapid network switching could cause unexpected VPN connections and disconnection. Fixed by changing from real time network VPN triggers to three second interval checks. Attempts to optimize battery drain while VPN connected by switching VPN statistics gathering to 10 second intervals. Closes #11, Closes #12, Closes #10
This commit is contained in:
parent
08d11a53b4
commit
689c97f452
|
@ -17,7 +17,7 @@ android {
|
||||||
|
|
||||||
val versionMajor = 2
|
val versionMajor = 2
|
||||||
val versionMinor = 3
|
val versionMinor = 3
|
||||||
val versionPatch = 1
|
val versionPatch = 2
|
||||||
val versionBuild = 0
|
val versionBuild = 0
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L;
|
||||||
|
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L;
|
||||||
|
}
|
|
@ -8,7 +8,10 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
|
||||||
|
@ -22,6 +25,9 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -46,15 +52,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var vpnService : VpnService
|
lateinit var vpnService : VpnService
|
||||||
|
|
||||||
|
private var isWifiConnected = false;
|
||||||
|
private var isMobileDataConnected = false;
|
||||||
|
private var currentNetworkSSID = "";
|
||||||
|
|
||||||
private lateinit var watcherJob : Job;
|
private lateinit var watcherJob : Job;
|
||||||
private lateinit var setting : Settings
|
private lateinit var setting : Settings
|
||||||
private lateinit var tunnelId: String
|
private lateinit var tunnelId: String
|
||||||
|
|
||||||
private var connecting = false
|
|
||||||
private var disconnecting = false
|
|
||||||
private var isWifiConnected = false
|
|
||||||
private var isMobileDataConnected = false
|
|
||||||
|
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private val tag = this.javaClass.name;
|
private val tag = this.javaClass.name;
|
||||||
|
|
||||||
|
@ -136,6 +141,9 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
watchForMobileDataConnectivityChanges()
|
watchForMobileDataConnectivityChanges()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CoroutineScope(watcherJob).launch {
|
||||||
|
manageVpn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,17 +157,9 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
isMobileDataConnected = true
|
isMobileDataConnected = true
|
||||||
Timber.d("Mobile data capabilities changed")
|
Timber.d("Mobile data capabilities changed")
|
||||||
if(!disconnecting && !connecting) {
|
|
||||||
if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
|
|
||||||
&& vpnService.getState() == Tunnel.State.DOWN)
|
|
||||||
startVPN()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
isMobileDataConnected = false
|
isMobileDataConnected = false
|
||||||
if(!disconnecting && !connecting) {
|
|
||||||
if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
|
|
||||||
}
|
|
||||||
Timber.d("Lost mobile data connection")
|
Timber.d("Lost mobile data connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,61 +176,52 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.d("Wifi capabilities changed")
|
Timber.d("Wifi capabilities changed")
|
||||||
isWifiConnected = true
|
isWifiConnected = true
|
||||||
if (!connecting && !disconnecting) {
|
currentNetworkSSID = wifiService.getNetworkName(it.networkCapabilities) ?: "";
|
||||||
Timber.d("Not connect and not disconnecting")
|
|
||||||
val ssid = wifiService.getNetworkName(it.networkCapabilities);
|
|
||||||
Timber.d("SSID: $ssid")
|
|
||||||
if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) {
|
|
||||||
Timber.d("Starting VPN Tunnel for untrusted network: $ssid")
|
|
||||||
startVPN()
|
|
||||||
} else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains(
|
|
||||||
ssid
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Timber.d("Stopping VPN Tunnel for trusted network with ssid: $ssid")
|
|
||||||
stopVPN()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
isWifiConnected = false
|
isWifiConnected = false
|
||||||
Timber.d("Lost Wi-Fi connection")
|
Timber.d("Lost Wi-Fi connection")
|
||||||
if(!connecting || !disconnecting) {
|
|
||||||
if(setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.DOWN
|
|
||||||
&& isMobileDataConnected){
|
|
||||||
Timber.d("Wifi not available so starting vpn for mobile data")
|
|
||||||
startVPN()
|
|
||||||
}
|
}
|
||||||
if(!setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.UP) {
|
}
|
||||||
Timber.d("Lost WiFi connection, disabling vpn")
|
|
||||||
stopVPN()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun manageVpn() {
|
||||||
|
while(watcherJob.isActive) {
|
||||||
|
if(setting.isTunnelOnMobileDataEnabled &&
|
||||||
|
!isWifiConnected &&
|
||||||
|
isMobileDataConnected
|
||||||
|
&& vpnService.getState() == Tunnel.State.DOWN) {
|
||||||
|
startVPN()
|
||||||
|
} else if(!setting.isTunnelOnMobileDataEnabled &&
|
||||||
|
!isWifiConnected &&
|
||||||
|
vpnService.getState() == Tunnel.State.UP) {
|
||||||
|
stopVPN()
|
||||||
|
} else if(isWifiConnected &&
|
||||||
|
!setting.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
|
||||||
|
(vpnService.getState() != Tunnel.State.UP)) {
|
||||||
|
startVPN()
|
||||||
|
} else if((isWifiConnected &&
|
||||||
|
setting.trustedNetworkSSIDs.contains(currentNetworkSSID)) &&
|
||||||
|
(vpnService.getState() == Tunnel.State.UP)) {
|
||||||
|
stopVPN()
|
||||||
|
}
|
||||||
|
delay(Constants.VPN_CONNECTIVITY_CHECK_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun startVPN() {
|
private fun startVPN() {
|
||||||
if(!connecting) {
|
|
||||||
connecting = true
|
|
||||||
ServiceTracker.actionOnService(
|
ServiceTracker.actionOnService(
|
||||||
Action.START,
|
Action.START,
|
||||||
this.applicationContext as Application,
|
this.applicationContext as Application,
|
||||||
WireGuardTunnelService::class.java,
|
WireGuardTunnelService::class.java,
|
||||||
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
|
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
|
||||||
connecting = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private fun stopVPN() {
|
private fun stopVPN() {
|
||||||
if(!disconnecting) {
|
|
||||||
disconnecting = true
|
|
||||||
ServiceTracker.actionOnService(
|
ServiceTracker.actionOnService(
|
||||||
Action.STOP,
|
Action.STOP,
|
||||||
this.applicationContext as Application,
|
this.applicationContext as Application,
|
||||||
WireGuardTunnelService::class.java
|
WireGuardTunnelService::class.java
|
||||||
)
|
)
|
||||||
disconnecting = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import com.wireguard.android.backend.BackendException
|
||||||
import com.wireguard.android.backend.Statistics
|
import com.wireguard.android.backend.Statistics
|
||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.crypto.Key
|
import com.wireguard.crypto.Key
|
||||||
|
import com.zaneschepke.wireguardautotunnel.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -103,7 +104,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
|
||||||
_handshakeStatus.emit(HandshakeStatus.NOT_STARTED)
|
_handshakeStatus.emit(HandshakeStatus.NOT_STARTED)
|
||||||
}
|
}
|
||||||
if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
|
if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
|
||||||
neverHadHandshakeCounter++
|
neverHadHandshakeCounter += 10
|
||||||
}
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
@ -114,7 +115,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lastHandshake.emit(handshakeMap)
|
_lastHandshake.emit(handshakeMap)
|
||||||
delay(1000)
|
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue