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 {
this.contains(Constants.NIGHTLY) || this.contains(Constants.PRERELEASE) -> {
if (versionFile.exists()) {
versionFile.readText().toInt() + 1
versionFile.readText().trim().toInt() + 1
} else {
1
}

View File

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

View File

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