feat: toggle to set amnezia/wg compatibility values

closes #469
closes #518
This commit is contained in:
Zane Schepke 2024-12-27 02:52:44 -05:00
parent b2e266fc9f
commit 11e385d350
8 changed files with 107 additions and 103 deletions

View File

@ -331,6 +331,15 @@ constructor(
}
}
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.updatePrimaryTunnel(
when (tunnelConfig.isPrimaryTunnel) {
true -> null
false -> tunnelConfig
},
)
}
private suspend fun rebuildConfigsAndSave(
config: TunnelConfig,
amConfig: org.amnezia.awg.config.Config,

View File

@ -227,6 +227,7 @@ class MainActivity : AppCompatActivity() {
OptionsScreen(
tunnelId = args.id,
appUiState = appUiState,
appViewModel = viewModel,
)
}
composable<Route.Lock> {

View File

@ -8,6 +8,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.CallSplit
import androidx.compose.material.icons.outlined.Adjust
import androidx.compose.material.icons.outlined.Bolt
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Star
@ -24,24 +25,35 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.util.extensions.isWgCompatibilityMode
import com.zaneschepke.wireguardautotunnel.util.extensions.resetAmneziaProperties
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
import com.zaneschepke.wireguardautotunnel.util.extensions.toAmneziaCompatibilityConfig
@Composable
fun OptionsScreen(tunnelOptionsViewModel: TunnelOptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) {
fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId: Int) {
val navController = LocalNavController.current
val config = appUiState.tunnels.first { it.id == tunnelId }
// TODO optimize
val amConfig = config.toAmConfig()
val isAmneziaCompatibilityEnabled = amConfig.`interface`.isWgCompatibilityMode()
var currentText by remember { mutableStateOf("") }
LaunchedEffect(config.tunnelNetworks) {
@ -82,10 +94,10 @@ fun OptionsScreen(tunnelOptionsViewModel: TunnelOptionsViewModel = hiltViewModel
trailing = {
ScaledSwitch(
config.isPrimaryTunnel,
onClick = { tunnelOptionsViewModel.onTogglePrimaryTunnel(config) },
onClick = { appViewModel.onTogglePrimaryTunnel(config) },
)
},
onClick = { tunnelOptionsViewModel.onTogglePrimaryTunnel(config) },
onClick = { appViewModel.onTogglePrimaryTunnel(config) },
),
SelectionItem(
Icons.Outlined.Bolt,
@ -140,7 +152,38 @@ fun OptionsScreen(tunnelOptionsViewModel: TunnelOptionsViewModel = hiltViewModel
),
),
)
// GroupLabel(stringResource(R.string.quick_actions))
val amneziaClick = {
val proxy = InterfaceProxy.from(amConfig.`interface`)
val `interface` = if (!isAmneziaCompatibilityEnabled) proxy.toAmneziaCompatibilityConfig() else proxy.resetAmneziaProperties()
appViewModel.saveConfigChanges(config, `interface` = `interface`)
}
GroupLabel(stringResource(R.string.quick_actions))
SurfaceSelectionGroupButton(
listOf(
SelectionItem(
Icons.Outlined.Adjust,
title = {
Text(
stringResource(R.string.enable_amnezia),
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
)
},
description = {
Text(
stringResource(R.string.wg_compat_mode),
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
)
},
trailing = {
ScaledSwitch(
isAmneziaCompatibilityEnabled,
onClick = { amneziaClick() },
)
},
onClick = { amneziaClick() },
),
),
)
}
}
}

View File

