fix: auto tunneling and backup

Fixes a bug where android backups can cause app crashes due to pin lock feature keystore.

Fixes a bug where auto tunneling to SSID tunnel was not working correctly.

Fixes a mobile data tunneling bug which was causing mobile data tunneling to not perform correctly.

Additional strictmode improvements.
This commit is contained in:
Zane Schepke 2024-06-01 02:37:32 -04:00
parent 6448386f76
commit 1d74d0984e
10 changed files with 92 additions and 53 deletions

View File

@ -51,7 +51,7 @@
</queries> </queries>
<application <application
android:name=".WireGuardAutoTunnel" android:name=".WireGuardAutoTunnel"
android:allowBackup="true" android:allowBackup="false"
android:banner="@drawable/ic_banner" android:banner="@drawable/ic_banner"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"

View File

@ -17,6 +17,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import javax.inject.Provider
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -51,9 +52,9 @@ class TunnelModule {
@Provides @Provides
@Singleton @Singleton
fun provideVpnService( fun provideVpnService(
amneziaBackend: org.amnezia.awg.backend.Backend, amneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
@Userspace userspaceBackend: Backend, @Userspace userspaceBackend: Provider<Backend>,
@Kernel kernelBackend: Backend, @Kernel kernelBackend: Provider<Backend>,
appDataRepository: AppDataRepository, appDataRepository: AppDataRepository,
@ApplicationScope applicationScope: CoroutineScope, @ApplicationScope applicationScope: CoroutineScope,
@IoDispatcher ioDispatcher: CoroutineDispatcher @IoDispatcher ioDispatcher: CoroutineDispatcher

View File

@ -390,19 +390,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
} }
watcherState.isMobileDataConditionMet() -> { watcherState.isMobileDataConditionMet() -> {
Timber.i("$autoTunnel - tunnel on on mobile data condition met") Timber.i("$autoTunnel - tunnel on mobile data condition met")
val mobileDataTunnel = getMobileDataTunnel() val mobileDataTunnel = getMobileDataTunnel()
val tunnel = val tunnel =
mobileDataTunnel ?: appDataRepository.getPrimaryOrFirstTunnel() mobileDataTunnel ?: appDataRepository.getPrimaryOrFirstTunnel()
if (isTunnelDown()) return@collectLatest serviceManager.startVpnServiceForeground( if (isTunnelDown() || tunnelConfig?.isMobileDataTunnel == false) {
context,
tunnel?.id,
)
if (tunnelConfig?.isMobileDataTunnel == false && mobileDataTunnel != null) {
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
serviceManager.startVpnServiceForeground( serviceManager.startVpnServiceForeground(
context, context,
mobileDataTunnel.id, tunnel?.id,
) )
} }
} }
@ -417,8 +412,8 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
tunnelConfig == null) { tunnelConfig == null) {
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met") Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
getSsidTunnel(watcherState.currentNetworkSSID)?.let { getSsidTunnel(watcherState.currentNetworkSSID)?.let {
Timber.i("Found tunnel associated with this SSID, bringing tunnel up") Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}")
if (isTunnelDown()) serviceManager.startVpnServiceForeground( if (isTunnelDown() || tunnelConfig?.id != it.id) serviceManager.startVpnServiceForeground(
context, context,
it.id, it.id,
) )

View File

@ -27,13 +27,14 @@ import kotlinx.coroutines.withContext
import org.amnezia.awg.backend.Tunnel import org.amnezia.awg.backend.Tunnel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
class WireGuardTunnel class WireGuardTunnel
@Inject @Inject
constructor( constructor(
private val userspaceAmneziaBackend: org.amnezia.awg.backend.Backend, private val userspaceAmneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
@Userspace private val userspaceBackend: Backend, @Userspace private val userspaceBackend: Provider<Backend>,
@Kernel private val kernelBackend: Backend, @Kernel private val kernelBackend: Provider<Backend>,
private val appDataRepository: AppDataRepository, private val appDataRepository: AppDataRepository,
@ApplicationScope private val applicationScope: CoroutineScope, @ApplicationScope private val applicationScope: CoroutineScope,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher @IoDispatcher private val ioDispatcher: CoroutineDispatcher
@ -44,7 +45,7 @@ constructor(
private var statsJob: Job? = null private var statsJob: Job? = null
private var backend: Backend = userspaceBackend private lateinit var backend: Backend;
private var backendIsWgUserspace = true private var backendIsWgUserspace = true
@ -52,15 +53,16 @@ constructor(
init { init {
applicationScope.launch(ioDispatcher) { applicationScope.launch(ioDispatcher) {
backend = userspaceBackend.get()
appDataRepository.settings.getSettingsFlow().collect { appDataRepository.settings.getSettingsFlow().collect {
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) { if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
Timber.i("Setting kernel backend") Timber.i("Setting kernel backend")
backend = kernelBackend backend = kernelBackend.get()
backendIsWgUserspace = false backendIsWgUserspace = false
backendIsAmneziaUserspace = false backendIsAmneziaUserspace = false
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) { } else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
Timber.i("Setting WireGuard userspace backend") Timber.i("Setting WireGuard userspace backend")
backend = userspaceBackend backend = userspaceBackend.get()
backendIsWgUserspace = true backendIsWgUserspace = true
backendIsAmneziaUserspace = false backendIsAmneziaUserspace = false
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) { } else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
@ -81,7 +83,8 @@ constructor(
TunnelConfig.configFromAmQuick(it.wgQuick) TunnelConfig.configFromAmQuick(it.wgQuick)
} }
} }
val state = userspaceAmneziaBackend.setState(this, tunnelState.toAmState(), config) val state =
userspaceAmneziaBackend.get().setState(this, tunnelState.toAmState(), config)
TunnelState.from(state) TunnelState.from(state)
} else { } else {
Timber.i("Using Wg backend") Timber.i("Using Wg backend")
@ -156,7 +159,9 @@ constructor(
} }
override fun getState(): TunnelState { override fun getState(): TunnelState {
return if (backendIsAmneziaUserspace) TunnelState.from(userspaceAmneziaBackend.getState(this)) return if (backendIsAmneziaUserspace) TunnelState.from(
userspaceAmneziaBackend.get().getState(this),
)
else TunnelState.from(backend.getState(this)) else TunnelState.from(backend.getState(this))
} }
@ -187,7 +192,11 @@ constructor(
private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) {
while (true) { while (true) {
if (backendIsAmneziaUserspace) { if (backendIsAmneziaUserspace) {
emitBackendStatistics(AmneziaStatistics(userspaceAmneziaBackend.getStatistics(this@WireGuardTunnel))) emitBackendStatistics(
AmneziaStatistics(
userspaceAmneziaBackend.get().getStatistics(this@WireGuardTunnel),
),
)
} else { } else {
emitBackendStatistics(WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel))) emitBackendStatistics(WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel)))
} }

View File

@ -44,6 +44,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -70,7 +71,6 @@ import androidx.navigation.NavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@ -106,6 +106,7 @@ fun SettingsScreen(
val pinExists = remember { mutableStateOf(PinManager.pinExists()) } val pinExists = remember { mutableStateOf(PinManager.pinExists()) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
@ -117,6 +118,10 @@ fun SettingsScreen(
val screenPadding = 5.dp val screenPadding = 5.dp
val fillMaxWidth = .85f val fillMaxWidth = .85f
LaunchedEffect(Unit) {
viewModel.checkKernelSupport()
}
val startForResult = val startForResult =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
@ -591,7 +596,7 @@ fun SettingsScreen(
viewModel.onToggleAmnezia() viewModel.onToggleAmnezia()
}, },
) )
if (WgQuickBackend.hasKernelSupport()) { if (kernelSupport) {
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.use_kernel), stringResource(R.string.use_kernel),
enabled = enabled =
@ -601,9 +606,11 @@ fun SettingsScreen(
checked = uiState.settings.isKernelEnabled, checked = uiState.settings.isKernelEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch {
viewModel.onToggleKernelMode().onFailure { viewModel.onToggleKernelMode().onFailure {
appViewModel.showSnackbarMessage(it.getMessage(context)) appViewModel.showSnackbarMessage(it.getMessage(context))
} }
}
}, },
) )
} }

View File

@ -9,5 +9,5 @@ data class SettingsUiState(
val tunnels: List<TunnelConfig> = emptyList(), val tunnels: List<TunnelConfig> = emptyList(),
val vpnState: VpnState = VpnState(), val vpnState: VpnState = VpnState(),
val isLocationDisclosureShown: Boolean = true, val isLocationDisclosureShown: Boolean = true,
val isBatteryOptimizeDisableShown: Boolean = false val isBatteryOptimizeDisableShown: Boolean = false,
) )

