Compare commits

..

3 Commits

Author SHA1 Message Date
Zane Schepke c8634c71a3 fix checksum 2025-08-08 15:08:11 -04:00
Zane Schepke 3894efa066 fix checksum 2025-08-08 15:05:11 -04:00
Zane Schepke 3c60913917 revert amnezia tun version bump 2025-08-08 14:43:23 -04:00
30 changed files with 205 additions and 232 deletions
+2 -7
View File
@@ -76,7 +76,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v5 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
@@ -118,11 +118,6 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: android_artifacts_${{ inputs.flavor }} name: android_artifacts_${{ inputs.flavor }}
path: >- path: app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/wgtunnel-${{ inputs.flavor }}${{ inputs.flavor == 'fdroid' && '-release' || '' }}-*.apk
app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/${{
inputs.flavor == 'fdroid' && inputs.build_type == 'release'
&& 'wgtunnel-fdroid-release-*.apk'
|| format('wgtunnel-{0}-v*.apk', inputs.flavor)
}}
retention-days: 1 retention-days: 1
if-no-files-found: warn if-no-files-found: warn
+1 -1
View File
@@ -69,7 +69,7 @@ jobs:
run: mkdir ${{ github.workspace }}/temp run: mkdir ${{ github.workspace }}/temp
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v5 uses: actions/download-artifact@v4
with: with:
pattern: android_artifacts_* pattern: android_artifacts_*
path: ${{ github.workspace }}/temp path: ${{ github.workspace }}/temp
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v5 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
+2 -2
View File
@@ -108,7 +108,7 @@ jobs:
run: mkdir ${{ github.workspace }}/temp run: mkdir ${{ github.workspace }}/temp
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v5 uses: actions/download-artifact@v4
with: with:
pattern: android_artifacts_* pattern: android_artifacts_*
path: ${{ github.workspace }}/temp path: ${{ github.workspace }}/temp
@@ -191,7 +191,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v5 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
-4
View File
@@ -47,10 +47,6 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent> </intent>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent>
</queries> </queries>
<application <application
android:name=".WireGuardAutoTunnel" android:name=".WireGuardAutoTunnel"
@@ -301,7 +301,7 @@ class MainActivity : AppCompatActivity() {
val args = backStack.toRoute<Route.Config>() val args = backStack.toRoute<Route.Config>()
val config = val config =
appUiState.tunnels.firstOrNull { it.id == args.id } appUiState.tunnels.firstOrNull { it.id == args.id }
ConfigScreen(config, appUiState, viewModel) ConfigScreen(config, viewModel)
} }
composable<Route.TunnelOptions> { backStack -> composable<Route.TunnelOptions> { backStack ->
val args = backStack.toRoute<Route.TunnelOptions>() val args = backStack.toRoute<Route.TunnelOptions>()
@@ -13,7 +13,7 @@ import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
@@ -91,7 +91,7 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
override fun onTerminate() { override fun onTerminate() {
applicationScope.cancel() applicationScope.cancel()
tunnelManager.setBackendStatus(BackendStatus.Inactive) tunnelManager.setBackendState(BackendState.INACTIVE, emptyList())
super.onTerminate() super.onTerminate()
} }
@@ -16,7 +16,7 @@ import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelMonitor import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelMonitor
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus.StopReason.Ping import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus.StopReason.Ping
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
@@ -109,13 +109,13 @@ class AutoTunnelService : LifecycleService() {
with(autoTunnelStateFlow.value) { with(autoTunnelStateFlow.value) {
if ( if (
settings.isVpnKillSwitchEnabled && settings.isVpnKillSwitchEnabled &&
tunnelManager.getBackendStatus() !is BackendStatus.KillSwitch tunnelManager.getBackendState() != BackendState.KILL_SWITCH_ACTIVE
) { ) {
eventHandlerJob?.cancel() eventHandlerJob?.cancel()
val allowedIps = val allowedIps =
if (settings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS if (settings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS
else emptyList() else emptyList()
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(allowedIps)) tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
} }
} }
} }
@@ -402,11 +402,11 @@ class AutoTunnelService : LifecycleService() {
handleBounceWithBackoff(event.configsPeerKeyResolvedMap) handleBounceWithBackoff(event.configsPeerKeyResolvedMap)
is AutoTunnelEvent.StartKillSwitch -> { is AutoTunnelEvent.StartKillSwitch -> {
Timber.d("Starting kill switch") Timber.d("Starting kill switch")
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(event.allowedIps)) tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps)
} }
AutoTunnelEvent.StopKillSwitch -> { AutoTunnelEvent.StopKillSwitch -> {
Timber.d("Stopping kill switch") Timber.d("Stopping kill switch")
tunnelManager.setBackendStatus(BackendStatus.Active) tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
} }
} }
} }
@@ -5,7 +5,7 @@ import com.wireguard.android.backend.BackendException
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
@@ -59,12 +59,12 @@ constructor(
} }
} }
override fun setBackendStatus(backendStatus: BackendStatus) { override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
Timber.w("Not yet implemented for kernel") Timber.w("Not yet implemented for kernel")
} }
override fun getBackendStatus(): BackendStatus { override fun getBackendState(): BackendState {
return BackendStatus.Inactive return BackendState.INACTIVE
} }
override suspend fun runningTunnelNames(): Set<String> { override suspend fun runningTunnelNames(): Set<String> {
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.core.tunnel package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
@@ -34,8 +34,7 @@ constructor(
appDataRepository.settings.flow appDataRepository.settings.flow
.filterNotNull() .filterNotNull()
.flatMapLatest { settings -> .flatMapLatest { settings ->
val backend = if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel MutableStateFlow(if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel)
MutableStateFlow(backend)
} }
.stateIn( .stateIn(
scope = applicationScope.plus(ioDispatcher), scope = applicationScope.plus(ioDispatcher),
@@ -90,12 +89,12 @@ constructor(
tunnelProviderFlow.value.bounceTunnel(tunnelConf, reason) tunnelProviderFlow.value.bounceTunnel(tunnelConf, reason)
} }
override fun setBackendStatus(backendStatus: BackendStatus) { override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
tunnelProviderFlow.value.setBackendStatus(backendStatus) tunnelProviderFlow.value.setBackendState(backendState, allowedIps)
} }
override fun getBackendStatus(): BackendStatus { override fun getBackendState(): BackendState {
return tunnelProviderFlow.value.getBackendStatus() return tunnelProviderFlow.value.getBackendState()
} }
override suspend fun runningTunnelNames(): Set<String> { override suspend fun runningTunnelNames(): Set<String> {
@@ -138,22 +138,19 @@ constructor(
} }
val host = val host =
tunnelConf.pingTarget {
?: { val parts = allowedIpStr.split("/")
val parts = allowedIpStr.split("/") val internalIp = if (parts.size == 2) parts[0] else allowedIpStr
val internalIp =
if (parts.size == 2) parts[0] else allowedIpStr
val prefix = val prefix =
if (parts.size == 2) parts[1].toIntOrNull() ?: 32 if (parts.size == 2) parts[1].toIntOrNull() ?: 32 else 32
else 32 if (prefix <= 1) {
if (prefix <= 1) { tunnelConf.pingTarget ?: CLOUDFLARE_IPV4_IP
CLOUDFLARE_IPV4_IP } else {
} else { internalIp.removeSurrounding("[", "]")
internalIp.removeSurrounding("[", "]")
}
} }
.invoke() }
.invoke()
val attemptTime = System.currentTimeMillis() val attemptTime = System.currentTimeMillis()
runCatching { runCatching {
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.core.tunnel package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
@@ -41,9 +41,9 @@ interface TunnelProvider {
reason: TunnelStatus.StopReason = TunnelStatus.StopReason.User, reason: TunnelStatus.StopReason = TunnelStatus.StopReason.User,
) )
fun setBackendStatus(backendStatus: BackendStatus) fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
fun getBackendStatus(): BackendStatus fun getBackendState(): BackendState
suspend fun runningTunnelNames(): Set<String> suspend fun runningTunnelNames(): Set<String>
@@ -1,15 +1,15 @@
package com.zaneschepke.wireguardautotunnel.core.tunnel package com.zaneschepke.wireguardautotunnel.core.tunnel
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendStatus import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendStatus import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
import javax.inject.Inject import javax.inject.Inject
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@@ -17,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
import org.amnezia.awg.backend.Backend import org.amnezia.awg.backend.Backend
import org.amnezia.awg.backend.BackendException import org.amnezia.awg.backend.BackendException
import org.amnezia.awg.backend.Tunnel import org.amnezia.awg.backend.Tunnel
import org.amnezia.awg.config.Config
import timber.log.Timber import timber.log.Timber
class UserspaceTunnel class UserspaceTunnel
@@ -28,21 +29,14 @@ constructor(
private val backend: Backend, private val backend: Backend,
) : BaseTunnel(applicationScope, appDataRepository, serviceManager) { ) : BaseTunnel(applicationScope, appDataRepository, serviceManager) {
private var previousBackendState: Pair<BackendState, Boolean>? = null
override suspend fun startBackend(tunnel: TunnelConf) { override suspend fun startBackend(tunnel: TunnelConf) {
try { try {
updateTunnelStatus(tunnel, TunnelStatus.Starting) updateTunnelStatus(tunnel, TunnelStatus.Starting)
val amConfig = tunnel.toAmConfig() val amConfig = tunnel.toAmConfig()
var previousKillSwitch: Backend.BackendStatus? = null handleVpnKillSwitchWithDomainEndpoints(amConfig)
// prevent dns failures from bringing tuns up when vpn kill switch active
if (
amConfig.peers.any { it.endpoint.getOrNull()?.toString()?.isUrl() == true } &&
backend.backendStatus is Backend.BackendStatus.KillSwitchActive
) {
previousKillSwitch = backend.backendStatus
setBackendStatus(BackendStatus.Active)
}
backend.setState(tunnel, Tunnel.State.UP, amConfig) backend.setState(tunnel, Tunnel.State.UP, amConfig)
previousKillSwitch?.let { backend.backendStatus = it }
} catch (e: BackendException) { } catch (e: BackendException) {
Timber.e(e, "Failed to start up backend for tunnel ${tunnel.name}") Timber.e(e, "Failed to start up backend for tunnel ${tunnel.name}")
throw e.toBackendError() throw e.toBackendError()
@@ -59,20 +53,47 @@ constructor(
} catch (e: BackendException) { } catch (e: BackendException) {
Timber.e(e, "Failed to stop tunnel ${tunnel.id}") Timber.e(e, "Failed to stop tunnel ${tunnel.id}")
throw e.toBackendError() throw e.toBackendError()
} finally {
handlePreviouslyEnabledVpnKillSwitch()
} }
} }
override fun setBackendStatus(backendStatus: BackendStatus) { // stop vpn kill switch if we need to resolve DNS for peer endpoints
Timber.d("Setting backend state: $backendStatus") private suspend fun handleVpnKillSwitchWithDomainEndpoints(config: Config) {
if (
config.peers.any { it.endpoint.getOrNull()?.toString()?.isUrl() == true } &&
backend.backendState.asBackendState() == BackendState.KILL_SWITCH_ACTIVE
) {
val bypassLan = appDataRepository.settings.get().isLanOnKillSwitchEnabled
previousBackendState = Pair(BackendState.KILL_SWITCH_ACTIVE, bypassLan)
setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
}
}
// restore vpn kill switch if needed
private fun handlePreviouslyEnabledVpnKillSwitch() {
// let auto tunnel handle this if it is active
if (serviceManager.autoTunnelService.value == null) {
previousBackendState?.let { (state, lanEnabled) ->
Timber.d("Restoring kill switch configuration")
val lan = if (lanEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS else emptyList()
backend.setBackendState(state.asAmBackendState(), lan)
}
}
previousBackendState = null
}
override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
Timber.d("Setting backend state: $backendState with allowedIps: $allowedIps")
try { try {
backend.backendStatus = backendStatus.asAmBackendStatus() backend.setBackendState(backendState.asAmBackendState(), allowedIps)
} catch (e: BackendException) { } catch (e: BackendException) {
throw e.toBackendError() throw e.toBackendError()
} }
} }
override fun getBackendStatus(): BackendStatus { override fun getBackendState(): BackendState {
return backend.backendStatus.asBackendStatus() return backend.backendState.asBackendState()
} }
override suspend fun runningTunnelNames(): Set<String> { override suspend fun runningTunnelNames(): Set<String> {
@@ -38,13 +38,13 @@ class GitHubUpdateRepository(
gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e) gitHubApi.getLatestRelease(githubOwner, githubRepo).onFailure(Timber::e)
} }
release.map { release -> release.map { release ->
val standaloneApkAsset = val apkAsset =
release.assets.find { asset -> release.assets.find { asset ->
asset.name.startsWith("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") && asset.name.startsWith("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") &&
asset.name.endsWith(".apk") asset.name.endsWith(".apk")
} }
val newVersion = val newVersion =
standaloneApkAsset apkAsset
?.name ?.name
?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v") ?.removePrefix("wgtunnel-${Constants.STANDALONE_FLAVOR}-v")
?.removeSuffix(".apk") ?: return@map null ?.removeSuffix(".apk") ?: return@map null
@@ -53,9 +53,7 @@ class GitHubUpdateRepository(
if (isNightly && newVersion != currentVersion) if (isNightly && newVersion != currentVersion)
return@map GitHubReleaseMapper.toAppUpdate(release, newVersion) return@map GitHubReleaseMapper.toAppUpdate(release, newVersion)
if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) { if (NumberUtils.compareVersions(newVersion, currentVersion) > 0) {
GitHubReleaseMapper.toAppUpdate(release.copy( GitHubReleaseMapper.toAppUpdate(release, newVersion)
assets = listOf(standaloneApkAsset)
), newVersion)
} else { } else {
null null
} }
@@ -65,7 +63,7 @@ class GitHubUpdateRepository(
override suspend fun downloadApk( override suspend fun downloadApk(
apkUrl: String, apkUrl: String,
fileName: String, fileName: String,
onProgress: suspend (Float) -> Unit, onProgress: (Float) -> Unit,
): Result<File> = ): Result<File> =
withContext(ioDispatcher) { withContext(ioDispatcher) {
try { try {
@@ -103,4 +101,4 @@ class GitHubUpdateRepository(
Result.failure(e) Result.failure(e)
} }
} }
} }
@@ -1,9 +1,7 @@
package com.zaneschepke.wireguardautotunnel.domain.enums package com.zaneschepke.wireguardautotunnel.domain.enums
sealed class BackendStatus { enum class BackendState {
data object Inactive : BackendStatus() KILL_SWITCH_ACTIVE,
SERVICE_ACTIVE,
data object Active : BackendStatus() INACTIVE,
data class KillSwitch(val allowedIps: List<String>) : BackendStatus()
} }
@@ -9,6 +9,6 @@ interface UpdateRepository {
suspend fun downloadApk( suspend fun downloadApk(
apkUrl: String, apkUrl: String,
fileName: String, fileName: String,
onProgress: suspend (Float) -> Unit, onProgress: (Float) -> Unit,
): Result<File> ): Result<File>
} }
@@ -1,12 +1,12 @@
package com.zaneschepke.wireguardautotunnel.domain.state package com.zaneschepke.wireguardautotunnel.domain.state
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import org.amnezia.awg.crypto.Key import org.amnezia.awg.crypto.Key
data class TunnelState( data class TunnelState(
val status: TunnelStatus = TunnelStatus.Down, val status: TunnelStatus = TunnelStatus.Down,
val backendState: BackendStatus = BackendStatus.Inactive, val backendState: BackendState = BackendState.INACTIVE,
val statistics: TunnelStatistics? = null, val statistics: TunnelStatistics? = null,
val pingStates: Map<Key, PingState>? = null, val pingStates: Map<Key, PingState>? = null,
val handshakeSuccessLogs: Boolean? = null, val handshakeSuccessLogs: Boolean? = null,
@@ -7,7 +7,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -22,7 +25,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.PeersSection import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.PeersSection
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@@ -30,7 +32,6 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
@Composable @Composable
fun ConfigScreen( fun ConfigScreen(
tunnelConf: TunnelConf?, tunnelConf: TunnelConf?,
appUiState: AppUiState,
appViewModel: AppViewModel, appViewModel: AppViewModel,
viewModel: ConfigViewModel = hiltViewModel(), viewModel: ConfigViewModel = hiltViewModel(),
) { ) {
@@ -41,17 +42,6 @@ fun ConfigScreen(
val activity = context as? MainActivity val activity = context as? MainActivity
var save by remember { mutableStateOf(false) }
val isTunnelNameTaken by
remember(uiState.tunnelName, appUiState.tunnels) {
derivedStateOf {
appUiState.tunnels
.filter { it.id != tunnelConf?.id }
.any { it.name == uiState.tunnelName }
}
}
// Secure screen due to sensitive information // Secure screen due to sensitive information
DisposableEffect(Unit) { DisposableEffect(Unit) {
activity activity
@@ -68,34 +58,26 @@ fun ConfigScreen(
appViewModel.handleEvent( appViewModel.handleEvent(
AppEvent.SetScreenAction { AppEvent.SetScreenAction {
keyboardController?.hide() keyboardController?.hide()
if (!isTunnelNameTaken) { viewModel.save(tunnelConf)
save = true
}
} }
) )
} }
LaunchedEffect(tunnelConf) { viewModel.initFromTunnel(tunnelConf) } LaunchedEffect(tunnelConf) { viewModel.initFromTunnel(tunnelConf) }
// TODO improve error messages LaunchedEffect(uiState.success) {
LaunchedEffect(save) { if (uiState.success == true) {
if (save) { appViewModel.handleEvent(
try { AppEvent.ShowMessage(StringValue.StringResource(R.string.config_changes_saved))
appViewModel.handleEvent( )
AppEvent.SaveTunnel( appViewModel.handleEvent(AppEvent.PopBackStack(true))
uiState.configProxy.buildTunnelConfFromState(uiState.tunnelName, tunnelConf) }
) }
)
appViewModel.handleEvent( LaunchedEffect(uiState.message) {
AppEvent.ShowMessage(StringValue.StringResource(R.string.config_changes_saved)) uiState.message?.let { message ->
) appViewModel.handleEvent(AppEvent.ShowMessage(message))
appViewModel.handleEvent(AppEvent.PopBackStack(true)) viewModel.setMessage(null)
} catch (e: Exception) {
val message = e.message ?: context.resources.getString(R.string.unknown_error)
appViewModel.handleEvent(AppEvent.ShowMessage(StringValue.DynamicString(message)))
} finally {
save = false
}
} }
} }
@@ -129,7 +111,7 @@ fun ConfigScreen(
modifier = modifier =
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 24.dp), Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 24.dp),
) { ) {
InterfaceSection(isTunnelNameTaken, uiState, viewModel) InterfaceSection(uiState, viewModel)
PeersSection(uiState, viewModel) PeersSection(uiState, viewModel)
AddPeerButton(viewModel) AddPeerButton(viewModel)
} }
@@ -1,20 +1,32 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main.config package com.zaneschepke.wireguardautotunnel.ui.screens.main.config
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class ConfigViewModel @Inject constructor() : ViewModel() { class ConfigViewModel
@Inject
constructor(
private val tunnelRepository: TunnelRepository,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private val _uiState = MutableStateFlow(ConfigUiState()) private val _uiState = MutableStateFlow(ConfigUiState())
val uiState: StateFlow<ConfigUiState> = _uiState.asStateFlow() val uiState: StateFlow<ConfigUiState> = _uiState.asStateFlow()
@@ -109,6 +121,41 @@ class ConfigViewModel @Inject constructor() : ViewModel() {
updatePeer(index, updated) updatePeer(index, updated)
} }
fun setMessage(message: StringValue?) {
_uiState.update { it.copy(message = message) }
}
// TODO improve error messaging
fun save(tunnelConf: TunnelConf?) =
viewModelScope.launch(ioDispatcher) {
val message =
try {
val saveConfig = buildTunnelConfFromState(tunnelConf)
tunnelRepository.save(saveConfig)
_uiState.update { it.copy(success = true) }
} catch (e: Exception) {
setMessage(
e.message?.let { message -> (StringValue.DynamicString(message)) }
?: StringValue.StringResource(R.string.unknown_error)
)
}
}
private fun buildTunnelConfFromState(tunnelConf: TunnelConf?): TunnelConf {
val (wg, am) = _uiState.value.configProxy.buildConfigs()
val name = _uiState.value.tunnelName
return tunnelConf?.copyWithCallback(
tunName = name,
amQuick = am.toAwgQuickString(true),
wgQuick = wg.toWgQuickString(true),
)
?: TunnelConf(
tunName = name,
amQuick = am.toAwgQuickString(true),
wgQuick = wg.toWgQuickString(true),
)
}
fun onAuthenticated() { fun onAuthenticated() {
_uiState.update { it.copy(isAuthenticated = true) } _uiState.update { it.copy(isAuthenticated = true) }
} }
@@ -17,11 +17,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigViewMode
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
@Composable @Composable
fun InterfaceSection( fun InterfaceSection(uiState: ConfigUiState, viewModel: ConfigViewModel) {
isTunnelNameTaken: Boolean,
uiState: ConfigUiState,
viewModel: ConfigViewModel,
) {
var isDropDownExpanded by remember { mutableStateOf(false) } var isDropDownExpanded by remember { mutableStateOf(false) }
val isAmneziaCompatibilitySet = val isAmneziaCompatibilitySet =
remember(uiState.configProxy.`interface`) { remember(uiState.configProxy.`interface`) {
@@ -54,7 +50,6 @@ fun InterfaceSection(
value = uiState.tunnelName, value = uiState.tunnelName,
onValueChange = viewModel::updateTunnelName, onValueChange = viewModel::updateTunnelName,
label = stringResource(R.string.name), label = stringResource(R.string.name),
isError = isTunnelNameTaken,
hint = stringResource(R.string.tunnel_name).lowercase(), hint = stringResource(R.string.tunnel_name).lowercase(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.StringValue
data class ConfigUiState( data class ConfigUiState(
val tunnelName: String = "", val tunnelName: String = "",
@@ -12,5 +13,6 @@ data class ConfigUiState(
val showScripts: Boolean = false, val showScripts: Boolean = false,
val isAuthenticated: Boolean = true, val isAuthenticated: Boolean = true,
val showAuthPrompt: Boolean = false, val showAuthPrompt: Boolean = false,
val saveChanges: Boolean = false, val message: StringValue? = null,
val success: Boolean? = null,
) )
@@ -4,25 +4,21 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.BuildConfig import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate import com.zaneschepke.wireguardautotunnel.domain.model.AppUpdate
import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository import com.zaneschepke.wireguardautotunnel.domain.repository.UpdateRepository
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import jakarta.inject.Inject import jakarta.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@HiltViewModel @HiltViewModel
class SupportViewModel class SupportViewModel
@Inject @Inject
constructor(private val updateRepository: UpdateRepository, private val fileUtils: FileUtils, constructor(private val updateRepository: UpdateRepository, private val fileUtils: FileUtils) :
@MainDispatcher private val mainDispatcher: CoroutineDispatcher) :
ViewModel() { ViewModel() {
private val _uiState = MutableStateFlow(SupportUiState()) private val _uiState = MutableStateFlow(SupportUiState())
@@ -66,9 +62,7 @@ constructor(private val updateRepository: UpdateRepository, private val fileUtil
_uiState.update { it.copy(isLoading = true) } _uiState.update { it.copy(isLoading = true) }
updateRepository updateRepository
.downloadApk(appUpdate.apkUrl, appUpdate.apkFileName) { progress -> .downloadApk(appUpdate.apkUrl, appUpdate.apkFileName) { progress ->
withContext(mainDispatcher) { _uiState.update { it.copy(downloadProgress = progress) }
_uiState.update { it.copy(downloadProgress = progress) }
}
} }
.onSuccess { apk -> .onSuccess { apk ->
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.state package com.zaneschepke.wireguardautotunnel.ui.state
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
import org.amnezia.awg.config.Config import org.amnezia.awg.config.Config
data class ConfigProxy(val peers: List<PeerProxy>, val `interface`: InterfaceProxy) { data class ConfigProxy(val peers: List<PeerProxy>, val `interface`: InterfaceProxy) {
@@ -29,20 +28,6 @@ data class ConfigProxy(val peers: List<PeerProxy>, val `interface`: InterfacePro
) )
} }
fun buildTunnelConfFromState(name: String, tunnelConf: TunnelConf?): TunnelConf {
val (wg, am) = buildConfigs()
return tunnelConf?.copyWithCallback(
tunName = name,
amQuick = am.toAwgQuickString(true),
wgQuick = wg.toWgQuickString(true),
)
?: TunnelConf(
tunName = name,
amQuick = am.toAwgQuickString(true),
wgQuick = wg.toWgQuickString(true),
)
}
companion object { companion object {
fun from(amConfig: Config): ConfigProxy { fun from(amConfig: Config): ConfigProxy {
return ConfigProxy( return ConfigProxy(
@@ -21,10 +21,10 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import timber.log.Timber
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import kotlin.system.exitProcess import kotlin.system.exitProcess
import timber.log.Timber
fun Context.openWebUrl(url: String): Result<Unit> { fun Context.openWebUrl(url: String): Result<Unit> {
return kotlin return kotlin
@@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import com.wireguard.android.backend.BackendException import com.wireguard.android.backend.BackendException
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.HandshakeStatus import com.zaneschepke.wireguardautotunnel.domain.enums.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
@@ -75,21 +75,12 @@ fun Config.defaultName(): String {
} }
} }
fun Backend.BackendStatus.asBackendStatus(): BackendStatus { fun Backend.BackendState.asBackendState(): BackendState {
return when (val status = this) { return BackendState.valueOf(this.name)
is Backend.BackendStatus.KillSwitchActive ->
BackendStatus.KillSwitch(status.allowedIps.toList())
is Backend.BackendStatus.ServiceActive -> BackendStatus.Active
else -> BackendStatus.Inactive
}
} }
fun BackendStatus.asAmBackendStatus(): Backend.BackendStatus { fun BackendState.asAmBackendState(): Backend.BackendState {
return when (val status = this) { return Backend.BackendState.valueOf(this.name)
is BackendStatus.Active -> Backend.BackendStatus.ServiceActive.INSTANCE
is BackendStatus.Inactive -> Backend.BackendStatus.Inactive.INSTANCE
is BackendStatus.KillSwitch -> Backend.BackendStatus.KillSwitchActive(status.allowedIps)
}
} }
fun Tunnel.State.asTunnelState(): TunnelStatus { fun Tunnel.State.asTunnelState(): TunnelStatus {
@@ -19,7 +19,7 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
import com.zaneschepke.wireguardautotunnel.di.AppShell import com.zaneschepke.wireguardautotunnel.di.AppShell
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
@@ -35,12 +35,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.withFirstState
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
import com.zaneschepke.wireguardautotunnel.viewmodel.event.UiEvent import com.zaneschepke.wireguardautotunnel.viewmodel.event.UiEvent
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.IOException
import java.net.URL
import java.time.Instant
import java.util.*
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -51,6 +45,12 @@ import org.amnezia.awg.config.Config
import rikka.shizuku.Shizuku import rikka.shizuku.Shizuku
import timber.log.Timber import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager import xyz.teamgravity.pin_lock_compose.PinManager
import java.io.IOException
import java.net.URL
import java.time.Instant
import java.util.*
import javax.inject.Inject
import javax.inject.Provider
@HiltViewModel @HiltViewModel
class AppViewModel class AppViewModel
@@ -148,7 +148,8 @@ constructor(
is AppEvent.ImportTunnelFromClipboard -> is AppEvent.ImportTunnelFromClipboard ->
handleClipboardImport(event.text, state.tunnels) handleClipboardImport(event.text, state.tunnels)
is AppEvent.ImportTunnelFromFile -> handleImportTunnelFromFile(event.data) is AppEvent.ImportTunnelFromFile ->
handleImportTunnelFromFile(event.data, state.tunnels)
is AppEvent.ImportTunnelFromUrl -> is AppEvent.ImportTunnelFromUrl ->
handleImportTunnelFromUrl(event.url, state.tunnels) handleImportTunnelFromUrl(event.url, state.tunnels)
@@ -263,45 +264,11 @@ constructor(
saveSettings( saveSettings(
state.appSettings.copy(tunnelPingTimeoutSeconds = event.timeout) state.appSettings.copy(tunnelPingTimeoutSeconds = event.timeout)
) )
is AppEvent.SaveTunnel -> saveTunnel(event.tunnel)
} }
} }
} }
} }
private suspend fun saveTunnelsUniquely(tunnels: List<TunnelConf>) {
withContext(ioDispatcher) {
tunnelMutex.withLock {
val existingTunnels = appDataRepository.tunnels.getAll()
val uniqueTuns = generateUniquelyNamedConfigs(tunnels, existingTunnels)
appDataRepository.tunnels.saveAll(uniqueTuns)
}
}
}
private fun generateUniquelyNamedConfigs(
incoming: List<TunnelConf>,
existing: List<TunnelConf>,
): List<TunnelConf> {
val usedNames = existing.map { it.tunName }.toMutableSet()
val result = mutableListOf<TunnelConf>()
for (tun in incoming) {
var uniqueName = tun.tunName
var counter = 1
while (uniqueName in usedNames) {
uniqueName = "${tun.tunName} ($counter)"
counter++
}
usedNames.add(uniqueName)
result.add(tun.copy(tunName = uniqueName))
}
return result
}
fun handleUiEvent(event: UiEvent): Job = fun handleUiEvent(event: UiEvent): Job =
viewModelScope.launch(mainDispatcher) { _uiEvent.emit(event) } viewModelScope.launch(mainDispatcher) { _uiEvent.emit(event) }
@@ -452,7 +419,7 @@ constructor(
_appViewState.update { it.copy(bottomSheet = bottomSheet) } _appViewState.update { it.copy(bottomSheet = bottomSheet) }
private suspend fun handleTunnelPingTargetChange(tunnelConf: TunnelConf, target: String) = private suspend fun handleTunnelPingTargetChange(tunnelConf: TunnelConf, target: String) =
saveTunnel(tunnelConf.copy(pingTarget = target.ifBlank { null })) saveTunnel(tunnelConf.copy(pingTarget = target))
private suspend fun handleTogglePingTunnel(tunnel: TunnelConf) = private suspend fun handleTogglePingTunnel(tunnel: TunnelConf) =
saveTunnel(tunnel.copy(restartOnPingFailure = !tunnel.restartOnPingFailure)) saveTunnel(tunnel.copy(restartOnPingFailure = !tunnel.restartOnPingFailure))
@@ -548,10 +515,17 @@ constructor(
_appViewState.update { it.copy(popBackStack = true) } _appViewState.update { it.copy(popBackStack = true) }
} }
private suspend fun handleImportTunnelFromFile(uri: Uri) { private suspend fun handleImportTunnelFromFile(uri: Uri, tunnels: List<TunnelConf>) {
runCatching { runCatching {
val tunnelConfigs = fileUtils.buildTunnelsFromUri(uri) val tunnelConfigs = fileUtils.buildTunnelsFromUri(uri)
saveTunnelsUniquely(tunnelConfigs) val existingNames = tunnels.map { it.tunName }.toMutableList()
val uniqueTunnelConfigs =
tunnelConfigs.map { config ->
val uniqueName = config.generateUniqueName(existingNames)
existingNames.add(uniqueName)
config.copy(tunName = uniqueName)
}
appDataRepository.tunnels.saveAll(uniqueTunnelConfigs)
} }
.onFailure { .onFailure {
when (it) { when (it) {
@@ -570,7 +544,11 @@ constructor(
runCatching { runCatching {
val amConfig = TunnelConf.configFromAmQuick(config) val amConfig = TunnelConf.configFromAmQuick(config)
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig) val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
saveTunnelsUniquely(listOf(tunnelConf)) saveTunnel(
tunnelConf.copy(
tunName = tunnelConf.generateUniqueName(tunnels.map { it.tunName })
)
)
} }
.onFailure { .onFailure {
Timber.e(it) Timber.e(it)
@@ -588,7 +566,11 @@ constructor(
url.openStream().use { stream -> url.openStream().use { stream ->
val amConfig = Config.parse(stream) val amConfig = Config.parse(stream)
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig) val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
saveTunnelsUniquely(listOf(tunnelConf)) saveTunnel(
tunnelConf.copy(
tunName = tunnelConf.generateUniqueName(tunnels.map { it.tunName })
)
)
} }
} }
.onFailure { .onFailure {
@@ -666,19 +648,19 @@ constructor(
val updatedSettings = val updatedSettings =
appSettings.copy(isLanOnKillSwitchEnabled = !appSettings.isLanOnKillSwitchEnabled) appSettings.copy(isLanOnKillSwitchEnabled = !appSettings.isLanOnKillSwitchEnabled)
saveSettings(updatedSettings) saveSettings(updatedSettings)
handleKillSwitchChange(updatedSettings) handleKillSwitchChange(appSettings)
} }
private fun handleKillSwitchChange(appSettings: AppSettings) { private fun handleKillSwitchChange(appSettings: AppSettings) {
// let auto tunnel handle kill switch changes if running // let auto tunnel handle kill switch changes if running
if (uiState.value.isAutoTunnelActive) return if (uiState.value.isAutoTunnelActive) return
if (!appSettings.isVpnKillSwitchEnabled) if (!appSettings.isVpnKillSwitchEnabled)
return tunnelManager.setBackendStatus(BackendStatus.Active) return tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
Timber.d("Starting kill switch") Timber.d("Starting kill switch")
val allowedIps = val allowedIps =
if (appSettings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS if (appSettings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS
else emptyList() else emptyList()
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(allowedIps)) tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
} }
private suspend fun handleToggleAppShortcuts(appSettings: AppSettings) { private suspend fun handleToggleAppShortcuts(appSettings: AppSettings) {
@@ -713,7 +695,7 @@ constructor(
} }
if (enabled && !requestRoot()) return if (enabled && !requestRoot()) return
// disable kill switch feature in kernel mode // disable kill switch feature in kernel mode
tunnelManager.setBackendStatus(BackendStatus.Inactive) tunnelManager.setBackendState(BackendState.INACTIVE, emptyList())
saveSettings( saveSettings(
appSettings.copy( appSettings.copy(
isKernelEnabled = enabled, isKernelEnabled = enabled,
@@ -77,8 +77,6 @@ sealed class AppEvent {
data class SetTheme(val theme: Theme) : AppEvent() data class SetTheme(val theme: Theme) : AppEvent()
data class SaveTunnel(val tunnel: TunnelConf) : AppEvent()
data class SaveMonitoringSettings( data class SaveMonitoringSettings(
val pingInterval: Int, val pingInterval: Int,
val tunnelPingAttempts: Int, val tunnelPingAttempts: Int,
+2 -2
View File
@@ -1,6 +1,6 @@
object Constants { object Constants {
const val VERSION_NAME = "3.9.5" const val VERSION_NAME = "3.9.4"
const val VERSION_CODE = 39500 const val VERSION_CODE = 39400
const val TARGET_SDK = 35 const val TARGET_SDK = 35
const val MIN_SDK = 26 const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel" const val APP_ID = "com.zaneschepke.wireguardautotunnel"
@@ -1,7 +0,0 @@
What's new:
- Fix for tunnel sort bug
- Improved location permissions flow
- Location permission detection and notifications
- Fix for AndroidTV apps detection for split tunneling
- Improved tunnel monitoring and reboot recovery
- Fix tunnel slow reconnect from sleep
+1 -1
View File
@@ -1,7 +1,7 @@
[versions] [versions]
accompanist = "0.37.3" accompanist = "0.37.3"
activityCompose = "1.10.1" activityCompose = "1.10.1"
amneziawgAndroid = "1.6.2" amneziawgAndroid = "1.4.0"
androidx-junit = "1.3.0" androidx-junit = "1.3.0"
icmp4a = "1.0.0" icmp4a = "1.0.0"
roomdatabasebackup = "1.1.0" roomdatabasebackup = "1.1.0"