From 3566b8e049627f8117613fe94e716674de29d255 Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Sat, 1 Feb 2025 13:28:42 -0500 Subject: [PATCH] initial changes --- .../16.json | 288 +++++++++++ .../WireGuardAutoTunnel.kt | 9 +- .../wireguardautotunnel/data/AppDatabase.kt | 6 +- .../data/domain/TunnelConfig.kt | 5 + .../module/BackendQualifiers.kt | 8 - .../module/TunnelModule.kt | 53 +-- .../receiver/AppUpdateReceiver.kt | 5 +- .../receiver/BootReceiver.kt | 15 +- .../receiver/KernelReceiver.kt | 17 +- .../receiver/NotificationActionReceiver.kt | 9 +- .../autotunnel/AutoTunnelService.kt | 125 +++-- .../autotunnel/model/AutoTunnelState.kt | 126 ++--- .../service/shortcut/ShortcutsActivity.kt | 17 +- .../service/tile/TunnelControlTile.kt | 34 +- .../service/tunnel/AlwaysOnVpnService.kt | 5 +- .../service/tunnel/KernelTunnel.kt | 195 ++++++++ .../service/tunnel/TunnelFactory.kt | 110 +++++ .../service/tunnel/TunnelService.kt | 114 ++++- .../service/tunnel/TunnelState.kt | 52 -- .../service/tunnel/TunnelsManager.kt | 10 + .../service/tunnel/UserspaceTunnel.kt | 209 ++++++++ .../service/tunnel/VpnState.kt | 11 - .../service/tunnel/WireGuardTunnel.kt | 450 ------------------ .../tunnel/{ => model}/BackendState.kt | 2 +- .../tunnel/{ => model}/HandshakeStatus.kt | 2 +- .../service/tunnel/model/TunnelState.kt | 15 + .../service/tunnel/model/VpnState.kt | 9 + .../wireguardautotunnel/ui/AppUiState.kt | 2 - .../wireguardautotunnel/ui/AppViewModel.kt | 54 +-- .../wireguardautotunnel/ui/MainActivity.kt | 4 +- .../ui/screens/main/MainScreen.kt | 12 +- .../ui/screens/main/MainViewModel.kt | 13 +- .../screens/main/components/TunnelRowItem.kt | 2 +- .../ui/screens/settings/SettingsScreen.kt | 11 +- .../tunneloptions/TunnelOptionsScreen.kt | 23 + .../tunneloptions/TunnelOptionsViewModel.kt | 8 + .../util/extensions/TunnelExtensions.kt | 28 +- app/src/main/res/values/strings.xml | 2 + gradle/libs.versions.toml | 4 +- 39 files changed, 1232 insertions(+), 832 deletions(-) create mode 100644 app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/16.json create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/KernelTunnel.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelFactory.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelState.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelsManager.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/UserspaceTunnel.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt delete mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt rename app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/{ => model}/BackendState.kt (54%) rename app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/{ => model}/HandshakeStatus.kt (85%) create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/TunnelState.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/VpnState.kt diff --git a/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/16.json b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/16.json new file mode 100644 index 00000000..c912f396 --- /dev/null +++ b/app/schemas/com.zaneschepke.wireguardautotunnel.data.AppDatabase/16.json @@ -0,0 +1,288 @@ +{ + "formatVersion": 1, + "database": { + "version": 16, + "identityHash": "ae51793c4d09ea3194ecd26f0606f35c", + "entities": [ + { + "tableName": "Settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false, `is_wildcards_enabled` INTEGER NOT NULL DEFAULT false, `is_wifi_by_shell_enabled` INTEGER NOT NULL DEFAULT false, `is_stop_on_no_internet_enabled` INTEGER NOT NULL DEFAULT false, `is_vpn_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `is_lan_on_kill_switch_enabled` INTEGER NOT NULL DEFAULT false, `debounce_delay_seconds` INTEGER NOT NULL DEFAULT 3, `is_disable_kill_switch_on_trusted_enabled` INTEGER NOT NULL DEFAULT false)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAutoTunnelEnabled", + "columnName": "is_tunnel_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isTunnelOnMobileDataEnabled", + "columnName": "is_tunnel_on_mobile_data_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trustedNetworkSSIDs", + "columnName": "trusted_network_ssids", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAlwaysOnVpnEnabled", + "columnName": "is_always_on_vpn_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isTunnelOnEthernetEnabled", + "columnName": "is_tunnel_on_ethernet_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isShortcutsEnabled", + "columnName": "is_shortcuts_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isTunnelOnWifiEnabled", + "columnName": "is_tunnel_on_wifi_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isKernelEnabled", + "columnName": "is_kernel_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isRestoreOnBootEnabled", + "columnName": "is_restore_on_boot_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isMultiTunnelEnabled", + "columnName": "is_multi_tunnel_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPingEnabled", + "columnName": "is_ping_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isAmneziaEnabled", + "columnName": "is_amnezia_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isWildcardsEnabled", + "columnName": "is_wildcards_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isWifiNameByShellEnabled", + "columnName": "is_wifi_by_shell_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isStopOnNoInternetEnabled", + "columnName": "is_stop_on_no_internet_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isVpnKillSwitchEnabled", + "columnName": "is_vpn_kill_switch_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isKernelKillSwitchEnabled", + "columnName": "is_kernel_kill_switch_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isLanOnKillSwitchEnabled", + "columnName": "is_lan_on_kill_switch_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "debounceDelaySeconds", + "columnName": "debounce_delay_seconds", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "3" + }, + { + "fieldPath": "isDisableKillSwitchOnTrustedEnabled", + "columnName": "is_disable_kill_switch_on_trusted_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TunnelConfig", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '', `is_Active` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `ping_interval` INTEGER DEFAULT null, `ping_cooldown` INTEGER DEFAULT null, `ping_ip` TEXT DEFAULT null, `is_ethernet_tunnel` INTEGER NOT NULL DEFAULT false, `is_ipv4_preferred` INTEGER NOT NULL DEFAULT true)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wgQuick", + "columnName": "wg_quick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tunnelNetworks", + "columnName": "tunnel_networks", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "isMobileDataTunnel", + "columnName": "is_mobile_data_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPrimaryTunnel", + "columnName": "is_primary_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "amQuick", + "columnName": "am_quick", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "isActive", + "columnName": "is_Active", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isPingEnabled", + "columnName": "is_ping_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "pingInterval", + "columnName": "ping_interval", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "pingCooldown", + "columnName": "ping_cooldown", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "pingIp", + "columnName": "ping_ip", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "null" + }, + { + "fieldPath": "isEthernetTunnel", + "columnName": "is_ethernet_tunnel", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isIpv4Preferred", + "columnName": "is_ipv4_preferred", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "true" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_TunnelConfig_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae51793c4d09ea3194ecd26f0606f35c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt index 2a73e6ba..2e168511 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt @@ -12,8 +12,6 @@ import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.MainDispatcher -import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.util.LocaleUtil import com.zaneschepke.wireguardautotunnel.util.ReleaseTree import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv @@ -41,9 +39,6 @@ class WireGuardAutoTunnel : Application() { @Inject lateinit var settingsRepository: SettingsRepository - @Inject - lateinit var tunnelService: TunnelService - @Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher @@ -75,7 +70,7 @@ class WireGuardAutoTunnel : Application() { if (appStateRepository.isLocalLogsEnabled() && !isRunningOnTv()) logReader.initialize() } if (!settingsRepository.getSettings().isKernelEnabled) { - tunnelService.setBackendState(BackendState.SERVICE_ACTIVE, emptyList()) + // tunnelProvider.get().setBackendState(BackendState.SERVICE_ACTIVE, emptyList()) } appStateRepository.getLocale()?.let { withContext(mainDispatcher) { @@ -87,7 +82,7 @@ class WireGuardAutoTunnel : Application() { override fun onTerminate() { applicationScope.launch { - tunnelService.setBackendState(BackendState.INACTIVE, emptyList()) + // tunnelProvider.get().setBackendState(BackendState.INACTIVE, emptyList()) } super.onTerminate() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt index 17e65402..0b2db237 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/AppDatabase.kt @@ -11,7 +11,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig @Database( entities = [Settings::class, TunnelConfig::class], - version = 15, + version = 16, autoMigrations = [ AutoMigration(from = 1, to = 2), @@ -57,6 +57,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig from = 14, to = 15, ), + AutoMigration( + from = 15, + to = 16, + ), ], exportSchema = true, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt index 1d7f4316..9eef9448 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/TunnelConfig.kt @@ -63,6 +63,11 @@ data class TunnelConfig( defaultValue = "false", ) var isEthernetTunnel: Boolean = false, + @ColumnInfo( + name = "is_ipv4_preferred", + defaultValue = "true", + ) + var isIpv4Preferred: Boolean = true, ) { fun toAmConfig(): org.amnezia.awg.config.Config { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/BackendQualifiers.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/BackendQualifiers.kt index 6612356f..f6ee05ca 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/BackendQualifiers.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/BackendQualifiers.kt @@ -2,14 +2,6 @@ package com.zaneschepke.wireguardautotunnel.module import javax.inject.Qualifier -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Kernel - -@Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class Userspace - @Qualifier @Retention(AnnotationRetention.BINARY) annotation class TunnelShell diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt index a68394d1..2ae21dc7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt @@ -2,18 +2,16 @@ package com.zaneschepke.wireguardautotunnel.module import android.content.Context import com.wireguard.android.backend.Backend -import com.wireguard.android.backend.GoBackend import com.wireguard.android.backend.RootTunnelActionHandler import com.wireguard.android.backend.WgQuickBackend import com.wireguard.android.util.RootShell import com.wireguard.android.util.ToolsInstaller import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository -import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.network.NetworkService -import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelFactory import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService -import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel +import com.zaneschepke.wireguardautotunnel.service.tunnel.UserspaceTunnel import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -21,6 +19,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import org.amnezia.awg.backend.TunnelActionHandler import javax.inject.Provider import javax.inject.Singleton @@ -44,53 +43,29 @@ class TunnelModule { @Provides @Singleton - fun provideRootShellAm(@ApplicationContext context: Context): org.amnezia.awg.util.RootShell { - return org.amnezia.awg.util.RootShell(context) + fun provideAmneziaBackend(@ApplicationContext context: Context): org.amnezia.awg.backend.Backend { + return org.amnezia.awg.backend.GoBackend(context, org.amnezia.awg.backend.RootTunnelActionHandler(org.amnezia.awg.util.RootShell(context))) } @Provides @Singleton - @Userspace - fun provideUserspaceBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend { - return GoBackend(context, RootTunnelActionHandler(rootShell)) - } - - @Provides - @Singleton - @Kernel - fun provideKernelBackend(@ApplicationContext context: Context, @TunnelShell rootShell: RootShell): Backend { - return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell)) - } - - @Provides - @Singleton - fun provideAmneziaBackend(@ApplicationContext context: Context, rootShell: org.amnezia.awg.util.RootShell): org.amnezia.awg.backend.Backend { - return org.amnezia.awg.backend.GoBackend(context, org.amnezia.awg.backend.RootTunnelActionHandler(rootShell)) - } - - @Provides - @Singleton - fun provideVpnService( - amneziaBackend: Provider, - @Kernel kernelBackend: Provider, + fun provideKernelTunnelFactory( + @ApplicationContext context: Context, + amBackend: Provider, appDataRepository: AppDataRepository, - tunnelConfigRepository: TunnelConfigRepository, @ApplicationScope applicationScope: CoroutineScope, @IoDispatcher ioDispatcher: CoroutineDispatcher, serviceManager: ServiceManager, - notificationService: NotificationService, internetConnectivityService: NetworkService, - ): TunnelService { - return WireGuardTunnel( - amneziaBackend, - tunnelConfigRepository, - kernelBackend, - appDataRepository, - applicationScope, + ): TunnelFactory { + return TunnelFactory( ioDispatcher, + applicationScope, serviceManager, - notificationService, + appDataRepository, + amBackend.get(), internetConnectivityService, + context, ) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/AppUpdateReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/AppUpdateReceiver.kt index 71985028..14a0c9fc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/AppUpdateReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/AppUpdateReceiver.kt @@ -24,9 +24,6 @@ class AppUpdateReceiver : BroadcastReceiver() { @Inject lateinit var appDataRepository: AppDataRepository - @Inject - lateinit var tunnelService: Provider - @Inject lateinit var serviceManager: ServiceManager @@ -42,7 +39,7 @@ class AppUpdateReceiver : BroadcastReceiver() { } if (!settings.isAutoTunnelEnabled) { val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive } - if (tunnels.isNotEmpty()) tunnelService.get().startTunnel(tunnels.first()) +// if (tunnels.isNotEmpty()) tunnelService.get().startTunnel(tunnels.first()) } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt index 606f9380..072f08e4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt @@ -7,7 +7,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -20,9 +20,6 @@ class BootReceiver : BroadcastReceiver() { @Inject lateinit var appDataRepository: AppDataRepository - @Inject - lateinit var tunnelService: Provider - @Inject @ApplicationScope lateinit var applicationScope: CoroutineScope @@ -38,11 +35,11 @@ class BootReceiver : BroadcastReceiver() { with(appDataRepository.settings.getSettings()) { if (isRestoreOnBootEnabled) { val activeTunnels = appDataRepository.tunnels.getActive() - val tunState = tunnelService.get().vpnState.value.status - if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) { - Timber.i("Starting previously active tunnel") - tunnelService.get().startTunnel(activeTunnels.first()) - } +// val tunState = tunnelService.get().vpnState.value.status +// if (activeTunnels.isNotEmpty() && tunState != TunnelState.UP) { +// Timber.i("Starting previously active tunnel") +// tunnelService.get().startTunnel(activeTunnels.first()) +// } if (isAutoTunnelEnabled) { Timber.i("Starting watcher service from boot") serviceManager.startAutoTunnel(true) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt index c43d1988..224615fa 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt @@ -16,9 +16,6 @@ import javax.inject.Provider @AndroidEntryPoint class KernelReceiver : BroadcastReceiver() { - @Inject - lateinit var tunnelService: Provider - @Inject @ApplicationScope lateinit var applicationScope: CoroutineScope @@ -33,13 +30,13 @@ class KernelReceiver : BroadcastReceiver() { val action = intent.action ?: return applicationScope.launch { if (action == REFRESH_TUNNELS_ACTION) { - tunnelService.get().runningTunnelNames().forEach { name -> - // TODO can optimize later - val tunnel = tunnelConfigRepository.findByTunnelName(name) - tunnel?.let { - tunnelConfigRepository.save(it.copy(isActive = true)) - } - } +// tunnelService.get().runningTunnelNames().forEach { name -> +// // TODO can optimize later +// val tunnel = tunnelConfigRepository.findByTunnelName(name) +// tunnel?.let { +// tunnelConfigRepository.save(it.copy(isActive = true)) +// } +// } serviceManager.updateTunnelTile() } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt index 9ae409bb..1e113c3f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt @@ -6,7 +6,6 @@ import android.content.Intent import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -18,9 +17,6 @@ class NotificationActionReceiver : BroadcastReceiver() { @Inject lateinit var serviceManager: ServiceManager - @Inject - lateinit var tunnelService: TunnelService - @Inject @ApplicationScope lateinit var applicationScope: CoroutineScope @@ -29,7 +25,10 @@ class NotificationActionReceiver : BroadcastReceiver() { applicationScope.launch { when (intent.action) { NotificationAction.AUTO_TUNNEL_OFF.name -> serviceManager.stopAutoTunnel() - NotificationAction.TUNNEL_OFF.name -> tunnelService.stopTunnel() + NotificationAction.TUNNEL_OFF.name -> { + // TODO fix for kernel + // tunnelProvider.get().stopTunnel() + } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt index 71073109..409c8c91 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt @@ -25,7 +25,7 @@ import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification -import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs @@ -61,10 +61,6 @@ class AutoTunnelService : LifecycleService() { @Inject lateinit var notificationService: NotificationService - - @Inject - lateinit var tunnelService: Provider - @Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher @@ -106,17 +102,17 @@ class AutoTunnelService : LifecycleService() { } fun start() { - kotlin.runCatching { - lifecycleScope.launch(mainImmediateDispatcher) { - launchWatcherNotification() - initWakeLock() - } - startAutoTunnelJob() - startAutoTunnelStateJob() - startKillSwitchJob() - }.onFailure { - Timber.e(it) - } +// kotlin.runCatching { +// lifecycleScope.launch(mainImmediateDispatcher) { +// launchWatcherNotification() +// initWakeLock() +// } +//// startAutoTunnelJob() +//// startAutoTunnelStateJob() +// startKillSwitchJob() +// }.onFailure { +// Timber.e(it) +// } } fun stop() { @@ -175,20 +171,20 @@ class AutoTunnelService : LifecycleService() { } } - private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) { - combine( - combineSettings(), - networkService.status.map { - buildNetworkState(it) - }.distinctUntilChanged(), - ) { double, networkState -> - AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second) - }.collect { state -> - autoTunnelStateFlow.update { - it.copy(vpnState = state.vpnState, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels) - } - } - } +// private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) { +// combine( +// combineSettings(), +// networkService.status.map { +// buildNetworkState(it) +// }.distinctUntilChanged(), +// ) { double, networkState -> +// AutoTunnelState(tunnelService.get().vpnState.value, networkState, double.first, double.second) +// }.collect { state -> +// autoTunnelStateFlow.update { +// it.copy(vpnState = state.vpnState, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels) +// } +// } +// } private suspend fun getWifiName(wifiCapabilities: NetworkCapabilities): String? { val setting = appDataRepository.get().settings.getSettings() @@ -211,39 +207,38 @@ class AutoTunnelService : LifecycleService() { }.distinctUntilChanged() } - private fun startKillSwitchJob() = lifecycleScope.launch(ioDispatcher) { - autoTunnelStateFlow.collect { - if (it == defaultState) return@collect - when (val event = it.asKillSwitchEvent()) { - KillSwitchEvent.DoNothing -> Unit - is KillSwitchEvent.Start -> { - Timber.d("Starting kill switch") - tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps) - } - KillSwitchEvent.Stop -> { - Timber.d("Stopping kill switch") - tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) - } - } - } - } - - @OptIn(FlowPreview::class) - private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) { - Timber.i("Starting auto-tunnel network event watcher") - val settings = appDataRepository.get().settings.getSettings() - Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds") - autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState -> - if (watcherState == defaultState) return@collect - Timber.d("New auto tunnel state emitted") - when (val event = watcherState.asAutoTunnelEvent()) { - is AutoTunnelEvent.Start -> tunnelService.get().startTunnel( - event.tunnelConfig - ?: appDataRepository.get().getPrimaryOrFirstTunnel(), - ) - is AutoTunnelEvent.Stop -> tunnelService.get().stopTunnel() - AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met") - } - } - } +// private fun startKillSwitchJob() = lifecycleScope.launch(ioDispatcher) { +// autoTunnelStateFlow.collect { +// if (it == defaultState) return@collect +// when (val event = it.asKillSwitchEvent()) { +// KillSwitchEvent.DoNothing -> Unit +// is KillSwitchEvent.Start -> { +// Timber.d("Starting kill switch") +// tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps) +// } +// KillSwitchEvent.Stop -> { +// Timber.d("Stopping kill switch") +// tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) +// } +// } +// } +// } + +// @OptIn(FlowPreview::class) +// private fun startAutoTunnelJob() = lifecycleScope.launch(ioDispatcher) { +// Timber.i("Starting auto-tunnel network event watcher") +// val settings = appDataRepository.get().settings.getSettings() +// Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds") +// autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState -> +// if (watcherState == defaultState) return@collect +// Timber.d("New auto tunnel state emitted") +// when (val event = watcherState.asAutoTunnelEvent()) { +// is AutoTunnelEvent.Start -> tunnelService.get().startTunnel( +// event.tunnelConfig ?: appDataRepository.get().getPrimaryOrFirstTunnel(), +// ) +// is AutoTunnelEvent.Stop -> tunnelService.get().stopTunnel() +// AutoTunnelEvent.DoNothing -> Timber.i("Auto-tunneling: no condition met") +// } +// } +// } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt index 28432c59..1c90a363 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/model/AutoTunnelState.kt @@ -2,8 +2,8 @@ package com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.model import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState -import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList @@ -18,16 +18,16 @@ data class AutoTunnelState( return !networkState.isEthernetConnected && !networkState.isWifiConnected && networkState.isMobileDataConnected } - private fun isMobileTunnelDataChangeNeeded(): Boolean { - val preferredTunnel = preferredMobileDataTunnel() - return preferredTunnel != null && - vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id - } - - private fun isEthernetTunnelChangeNeeded(): Boolean { - val preferredTunnel = preferredEthernetTunnel() - return preferredTunnel != null && vpnState.status.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id - } +// private fun isMobileTunnelDataChangeNeeded(): Boolean { +// val preferredTunnel = preferredMobileDataTunnel() +// return preferredTunnel != null && +// vpnState.state.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id +// } +// +// private fun isEthernetTunnelChangeNeeded(): Boolean { +// val preferredTunnel = preferredEthernetTunnel() +// return preferredTunnel != null && vpnState.state.isUp() && preferredTunnel.id != vpnState.tunnelConfig?.id +// } private fun preferredMobileDataTunnel(): TunnelConfig? { return tunnels.firstOrNull { it.isMobileDataTunnel } ?: tunnels.firstOrNull { it.isPrimaryTunnel } @@ -46,11 +46,11 @@ data class AutoTunnelState( } private fun startOnEthernet(): Boolean { - return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.status.isDown() + return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && vpnState.state.isDown() } private fun stopOnEthernet(): Boolean { - return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.status.isUp() + return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && vpnState.state.isUp() } private fun stopKillSwitchOnTrusted(): Boolean { @@ -66,64 +66,64 @@ data class AutoTunnelState( } private fun stopOnMobileData(): Boolean { - return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && vpnState.status.isUp() + return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && vpnState.state.isUp() } private fun startOnMobileData(): Boolean { - return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && vpnState.status.isDown() + return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && vpnState.state.isDown() } - private fun changeOnMobileData(): Boolean { - return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && isMobileTunnelDataChangeNeeded() - } - - private fun changeOnEthernet(): Boolean { - return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded() - } +// private fun changeOnMobileData(): Boolean { +// return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && isMobileTunnelDataChangeNeeded() +// } +// +// private fun changeOnEthernet(): Boolean { +// return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && isEthernetTunnelChangeNeeded() +// } private fun stopOnWifi(): Boolean { - return isWifiActive() && !settings.isTunnelOnWifiEnabled && vpnState.status.isUp() + return isWifiActive() && !settings.isTunnelOnWifiEnabled && vpnState.state.isUp() } private fun stopOnTrustedWifi(): Boolean { - return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isUp() && isCurrentSSIDTrusted() + return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.state.isUp() && isCurrentSSIDTrusted() } private fun startOnUntrustedWifi(): Boolean { - return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isDown() && !isCurrentSSIDTrusted() - } - - private fun changeOnUntrustedWifi(): Boolean { - return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.status.isUp() && !isCurrentSSIDTrusted() && !isWifiTunnelPreferred() - } - - private fun isWifiTunnelPreferred(): Boolean { - val preferred = preferredWifiTunnel() - val vpnTunnel = vpnState.tunnelConfig - return if (preferred != null && vpnTunnel != null) { - preferred.id == vpnTunnel.id - } else { - true - } - } - - fun asAutoTunnelEvent(): AutoTunnelEvent { - return when { - // ethernet scenarios - stopOnEthernet() -> AutoTunnelEvent.Stop - startOnEthernet() || changeOnEthernet() -> AutoTunnelEvent.Start(preferredEthernetTunnel()) - // mobile data scenarios - stopOnMobileData() -> AutoTunnelEvent.Stop - startOnMobileData() || changeOnMobileData() -> AutoTunnelEvent.Start(preferredMobileDataTunnel()) - // wifi scenarios - stopOnWifi() -> AutoTunnelEvent.Stop - stopOnTrustedWifi() -> AutoTunnelEvent.Stop - startOnUntrustedWifi() || changeOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel()) - // no connectivity - isNoConnectivity() && settings.isStopOnNoInternetEnabled -> AutoTunnelEvent.Stop - else -> AutoTunnelEvent.DoNothing - } - } + return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.state.isDown() && !isCurrentSSIDTrusted() + } + +// private fun changeOnUntrustedWifi(): Boolean { +// return isWifiActive() && settings.isTunnelOnWifiEnabled && vpnState.state.isUp() && !isCurrentSSIDTrusted() && !isWifiTunnelPreferred() +// } + +// private fun isWifiTunnelPreferred(): Boolean { +// val preferred = preferredWifiTunnel() +// val vpnTunnel = vpnState.tunnelConfig +// return if (preferred != null && vpnTunnel != null) { +// preferred.id == vpnTunnel.id +// } else { +// true +// } +// } + +// fun asAutoTunnelEvent(): AutoTunnelEvent { +// return when { +// // ethernet scenarios +// stopOnEthernet() -> AutoTunnelEvent.Stop +// startOnEthernet() || changeOnEthernet() -> AutoTunnelEvent.Start(preferredEthernetTunnel()) +// // mobile data scenarios +// stopOnMobileData() -> AutoTunnelEvent.Stop +// startOnMobileData() || changeOnMobileData() -> AutoTunnelEvent.Start(preferredMobileDataTunnel()) +// // wifi scenarios +// stopOnWifi() -> AutoTunnelEvent.Stop +// stopOnTrustedWifi() -> AutoTunnelEvent.Stop +// startOnUntrustedWifi() || changeOnUntrustedWifi() -> AutoTunnelEvent.Start(preferredWifiTunnel()) +// // no connectivity +// isNoConnectivity() && settings.isStopOnNoInternetEnabled -> AutoTunnelEvent.Stop +// else -> AutoTunnelEvent.DoNothing +// } +// } fun asKillSwitchEvent(): KillSwitchEvent { return when { @@ -158,8 +158,8 @@ data class AutoTunnelState( } } - fun isPingEnabled(): Boolean { - return settings.isPingEnabled || - (vpnState.status.isUp() && vpnState.tunnelConfig != null && tunnels.first { it.id == vpnState.tunnelConfig.id }.isPingEnabled) - } +// fun isPingEnabled(): Boolean { +// return settings.isPingEnabled || +// (vpnState.state.isUp() && vpnState.tunnelConfig != null && tunnels.first { it.id == vpnState.tunnelConfig.id }.isPingEnabled) +// } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt index 616f699f..e391fd5f 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt @@ -19,9 +19,6 @@ class ShortcutsActivity : ComponentActivity() { @Inject lateinit var appDataRepository: AppDataRepository - @Inject - lateinit var tunnelService: Provider - @Inject lateinit var serviceManager: ServiceManager @@ -43,13 +40,13 @@ class ShortcutsActivity : ComponentActivity() { .firstOrNull { it.name == tunnelName } } ?: appDataRepository.getStartTunnelConfig() Timber.d("Shortcut action on name: ${tunnelConfig?.name}") - tunnelConfig?.let { - when (intent.action) { - Action.START.name -> tunnelService.get().startTunnel(it) - Action.STOP.name -> tunnelService.get().stopTunnel() - else -> Unit - } - } +// tunnelConfig?.let { +// when (intent.action) { +// Action.START.name -> tunnelService.get().startTunnel(it) +// Action.STOP.name -> tunnelService.get().stopTunnel() +// else -> Unit +// } +// } } AutoTunnelService::class.java.simpleName, LEGACY_AUTO_TUNNEL_SERVICE_NAME -> { when (intent.action) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt index 03c237d2..5107c504 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt @@ -8,7 +8,6 @@ import android.service.quicksettings.TileService import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -21,9 +20,6 @@ class TunnelControlTile : TileService() { @Inject lateinit var appDataRepository: AppDataRepository - @Inject - lateinit var tunnelService: TunnelService - @Inject @ApplicationScope lateinit var applicationScope: CoroutineScope @@ -52,24 +48,26 @@ class TunnelControlTile : TileService() { fun updateTileState() = applicationScope.launch { if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable() - with(tunnelService.vpnState.value) { - if (status.isUp() && tunnelConfig != null) return@launch updateTile(tunnelConfig.name, true) - } - appDataRepository.getStartTunnelConfig()?.let { - updateTile(it.name, false) - } + // TODO FIX +// with(tunnelService.vpnState.value) { +// if (status.isUp() && tunnelConfig != null) return@launch updateTile(tunnelConfig.name, true) +// } +// appDataRepository.getStartTunnelConfig()?.let { +// updateTile(it.name, false) +// } } override fun onClick() { super.onClick() - unlockAndRun { - applicationScope.launch { - if (tunnelService.vpnState.value.status.isUp()) return@launch tunnelService.stopTunnel() - appDataRepository.getStartTunnelConfig()?.let { - tunnelService.startTunnel(it) - } - } - } + // TODO FIX +// unlockAndRun { +// applicationScope.launch { +// if (tunnelService.vpnState.value.status.isUp()) return@launch tunnelService.stopTunnel() +// appDataRepository.getStartTunnelConfig()?.let { +// tunnelService.startTunnel(it) +// } +// } +// } } private fun setActive() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/AlwaysOnVpnService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/AlwaysOnVpnService.kt index e65c2941..f2bec229 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/AlwaysOnVpnService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/AlwaysOnVpnService.kt @@ -14,9 +14,6 @@ import javax.inject.Provider @AndroidEntryPoint class AlwaysOnVpnService : LifecycleService() { - @Inject - lateinit var tunnelService: Provider - @Inject lateinit var appDataRepository: AppDataRepository @@ -34,7 +31,7 @@ class AlwaysOnVpnService : LifecycleService() { if (settings.isAlwaysOnVpnEnabled) { val tunnel = appDataRepository.getPrimaryOrFirstTunnel() tunnel?.let { - tunnelService.get().startTunnel(it) +// tunnelService.get().startTunnel(it) } } else { Timber.w("Always-on VPN is not enabled in app settings") diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/KernelTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/KernelTunnel.kt new file mode 100644 index 00000000..a8336a94 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/KernelTunnel.kt @@ -0,0 +1,195 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel + +import com.wireguard.android.backend.Backend +import com.wireguard.android.backend.Tunnel +import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig +import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.module.ApplicationScope +import com.zaneschepke.wireguardautotunnel.module.IoDispatcher +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.network.NetworkService +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState +import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics +import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState +import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject + +class KernelTunnel @Inject constructor( + @IoDispatcher private val ioDispatcher: CoroutineDispatcher, + @ApplicationScope private val applicationScope: CoroutineScope, + private val serviceManager: ServiceManager, + private val appDataRepository: AppDataRepository, + private val backend: Backend, + private val internetConnectivityService: NetworkService, + override var tunnelConfig: TunnelConfig, + private val onVpnStateChange: (tunnelConfig: TunnelConfig,state: VpnState) -> Unit, + private val onStop: (tunnelConfig : TunnelConfig) -> Unit, +) : TunnelService(), Tunnel { + + private var statsJob: Job? = null + private var tunnelChangesJob: Job? = null + private var pingJob: Job? = null + private var networkJob: Job? = null + private var stateJob: Job? = null + + private fun startStateJob() : Job = applicationScope.launch(ioDispatcher) { + state.collect { + onVpnStateChange(tunnelConfig,it) + } + } + + override suspend fun startTunnel() { + withContext(ioDispatcher) { + if (runningTunnelNames().contains(tunnelConfig.name)) return@withContext + serviceManager.startBackgroundService(tunnelConfig) + stateJob = startStateJob() + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) + runCatching { + val state = backend.setState(this@KernelTunnel, Tunnel.State.UP, tunnelConfig.toWgConfig()) + updateTunnelState(state.asTunnelState()) + startActiveTunnelJobs() + }.onFailure { + Timber.e(it) + } + } + } + + override fun startActiveTunnelJobs() { + statsJob = startTunnelStatisticsJob() + tunnelChangesJob = startTunnelConfigChangesJob() + if (tunnelConfig.isPingEnabled == true) { + startPingJobs() + } + } + + override fun cancelActiveTunnelJobs() { + stateJob?.cancelWithMessage("Tunnel state job canceled") + statsJob?.cancelWithMessage("Tunnel stats job canceled") + tunnelChangesJob?.cancelWithMessage("Tunnel changes job canceled") + cancelPingJobs() + } + + private fun startPingJobs() { + cancelPingJobs() + pingJob = startPingJob() + networkJob = startNetworkJob() + } + + private fun cancelPingJobs() { + pingJob?.cancelWithMessage("Ping job canceled") + networkJob?.cancelWithMessage("Network job canceled") + } + + private fun startPingJob() = applicationScope.launch(ioDispatcher) { + do { + runTunnelPingCheck() + } while (true) + } + + private fun startNetworkJob() = applicationScope.launch(ioDispatcher) { + internetConnectivityService.status.distinctUntilChanged().collect { + isNetworkAvailable.set(!it.allOffline) + } + } + + override suspend fun stopTunnel() { + runCatching { + backend.setState(this@KernelTunnel, Tunnel.State.DOWN, tunnelConfig.toWgConfig()) + onTunnelStop() + }.onFailure { + Timber.e(it) + } +} + + private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { + delay(STATS_START_DELAY) + while (true) { + val stats = backend.getStatistics(this@KernelTunnel) + updateBackendStatistics(WireGuardStatistics(stats)) + delay(VPN_STATISTIC_CHECK_INTERVAL) + } + } + + private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) { + appDataRepository.tunnels.getTunnelConfigsFlow().collect { tunnels -> + val storageConfig = tunnels.firstOrNull { it.id == tunnelConfig.id } + if (storageConfig == null) return@collect + val quickChanged = isQuickConfigChanged(storageConfig) + val pingMatching = isPingConfigMatching(storageConfig) + safeUpdateConfig(storageConfig) + if (quickChanged) bounceTunnel() + if (!pingMatching) handlePingConfigChanges() + } + } + + private fun restartPingJob() { + cancelPingJobs() + startPingJobs() + } + + private fun handlePingConfigChanges() { + if (!tunnelConfig.isPingEnabled && pingJob?.isActive == true) { + cancelPingJobs() + return + } + restartPingJob() + } + + override suspend fun bounceTunnel() { + if (getState().isUp()) { + toggleTunnel(Tunnel.State.DOWN) + toggleTunnel(Tunnel.State.UP) + } + } + + private suspend fun onTunnelStop() { + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false)) + serviceManager.stopBackgroundService() + cancelActiveTunnelJobs() + onStop(tunnelConfig) + } + + private suspend fun toggleTunnel(state: Tunnel.State) { + withContext(ioDispatcher) { + backend.setState(this@KernelTunnel, state, tunnelConfig.toWgConfig()) + } + } + + override suspend fun getBackendState(): BackendState { + // TODO Not implemented for kernel + return BackendState.INACTIVE + } + + override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection) { + } + override suspend fun runningTunnelNames(): Set { + return backend.runningTunnelNames + } + + override suspend fun getState(): TunnelState { + return backend.getState(this).asTunnelState() + } + + override fun getName(): String { + return tunnelConfig.name + } + + override fun onStateChange(newState: Tunnel.State) { + updateTunnelState(newState.asTunnelState()) + serviceManager.updateTunnelTile() + } + + override fun isIpv4ResolutionPreferred(): Boolean { + return false + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelFactory.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelFactory.kt new file mode 100644 index 00000000..2ac8e85e --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelFactory.kt @@ -0,0 +1,110 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel + +import android.content.Context +import androidx.compose.runtime.MutableState +import com.wireguard.android.backend.Backend +import com.wireguard.android.backend.RootTunnelActionHandler +import com.wireguard.android.backend.WgQuickBackend +import com.wireguard.android.util.RootShell +import com.wireguard.android.util.ToolsInstaller +import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig +import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.network.NetworkService +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +class TunnelFactory @Inject constructor( + private val ioDispatcher: CoroutineDispatcher, + private val applicationScope: CoroutineScope, + private val serviceManager: ServiceManager, + private val appDataRepository: AppDataRepository, + private val amBackend: org.amnezia.awg.backend.Backend, + private val internetConnectivityService: NetworkService, + private val context: Context, +) : TunnelsManager { + + private val _tunnelStates = MutableStateFlow(mapOf()) + override val tunnelStates: Flow> + get() = _tunnelStates.asStateFlow() + + private val _tunnels = MutableStateFlow(mapOf()) + + private suspend fun createTunnel(tunnelConfig: TunnelConfig, isKernelTunnel: Boolean): TunnelService { + if(!isKernelTunnel){ + _tunnels.value.forEach { + it.value.stopTunnel() + } + _tunnelStates.update { + it.toMutableMap().apply { + this.clear() + } + } + _tunnels.update { + it.toMutableMap().apply { + this.clear() + } + } + return UserspaceTunnel( + ioDispatcher, + applicationScope, + serviceManager, + appDataRepository, + amBackend, + internetConnectivityService, + tunnelConfig, + ::onVpnStateChange, + ::onStop + ) + } + + val rootShell = RootShell(context) + val tunnel = KernelTunnel( + ioDispatcher, + applicationScope, + serviceManager, + appDataRepository, + WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell), RootTunnelActionHandler(rootShell)), + internetConnectivityService, + tunnelConfig, + ::onVpnStateChange, + ::onStop + ) + return tunnel + } + + override suspend fun getTunnel(tunnelConfig: TunnelConfig, isKernelTunnel: Boolean): TunnelService { + return _tunnels.value[tunnelConfig.id] ?: createTunnel(tunnelConfig, isKernelTunnel).also { service -> _tunnels.update { + it.toMutableMap().apply { + this[tunnelConfig.id] = service + } + } } + } + + fun onVpnStateChange(tunnelConfig : TunnelConfig, state: VpnState) { + _tunnelStates.update { + it.toMutableMap().apply { + this[tunnelConfig.id] = state + } + } + } + + fun onStop(tunnelConfig: TunnelConfig) { + _tunnels.update { + it.toMutableMap().apply { + remove(tunnelConfig.id) + } + } + _tunnelStates.update { + it.toMutableMap().apply { + remove(tunnelConfig.id) + } + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt index bb6a709e..644bd7ce 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt @@ -1,28 +1,116 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel -import com.wireguard.android.backend.Tunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import kotlinx.coroutines.flow.StateFlow +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState +import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics +import com.zaneschepke.wireguardautotunnel.util.Constants +import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import timber.log.Timber +import java.net.InetAddress +import java.util.concurrent.atomic.AtomicBoolean -interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel { +abstract class TunnelService { - suspend fun startTunnel(tunnelConfig: TunnelConfig?) + internal open lateinit var tunnelConfig: TunnelConfig - suspend fun stopTunnel() + internal val state = MutableStateFlow(VpnState()) - suspend fun bounceTunnel() + protected val isNetworkAvailable = AtomicBoolean(false) - suspend fun getBackendState(): BackendState + abstract suspend fun startTunnel() - suspend fun setBackendState(backendState: BackendState, allowedIps: Collection) + abstract suspend fun stopTunnel() - val vpnState: StateFlow + abstract suspend fun bounceTunnel() - suspend fun runningTunnelNames(): Set + abstract suspend fun getBackendState(): BackendState - suspend fun getState(): TunnelState + abstract suspend fun setBackendState(backendState: BackendState, allowedIps: Collection) - fun cancelActiveTunnelJobs() + abstract suspend fun runningTunnelNames(): Set - fun startActiveTunnelJobs() + abstract suspend fun getState(): TunnelState + + abstract fun startActiveTunnelJobs() + + abstract fun cancelActiveTunnelJobs() + + companion object { + const val STATS_START_DELAY = 1_000L + const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L + } + + protected fun updateBackendStatistics(statistics: TunnelStatistics) { + state.update { + it.copy(statistics = statistics) + } + } + + protected fun resetState() { + state.update { + VpnState() + } + } + + @Synchronized + protected fun safeUpdateConfig(tunnelConfig: TunnelConfig) { + this.tunnelConfig = tunnelConfig + } + + protected fun updateTunnelState(tunnelState: TunnelState) { + state.update { + it.copy(state = tunnelState) + } + } + + protected fun isQuickConfigChanged(updatedConfig: TunnelConfig): Boolean { + return updatedConfig.wgQuick != tunnelConfig.wgQuick || + updatedConfig.amQuick != tunnelConfig.amQuick + } + + protected fun isPingConfigMatching(updatedConfig: TunnelConfig) : Boolean { + return updatedConfig.isPingEnabled == tunnelConfig.isPingEnabled && + tunnelConfig.pingIp == updatedConfig.pingIp && + updatedConfig.pingCooldown == tunnelConfig.pingCooldown && + updatedConfig.pingInterval == tunnelConfig.pingInterval + } + + private fun pingTunnel(tunnelConfig: TunnelConfig): List { + val config = tunnelConfig.toWgConfig() + return if (tunnelConfig.pingIp != null) { + Timber.i("Pinging custom ip") + listOf(InetAddress.getByName(tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt())) + } else { + Timber.i("Pinging all peers") + config.peers.map { peer -> + peer.isReachable(tunnelConfig.isIpv4Preferred) + } + } + } + + protected suspend fun runTunnelPingCheck() { + run { + if (state.value.state.isUp() && isNetworkAvailable.get()) { + val reachable = pingTunnel(tunnelConfig) + if (reachable.contains(false)) { + if (isNetworkAvailable.get()) { + Timber.i("Ping result: target was not reachable, bouncing the tunnel") + bounceTunnel() + delay(tunnelConfig.pingCooldown ?: Constants.PING_COOLDOWN) + } else { + Timber.i("Ping result: target was not reachable, but not network available") + } + return@run + } else { + Timber.i("Ping result: all ping targets were reached successfully") + } + } + } + delay(tunnelConfig.pingInterval ?: Constants.PING_INTERVAL) + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelState.kt deleted file mode 100644 index 59f968cb..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelState.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.tunnel - -import com.wireguard.android.backend.Tunnel - -enum class TunnelState { - UP, - DOWN, - TOGGLE, - ; - - fun toWgState(): Tunnel.State { - return when (this) { - UP -> Tunnel.State.UP - DOWN -> Tunnel.State.DOWN - TOGGLE -> Tunnel.State.TOGGLE - } - } - - fun toAmState(): org.amnezia.awg.backend.Tunnel.State { - return when (this) { - UP -> org.amnezia.awg.backend.Tunnel.State.UP - DOWN -> org.amnezia.awg.backend.Tunnel.State.DOWN - TOGGLE -> org.amnezia.awg.backend.Tunnel.State.TOGGLE - } - } - - fun isDown(): Boolean { - return this == DOWN - } - - fun isUp(): Boolean { - return this == UP - } - - companion object { - fun from(state: Tunnel.State): TunnelState { - return when (state) { - Tunnel.State.DOWN -> DOWN - Tunnel.State.TOGGLE -> TOGGLE - Tunnel.State.UP -> UP - } - } - - fun from(state: org.amnezia.awg.backend.Tunnel.State): TunnelState { - return when (state) { - org.amnezia.awg.backend.Tunnel.State.DOWN -> DOWN - org.amnezia.awg.backend.Tunnel.State.TOGGLE -> TOGGLE - org.amnezia.awg.backend.Tunnel.State.UP -> UP - } - } - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelsManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelsManager.kt new file mode 100644 index 00000000..1555c9c8 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelsManager.kt @@ -0,0 +1,10 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel + +import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState +import kotlinx.coroutines.flow.Flow + +interface TunnelsManager { + val tunnelStates : Flow> + suspend fun getTunnel(tunnelConfig: TunnelConfig, isKernelTunnel: Boolean) : TunnelService +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/UserspaceTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/UserspaceTunnel.kt new file mode 100644 index 00000000..b8247820 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/UserspaceTunnel.kt @@ -0,0 +1,209 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel + +import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig +import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.module.ApplicationScope +import com.zaneschepke.wireguardautotunnel.module.IoDispatcher +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.network.NetworkService +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState +import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics +import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState +import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState +import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState +import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.amnezia.awg.backend.Backend +import org.amnezia.awg.backend.Tunnel +import timber.log.Timber +import javax.inject.Inject + +class UserspaceTunnel @Inject constructor(@IoDispatcher private val ioDispatcher: CoroutineDispatcher, + @ApplicationScope private val applicationScope: CoroutineScope, + private val serviceManager: ServiceManager, + private val appDataRepository: AppDataRepository, + private val backend: Backend, + private val internetConnectivityService: NetworkService, + override var tunnelConfig: TunnelConfig, + private val onVpnStateChange: (tunnelConfig: TunnelConfig,state: VpnState) -> Unit, + private val onStop: (tunnelConfig : TunnelConfig) -> Unit, + ) : TunnelService(), Tunnel { + + private var statsJob: Job? = null + private var tunnelChangesJob: Job? = null + private var pingJob: Job? = null + private var networkJob: Job? = null + private var stateJob: Job? = null + + override suspend fun startTunnel() { + withContext(ioDispatcher) { + if (getState().isUp()) return@withContext + serviceManager.startBackgroundService(tunnelConfig) + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) + runCatching { + val state = backend.setState(this@UserspaceTunnel, Tunnel.State.UP, tunnelConfig.toAmConfig()) + updateTunnelState(state.asTunnelState()) + startActiveTunnelJobs() + }.onFailure { + onTunnelStop() + Timber.e(it) + } + } + } + + private fun startStateJob() : Job = applicationScope.launch(ioDispatcher) { + state.collect { + onVpnStateChange(tunnelConfig,it) + } + } + + override fun startActiveTunnelJobs() { + stateJob = startStateJob() + statsJob = startTunnelStatisticsJob() + tunnelChangesJob = startTunnelConfigChangesJob() + if (tunnelConfig.isPingEnabled == true) { + startPingJobs() + } + } + + override fun cancelActiveTunnelJobs() { + stateJob?.cancelWithMessage("Tunnel state job canceled") + statsJob?.cancelWithMessage("Tunnel stats job canceled") + tunnelChangesJob?.cancelWithMessage("Tunnel changes job canceled") + cancelPingJobs() + } + + private fun startPingJobs() { + cancelPingJobs() + pingJob = startPingJob() + networkJob = startNetworkJob() + } + + private fun cancelPingJobs() { + pingJob?.cancelWithMessage("Ping job canceled") + networkJob?.cancelWithMessage("Network job canceled") + } + + private fun startPingJob() = applicationScope.launch(ioDispatcher) { + do { + runTunnelPingCheck() + } while (true) + } + + private fun startNetworkJob() = applicationScope.launch(ioDispatcher) { + internetConnectivityService.status.distinctUntilChanged().collect { + isNetworkAvailable.set(!it.allOffline) + } + } + + override suspend fun stopTunnel() { + runCatching { + Timber.d("Stopping tun!") + backend.setState(this@UserspaceTunnel, Tunnel.State.DOWN, tunnelConfig.toAmConfig()) + onTunnelStop() + }.onFailure { + Timber.e(it) + } + } + + private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { + delay(STATS_START_DELAY) + while (true) { + val stats = backend.getStatistics(this@UserspaceTunnel) + updateBackendStatistics(AmneziaStatistics(stats)) + delay(VPN_STATISTIC_CHECK_INTERVAL) + } + } + + private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) { + appDataRepository.tunnels.getTunnelConfigsFlow().collect { tunnels -> + val storageConfig = tunnels.firstOrNull { it.id == tunnelConfig.id } + if (storageConfig == null) return@collect + val quickChanged = isQuickConfigChanged(storageConfig) + val pingMatching = isPingConfigMatching(storageConfig) + safeUpdateConfig(storageConfig) + if (quickChanged) bounceTunnel() + if (!pingMatching) handlePingConfigChanges() + } + } + + private fun restartPingJob() { + cancelPingJobs() + startPingJobs() + } + + private fun handlePingConfigChanges() { + if (!tunnelConfig.isPingEnabled && pingJob?.isActive == true) { + cancelPingJobs() + return + } + restartPingJob() + } + + override suspend fun bounceTunnel() { + if (getState().isUp()) { + toggleTunnel(Tunnel.State.DOWN) + toggleTunnel(Tunnel.State.UP) + } + } + + private suspend fun onTunnelStop() { + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false)) + serviceManager.stopBackgroundService() + cancelActiveTunnelJobs() + resetState() + onStop(tunnelConfig) + } + + private suspend fun toggleTunnel(state: Tunnel.State) { + withContext(ioDispatcher) { + backend.setState(this@UserspaceTunnel, state, tunnelConfig.toAmConfig()) + } + } + + override suspend fun getBackendState(): BackendState { + return backend.backendState.asBackendState() + } + + override suspend fun setBackendState( + backendState: BackendState, + allowedIps: Collection, + ) { + backend.setBackendState(backendState.asAmBackendState(), allowedIps) + state.update { + it.copy(backendState = backendState) + } + } + + override suspend fun runningTunnelNames(): Set { + return backend.runningTunnelNames + } + + override suspend fun getState(): TunnelState { + return backend.getState(this).asTunnelState() + } + + override fun getName(): String { + return tunnelConfig.name + } + + override fun isIpv4ResolutionPreferred(): Boolean { + return tunnelConfig.isIpv4Preferred + } + + override fun onStateChange(newState: Tunnel.State) { + state.update { + it.copy(state = newState.asTunnelState()) + } + serviceManager.updateTunnelTile() + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt deleted file mode 100644 index 4c405041..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.tunnel - -import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics - -data class VpnState( - val status: TunnelState = TunnelState.DOWN, - val backendState: BackendState = BackendState.INACTIVE, - val tunnelConfig: TunnelConfig? = null, - val statistics: TunnelStatistics? = null, -) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt deleted file mode 100644 index 91d1d8b2..00000000 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ /dev/null @@ -1,450 +0,0 @@ -package com.zaneschepke.wireguardautotunnel.service.tunnel - -import com.wireguard.android.backend.Backend -import com.wireguard.android.backend.Tunnel.State -import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel -import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository -import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository -import com.zaneschepke.wireguardautotunnel.module.ApplicationScope -import com.zaneschepke.wireguardautotunnel.module.IoDispatcher -import com.zaneschepke.wireguardautotunnel.module.Kernel -import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager -import com.zaneschepke.wireguardautotunnel.service.network.NetworkService -import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService -import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID -import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification -import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics -import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics -import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics -import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController -import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.StringValue -import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState -import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState -import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage -import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import org.amnezia.awg.backend.Tunnel -import timber.log.Timber -import java.net.InetAddress -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject -import javax.inject.Provider - -class WireGuardTunnel -@Inject -constructor( - private val amneziaBackend: Provider, - private val tunnelConfigRepository: TunnelConfigRepository, - @Kernel private val kernelBackend: Provider, - private val appDataRepository: AppDataRepository, - @ApplicationScope private val applicationScope: CoroutineScope, - @IoDispatcher private val ioDispatcher: CoroutineDispatcher, - private val serviceManager: ServiceManager, - private val notificationService: NotificationService, - private val internetConnectivityService: NetworkService, -) : TunnelService { - - private val _vpnState = MutableStateFlow(VpnState()) - override val vpnState: StateFlow = _vpnState.asStateFlow() - - private var statsJob: Job? = null - private var tunnelChangesJob: Job? = null - private var pingJob: Job? = null - private var networkJob: Job? = null - - @get:Synchronized @set:Synchronized - private var isKernelBackend: Boolean? = null - private val isNetworkAvailable = AtomicBoolean(false) - - private val tunnelControlMutex = Mutex() - - init { - applicationScope.launch(ioDispatcher) { - appDataRepository.settings.getSettingsFlow().collect { - isKernelBackend = it.isKernelEnabled - } - } - } - - private suspend fun backend(): Any { - val isKernelEnabled = isKernelBackend - ?: appDataRepository.settings.getSettings().isKernelEnabled - if (isKernelEnabled) return kernelBackend.get() - return amneziaBackend.get() - } - - override suspend fun runningTunnelNames(): Set { - return when (val backend = backend()) { - is Backend -> backend.runningTunnelNames - is org.amnezia.awg.backend.Backend -> backend.runningTunnelNames - else -> emptySet() - } - } - - private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result { - return runCatching { - when (val backend = backend()) { - is Backend -> backend.setState(this, tunnelState.toWgState(), tunnelConfig.toWgConfig()).let { TunnelState.from(it) } - is org.amnezia.awg.backend.Backend -> { - backend.setState(this, tunnelState.toAmState(), tunnelConfig.toAmConfig()).let { - TunnelState.from(it) - } - } - else -> throw NotImplementedError() - } - }.onFailure { - // TODO add better error message to user, especially for kernel as exceptions contain no details - Timber.e(it) - } - } - - private fun isTunnelAlreadyRunning(tunnelConfig: TunnelConfig): Boolean { - return with(_vpnState.value) { - this.tunnelConfig?.id == tunnelConfig.id && status.isUp().also { - if (it) Timber.w("Tunnel already running") - } - } - } - - override suspend fun startTunnel(tunnelConfig: TunnelConfig?) { - withContext(ioDispatcher) { - if (tunnelConfig == null || isTunnelAlreadyRunning(tunnelConfig)) return@withContext - onBeforeStart(tunnelConfig) - updateTunnelConfig(tunnelConfig) // need to update this here - appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) - withServiceActive { - setState(tunnelConfig, TunnelState.UP).onSuccess { - updateTunnelState(it, tunnelConfig) - startActiveTunnelJobs() - }.onFailure { - onTunnelStop(tunnelConfig) - // TODO improve this with better statuses and handling - showTunnelStartFailed() - } - } - } - } - - private fun showTunnelStartFailed() { - if (WireGuardAutoTunnel.isForeground()) { - SnackbarController.showMessage(StringValue.StringResource(R.string.error_tunnel_start)) - } else { - launchStartFailedNotification() - } - } - - private fun launchStartFailedNotification() { - with(notificationService) { - val notification = createNotification( - WireGuardNotification.NotificationChannels.VPN, - title = context.getString(R.string.error_tunnel_start), - ) - show(VPN_NOTIFICATION_ID, notification) - } - } - - override suspend fun stopTunnel() { - withContext(ioDispatcher) { - if (_vpnState.value.status.isDown()) return@withContext - with(_vpnState.value) { - if (tunnelConfig == null) return@withContext - setState(tunnelConfig, TunnelState.DOWN).onSuccess { - onTunnelStop(tunnelConfig) - updateTunnelState(it, null) - }.onFailure { - clearJobsAndStats() - Timber.e(it) - } - } - } - } - - private suspend fun toggleTunnel(tunnelConfig: TunnelConfig) { - withContext(ioDispatcher) { - tunnelControlMutex.withLock { - setState(tunnelConfig, TunnelState.TOGGLE) - } - } - } - - // utility to keep vpnService alive during rapid changes to prevent bad states - private suspend fun withServiceActive(callback: suspend () -> Unit) { - when (val backend = backend()) { - is org.amnezia.awg.backend.Backend -> { - val backendState = backend.backendState - if (backendState == org.amnezia.awg.backend.Backend.BackendState.INACTIVE) { - backend.setBackendState(org.amnezia.awg.backend.Backend.BackendState.SERVICE_ACTIVE, emptyList()) - } - callback() - } - is Backend -> callback() - } - } - - override suspend fun bounceTunnel() { - with(_vpnState.value) { - if (tunnelConfig != null && status.isUp()) { - withServiceActive { - toggleTunnel(tunnelConfig) - toggleTunnel(tunnelConfig) - } - } - } - } - - override suspend fun getBackendState(): BackendState { - return when (val backend = backend()) { - is org.amnezia.awg.backend.Backend -> { - backend.backendState.asBackendState() - } - is Backend -> BackendState.SERVICE_ACTIVE - else -> BackendState.INACTIVE - } - } - - override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection) { - kotlin.runCatching { - when (val backend = backend()) { - is org.amnezia.awg.backend.Backend -> { - backend.setBackendState(backendState.asAmBackendState(), allowedIps) - _vpnState.update { - it.copy(backendState = backendState) - } - } - is Backend -> { - // TODO not yet implemented - Timber.d("Kernel backend state not yet implemented") - } - else -> Unit - } - } - } - - private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) { - with(_vpnState.value) { - if (status.isUp()) stopTunnel() else clearJobsAndStats() - serviceManager.startBackgroundService(tunnelConfig) - } - } - - private suspend fun onTunnelStop(tunnelConfig: TunnelConfig) { - runCatching { - appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false)) - serviceManager.stopBackgroundService() - notificationService.remove(VPN_NOTIFICATION_ID) - clearJobsAndStats() - } - } - - private fun clearJobsAndStats() { - cancelActiveTunnelJobs() - resetBackendStatistics() - } - - private fun updateTunnelState(state: TunnelState, tunnelConfig: TunnelConfig?) { - _vpnState.update { - it.copy(status = state, tunnelConfig = tunnelConfig) - } - } - - private fun updateTunnelConfig(tunnelConfig: TunnelConfig?) { - _vpnState.update { - it.copy(tunnelConfig = tunnelConfig) - } - } - - private fun updateBackendStatistics(statistics: TunnelStatistics) { - _vpnState.update { - it.copy(statistics = statistics) - } - } - - private fun resetBackendStatistics() { - _vpnState.update { - it.copy(statistics = null) - } - } - - override suspend fun getState(): TunnelState { - return when (val backend = backend()) { - is Backend -> backend.getState(this).let { TunnelState.from(it) } - is org.amnezia.awg.backend.Backend -> backend.getState(this).let { TunnelState.from(it) } - else -> TunnelState.DOWN - } - } - - override fun cancelActiveTunnelJobs() { - statsJob?.cancelWithMessage("Tunnel stats job cancelled") - tunnelChangesJob?.cancelWithMessage("Tunnel changes job cancelled") - cancelPingJobs() - } - - override fun startActiveTunnelJobs() { - statsJob = startTunnelStatisticsJob() - tunnelChangesJob = startTunnelConfigChangesJob() - if (_vpnState.value.tunnelConfig?.isPingEnabled == true) { - startPingJobs() - } - } - - private fun startPingJobs() { - cancelPingJobs() - pingJob = startPingJob() - networkJob = startNetworkJob() - } - override fun getName(): String { - return _vpnState.value.tunnelConfig?.name ?: "" - } - - private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { - val backend = backend() - delay(STATS_START_DELAY) - while (true) { - when (backend) { - is Backend -> updateBackendStatistics( - WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel)), - ) - is org.amnezia.awg.backend.Backend -> updateBackendStatistics( - AmneziaStatistics( - backend.getStatistics(this@WireGuardTunnel), - ), - ) - } - delay(VPN_STATISTIC_CHECK_INTERVAL) - } - } - - private fun isQuickConfigChanged(config: TunnelConfig): Boolean { - return with(_vpnState.value) { - if (tunnelConfig == null) return false - config.wgQuick != tunnelConfig.wgQuick || - config.amQuick != tunnelConfig.amQuick - } - } - - private fun isPingConfigMatching(config: TunnelConfig): Boolean { - return with(_vpnState.value.tunnelConfig) { - if (this == null) return true - config.isPingEnabled == isPingEnabled && - pingIp == config.pingIp && - config.pingCooldown == pingCooldown && - config.pingInterval == pingInterval - } - } - - private fun handlePingConfigChanges() { - with(_vpnState.value.tunnelConfig) { - if (this == null) return - if (!isPingEnabled && pingJob?.isActive == true) { - cancelPingJobs() - return - } - restartPingJob() - } - } - - private fun restartPingJob() { - cancelPingJobs() - startPingJobs() - } - - private fun cancelPingJobs() { - pingJob?.cancelWithMessage("Ping job cancelled") - networkJob?.cancelWithMessage("Network job cancelled") - } - - private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) { - tunnelConfigRepository.getTunnelConfigsFlow().collect { tunnels -> - with(_vpnState.value) { - if (tunnelConfig == null) return@collect - val storageConfig = tunnels.firstOrNull { it.id == tunnelConfig.id } - if (storageConfig == null) return@collect - val quickChanged = isQuickConfigChanged(storageConfig) - val pingMatching = isPingConfigMatching(storageConfig) - updateTunnelConfig(storageConfig) - if (quickChanged) bounceTunnel() - if (!pingMatching) handlePingConfigChanges() - } - } - } - - private suspend fun pingTunnel(tunnelConfig: TunnelConfig): List { - return withContext(ioDispatcher) { - val config = tunnelConfig.toWgConfig() - if (tunnelConfig.pingIp != null) { - Timber.i("Pinging custom ip") - listOf(InetAddress.getByName(tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt())) - } else { - Timber.i("Pinging all peers") - config.peers.map { peer -> - peer.isReachable() - } - } - } - } - - private fun startPingJob() = applicationScope.launch(ioDispatcher) { - do { - run { - with(_vpnState.value) { - if (status.isUp() && tunnelConfig != null && isNetworkAvailable.get()) { - val reachable = pingTunnel(tunnelConfig) - if (reachable.contains(false)) { - if (isNetworkAvailable.get()) { - Timber.i("Ping result: target was not reachable, bouncing the tunnel") - bounceTunnel() - delay(tunnelConfig.pingCooldown ?: Constants.PING_COOLDOWN) - } else { - Timber.i("Ping result: target was not reachable, but not network available") - } - return@run - } else { - Timber.i("Ping result: all ping targets were reached successfully") - } - } - delay(tunnelConfig?.pingInterval ?: Constants.PING_INTERVAL) - } - } - } while (true) - } - - private fun startNetworkJob() = applicationScope.launch(ioDispatcher) { - internetConnectivityService.status.distinctUntilChanged().collect { - isNetworkAvailable.set(!it.allOffline) - } - } - - override fun onStateChange(newState: Tunnel.State) { - _vpnState.update { - it.copy(status = TunnelState.from(newState)) - } - serviceManager.updateTunnelTile() - } - - override fun onStateChange(state: State) { - _vpnState.update { - it.copy(status = TunnelState.from(state)) - } - serviceManager.updateTunnelTile() - } - - companion object { - const val STATS_START_DELAY = 1_000L - const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L - } -} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/BackendState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/BackendState.kt similarity index 54% rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/BackendState.kt rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/BackendState.kt index ee3845b1..aa85fd24 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/BackendState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/BackendState.kt @@ -1,4 +1,4 @@ -package com.zaneschepke.wireguardautotunnel.service.tunnel +package com.zaneschepke.wireguardautotunnel.service.tunnel.model enum class BackendState { KILL_SWITCH_ACTIVE, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/HandshakeStatus.kt similarity index 85% rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/HandshakeStatus.kt index 3ceed201..dce8afbd 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/HandshakeStatus.kt @@ -1,4 +1,4 @@ -package com.zaneschepke.wireguardautotunnel.service.tunnel +package com.zaneschepke.wireguardautotunnel.service.tunnel.model enum class HandshakeStatus { HEALTHY, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/TunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/TunnelState.kt new file mode 100644 index 00000000..a6c26d63 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/TunnelState.kt @@ -0,0 +1,15 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel.model + +enum class TunnelState { + UP, + DOWN, + ; + + fun isDown(): Boolean { + return this == DOWN + } + + fun isUp(): Boolean { + return this == UP + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/VpnState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/VpnState.kt new file mode 100644 index 00000000..3e68f483 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/model/VpnState.kt @@ -0,0 +1,9 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel.model + +import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics + +data class VpnState( + val state: TunnelState = TunnelState.DOWN, + val backendState: BackendState = BackendState.INACTIVE, + val statistics: TunnelStatistics? = null, +) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppUiState.kt index ae7ab7ec..d4788329 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppUiState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppUiState.kt @@ -3,12 +3,10 @@ package com.zaneschepke.wireguardautotunnel.ui import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState data class AppUiState( val settings: Settings = Settings(), val tunnels: List = emptyList(), - val vpnState: VpnState = VpnState(), val generalState: GeneralState = GeneralState(), val autoTunnelActive: Boolean = false, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt index caa08415..673e4847 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt @@ -15,9 +15,9 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.AppShell import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager -import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.BackendState import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.PeerProxy @@ -28,7 +28,6 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.extensions.getAllInternetCapablePackages import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -53,7 +52,6 @@ class AppViewModel @Inject constructor( private val appDataRepository: AppDataRepository, - private val tunnelService: Provider, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, @AppShell private val rootShell: Provider, private val serviceManager: ServiceManager, @@ -76,14 +74,12 @@ constructor( combine( appDataRepository.settings.getSettingsFlow(), appDataRepository.tunnels.getTunnelConfigsFlow(), - tunnelService.get().vpnState, appDataRepository.appState.generalStateFlow, serviceManager.autoTunnelActive, - ) { settings, tunnels, tunnelState, generalState, autoTunnel -> + ) { settings, tunnels, generalState, autoTunnel -> AppUiState( settings, tunnels, - tunnelState, generalState, autoTunnel, ) @@ -113,13 +109,13 @@ constructor( private suspend fun initTunnel() { withContext(ioDispatcher) { - if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs() - val activeTunnels = appDataRepository.tunnels.getActive() - if (activeTunnels.isNotEmpty() && - tunnelService.get().getState() == TunnelState.DOWN - ) { - tunnelService.get().startTunnel(activeTunnels.first()) - } +// if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startActiveTunnelJobs() +// val activeTunnels = appDataRepository.tunnels.getActive() +// if (activeTunnels.isNotEmpty() && +// tunnelService.get().getState() == TunnelState.DOWN +// ) { +//// tunnelService.get().startTunnel(activeTunnels.first()) +// } } } @@ -209,16 +205,16 @@ constructor( } private suspend fun handleVpnKillSwitchChange(enabled: Boolean) { - withContext(ioDispatcher) { - if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) - Timber.d("Starting kill switch") - val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) { - TunnelConfig.LAN_BYPASS_ALLOWED_IPS - } else { - emptySet() - } - tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) - } +// withContext(ioDispatcher) { +// if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) +// Timber.d("Starting kill switch") +// val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) { +// TunnelConfig.LAN_BYPASS_ALLOWED_IPS +// } else { +// emptySet() +// } +// tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) +// } } fun onToggleLanOnKillSwitch(enabled: Boolean) = viewModelScope.launch(ioDispatcher) { @@ -229,7 +225,7 @@ constructor( ) val allowedIps = if (enabled) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet() Timber.d("Setting allowedIps $allowedIps") - tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) +// tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) } fun onToggleShortcutsEnabled() = viewModelScope.launch { @@ -257,7 +253,7 @@ constructor( if (!isKernelEnabled) { requestRoot().onSuccess { if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported)) - tunnelService.get().setBackendState(BackendState.INACTIVE, emptyList()) +// tunnelService.get().setBackendState(BackendState.INACTIVE, emptyList()) appDataRepository.settings.save( copy( isKernelEnabled = true, @@ -377,12 +373,6 @@ constructor( } } - fun bounceAutoTunnel() = viewModelScope.launch(ioDispatcher) { - serviceManager.stopAutoTunnel() - delay(1000L) - serviceManager.startAutoTunnel(true) - } - private suspend fun rebuildConfigs( amConfig: org.amnezia.awg.config.Config, wgConfig: Config, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 7e129c64..1c482ac8 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -42,7 +42,7 @@ import androidx.navigation.toRoute import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutManager -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService +import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelFactory import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController @@ -79,7 +79,7 @@ class MainActivity : AppCompatActivity() { lateinit var appStateRepository: AppStateRepository @Inject - lateinit var tunnelService: TunnelService + lateinit var tunnelFactory: TunnelFactory @Inject lateinit var shortcutManager: ShortcutManager diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt index e1abcf1a..ccfb3576 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt @@ -36,8 +36,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState import com.zaneschepke.wireguardautotunnel.ui.AppUiState import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.common.NestedScrollListener @@ -74,6 +76,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) var selectedTunnel by remember { mutableStateOf(null) } val isRunningOnTv = remember { context.isRunningOnTv() } + val tunnelStates by viewModel.tunnelFactory.tunnelStates.collectAsStateWithLifecycle(emptyMap()) + val collator = Collator.getInstance(Locale.getDefault()) val sortedTunnels = remember(uiState.tunnels) { @@ -127,7 +131,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) } fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) { - if (!checked) viewModel.onTunnelStop().also { return } + if (!checked) viewModel.onTunnelStop(tunnel).also { return } if (uiState.settings.isKernelEnabled) { viewModel.onTunnelStart(tunnel) } else { @@ -226,13 +230,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState) ) { tunnel -> val expanded = uiState.generalState.isTunnelStatsExpanded TunnelRowItem( - tunnel.id == uiState.vpnState.tunnelConfig?.id && ( - uiState.vpnState.status.isUp() || (uiState.settings.isKernelEnabled && tunnel.isActive) - ), + tunnel.isActive, expanded, selectedTunnel?.id == tunnel.id, tunnel, - vpnState = uiState.vpnState, + vpnState = tunnelStates[tunnel.id] ?: VpnState(), { selectedTunnel = tunnel }, { viewModel.onExpandedChanged(!expanded) }, onDelete = { showDeleteTunnelAlertDialog = true }, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt index 603ceafd..251265c0 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt @@ -12,7 +12,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager -import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService +import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelFactory import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.FileReadException @@ -30,14 +30,13 @@ import timber.log.Timber import java.io.InputStream import java.util.zip.ZipInputStream import javax.inject.Inject -import javax.inject.Provider @HiltViewModel class MainViewModel @Inject constructor( private val appDataRepository: AppDataRepository, - private val tunnelService: Provider, + val tunnelFactory: TunnelFactory, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, private val serviceManager: ServiceManager, ) : ViewModel() { @@ -69,12 +68,14 @@ constructor( fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch { Timber.i("Starting tunnel ${tunnelConfig.name}") - tunnelService.get().startTunnel(tunnelConfig) + val setting = appDataRepository.settings.getSettings() + this@MainViewModel.tunnelFactory.getTunnel(tunnelConfig, setting.isKernelEnabled).startTunnel() } - fun onTunnelStop() = viewModelScope.launch { + fun onTunnelStop(tunnelConfig: TunnelConfig) = viewModelScope.launch { Timber.i("Stopping active tunnel") - tunnelService.get().stopTunnel() + val setting = appDataRepository.settings.getSettings() + this@MainViewModel.tunnelFactory.getTunnel(tunnelConfig, setting.isKernelEnabled).stopTunnel() } private fun generateQrCodeDefaultName(config: String): String { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt index aca7ef44..71a570e3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/components/TunnelRowItem.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig -import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.VpnState import com.zaneschepke.wireguardautotunnel.ui.Route import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt index 5c586d1d..8f3b6cba 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt @@ -38,7 +38,6 @@ 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.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.ui.AppUiState import com.zaneschepke.wireguardautotunnel.ui.AppViewModel import com.zaneschepke.wireguardautotunnel.ui.Route @@ -297,11 +296,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: ScaledSwitch( uiState.settings.isKernelEnabled, onClick = { appViewModel.onToggleKernelMode() }, - enabled = !( - uiState.settings.isAutoTunnelEnabled || - uiState.settings.isAlwaysOnVpnEnabled || - (uiState.vpnState.status == TunnelState.UP) - ), +// enabled = !( +// uiState.settings.isAutoTunnelEnabled || +// uiState.settings.isAlwaysOnVpnEnabled || +// (uiState.vpnState.status == TunnelState.UP) +// ), ) }, onClick = { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt index 77d62c72..a22e5b2b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt @@ -11,6 +11,7 @@ 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.Bolt +import androidx.compose.material.icons.outlined.Dns import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.NetworkPing import androidx.compose.material.icons.outlined.Star @@ -139,6 +140,28 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel ForwardButton { navController.navigate(Route.Config(id = tunnelConfig.id)) } }, ), + SelectionItem( + Icons.Outlined.Dns, + title = { + Text( + stringResource(R.string.server_ipv4), + style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface), + ) + }, + description = { + Text( + stringResource(R.string.prefer_ipv4), + style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline), + ) + }, + trailing = { + ScaledSwitch( + tunnelConfig.isIpv4Preferred, + onClick = { viewModel.onToggleIpv4(tunnelConfig) }, + ) + }, + onClick = { viewModel.onToggleIpv4(tunnelConfig) }, + ), SelectionItem( Icons.AutoMirrored.Outlined.CallSplit, title = { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsViewModel.kt index 0db17550..cfd3dc1b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsViewModel.kt @@ -24,6 +24,14 @@ constructor( ) } + fun onToggleIpv4(tunnelConfig: TunnelConfig) = viewModelScope.launch { + appDataRepository.tunnels.save( + tunnelConfig.copy( + isIpv4Preferred = !tunnelConfig.isIpv4Preferred + ) + ) + } + fun saveTunnelChanges(tunnelConfig: TunnelConfig) = viewModelScope.launch { appDataRepository.tunnels.save(tunnelConfig) } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt index 84407e0e..8e2eff29 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt @@ -3,14 +3,18 @@ package com.zaneschepke.wireguardautotunnel.util.extensions import androidx.compose.ui.graphics.Color import com.wireguard.android.util.RootShell 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.model.BackendState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.HandshakeStatus +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState.DOWN +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelState.UP import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics 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.backend.Tunnel import org.amnezia.awg.config.Config import timber.log.Timber import java.net.InetAddress @@ -37,12 +41,12 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus { } } -fun Peer.isReachable(): Boolean { +fun Peer.isReachable(preferIpv4 : Boolean): Boolean { val host = if (this.endpoint.isPresent && - this.endpoint.get().resolved.isPresent + this.endpoint.get().getResolved(preferIpv4).isPresent ) { - this.endpoint.get().resolved.get().host + this.endpoint.get().getResolved(preferIpv4).get().host } else { Constants.DEFAULT_PING_IP } @@ -94,3 +98,17 @@ fun Backend.BackendState.asBackendState(): BackendState { fun BackendState.asAmBackendState(): Backend.BackendState { return Backend.BackendState.valueOf(this.name) } + +fun Tunnel.State.asTunnelState(): TunnelState { + return when (this) { + Tunnel.State.DOWN -> DOWN + Tunnel.State.UP -> UP + } +} + +fun com.wireguard.android.backend.Tunnel.State.asTunnelState(): TunnelState { + return when (this) { + com.wireguard.android.backend.Tunnel.State.DOWN -> DOWN + com.wireguard.android.backend.Tunnel.State.UP -> UP + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92a56252..48ef7696 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,4 +206,6 @@ Tunnel control Auto-tunnel Stop kill switch on trusted + IPv4 hostname resolution + Prefer IPv4 connection diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1118afab..c832e26a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanist = "0.37.0" activityCompose = "1.10.0" -amneziawgAndroid = "1.2.4" +amneziawgAndroid = "1.2.6" androidx-junit = "1.2.1" appcompat = "1.7.0" biometricKtx = "1.2.0-alpha05" @@ -19,7 +19,7 @@ navigationCompose = "2.8.5" pinLockCompose = "1.0.4" roomVersion = "2.6.1" timber = "5.0.1" -tunnel = "1.2.1" +tunnel = "1.2.9" androidGradlePlugin = "8.8.0" kotlin = "2.1.0" ksp = "2.1.0-1.0.29"