View File

@ -5,23 +5,31 @@ import android.location.LocationManager
import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationManagerCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
@HiltViewModel @HiltViewModel
class SettingsViewModel class SettingsViewModel
@ -29,11 +37,15 @@ class SettingsViewModel
constructor( constructor(
private val appDataRepository: AppDataRepository, private val appDataRepository: AppDataRepository,
private val serviceManager: ServiceManager, private val serviceManager: ServiceManager,
private val rootShell: RootShell, private val rootShell: Provider<RootShell>,
private val fileUtils: FileUtils, private val fileUtils: FileUtils,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
vpnService: VpnService vpnService: VpnService
) : ViewModel() { ) : ViewModel() {
private val _kernelSupport = MutableStateFlow(false)
val kernelSupport = _kernelSupport.asStateFlow()
val uiState = val uiState =
combine( combine(
appDataRepository.settings.getSettingsFlow(), appDataRepository.settings.getSettingsFlow(),
@ -181,10 +193,11 @@ constructor(
) )
} }
fun onToggleKernelMode(): Result<Unit> { suspend fun onToggleKernelMode(): Result<Unit> {
return withContext(ioDispatcher) {
if (!uiState.value.settings.isKernelEnabled) { if (!uiState.value.settings.isKernelEnabled) {
try { try {
rootShell.start() rootShell.get().start()
Timber.i("Root shell accepted!") Timber.i("Root shell accepted!")
saveSettings( saveSettings(
uiState.value.settings.copy( uiState.value.settings.copy(
@ -196,12 +209,13 @@ constructor(
} catch (e: RootShell.RootShellException) { } catch (e: RootShell.RootShellException) {
Timber.e(e) Timber.e(e)
saveKernelMode(on = false) saveKernelMode(on = false)
return Result.failure(WgTunnelExceptions.RootDenied()) return@withContext Result.failure(WgTunnelExceptions.RootDenied())
} }
} else { } else {
saveKernelMode(on = false) saveKernelMode(on = false)
} }
return Result.success(Unit) Result.success(Unit)
}
} }
fun onToggleRestartOnPing() = viewModelScope.launch { fun onToggleRestartOnPing() = viewModelScope.launch {
@ -211,4 +225,13 @@ constructor(
), ),
) )
} }
fun checkKernelSupport() = viewModelScope.launch {
val kernelSupport = withContext(ioDispatcher) {
WgQuickBackend.hasKernelSupport()
}
_kernelSupport.update {
kernelSupport
}
}
} }

View File

@ -1,12 +1,12 @@
object Constants { object Constants {
const val VERSION_NAME = "3.4.5" const val VERSION_NAME = "3.4.6"
const val JVM_TARGET = "17" const val JVM_TARGET = "17"
const val VERSION_CODE = 34500 const val VERSION_CODE = 34600
const val TARGET_SDK = 34 const val TARGET_SDK = 34
const val MIN_SDK = 26 const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel" const val APP_ID = "com.zaneschepke.wireguardautotunnel"
const val APP_NAME = "wgtunnel" const val APP_NAME = "wgtunnel"
const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.11" const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.14"
const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD" const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD"

View File

@ -0,0 +1,4 @@
What's new:
- Fixes auto tunneling bugs
- Fixes android backup bug
- Bump versions

View File

@ -3,7 +3,7 @@ accompanist = "0.34.0"
activityCompose = "1.9.0" activityCompose = "1.9.0"
amneziawgAndroid = "1.2.0" amneziawgAndroid = "1.2.0"
androidx-junit = "1.1.5" androidx-junit = "1.1.5"
appcompat = "1.6.1" appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05" biometricKtx = "1.2.0-alpha05"
coreGoogleShortcuts = "1.1.0" coreGoogleShortcuts = "1.1.0"
coreKtx = "1.13.1" coreKtx = "1.13.1"
@ -23,14 +23,14 @@ roomVersion = "2.6.1"
timber = "5.0.1" timber = "5.0.1"
tunnel = "1.0.20230706" tunnel = "1.0.20230706"
androidGradlePlugin = "8.4.1" androidGradlePlugin = "8.4.1"
kotlin = "1.9.23" kotlin = "1.9.24"
ksp = "1.9.23-1.0.19" ksp = "1.9.24-1.0.20"
composeBom = "2024.05.00" composeBom = "2024.05.00"
compose = "1.6.7" compose = "1.6.7"
zxingAndroidEmbedded = "4.3.0" zxingAndroidEmbedded = "4.3.0"
#plugins #plugins
gradlePlugins-kotlinxSerialization = "1.9.23" gradlePlugins-kotlinxSerialization = "1.9.24"
material = "1.12.0" material = "1.12.0"