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>
<application
android:name=".WireGuardAutoTunnel"
android:allowBackup="true"
android:allowBackup="false"
android:banner="@drawable/ic_banner"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"

View File

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

View File

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

View File

@ -27,13 +27,14 @@ import kotlinx.coroutines.withContext
import org.amnezia.awg.backend.Tunnel
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider
class WireGuardTunnel
@Inject
constructor(
private val userspaceAmneziaBackend: org.amnezia.awg.backend.Backend,
@Userspace private val userspaceBackend: Backend,
@Kernel private val kernelBackend: Backend,
private val userspaceAmneziaBackend: Provider<org.amnezia.awg.backend.Backend>,
@Userspace private val userspaceBackend: Provider<Backend>,
@Kernel private val kernelBackend: Provider<Backend>,
private val appDataRepository: AppDataRepository,
@ApplicationScope private val applicationScope: CoroutineScope,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
@ -44,7 +45,7 @@ constructor(
private var statsJob: Job? = null
private var backend: Backend = userspaceBackend
private lateinit var backend: Backend;
private var backendIsWgUserspace = true
@ -52,15 +53,16 @@ constructor(
init {
applicationScope.launch(ioDispatcher) {
backend = userspaceBackend.get()
appDataRepository.settings.getSettingsFlow().collect {
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
Timber.i("Setting kernel backend")
backend = kernelBackend
backend = kernelBackend.get()
backendIsWgUserspace = false
backendIsAmneziaUserspace = false
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
Timber.i("Setting WireGuard userspace backend")
backend = userspaceBackend
backend = userspaceBackend.get()
backendIsWgUserspace = true
backendIsAmneziaUserspace = false
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
@ -81,7 +83,8 @@ constructor(
TunnelConfig.configFromAmQuick(it.wgQuick)
}
}
val state = userspaceAmneziaBackend.setState(this, tunnelState.toAmState(), config)
val state =
userspaceAmneziaBackend.get().setState(this, tunnelState.toAmState(), config)
TunnelState.from(state)
} else {
Timber.i("Using Wg backend")
@ -156,7 +159,9 @@ constructor(
}
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))
}
@ -187,7 +192,11 @@ constructor(
private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) {
while (true) {
if (backendIsAmneziaUserspace) {
emitBackendStatistics(AmneziaStatistics(userspaceAmneziaBackend.getStatistics(this@WireGuardTunnel)))
emitBackendStatistics(
AmneziaStatistics(
userspaceAmneziaBackend.get().getStatistics(this@WireGuardTunnel),
),
)
} else {
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.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -70,7 +71,6 @@ import androidx.navigation.NavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@ -106,6 +106,7 @@ fun SettingsScreen(
val pinExists = remember { mutableStateOf(PinManager.pinExists()) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") }
@ -117,6 +118,10 @@ fun SettingsScreen(
val screenPadding = 5.dp
val fillMaxWidth = .85f
LaunchedEffect(Unit) {
viewModel.checkKernelSupport()
}
val startForResult =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
@ -591,7 +596,7 @@ fun SettingsScreen(
viewModel.onToggleAmnezia()
},
)
if (WgQuickBackend.hasKernelSupport()) {
if (kernelSupport) {
ConfigurationToggle(
stringResource(R.string.use_kernel),
enabled =
@ -601,8 +606,10 @@ fun SettingsScreen(
checked = uiState.settings.isKernelEnabled,
padding = screenPadding,
onCheckChanged = {
viewModel.onToggleKernelMode().onFailure {
appViewModel.showSnackbarMessage(it.getMessage(context))
scope.launch {
viewModel.onToggleKernelMode().onFailure {
appViewModel.showSnackbarMessage(it.getMessage(context))
}
}
},
)

View File

@ -9,5 +9,5 @@ data class SettingsUiState(
val tunnels: List<TunnelConfig> = emptyList(),
val vpnState: VpnState = VpnState(),
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.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
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.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import javax.inject.Inject
import javax.inject.Provider
@HiltViewModel
class SettingsViewModel
@ -29,11 +37,15 @@ class SettingsViewModel
constructor(
private val appDataRepository: AppDataRepository,
private val serviceManager: ServiceManager,
private val rootShell: RootShell,
private val rootShell: Provider<RootShell>,
private val fileUtils: FileUtils,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
vpnService: VpnService
) : ViewModel() {
private val _kernelSupport = MutableStateFlow(false)
val kernelSupport = _kernelSupport.asStateFlow()
val uiState =
combine(
appDataRepository.settings.getSettingsFlow(),
@ -181,27 +193,29 @@ constructor(
)
}
fun onToggleKernelMode(): Result<Unit> {
if (!uiState.value.settings.isKernelEnabled) {
try {
rootShell.start()
Timber.i("Root shell accepted!")
saveSettings(
uiState.value.settings.copy(
isKernelEnabled = true,
isAmneziaEnabled = false,
),
)
suspend fun onToggleKernelMode(): Result<Unit> {
return withContext(ioDispatcher) {
if (!uiState.value.settings.isKernelEnabled) {
try {
rootShell.get().start()
Timber.i("Root shell accepted!")
saveSettings(
uiState.value.settings.copy(
isKernelEnabled = true,
isAmneziaEnabled = false,
),
)
} catch (e: RootShell.RootShellException) {
Timber.e(e)
} catch (e: RootShell.RootShellException) {
Timber.e(e)
saveKernelMode(on = false)
return@withContext Result.failure(WgTunnelExceptions.RootDenied())
}
} else {
saveKernelMode(on = false)
return Result.failure(WgTunnelExceptions.RootDenied())
}
} else {
saveKernelMode(on = false)
Result.success(Unit)
}
return Result.success(Unit)
}
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 {
const val VERSION_NAME = "3.4.5"
const val VERSION_NAME = "3.4.6"
const val JVM_TARGET = "17"
const val VERSION_CODE = 34500
const val VERSION_CODE = 34600
const val TARGET_SDK = 34
const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
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"

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