fix: create tunnel from scratch bug

closes #524
This commit is contained in:
Zane Schepke 2024-12-28 23:46:21 -05:00
parent 7a3627bf6a
commit 4dc91b5fae
5 changed files with 108 additions and 54 deletions

View File

@ -14,7 +14,7 @@ val versionCodeIncrement = with(getBuildTaskName().lowercase()) {
when { when {
this.contains(Constants.NIGHTLY) || this.contains(Constants.PRERELEASE) -> { this.contains(Constants.NIGHTLY) || this.contains(Constants.PRERELEASE) -> {
if (versionFile.exists()) { if (versionFile.exists()) {
versionFile.readText().toInt() + 1 versionFile.readText().trim().toInt() + 1
} else { } else {
1 1
} }

View File

@ -202,18 +202,14 @@ constructor(
private suspend fun handleVpnKillSwitchChange(enabled: Boolean) { private suspend fun handleVpnKillSwitchChange(enabled: Boolean) {
withContext(ioDispatcher) { withContext(ioDispatcher) {
if (enabled) { if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
Timber.d("Starting kill switch") Timber.d("Starting kill switch")
val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) { val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) {
TunnelConfig.IPV4_PUBLIC_NETWORKS TunnelConfig.IPV4_PUBLIC_NETWORKS
} else {
emptySet()
}
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
} else { } else {
Timber.d("Sending shutdown of kill switch") emptySet()
tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
} }
tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
} }
} }
@ -297,25 +293,67 @@ constructor(
} }
} }
fun saveConfigChanges(config: TunnelConfig, peers: List<PeerProxy>? = null, `interface`: InterfaceProxy? = null) = viewModelScope.launch( fun updateExistingTunnelConfig(
ioDispatcher, tunnelConfig: TunnelConfig,
) { tunnelName: String? = null,
peers: List<PeerProxy>? = null,
`interface`: InterfaceProxy? = null,
) = viewModelScope.launch {
runCatching { runCatching {
val amConfig = config.toAmConfig() val amConfig = tunnelConfig.toAmConfig()
val wgConfig = config.toWgConfig() val wgConfig = tunnelConfig.toWgConfig()
rebuildConfigsAndSave(config, amConfig, wgConfig, peers, `interface`) updateTunnelConfig(tunnelConfig, tunnelName, amConfig, wgConfig, peers, `interface`)
_popBackStack.emit(true) _popBackStack.emit(true)
SnackbarController.showMessage(StringValue.StringResource(R.string.config_changes_saved)) SnackbarController.showMessage(StringValue.StringResource(R.string.config_changes_saved))
}.onFailure { }.onFailure {
Timber.e(it) onConfigSaveError(it)
SnackbarController.showMessage(
it.message?.let { message ->
(StringValue.DynamicString(message))
} ?: StringValue.StringResource(R.string.unknown_error),
)
} }
} }
fun saveNewTunnel(tunnelName: String, peers: List<PeerProxy>, `interface`: InterfaceProxy) = viewModelScope.launch {
runCatching {
val config = buildConfigs(peers, `interface`)
appDataRepository.tunnels.save(
TunnelConfig(
name = tunnelName,
wgQuick = config.first.toWgQuickString(true),
amQuick = config.second.toAwgQuickString(true),
),
)
_popBackStack.emit(true)
SnackbarController.showMessage(StringValue.StringResource(R.string.config_changes_saved))
}.onFailure {
onConfigSaveError(it)
}
}
private fun onConfigSaveError(throwable: Throwable) {
Timber.e(throwable)
SnackbarController.showMessage(
throwable.message?.let { message ->
(StringValue.DynamicString(message))
} ?: StringValue.StringResource(R.string.unknown_error),
)
}
private suspend fun updateTunnelConfig(
tunnelConfig: TunnelConfig,
tunnelName: String? = null,
amConfig: org.amnezia.awg.config.Config,
wgConfig: Config,
peers: List<PeerProxy>? = null,
`interface`: InterfaceProxy? = null,
) {
val configs = rebuildConfigs(amConfig, wgConfig, peers, `interface`)
appDataRepository.tunnels.save(
tunnelConfig.copy(
name = tunnelName ?: tunnelConfig.name,
amQuick = configs.second.toAwgQuickString(true),
wgQuick = configs.first.toWgQuickString(true),
),
)
}
fun cleanUpUninstalledApps(tunnelConfig: TunnelConfig, packages: List<String>) = viewModelScope.launch(ioDispatcher) { fun cleanUpUninstalledApps(tunnelConfig: TunnelConfig, packages: List<String>) = viewModelScope.launch(ioDispatcher) {
runCatching { runCatching {
val amConfig = tunnelConfig.toAmConfig() val amConfig = tunnelConfig.toAmConfig()
@ -323,8 +361,8 @@ constructor(
val proxy = InterfaceProxy.from(amConfig.`interface`) val proxy = InterfaceProxy.from(amConfig.`interface`)
if (proxy.includedApplications.isEmpty() && proxy.excludedApplications.isEmpty()) return@launch if (proxy.includedApplications.isEmpty() && proxy.excludedApplications.isEmpty()) return@launch
if (proxy.includedApplications.retainAll(packages.toSet()) || proxy.excludedApplications.retainAll(packages.toSet())) { if (proxy.includedApplications.retainAll(packages.toSet()) || proxy.excludedApplications.retainAll(packages.toSet())) {
Timber.i("Removing split tunnel package for app that no longer exists on the device") updateTunnelConfig(tunnelConfig, amConfig = amConfig, wgConfig = wgConfig, `interface` = proxy)
rebuildConfigsAndSave(tunnelConfig, amConfig, wgConfig, `interface` = proxy) Timber.i("Removed split tunnel package for app that no longer exists on the device")
} }
}.onFailure { }.onFailure {
Timber.e(it) Timber.e(it)
@ -340,24 +378,38 @@ constructor(
) )
} }
private suspend fun rebuildConfigsAndSave( private suspend fun rebuildConfigs(
config: TunnelConfig,
amConfig: org.amnezia.awg.config.Config, amConfig: org.amnezia.awg.config.Config,
wgConfig: Config, wgConfig: Config,
peers: List<PeerProxy>? = null, peers: List<PeerProxy>? = null,
`interface`: InterfaceProxy? = null, `interface`: InterfaceProxy? = null,
) { ): Pair<Config, org.amnezia.awg.config.Config> {
appDataRepository.tunnels.save( return withContext(ioDispatcher) {
config.copy( Pair(
wgQuick = Config.Builder().apply { Config.Builder().apply {
addPeers(peers?.map { it.toWgPeer() } ?: wgConfig.peers) addPeers(peers?.map { it.toWgPeer() } ?: wgConfig.peers)
setInterface(`interface`?.toWgInterface() ?: wgConfig.`interface`) setInterface(`interface`?.toWgInterface() ?: wgConfig.`interface`)
}.build().toWgQuickString(true), }.build(),
amQuick = org.amnezia.awg.config.Config.Builder().apply { org.amnezia.awg.config.Config.Builder().apply {
addPeers(peers?.map { it.toAmPeer() } ?: amConfig.peers) addPeers(peers?.map { it.toAmPeer() } ?: amConfig.peers)
setInterface(`interface`?.toAmInterface() ?: amConfig.`interface`) setInterface(`interface`?.toAmInterface() ?: amConfig.`interface`)
}.build().toAwgQuickString(true), }.build(),
), )
) }
}
private suspend fun buildConfigs(peers: List<PeerProxy>, `interface`: InterfaceProxy): Pair<Config, org.amnezia.awg.config.Config> {
return withContext(ioDispatcher) {
Pair(
Config.Builder().apply {
addPeers(peers.map { it.toWgPeer() })
setInterface(`interface`.toWgInterface())
}.build(),
org.amnezia.awg.config.Config.Builder().apply {
addPeers(peers.map { it.toAmPeer() })
setInterface(`interface`.toAmInterface())
}.build(),
)
}
} }
} }

View File

@ -155,7 +155,7 @@ fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId:
val amneziaClick = { val amneziaClick = {
val proxy = InterfaceProxy.from(amConfig.`interface`) val proxy = InterfaceProxy.from(amConfig.`interface`)
val `interface` = if (!isAmneziaCompatibilityEnabled) proxy.toAmneziaCompatibilityConfig() else proxy.resetAmneziaProperties() val `interface` = if (!isAmneziaCompatibilityEnabled) proxy.toAmneziaCompatibilityConfig() else proxy.resetAmneziaProperties()
appViewModel.saveConfigChanges(config, `interface` = `interface`) appViewModel.updateExistingTunnelConfig(config, `interface` = `interface`)
} }
GroupLabel(stringResource(R.string.quick_actions)) GroupLabel(stringResource(R.string.quick_actions))
SurfaceSelectionGroupButton( SurfaceSelectionGroupButton(

View File

@ -83,13 +83,13 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
val tunnelConfig by remember { val tunnelConfig by remember {
derivedStateOf { derivedStateOf {
appUiState.tunnels.first { it.id == tunnelId } appUiState.tunnels.firstOrNull { it.id == tunnelId }
} }
} }
val configPair by remember { val configPair by remember {
derivedStateOf { derivedStateOf {
Pair(tunnelConfig.name, tunnelConfig.toAmConfig()) Pair(tunnelConfig?.name ?: "", tunnelConfig?.toAmConfig())
} }
} }
@ -98,11 +98,11 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
} }
var interfaceState by remember { var interfaceState by remember {
mutableStateOf(InterfaceProxy.from(configPair.second.`interface`)) mutableStateOf(configPair.second?.let { InterfaceProxy.from(it.`interface`) } ?: InterfaceProxy())
} }
var showAmneziaValues by remember { var showAmneziaValues by remember {
mutableStateOf(configPair.second.`interface`.junkPacketCount.isPresent) mutableStateOf(configPair.second?.`interface`?.junkPacketCount?.isPresent == true)
} }
var showScripts by remember { var showScripts by remember {
@ -110,7 +110,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
} }
val peersState = remember { val peersState = remember {
configPair.second.peers.map { PeerProxy.from(it) }.toMutableStateList() (configPair.second?.peers?.map { PeerProxy.from(it) } ?: listOf(PeerProxy())).toMutableStateList()
} }
var showAuthPrompt by remember { mutableStateOf(false) } var showAuthPrompt by remember { mutableStateOf(false) }
@ -148,13 +148,14 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
topBar = { topBar = {
TopNavBar(stringResource(R.string.edit_tunnel), trailing = { TopNavBar(stringResource(R.string.edit_tunnel), trailing = {
IconButton(onClick = { IconButton(onClick = {
appViewModel.saveConfigChanges( tunnelConfig?.let {
tunnelConfig.copy( appViewModel.updateExistingTunnelConfig(
name = tunnelName, it,
), tunnelName,
peers = peersState, peersState,
`interface` = interfaceState, interfaceState,
) )
} ?: appViewModel.saveNewTunnel(tunnelName, peersState, interfaceState)
}) { }) {
val icon = Icons.Outlined.Save val icon = Icons.Outlined.Save
Icon( Icon(
@ -226,6 +227,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
Modifier Modifier
.fillMaxWidth(), .fillMaxWidth(),
) )
val privateKeyEnabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated
OutlinedTextField( OutlinedTextField(
textStyle = MaterialTheme.typography.labelLarge, textStyle = MaterialTheme.typography.labelLarge,
modifier = modifier =
@ -234,16 +236,16 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
.clickable { showAuthPrompt = true }, .clickable { showAuthPrompt = true },
value = interfaceState.privateKey, value = interfaceState.privateKey,
visualTransformation = visualTransformation =
if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated) { if (privateKeyEnabled) {
VisualTransformation.None VisualTransformation.None
} else { } else {
PasswordVisualTransformation() PasswordVisualTransformation()
}, },
enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated, enabled = privateKeyEnabled,
onValueChange = { interfaceState = interfaceState.copy(privateKey = it) }, onValueChange = { interfaceState = interfaceState.copy(privateKey = it) },
trailingIcon = { trailingIcon = {
IconButton( IconButton(
enabled = isAuthenticated, enabled = privateKeyEnabled,
modifier = Modifier.focusRequester(FocusRequester.Default), modifier = Modifier.focusRequester(FocusRequester.Default),
onClick = { onClick = {
val keypair = KeyPair() val keypair = KeyPair()
@ -256,7 +258,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
Icon( Icon(
Icons.Rounded.Refresh, Icons.Rounded.Refresh,
stringResource(R.string.rotate_keys), stringResource(R.string.rotate_keys),
tint = if (isAuthenticated) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline, tint = if (privateKeyEnabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline,
) )
} }
}, },

View File

@ -127,7 +127,7 @@ fun SplitTunnelScreen(appUiState: AppUiState, tunnelId: Int, viewModel: AppViewM
} }
SplitOptions.ALL -> Unit SplitOptions.ALL -> Unit
} }
viewModel.saveConfigChanges(config, `interface` = proxyInterface) viewModel.updateExistingTunnelConfig(config, `interface` = proxyInterface)
}) { }) {
val icon = Icons.Outlined.Save val icon = Icons.Outlined.Save
Icon( Icon(