feat: toggle to set amnezia/wg compatibility values
closes #469 closes #518
This commit is contained in:
parent
b2e266fc9f
commit
11e385d350
|
@ -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(
|
private suspend fun rebuildConfigsAndSave(
|
||||||
config: TunnelConfig,
|
config: TunnelConfig,
|
||||||
amConfig: org.amnezia.awg.config.Config,
|
amConfig: org.amnezia.awg.config.Config,
|
||||||
|
|
|
@ -227,6 +227,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
OptionsScreen(
|
OptionsScreen(
|
||||||
tunnelId = args.id,
|
tunnelId = args.id,
|
||||||
appUiState = appUiState,
|
appUiState = appUiState,
|
||||||
|
appViewModel = viewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Lock> {
|
composable<Route.Lock> {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
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.Bolt
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material.icons.outlined.Star
|
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.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
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.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
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.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
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.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toAmneziaCompatibilityConfig
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(tunnelOptionsViewModel: TunnelOptionsViewModel = hiltViewModel(), appUiState: AppUiState, tunnelId: Int) {
|
fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId: Int) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val config = appUiState.tunnels.first { it.id == tunnelId }
|
val config = appUiState.tunnels.first { it.id == tunnelId }
|
||||||
|
|
||||||
|
// TODO optimize
|
||||||
|
|
||||||
|
val amConfig = config.toAmConfig()
|
||||||
|
|
||||||
|
val isAmneziaCompatibilityEnabled = amConfig.`interface`.isWgCompatibilityMode()
|
||||||
|
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
|
|
||||||
LaunchedEffect(config.tunnelNetworks) {
|
LaunchedEffect(config.tunnelNetworks) {
|
||||||
|
@ -82,10 +94,10 @@ fun OptionsScreen(tunnelOptionsViewModel: TunnelOptionsViewModel = hiltViewModel
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
config.isPrimaryTunnel,
|
config.isPrimaryTunnel,
|
||||||
onClick = { tunnelOptionsViewModel.onTogglePrimaryTunnel(config) },
|
onClick = { appViewModel.onTogglePrimaryTunnel(config) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { tunnelOptionsViewModel.onTogglePrimaryTunnel(config) },
|
onClick = { appViewModel.onTogglePrimaryTunnel(config) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Bolt,
|
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() },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -87,7 +87,7 @@ fun SplitTunnelScreen(appUiState: AppUiState, tunnelId: Int, viewModel: AppViewM
|
||||||
val selectedPackages = remember { mutableStateListOf<String>() }
|
val selectedPackages = remember { mutableStateListOf<String>() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
proxyInterface = InterfaceProxy.from(config.toWgConfig().`interface`)
|
proxyInterface = InterfaceProxy.from(config.toAmConfig().`interface`)
|
||||||
val pair = when {
|
val pair = when {
|
||||||
proxyInterface.excludedApplications.isNotEmpty() -> Pair(SplitOptions.EXCLUDE, proxyInterface.excludedApplications)
|
proxyInterface.excludedApplications.isNotEmpty() -> Pair(SplitOptions.EXCLUDE, proxyInterface.excludedApplications)
|
||||||
proxyInterface.includedApplications.isNotEmpty() -> Pair(SplitOptions.INCLUDE, proxyInterface.includedApplications)
|
proxyInterface.includedApplications.isNotEmpty() -> Pair(SplitOptions.INCLUDE, proxyInterface.includedApplications)
|
||||||
|
|
|
@ -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 {
|
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
appDataRepository.tunnels.save(
|
appDataRepository.tunnels.save(
|
||||||
tunnelConfig.copy(
|
tunnelConfig.copy(
|
||||||
|
|
|
@ -6,14 +6,17 @@ import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
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.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import org.amnezia.awg.backend.Backend
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
|
import org.amnezia.awg.config.Interface
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
|
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
|
||||||
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
|
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
|
||||||
|
@ -95,3 +98,45 @@ fun Backend.BackendState.asBackendState(): BackendState {
|
||||||
fun BackendState.asAmBackendState(): Backend.BackendState {
|
fun BackendState.asAmBackendState(): Backend.BackendState {
|
||||||
return Backend.BackendState.valueOf(this.name)
|
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 = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -191,4 +191,7 @@
|
||||||
<string name="pre_down">Pre down</string>
|
<string name="pre_down">Pre down</string>
|
||||||
<string name="post_down">Post down</string>
|
<string name="post_down">Post down</string>
|
||||||
<string name="amnezia_kernel_message">Amnezia unavailable in kernel mode</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>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue