Skip to content

Commit

Permalink
feat: improved imports
Browse files Browse the repository at this point in the history
Added a feature where you can now add a commented "# Name = " property to QR code configs to import them with a name. If there is no name configured, app will use the first peer's host address as a name.

Improved imports so they no longer replace an existing config if that config has the same name. Instead, they will import with a (number) appended for config name duplicates.

Closes #68

Fixed a bug where the initial state of auto tunneling may not be correct and cause unexpected behavior.

Fixed a bug where Amnezia imports were not working when being imported as a zip.

Improved/refactored error handling.
  • Loading branch information
zaneschepke committed May 12, 2024
1 parent cb1b8ee commit d44baa8
Show file tree
Hide file tree
Showing 28 changed files with 421 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel

import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.service.quicksettings.TileService
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
Expand Down Expand Up @@ -40,16 +39,16 @@ class WireGuardAutoTunnel : Application() {
return instance.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}

fun requestTunnelTileServiceStateUpdate(context: Context) {
fun requestTunnelTileServiceStateUpdate() {
TileService.requestListeningState(
context,
instance,
ComponentName(instance, TunnelControlTile::class.java),
)
}

fun requestAutoTunnelTileServiceUpdate(context: Context) {
fun requestAutoTunnelTileServiceUpdate() {
TileService.requestListeningState(
context,
instance,
ComponentName(instance, AutoTunnelControlTile::class.java),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface TunnelConfigDao {
@Query("SELECT * FROM TunnelConfig WHERE id=:id")
suspend fun getById(id: Long): TunnelConfig?

@Query("SELECT * FROM TunnelConfig WHERE name=:name")
suspend fun getByName(name: String) : TunnelConfig?

@Query("SELECT * FROM TunnelConfig")
suspend fun getAll(): TunnelConfigs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class RoomTunnelConfigRepository(private val tunnelConfigDao: TunnelConfigDao) :
return tunnelConfigDao.count().toInt()
}

override suspend fun findByTunnelName(name: String): TunnelConfig? {
return tunnelConfigDao.getByName(name)
}

override suspend fun findByTunnelNetworksName(name: String): TunnelConfigs {
return tunnelConfigDao.findByTunnelNetworkName(name)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface TunnelConfigRepository {

suspend fun count(): Int

suspend fun findByTunnelName(name : String) : TunnelConfig?

suspend fun findByTunnelNetworksName(name: String): TunnelConfigs

suspend fun findByMobileDataTunnel(): TunnelConfigs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.zaneschepke.wireguardautotunnel.service.foreground

import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig

data class WatcherState(
val isWifiConnected: Boolean = false,
val config: TunnelConfig? = null,
val isEthernetConnected: Boolean = false,
val isMobileDataConnected: Boolean = false,
val currentNetworkSSID: String = "",
Expand All @@ -27,8 +25,7 @@ data class WatcherState(
return (!isEthernetConnected &&
settings.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
isMobileDataConnected &&
config?.isMobileDataTunnel == false)
isMobileDataConnected)
}

fun isTunnelOffOnMobileDataConditionMet(): Boolean {
Expand All @@ -45,13 +42,6 @@ data class WatcherState(
settings.isTunnelOnWifiEnabled)
}

fun isTunnelNotWifiNamePreferredMet(ssid: String): Boolean {
return (!isEthernetConnected &&
isWifiConnected &&
!settings.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
settings.isTunnelOnWifiEnabled && config?.tunnelNetworks?.contains(ssid) == false)
}

fun isTrustedWifiConditionMet(): Boolean {
return (!isEthernetConnected &&
(isWifiConnected &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import java.net.InetAddress
Expand Down Expand Up @@ -162,10 +163,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watchForEthernetConnectivityChanges()
}
}
launch {
Timber.i("Starting vpn state watcher")
watchForVpnConnectivityChanges()
}
launch {
Timber.i("Starting settings watcher")
watchForSettingsChanges()
Expand All @@ -185,29 +182,32 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}

private suspend fun watchForMobileDataConnectivityChanges() {
mobileDataService.networkStatus.collect {
when (it) {
mobileDataService.networkStatus.collect { status ->
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Mobile data connection")
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isMobileDataConnected = true,
)
}
}

is NetworkStatus.CapabilitiesChanged -> {
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isMobileDataConnected = true,
)
}
Timber.i("Mobile data capabilities changed")
}

is NetworkStatus.Unavailable -> {
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isMobileDataConnected = false,
)
}
Timber.i("Lost mobile data connection")
}
}
Expand Down Expand Up @@ -249,94 +249,92 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}

private suspend fun watchForSettingsChanges() {
appDataRepository.settings.getSettingsFlow().collect {
if (networkEventsFlow.value.settings.isAutoTunnelPaused != it.isAutoTunnelPaused) {
when (it.isAutoTunnelPaused) {
appDataRepository.settings.getSettingsFlow().collect { settings ->
if (networkEventsFlow.value.settings.isAutoTunnelPaused != settings.isAutoTunnelPaused) {
when (settings.isAutoTunnelPaused) {
true -> launchWatcherPausedNotification()
false -> launchWatcherNotification()
}
}
networkEventsFlow.value =
networkEventsFlow.value.copy(
settings = it,
)
}
}

private suspend fun watchForVpnConnectivityChanges() {
vpnService.vpnState.collect {
networkEventsFlow.value =
networkEventsFlow.value.copy(
config = it.tunnelConfig,
networkEventsFlow.update {
it.copy(
settings = settings,
)
}
}
}

private suspend fun watchForEthernetConnectivityChanges() {
ethernetService.networkStatus.collect {
when (it) {
ethernetService.networkStatus.collect { status ->
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Ethernet connection")
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isEthernetConnected = true,
)
}
}

is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Ethernet capabilities changed")
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isEthernetConnected = true,
)
}
}

is NetworkStatus.Unavailable -> {
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isEthernetConnected = false,
)
}
Timber.i("Lost Ethernet connection")
}
}
}
}

private suspend fun watchForWifiConnectivityChanges() {
wifiService.networkStatus.collect {
when (it) {
wifiService.networkStatus.collect { status ->
when (status) {
is NetworkStatus.Available -> {
Timber.i("Gained Wi-Fi connection")
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isWifiConnected = true,
)
}
}

is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Wifi capabilities changed")
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isWifiConnected = true,
)
val ssid = wifiService.getNetworkName(it.networkCapabilities)
}
val ssid = wifiService.getNetworkName(status.networkCapabilities)
ssid?.let { name ->
if(name.contains(Constants.UNREADABLE_SSID)) {
Timber.w("SSID unreadable: missing permissions")
} else Timber.i("Detected valid SSID")
appDataRepository.appState.setCurrentSsid(name)
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
currentNetworkSSID = name,
)
}
} ?: Timber.w("Failed to read ssid")
}

is NetworkStatus.Unavailable -> {
networkEventsFlow.value =
networkEventsFlow.value.copy(
networkEventsFlow.update {
it.copy(
isWifiConnected = false,
)
}
Timber.i("Lost Wi-Fi connection")
}
}
Expand All @@ -361,6 +359,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
if (!watcherState.settings.isAutoTunnelPaused) {
//delay for rapid network state changes and then collect latest
delay(Constants.WATCHER_COLLECTION_DELAY)
val tunnelConfig = vpnService.vpnState.value.tunnelConfig
when {
watcherState.isEthernetConditionMet() -> {
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
Expand All @@ -373,12 +372,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}

watcherState.isTunnelOnMobileDataPreferredConditionMet() -> {
getMobileDataTunnel()?.let {
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
this,
getMobileDataTunnel()?.id,
)
if(tunnelConfig?.isMobileDataTunnel == false) {
getMobileDataTunnel()?.let {
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
this,
getMobileDataTunnel()?.id,
)
}
}
}

Expand All @@ -387,25 +388,20 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
}

watcherState.isTunnelNotWifiNamePreferredMet(watcherState.currentNetworkSSID) -> {
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
Timber.i("Found tunnel associated with this SSID, bringing tunnel up")
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this, it.id)
} ?: suspend {
Timber.i("No tunnel associated with this SSID, using defaults")
if (appDataRepository.getPrimaryOrFirstTunnel()?.name != vpnService.name) {
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
}
}.invoke()
}

watcherState.isUntrustedWifiConditionMet() -> {
Timber.i("$autoTunnel - tunnel on untrusted wifi condition met")
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
this,
getSsidTunnel(watcherState.currentNetworkSSID)?.id,
)
if(tunnelConfig?.tunnelNetworks?.contains(watcherState.currentNetworkSSID) == false ||
tunnelConfig == null) {
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
Timber.i("Found tunnel associated with this SSID, bringing tunnel up")
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this, it.id)
} ?: suspend {
Timber.i("No tunnel associated with this SSID, using defaults")
if (appDataRepository.getPrimaryOrFirstTunnel()?.name != vpnService.name) {
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
}
}.invoke()
}
}

watcherState.isTrustedWifiConditionMet() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import android.content.Intent
import android.os.Bundle
import androidx.core.app.ServiceCompat
import androidx.lifecycle.lifecycleScope
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.amnezia.awg.backend.Tunnel
import org.amnezia.awg.config.Config
import timber.log.Timber
import javax.inject.Inject

Expand Down Expand Up @@ -130,10 +129,15 @@ constructor(
)
}

private fun resetVpnState() {
_vpnState.tryEmit(VpnState())
}

override suspend fun stopTunnel() {
try {
if (getState() == TunnelState.UP) {
val state = setState(null, TunnelState.DOWN)
resetVpnState()
emitTunnelState(state)
}
} catch (e: BackendException) {
Expand All @@ -160,7 +164,7 @@ constructor(
private fun handleStateChange(state: TunnelState) {
val tunnel = this
emitTunnelState(state)
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(WireGuardAutoTunnel.instance)
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
if (state == TunnelState.UP) {
statsJob =
scope.launch {
Expand Down
Loading

0 comments on commit d44baa8

Please sign in to comment.