@ -1,88 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class TunnelOptionsViewModel
@Inject
constructor(
private val appDataRepository: AppDataRepository,
) : ViewModel() {
fun onDeleteRunSSID(ssid: String, tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(
tunnelConfig =
tunnelConfig.copy(
tunnelNetworks = (tunnelConfig.tunnelNetworks - ssid).toMutableList(),
),
)
}
fun saveTunnelChanges(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(tunnelConfig)
}
fun onSaveRunSSID(ssid: String, tunnelConfig: TunnelConfig) = viewModelScope.launch {
if (ssid.isBlank()) return@launch
val trimmed = ssid.trim()
val tunnelsWithName = appDataRepository.tunnels.findByTunnelNetworksName(trimmed)
if (!tunnelConfig.tunnelNetworks.contains(trimmed) &&
tunnelsWithName.isEmpty()
) {
saveTunnelChanges(
tunnelConfig.copy(
tunnelNetworks = (tunnelConfig.tunnelNetworks + ssid).toMutableList(),
),
)
} else {
SnackbarController.showMessage(
StringValue.StringResource(
R.string.error_ssid_exists,
),
)
}
}
fun onToggleIsMobileDataTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
if (tunnelConfig.isMobileDataTunnel) {
appDataRepository.tunnels.updateMobileDataTunnel(null)
} else {
appDataRepository.tunnels.updateMobileDataTunnel(tunnelConfig)
}
}
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.updatePrimaryTunnel(
when (tunnelConfig.isPrimaryTunnel) {
true -> null
false -> tunnelConfig
},
)
}
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(
tunnelConfig.copy(
isPingEnabled = !tunnelConfig.isPingEnabled,
),
)
}
fun onToggleIsEthernetTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
if (tunnelConfig.isEthernetTunnel) {
appDataRepository.tunnels.updateEthernetTunnel(null)
} else {
appDataRepository.tunnels.updateEthernetTunnel(tunnelConfig)
}
}
}

View File

@ -87,7 +87,7 @@ fun SplitTunnelScreen(appUiState: AppUiState, tunnelId: Int, viewModel: AppViewM
val selectedPackages = remember { mutableStateListOf<String>() }
LaunchedEffect(Unit) {
proxyInterface = InterfaceProxy.from(config.toWgConfig().`interface`)
proxyInterface = InterfaceProxy.from(config.toAmConfig().`interface`)
val pair = when {
proxyInterface.excludedApplications.isNotEmpty() -> Pair(SplitOptions.EXCLUDE, proxyInterface.excludedApplications)
proxyInterface.includedApplications.isNotEmpty() -> Pair(SplitOptions.INCLUDE, proxyInterface.includedApplications)

View File

@ -61,15 +61,6 @@ constructor(
}
}
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.updatePrimaryTunnel(
when (tunnelConfig.isPrimaryTunnel) {
true -> null
false -> tunnelConfig
},
)
}
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
appDataRepository.tunnels.save(
tunnelConfig.copy(

View File

@ -6,14 +6,17 @@ import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import org.amnezia.awg.backend.Backend
import org.amnezia.awg.config.Config
import org.amnezia.awg.config.Interface
import timber.log.Timber
import java.net.InetAddress
import kotlin.jvm.optionals.getOrNull
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
@ -95,3 +98,45 @@ fun Backend.BackendState.asBackendState(): BackendState {
fun BackendState.asAmBackendState(): Backend.BackendState {
return Backend.BackendState.valueOf(this.name)
}
fun Interface.isWgCompatibilityMode(): Boolean {
return (
junkPacketCount.getOrNull() in 3..<5 &&
junkPacketMinSize.getOrNull() == 40 &&
junkPacketMaxSize.getOrNull() == 70 &&
with(initPacketJunkSize.getOrNull()) { this == 0 || this == null } &&
with(responsePacketJunkSize.getOrNull()) { this == 0 || this == null } &&
initPacketMagicHeader.getOrNull() == 1L &&
responsePacketMagicHeader.getOrNull() == 2L &&
underloadPacketMagicHeader.getOrNull() == 3L &&
transportPacketMagicHeader.getOrNull() == 4L
)
}
fun InterfaceProxy.toAmneziaCompatibilityConfig(): InterfaceProxy {
return copy(
junkPacketCount = "4",
junkPacketMinSize = "40",
junkPacketMaxSize = "70",
initPacketJunkSize = "0",
responsePacketJunkSize = "0",
initPacketMagicHeader = "1",
responsePacketMagicHeader = "2",
underloadPacketMagicHeader = "3",
transportPacketMagicHeader = "4",
)
}
fun InterfaceProxy.resetAmneziaProperties(): InterfaceProxy {
return copy(
junkPacketCount = "",
junkPacketMinSize = "",
junkPacketMaxSize = "",
initPacketJunkSize = "",
responsePacketJunkSize = "",
initPacketMagicHeader = "",
responsePacketMagicHeader = "",
underloadPacketMagicHeader = "",
transportPacketMagicHeader = "",
)
}

View File

@ -191,4 +191,7 @@
<string name="pre_down">Pre down</string>
<string name="post_down">Post down</string>
<string name="amnezia_kernel_message">Amnezia unavailable in kernel mode</string>
<string name="enable_amnezia">Enable Amnezia</string>
<string name="wg_compat_mode">WG compatibility mode</string>
<string name="quick_actions">Quick actions</string>
</resources>