summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonathan <jonathan@mullvad.net>2023-12-21 13:36:52 +0100
committerJonathan <jonathan@mullvad.net>2023-12-21 13:36:52 +0100
commit9d71eccc2f3507928b833f969c952347c7a76d4c (patch)
treec92a6f524fa9f659a0edb2bb702e466d051ddba2
parentce7f4f73f82295275631ef05dbe12ba17843ec2d (diff)
parent8ed53a012b6c510a0dfd92925b8b0e742fd79da3 (diff)
downloadmullvadvpn-9d71eccc2f3507928b833f969c952347c7a76d4c.tar.xz
mullvadvpn-9d71eccc2f3507928b833f969c952347c7a76d4c.zip
Merge branch 'out-ip-delay'
-rw-r--r--CHANGELOG.md7
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt72
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt11
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt26
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt44
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt49
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt2
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt72
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt11
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt2
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt2
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt139
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt3
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt2
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ExponentialBackoff.kt52
-rw-r--r--android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt2
-rw-r--r--gui/src/main/daemon-rpc.ts11
-rw-r--r--gui/src/main/index.ts2
-rw-r--r--gui/src/main/tunnel-state.ts19
-rw-r--r--gui/src/renderer/app.tsx51
-rw-r--r--gui/src/shared/daemon-rpc-types.ts4
-rw-r--r--gui/src/shared/ipc-schema.ts4
-rw-r--r--gui/test/e2e/setup/main.ts4
-rw-r--r--mullvad-cli/src/cmds/status.rs61
-rw-r--r--mullvad-cli/src/format.rs25
-rw-r--r--mullvad-daemon/src/geoip.rs83
-rw-r--r--mullvad-daemon/src/lib.rs162
-rw-r--r--mullvad-daemon/src/management_interface.rs11
-rw-r--r--mullvad-jni/src/daemon_interface.rs9
-rw-r--r--mullvad-jni/src/lib.rs28
-rw-r--r--mullvad-management-interface/proto/management_interface.proto3
-rw-r--r--mullvad-management-interface/src/client.rs25
-rw-r--r--mullvad-management-interface/src/types/conversions/states.rs14
-rw-r--r--mullvad-types/src/location.rs7
-rw-r--r--mullvad-types/src/states.rs4
-rw-r--r--test/test-manager/src/tests/helpers.rs2
-rw-r--r--test/test-manager/src/tests/settings.rs3
-rw-r--r--test/test-manager/src/tests/tunnel_state.rs8
51 files changed, 356 insertions, 714 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02927b8545..e248d5de6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,7 +22,14 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.
## [Unreleased]
+### Fixed
+#### Linux
+- Out IP missing forever when am.i.mullvad.net returns error
+
### Changed
+- Remove `--location` flag from `mullvad status` CLI. Location and IP will now always
+ be printed (if available). `mullvad status listen` no longer prints location info.
+
#### Android
- Migrated to Compose Navigation
- Allow for full rotation
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
index dae3bf2ed3..3838bdc7a0 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
@@ -79,7 +79,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.TunnelStateBlocked,
@@ -115,7 +114,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.TunnelStateBlocked,
@@ -149,7 +147,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -182,7 +179,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -216,7 +212,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = true,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -245,12 +240,11 @@ class ConnectScreenTest {
ConnectUiState(
location = null,
relayLocation = mockRelayLocation,
- tunnelUiState = TunnelState.Disconnected,
- tunnelRealState = TunnelState.Disconnected,
+ tunnelUiState = TunnelState.Disconnected(),
+ tunnelRealState = TunnelState.Disconnected(),
inAddress = null,
outAddress = "",
showLocation = true,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -286,7 +280,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = true,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification =
@@ -326,7 +319,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = true,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification =
@@ -363,7 +355,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.TunnelStateBlocked,
@@ -399,7 +390,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = true,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.TunnelStateBlocked,
@@ -430,12 +420,11 @@ class ConnectScreenTest {
ConnectUiState(
location = null,
relayLocation = mockRelayLocation,
- tunnelUiState = TunnelState.Disconnected,
- tunnelRealState = TunnelState.Disconnected,
+ tunnelUiState = TunnelState.Disconnected(),
+ tunnelRealState = TunnelState.Disconnected(),
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -468,7 +457,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -501,7 +489,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -528,12 +515,11 @@ class ConnectScreenTest {
ConnectUiState(
location = null,
relayLocation = null,
- tunnelUiState = TunnelState.Disconnected,
- tunnelRealState = TunnelState.Disconnected,
+ tunnelUiState = TunnelState.Disconnected(),
+ tunnelRealState = TunnelState.Disconnected(),
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -565,7 +551,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -583,39 +568,6 @@ class ConnectScreenTest {
}
@Test
- fun testToggleTunnelInfo() {
- // Arrange
- val mockedClickHandler: () -> Unit = mockk(relaxed = true)
- val dummyLocation = GeoIpLocation(null, null, "dummy country", null, "dummy hostname")
- composeTestRule.setContentWithTheme {
- ConnectScreen(
- uiState =
- ConnectUiState(
- location = dummyLocation,
- relayLocation = null,
- tunnelUiState = TunnelState.Connecting(null, null),
- tunnelRealState = TunnelState.Connecting(null, null),
- inAddress = null,
- outAddress = "",
- showLocation = false,
- isTunnelInfoExpanded = false,
- deviceName = "",
- daysLeftUntilExpiry = null,
- inAppNotification = null,
- isPlayBuild = false
- ),
- onToggleTunnelInfo = mockedClickHandler
- )
- }
-
- // Act
- composeTestRule.onNodeWithTag(LOCATION_INFO_TEST_TAG).performClick()
-
- // Assert
- verify { mockedClickHandler.invoke() }
- }
-
- @Test
fun showLocationInfo() {
// Arrange
val mockLocation: GeoIpLocation = mockk(relaxed = true)
@@ -638,7 +590,6 @@ class ConnectScreenTest {
inAddress = mockInAddress,
outAddress = mockOutAddress,
showLocation = false,
- isTunnelInfoExpanded = true,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = null,
@@ -647,6 +598,8 @@ class ConnectScreenTest {
)
}
+ composeTestRule.onNodeWithTag(LOCATION_INFO_TEST_TAG).performClick()
+
// Assert
composeTestRule.apply {
onNodeWithText(mockHostName).assertExists()
@@ -677,7 +630,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.UpdateAvailable(versionInfo),
@@ -714,7 +666,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.UnsupportedVersion(versionInfo),
@@ -748,7 +699,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.AccountExpiry(expiryDate),
@@ -787,7 +737,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.UnsupportedVersion(versionInfo),
@@ -820,7 +769,6 @@ class ConnectScreenTest {
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
inAppNotification = InAppNotification.AccountExpiry(expiryDate),
@@ -838,10 +786,8 @@ class ConnectScreenTest {
@Test
fun testOpenAccountView() {
-
- val onAccountClickMockk: () -> Unit = mockk(relaxed = true)
-
// Arrange
+ val onAccountClickMockk: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
ConnectScreen(uiState = ConnectUiState.INITIAL, onAccountClick = onAccountClickMockk)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt
index 742302ce91..903bb412dc 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt
@@ -18,7 +18,7 @@ import net.mullvad.talpid.tunnel.ErrorStateCause
private fun PreviewConnectionStatusText() {
AppTheme {
SpacedColumn {
- ConnectionStatusText(TunnelState.Disconnected)
+ ConnectionStatusText(TunnelState.Disconnected())
ConnectionStatusText(TunnelState.Connecting(null, null))
ConnectionStatusText(
state = TunnelState.Error(ErrorState(ErrorStateCause.Ipv6Unavailable, true))
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index b1de1ad809..646e89c987 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -19,7 +19,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -108,7 +110,6 @@ fun Connect(navigator: DestinationsNavigator) {
onSwitchLocationClick = {
navigator.navigate(SelectLocationDestination) { launchSingleTop = true }
},
- onToggleTunnelInfo = connectViewModel::toggleTunnelInfoExpansion,
onUpdateVersionClick = {
val intent =
Intent(
@@ -137,7 +138,6 @@ fun ConnectScreen(
onConnectClick: () -> Unit = {},
onCancelClick: () -> Unit = {},
onSwitchLocationClick: () -> Unit = {},
- onToggleTunnelInfo: () -> Unit = {},
onUpdateVersionClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onSettingsClick: () -> Unit = {},
@@ -233,12 +233,13 @@ fun ConnectScreen(
color = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(horizontal = Dimens.sideMargin)
)
+ var expanded by rememberSaveable { mutableStateOf(false) }
LocationInfo(
- onToggleTunnelInfo = onToggleTunnelInfo,
+ onToggleTunnelInfo = { expanded = !expanded },
isVisible =
- uiState.tunnelRealState != TunnelState.Disconnected &&
+ uiState.tunnelRealState !is TunnelState.Disconnected &&
uiState.location?.hostname != null,
- isExpanded = uiState.isTunnelInfoExpanded,
+ isExpanded = expanded,
location = uiState.location,
inAddress = uiState.inAddress,
outAddress = uiState.outAddress,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
index d9071be7d8..d0d0c7460d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
@@ -62,7 +62,7 @@ private fun PreviewOutOfTimeScreenDisconnected() {
OutOfTimeScreen(
uiState =
OutOfTimeUiState(
- tunnelState = TunnelState.Disconnected,
+ tunnelState = TunnelState.Disconnected(),
"Heroic Frog",
showSitePayment = true
),
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
index 54c1a0d7c0..dc26e24741 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt
@@ -15,7 +15,6 @@ data class ConnectUiState(
val outAddress: String,
val showLocation: Boolean,
val inAppNotification: InAppNotification?,
- val isTunnelInfoExpanded: Boolean,
val deviceName: String?,
val daysLeftUntilExpiry: Int?,
val isPlayBuild: Boolean
@@ -25,12 +24,11 @@ data class ConnectUiState(
ConnectUiState(
location = null,
relayLocation = null,
- tunnelUiState = TunnelState.Disconnected,
- tunnelRealState = TunnelState.Disconnected,
+ tunnelUiState = TunnelState.Disconnected(),
+ tunnelRealState = TunnelState.Disconnected(),
inAddress = null,
outAddress = "",
showLocation = false,
- isTunnelInfoExpanded = false,
inAppNotification = null,
deviceName = null,
daysLeftUntilExpiry = null,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
index 54fd414f86..d72e015194 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
@@ -3,7 +3,7 @@ package net.mullvad.mullvadvpn.compose.state
import net.mullvad.mullvadvpn.model.TunnelState
data class OutOfTimeUiState(
- val tunnelState: TunnelState = TunnelState.Disconnected,
+ val tunnelState: TunnelState = TunnelState.Disconnected(),
val deviceName: String = "",
val showSitePayment: Boolean = false,
val billingPaymentState: PaymentState? = null,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt
index e2673a0ddf..e43cf6bb98 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt
@@ -3,7 +3,7 @@ package net.mullvad.mullvadvpn.compose.state
import net.mullvad.mullvadvpn.model.TunnelState
data class WelcomeUiState(
- val tunnelState: TunnelState = TunnelState.Disconnected,
+ val tunnelState: TunnelState = TunnelState.Disconnected(),
val accountNumber: String? = null,
val deviceName: String? = null,
val showSitePayment: Boolean = false,
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
index d51bad461d..bbc267b2fa 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
@@ -19,8 +19,8 @@ const val ANTICIPATED_STATE_TIMEOUT_MS = 1500L
class ConnectionProxy(private val connection: Messenger, eventDispatcher: EventDispatcher) {
private var resetAnticipatedStateJob: Job? = null
- val onStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
- val onUiStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ val onStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected())
+ val onUiStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected())
var state by onStateChange.notifiable()
private set
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt
deleted file mode 100644
index 48f77d397d..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/LocationInfoCache.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package net.mullvad.mullvadvpn.ui.serviceconnection
-
-import kotlin.properties.Delegates.observable
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
-import net.mullvad.mullvadvpn.model.GeoIpLocation
-
-class LocationInfoCache(eventDispatcher: EventDispatcher) {
- private var location: GeoIpLocation? by
- observable(null) { _, _, newLocation -> onNewLocation?.invoke(newLocation) }
-
- var onNewLocation by
- observable<((GeoIpLocation?) -> Unit)?>(null) { _, _, callback ->
- callback?.invoke(location)
- }
-
- init {
- eventDispatcher.registerHandler(Event.NewLocation::class) { event ->
- location = event.location
- }
- }
-
- fun onDestroy() {
- onNewLocation = null
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
index ca156bed66..8aabe6c9f5 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
@@ -28,7 +28,6 @@ class ServiceConnectionContainer(
val authTokenCache = AuthTokenCache(connection, dispatcher)
val connectionProxy = ConnectionProxy(connection, dispatcher)
val deviceDataSource = ServiceConnectionDeviceDataSource(connection, dispatcher)
- val locationInfoCache = LocationInfoCache(dispatcher)
val settingsListener = SettingsListener(connection, dispatcher)
val splitTunneling = SplitTunneling(connection, dispatcher)
@@ -62,7 +61,6 @@ class ServiceConnectionContainer(
authTokenCache.onDestroy()
connectionProxy.onDestroy()
- locationInfoCache.onDestroy()
settingsListener.onDestroy()
voucherRedeemer.onDestroy()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
index f228bd7dbe..dec794c86c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCase.kt
@@ -39,7 +39,7 @@ class TunnelStateNotificationUseCase(
}
is TunnelState.Error -> InAppNotification.TunnelStateError(tunnelUiState.errorState)
is TunnelState.Connected,
- TunnelState.Disconnected -> null
+ is TunnelState.Disconnected -> null
}
private fun ConnectionProxy.tunnelUiStateFlow(): Flow<TunnelState> =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
index 3066006083..d25f360b51 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
@@ -5,30 +5,30 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
+import net.mullvad.mullvadvpn.model.GeoIpLocation
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
-import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
@@ -71,48 +71,46 @@ class ConnectViewModel(
}
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
- private val _isTunnelInfoExpanded = MutableStateFlow(false)
-
val uiState: StateFlow<ConnectUiState> =
_shared
.flatMapLatest { serviceConnection ->
combine(
- serviceConnection.locationInfoCache.locationCallbackFlow(),
relayListUseCase.selectedRelayItem(),
inAppNotificationController.notifications,
serviceConnection.connectionProxy.tunnelUiStateFlow(),
serviceConnection.connectionProxy.tunnelRealStateFlow(),
+ serviceConnection.connectionProxy.lastKnownDisconnectedLocation(),
accountRepository.accountExpiryState,
- _isTunnelInfoExpanded,
deviceRepository.deviceState.map { it.deviceName() }
) {
- location,
relayLocation,
notifications,
tunnelUiState,
tunnelRealState,
+ lastKnownDisconnectedLocation,
accountExpiry,
- isTunnelInfoExpanded,
deviceName ->
ConnectUiState(
location =
when (tunnelRealState) {
- is TunnelState.Connected -> tunnelRealState.location
+ is TunnelState.Disconnected -> tunnelRealState.location()
+ ?: lastKnownDisconnectedLocation
is TunnelState.Connecting -> tunnelRealState.location
- else -> null
- }
- ?: location,
+ ?: relayLocation?.location?.location
+ is TunnelState.Connected -> tunnelRealState.location
+ is TunnelState.Disconnecting -> lastKnownDisconnectedLocation
+ is TunnelState.Error -> null
+ },
relayLocation = relayLocation,
tunnelUiState = tunnelUiState,
tunnelRealState = tunnelRealState,
- isTunnelInfoExpanded = isTunnelInfoExpanded,
inAddress =
when (tunnelRealState) {
is TunnelState.Connected -> tunnelRealState.endpoint.toInAddress()
is TunnelState.Connecting -> tunnelRealState.endpoint?.toInAddress()
else -> null
},
- outAddress = location?.toOutAddress() ?: "",
+ outAddress = tunnelRealState.location()?.toOutAddress() ?: "",
showLocation =
when (tunnelUiState) {
is TunnelState.Disconnected -> true
@@ -149,20 +147,18 @@ class ConnectViewModel(
}
}
- private fun LocationInfoCache.locationCallbackFlow() = callbackFlow {
- onNewLocation = { this.trySend(it) }
- awaitClose { onNewLocation = null }
- }
-
private fun ConnectionProxy.tunnelUiStateFlow(): Flow<TunnelState> =
callbackFlowFromNotifier(this.onUiStateChange)
private fun ConnectionProxy.tunnelRealStateFlow(): Flow<TunnelState> =
callbackFlowFromNotifier(this.onStateChange)
- fun toggleTunnelInfoExpansion() {
- _isTunnelInfoExpanded.value = _isTunnelInfoExpanded.value.not()
- }
+ private fun ConnectionProxy.lastKnownDisconnectedLocation(): Flow<GeoIpLocation?> =
+ tunnelRealStateFlow()
+ .filterIsInstance<TunnelState.Disconnected>()
+ .filter { it.location != null }
+ .map { it.location }
+ .onStart { emit(null) }
fun onDisconnectClick() {
serviceConnectionManager.connectionProxy()?.disconnect()
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
index 74683813ae..bdc5ea49b8 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
@@ -63,7 +63,7 @@ class OutOfTimeUseCaseTest {
val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1))
val tunnelStateChanges =
listOf(
- TunnelState.Disconnected,
+ TunnelState.Disconnected(),
TunnelState.Connected(mockk(), null),
TunnelState.Connecting(null, null),
TunnelState.Disconnecting(mockk()),
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
index 1b89c92be7..e67630642d 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
@@ -35,7 +35,7 @@ class TunnelStateNotificationUseCaseTest {
MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
private lateinit var tunnelStateNotificationUseCase: TunnelStateNotificationUseCase
- private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected())
@Before
fun setup() {
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
index 7271c07433..47364652ce 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
@@ -6,12 +6,11 @@ import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
-import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
-import kotlin.test.assertTrue
+import kotlin.test.assertNull
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,7 +32,6 @@ import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
-import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
@@ -73,7 +71,6 @@ class ConnectViewModelTest {
// Service connections
private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
- private val mockLocationInfoCache: LocationInfoCache = mockk(relaxUnitFun = true)
private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
private val mockConnectionProxy: ConnectionProxy = mockk()
private val mockLocation: GeoIpLocation = mockk(relaxed = true)
@@ -93,12 +90,10 @@ class ConnectViewModelTest {
// Payment use case
private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
- // Captures
- private val locationSlot = slot<((GeoIpLocation?) -> Unit)>()
-
// Event notifiers
- private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected)
- private val eventNotifierTunnelRealState = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected())
+ private val eventNotifierTunnelRealState =
+ EventNotifier<TunnelState>(TunnelState.Disconnected())
// Flows
private val selectedRelayFlow = MutableStateFlow<RelayItem?>(null)
@@ -118,7 +113,6 @@ class ConnectViewModelTest {
}
every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
- every { mockServiceConnectionContainer.locationInfoCache } returns mockLocationInfoCache
every { mockServiceConnectionContainer.appVersionInfoCache } returns mockAppVersionInfoCache
every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
@@ -134,7 +128,6 @@ class ConnectViewModelTest {
every { mockLocation.country } returns "dummy country"
// Listeners
- every { mockLocationInfoCache.onNewLocation = capture(locationSlot) } answers {}
every { mockAppVersionInfoCache.onUpdate = any() } answers {}
// Flows
@@ -167,29 +160,14 @@ class ConnectViewModelTest {
}
@Test
- fun testTunnelInfoExpandedUpdate() =
- runTest(testCoroutineRule.testDispatcher) {
- viewModel.uiState.test {
- assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
- viewModel.toggleTunnelInfoExpansion()
- val result = awaitItem()
- assertTrue(result.isTunnelInfoExpanded)
- }
- }
-
- @Test
fun testTunnelRealStateUpdate() =
runTest(testCoroutineRule.testDispatcher) {
- val tunnelRealStateTestItem = TunnelState.Connected(mockk(relaxed = true), mockk())
+ val tunnelRealStateTestItem = TunnelState.Connected(mockk(relaxed = true), null)
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
eventNotifierTunnelRealState.notify(tunnelRealStateTestItem)
val result = awaitItem()
assertEquals(tunnelRealStateTestItem, result.tunnelRealState)
@@ -205,7 +183,6 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
eventNotifierTunnelUiState.notify(tunnelUiStateTestItem)
val result = awaitItem()
assertEquals(tunnelUiStateTestItem, result.tunnelUiState)
@@ -223,7 +200,6 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
val result = awaitItem()
assertEquals(relayTestItem, result.relayLocation)
}
@@ -241,13 +217,19 @@ class ConnectViewModelTest {
hostname = "Host"
)
+ // Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
+ eventNotifierTunnelRealState.notify(TunnelState.Disconnected(null))
+
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(locationTestItem)
- val result = awaitItem()
- assertEquals(locationTestItem, result.location)
+ // Start of with no location
+ assertNull(awaitItem().location)
+
+ // After updated we show latest
+ eventNotifierTunnelRealState.notify(TunnelState.Disconnected(locationTestItem))
+ assertEquals(locationTestItem, awaitItem().location)
}
}
@@ -262,7 +244,6 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(locationTestItem)
expectNoEvents()
val result = awaitItem()
assertEquals(locationTestItem, result.location)
@@ -320,7 +301,6 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
eventNotifierTunnelUiState.notify(tunnelUiState)
val result = awaitItem()
assertEquals(expectedConnectNotificationState, result.inAppNotification)
@@ -356,7 +336,6 @@ class ConnectViewModelTest {
awaitItem()
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
outOfTimeViewFlow.value = true
awaitItem()
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
index 8959b1f9e3..a73ecfc4e7 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
@@ -105,7 +105,7 @@ class DeviceRevokedViewModelTest {
// Arrange
val mockedContainer =
mockk<ServiceConnectionContainer>().also {
- every { it.connectionProxy.state } returns TunnelState.Disconnected
+ every { it.connectionProxy.state } returns TunnelState.Disconnected()
every { it.connectionProxy.disconnect() } just Runs
every { mockedAccountRepository.logout() } just Runs
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
index ab861b1e20..50a9d8fb98 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
@@ -59,7 +59,8 @@ class OutOfTimeViewModelTest {
private val mockConnectionProxy: ConnectionProxy = mockk()
// Event notifiers
- private val eventNotifierTunnelRealState = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ private val eventNotifierTunnelRealState =
+ EventNotifier<TunnelState>(TunnelState.Disconnected())
private val mockAccountRepository: AccountRepository = mockk(relaxed = true)
private val mockDeviceRepository: DeviceRepository = mockk()
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
index ac047db2ea..29b0dfde75 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
@@ -58,7 +58,7 @@ class WelcomeViewModelTest {
private val mockConnectionProxy: ConnectionProxy = mockk()
// Event notifiers
- private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected())
private val mockAccountRepository: AccountRepository = mockk(relaxed = true)
private val mockDeviceRepository: DeviceRepository = mockk()
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
index e1079807f1..69c28bb379 100644
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
+++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
@@ -7,7 +7,6 @@ import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.AccountHistory
import net.mullvad.mullvadvpn.model.DeviceListEvent
import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.GeoIpLocation
import net.mullvad.mullvadvpn.model.LoginResult
import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
@@ -45,8 +44,6 @@ sealed class Event : Message.EventMessage() {
@Parcelize data class LoginEvent(val result: LoginResult) : Event()
- @Parcelize data class NewLocation(val location: GeoIpLocation?) : Event()
-
@Parcelize data class NewRelayList(val relayList: RelayList?) : Event()
@Parcelize data class SettingsUpdate(val settings: Settings?) : Event()
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt
index 26f776b223..4ab925d014 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt
@@ -5,11 +5,10 @@ import kotlinx.parcelize.Parcelize
import net.mullvad.talpid.net.TunnelEndpoint
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.tunnel.FirewallPolicyError
sealed class TunnelState : Parcelable {
- @Parcelize object Disconnected : TunnelState(), Parcelable
+ @Parcelize
+ data class Disconnected(val location: GeoIpLocation? = null) : TunnelState(), Parcelable
@Parcelize
class Connecting(val endpoint: TunnelEndpoint?, val location: GeoIpLocation?) :
@@ -25,6 +24,16 @@ sealed class TunnelState : Parcelable {
@Parcelize class Error(val errorState: ErrorState) : TunnelState(), Parcelable
+ fun location(): GeoIpLocation? {
+ return when (this) {
+ is Connected -> location
+ is Connecting -> location
+ is Disconnecting -> null
+ is Disconnected -> location
+ is Error -> null
+ }
+ }
+
fun isSecured(): Boolean {
return when (this) {
is Connected,
@@ -34,61 +43,4 @@ sealed class TunnelState : Parcelable {
is Error -> this.errorState.isBlocking
}
}
-
- override fun toString(): String =
- when (this) {
- is Disconnected -> DISCONNECTED
- is Connecting -> CONNECTING
- is Connected -> CONNECTED
- is Disconnecting -> {
- if (actionAfterDisconnect == ActionAfterDisconnect.Reconnect) {
- RECONNECTING
- } else {
- DISCONNECTING
- }
- }
- is Error -> {
- if (errorState.isBlocking) {
- BLOCKING
- } else {
- ERROR
- }
- }
- }
-
- companion object {
- const val DISCONNECTED = "disconnected"
- const val CONNECTING = "connecting"
- const val CONNECTED = "connected"
- const val RECONNECTING = "reconnecting"
- const val DISCONNECTING = "disconnecting"
- const val BLOCKING = "blocking"
- const val ERROR = "error"
-
- fun fromString(description: String, endpoint: TunnelEndpoint?): TunnelState {
- return when (description) {
- DISCONNECTED -> Disconnected
- CONNECTING -> Connecting(endpoint, null)
- CONNECTED -> Connected(endpoint!!, null)
- RECONNECTING -> Disconnecting(ActionAfterDisconnect.Reconnect)
- DISCONNECTING -> Disconnecting(ActionAfterDisconnect.Nothing)
- BLOCKING -> Error(ErrorState(ErrorStateCause.StartTunnelError, true))
- ERROR -> {
- Error(
- ErrorState(
- ErrorStateCause.SetFirewallPolicyError(FirewallPolicyError.Generic),
- false
- )
- )
- }
- else ->
- Error(
- ErrorState(
- ErrorStateCause.SetFirewallPolicyError(FirewallPolicyError.Generic),
- false
- )
- )
- }
- }
- }
}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
index 0cfd0e1f7d..107f5d35fe 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
@@ -10,7 +10,6 @@ import net.mullvad.mullvadvpn.model.DeviceEvent
import net.mullvad.mullvadvpn.model.DeviceListEvent
import net.mullvad.mullvadvpn.model.DeviceState
import net.mullvad.mullvadvpn.model.DnsOptions
-import net.mullvad.mullvadvpn.model.GeoIpLocation
import net.mullvad.mullvadvpn.model.GetAccountDataResult
import net.mullvad.mullvadvpn.model.LoginResult
import net.mullvad.mullvadvpn.model.ObfuscationSettings
@@ -34,7 +33,7 @@ class MullvadDaemon(
protected var daemonInterfaceAddress = 0L
val onSettingsChange = EventNotifier<Settings?>(null)
- var onTunnelStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ var onTunnelStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected())
var onAppVersionInfoChange: ((AppVersionInfo) -> Unit)? = null
var onRelayListChange: ((RelayList) -> Unit)? = null
@@ -58,7 +57,7 @@ class MullvadDaemon(
onSettingsChange.notify(getSettings())
- onTunnelStateChange.notify(getState() ?: TunnelState.Disconnected)
+ onTunnelStateChange.notify(getState() ?: TunnelState.Disconnected())
}
fun connect() {
@@ -85,10 +84,6 @@ class MullvadDaemon(
return getWwwAuthToken(daemonInterfaceAddress) ?: ""
}
- fun getCurrentLocation(): GeoIpLocation? {
- return getCurrentLocation(daemonInterfaceAddress)
- }
-
fun getCurrentVersion(): String? {
return getCurrentVersion(daemonInterfaceAddress)
}
@@ -229,8 +224,6 @@ class MullvadDaemon(
private external fun getWwwAuthToken(daemonInterfaceAddress: Long): String?
- private external fun getCurrentLocation(daemonInterfaceAddress: Long): GeoIpLocation?
-
private external fun getCurrentVersion(daemonInterfaceAddress: Long): String?
private external fun getRelayLocations(daemonInterfaceAddress: Long): RelayList?
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index e714295d9c..b0c55540a6 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -189,7 +189,7 @@ class MullvadVpnService : TalpidVpnService() {
override fun onTaskRemoved(rootIntent: Intent?) {
connectionProxy.onStateChange.latestEvent.let { tunnelState ->
Log.d(TAG, "Task removed (tunnelState=$tunnelState)")
- if (tunnelState == TunnelState.Disconnected) {
+ if (tunnelState is TunnelState.Disconnected) {
notificationManager.cancelNotification()
stop()
}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt
index a2c97a05bd..65a27c8f69 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ConnectionProxy.kt
@@ -20,7 +20,7 @@ class ConnectionProxy(val vpnPermission: VpnPermission, endpoint: ServiceEndpoin
private val commandChannel = spawnActor()
private val daemon = endpoint.intermittentDaemon
- private val initialState = TunnelState.Disconnected
+ private val initialState = TunnelState.Disconnected()
var onStateChange = EventNotifier<TunnelState>(initialState)
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
deleted file mode 100644
index 2d06cc109f..0000000000
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/LocationInfoCache.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-package net.mullvad.mullvadvpn.service.endpoint
-
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.channels.ReceiveChannel
-import kotlinx.coroutines.channels.actor
-import kotlinx.coroutines.channels.trySendBlocking
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.receiveAsFlow
-import net.mullvad.mullvadvpn.lib.common.util.toGeographicLocationConstraint
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.GeoIpLocation
-import net.mullvad.mullvadvpn.model.RelaySettings
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.service.util.ExponentialBackoff
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
-
-class LocationInfoCache(private val endpoint: ServiceEndpoint) {
-
- private val fetchRetryDelays =
- ExponentialBackoff().apply {
- scale = 50
- cap = 30 /* min */ * 60 /* s */ * 1000 /* ms */
- count = 17 // ceil(log2(cap / scale) + 1)
- }
-
- private val fetchRequestChannel = runFetcher()
-
- private val daemon
- get() = endpoint.intermittentDaemon
-
- private var lastKnownRealLocation: GeoIpLocation? = null
- private var selectedRelayLocation: GeoIpLocation? = null
-
- var location: GeoIpLocation? by
- observable(null) { _, _, newLocation -> endpoint.sendEvent(Event.NewLocation(newLocation)) }
-
- var state by
- observable<TunnelState>(TunnelState.Disconnected) { _, _, newState ->
- when (newState) {
- is TunnelState.Disconnected -> {
- location = lastKnownRealLocation
- fetchRequestChannel.trySendBlocking(RequestFetch.ForRealLocation)
- }
- is TunnelState.Connecting -> location = newState.location
- is TunnelState.Connected -> {
- location = newState.location
- fetchRequestChannel.trySendBlocking(RequestFetch.ForRelayLocation)
- }
- is TunnelState.Disconnecting -> {
- when (newState.actionAfterDisconnect) {
- ActionAfterDisconnect.Nothing -> location = lastKnownRealLocation
- ActionAfterDisconnect.Block -> location = null
- ActionAfterDisconnect.Reconnect -> {
- lastKnownRealLocation?.let { location = it }
- }
- }
- }
- is TunnelState.Error -> location = null
- }
- }
-
- init {
- endpoint.connectionProxy.onStateChange.subscribe(this) { newState -> state = newState }
-
- endpoint.connectivityListener.connectivityNotifier.subscribe(this) { isConnected ->
- if (isConnected && state is TunnelState.Disconnected) {
- fetchRequestChannel.trySendBlocking(RequestFetch.ForRealLocation)
- }
- }
-
- endpoint.settingsListener.relaySettingsNotifier.subscribe(this, ::updateSelectedLocation)
- }
-
- fun onDestroy() {
- endpoint.connectionProxy.onStateChange.unsubscribe(this)
- endpoint.connectivityListener.connectivityNotifier.unsubscribe(this)
- endpoint.settingsListener.relaySettingsNotifier.unsubscribe(this)
-
- fetchRequestChannel.close()
- }
-
- private fun runFetcher() =
- GlobalScope.actor<RequestFetch>(Dispatchers.Default, Channel.CONFLATED) {
- try {
- fetcherLoop(channel)
- } catch (exception: ClosedReceiveChannelException) {}
- }
-
- private suspend fun fetcherLoop(channel: ReceiveChannel<RequestFetch>) {
- channel
- .receiveAsFlow()
- .flatMapLatest(::fetchCurrentLocation)
- .collect(::handleFetchedLocation)
- }
-
- private fun fetchCurrentLocation(fetchType: RequestFetch) = flow {
- var newLocation = daemon.await().getCurrentLocation()
-
- fetchRetryDelays.reset()
-
- while (newLocation == null) {
- delay(fetchRetryDelays.next())
- newLocation = daemon.await().getCurrentLocation()
- }
-
- emit(Pair(newLocation, fetchType))
- }
-
- private suspend fun handleFetchedLocation(pairItem: Pair<GeoIpLocation, RequestFetch>) {
- val (newLocation, fetchType) = pairItem
-
- if (fetchType == RequestFetch.ForRealLocation) {
- lastKnownRealLocation = newLocation
- }
-
- location = newLocation
- }
-
- private fun updateSelectedLocation(relaySettings: RelaySettings?) {
- val settings = relaySettings as? RelaySettings.Normal
- val constraint = settings?.relayConstraints?.location as? Constraint.Only
-
- selectedRelayLocation = constraint?.value?.toGeographicLocationConstraint()?.location
- }
-
- companion object {
- private enum class RequestFetch {
- ForRealLocation,
- ForRelayLocation,
- }
- }
-}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
index 70e7807ff9..09fc73e5d3 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
@@ -46,7 +46,6 @@ class ServiceEndpoint(
val appVersionInfoCache = AppVersionInfoCache(this)
val authTokenCache = AuthTokenCache(this)
val customDns = CustomDns(this)
- val locationInfoCache = LocationInfoCache(this)
val relayListListener = RelayListListener(this)
val splitTunneling = SplitTunneling(SplitTunnelingPersistence(context), this)
val voucherRedeemer = VoucherRedeemer(this, accountCache)
@@ -77,7 +76,6 @@ class ServiceEndpoint(
connectionProxy.onDestroy()
customDns.onDestroy()
deviceRepositoryBackend.onDestroy()
- locationInfoCache.onDestroy()
relayListListener.onDestroy()
settingsListener.onDestroy()
splitTunneling.onDestroy()
@@ -127,7 +125,6 @@ class ServiceEndpoint(
Event.TunnelStateChange(connectionProxy.state),
Event.AccountHistoryEvent(accountCache.onAccountHistoryChange.latestEvent),
Event.SettingsUpdate(settingsListener.settings),
- Event.NewLocation(locationInfoCache.location),
Event.SplitTunnelingUpdate(splitTunneling.onChange.latestEvent),
Event.CurrentVersion(appVersionInfoCache.currentVersion),
Event.AppVersionInfo(appVersionInfoCache.appVersionInfo),
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
index 97bddc8608..ede083a2a1 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
@@ -77,7 +77,7 @@ class TunnelStateNotification(val context: Context) {
var showAction by observable(false) { _, _, _ -> update() }
var tunnelState by
- observable<TunnelState>(TunnelState.Disconnected) { _, _, newState ->
+ observable<TunnelState>(TunnelState.Disconnected()) { _, _, newState ->
val isReconnecting = newState is TunnelState.Connecting && reconnecting
val shouldBeginReconnecting =
(newState as? TunnelState.Disconnecting)?.actionAfterDisconnect ==
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ExponentialBackoff.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ExponentialBackoff.kt
deleted file mode 100644
index 12e94a9241..0000000000
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/util/ExponentialBackoff.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package net.mullvad.mullvadvpn.service.util
-
-// Calculates a series of delays that increase exponentially.
-//
-// The delays follow the formula:
-//
-// (base ^ retryAttempt) * scale
-//
-// but it is never larger than the specified cap value.
-class ExponentialBackoff : Iterator<Long> {
- private var unscaledValue = 1L
- private var current = 1L
-
- var iteration = 1
- private set
-
- var base = 2L
- var scale = 1000L
- var cap = Long.MAX_VALUE
- var count: Int? = null
-
- override fun hasNext(): Boolean {
- val maxIterations = count
-
- if (maxIterations != null) {
- return iteration < maxIterations
- } else {
- return true
- }
- }
-
- override fun next(): Long {
- iteration += 1
-
- if (current >= cap) {
- return cap
- } else {
- val value = current
-
- unscaledValue *= base
- current = Math.min(cap, scale * unscaledValue)
-
- return value
- }
- }
-
- fun reset() {
- unscaledValue = 1L
- current = 1L
- iteration = 1
- }
-}
diff --git a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt
index a26351162d..93218c66dc 100644
--- a/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt
+++ b/android/tile/src/main/kotlin/net/mullvad/mullvadvpn/tile/ServiceConnection.kt
@@ -60,7 +60,7 @@ class ServiceConnection(context: Context, scope: CoroutineScope) {
subscribeToState(
Event.TunnelStateChange::class,
scope,
- TunnelState.Disconnected
+ TunnelState.Disconnected()
) {
tunnelState
}
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 174b2aea77..ad7b60dec2 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -33,7 +33,6 @@ import {
IDevice,
IDeviceRemoval,
IDnsOptions,
- ILocation,
IObfuscationEndpoint,
IOpenVpnConstraints,
IProxyEndpoint,
@@ -434,11 +433,6 @@ export class DaemonRpc {
await this.callEmpty(this.client.reconnectTunnel);
}
- public async getLocation(): Promise<ILocation> {
- const response = await this.callEmpty<grpcTypes.GeoIpLocation>(this.client.getCurrentLocation);
- return response.toObject();
- }
-
public async getState(): Promise<TunnelState> {
const response = await this.callEmpty<grpcTypes.TunnelState>(this.client.getTunnelState);
return convertFromTunnelState(response)!;
@@ -851,7 +845,10 @@ function convertFromTunnelState(tunnelState: grpcTypes.TunnelState): TunnelState
case grpcTypes.TunnelState.StateCase.STATE_NOT_SET:
return undefined;
case grpcTypes.TunnelState.StateCase.DISCONNECTED:
- return { state: 'disconnected' };
+ return {
+ state: 'disconnected',
+ location: tunnelStateObject.disconnected!.disconnectedLocation,
+ };
case grpcTypes.TunnelState.StateCase.DISCONNECTING: {
const detailsMap: Record<grpcTypes.AfterDisconnect, AfterDisconnect> = {
[grpcTypes.AfterDisconnect.NOTHING]: 'nothing',
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index da3d677cc0..6c3a6373fb 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -728,8 +728,6 @@ class ApplicationMain
navigationHistory: this.navigationHistory,
}));
- IpcMainEventChannel.location.handleGet(() => this.daemonRpc.getLocation());
-
IpcMainEventChannel.tunnel.handleConnect(this.connectTunnel);
IpcMainEventChannel.tunnel.handleReconnect(this.reconnectTunnel);
IpcMainEventChannel.tunnel.handleDisconnect(this.disconnectTunnel);
diff --git a/gui/src/main/tunnel-state.ts b/gui/src/main/tunnel-state.ts
index 029386f3b7..6829118818 100644
--- a/gui/src/main/tunnel-state.ts
+++ b/gui/src/main/tunnel-state.ts
@@ -1,5 +1,5 @@
import { connectEnabled, disconnectEnabled, reconnectEnabled } from '../shared/connect-helper';
-import { TunnelState } from '../shared/daemon-rpc-types';
+import { ILocation, TunnelState } from '../shared/daemon-rpc-types';
import { Scheduler } from '../shared/scheduler';
export interface TunnelStateProvider {
@@ -20,6 +20,8 @@ export default class TunnelStateHandler {
// Scheduler for discarding the assumed next state.
private tunnelStateFallbackScheduler = new Scheduler();
+ private lastKnownDisconnectedLocation: Partial<ILocation> | undefined;
+
public constructor(private delegate: TunnelStateHandlerDelegate) {}
public get tunnelState() {
@@ -37,7 +39,9 @@ export default class TunnelStateHandler {
this.tunnelStateFallback = this.tunnelState;
this.setTunnelState(
- state === 'disconnecting' ? { state, details: 'nothing' as const } : { state },
+ state === 'disconnecting'
+ ? { state, details: 'nothing' as const, location: this.lastKnownDisconnectedLocation }
+ : { state },
);
this.tunnelStateFallbackScheduler.schedule(() => {
@@ -67,6 +71,17 @@ export default class TunnelStateHandler {
this.expectNextTunnelState('connecting');
this.tunnelStateFallback = newState;
} else {
+ if (newState.state === 'disconnected' && newState.location !== undefined) {
+ this.lastKnownDisconnectedLocation = newState.location;
+ }
+
+ if (
+ newState.state === 'disconnecting' ||
+ (newState.state === 'disconnected' && newState.location === undefined)
+ ) {
+ newState.location = this.lastKnownDisconnectedLocation;
+ }
+
this.setTunnelState(newState);
}
}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 584e80ed4f..b5eacf17b3 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -94,7 +94,6 @@ export default class AppRenderer {
};
private location?: Partial<ILocation>;
- private lastDisconnectedLocation?: Partial<ILocation>;
private relayList?: IRelayListWithEndpointData;
private tunnelState!: TunnelState;
private settings!: ISettings;
@@ -103,7 +102,6 @@ export default class AppRenderer {
private previousLoginState: LoginState = 'none';
private loginScheduler = new Scheduler();
private connectedToDaemon = false;
- private getLocationPromise?: Promise<ILocation>;
constructor() {
log.addOutput(new ConsoleOutput(LogLevel.debug));
@@ -255,7 +253,7 @@ export default class AppRenderer {
);
}
- void this.updateLocation();
+ this.updateLocation();
if (initialState.navigationHistory) {
// Set last action to POP to trigger automatic scrolling to saved coordinates.
@@ -765,7 +763,7 @@ export default class AppRenderer {
}
// Update the location when entering a new tunnel state since it's likely changed.
- void this.updateLocation();
+ this.updateLocation();
});
}
@@ -944,22 +942,16 @@ export default class AppRenderer {
this.reduxActions.userInterface.setForceShowChanges(forceShowChanges);
}
- private async updateLocation() {
+ private updateLocation() {
switch (this.tunnelState.state) {
- case 'disconnected': {
- if (this.lastDisconnectedLocation) {
- this.setLocation(this.lastDisconnectedLocation);
- }
- const location = await this.fetchLocation();
- if (location) {
- this.setLocation(location);
- this.lastDisconnectedLocation = location;
+ case 'disconnected':
+ if (this.tunnelState.location) {
+ this.setLocation(this.tunnelState.location);
}
break;
- }
case 'disconnecting':
- if (this.lastDisconnectedLocation) {
- this.setLocation(this.lastDisconnectedLocation);
+ if (this.tunnelState.location) {
+ this.setLocation(this.tunnelState.location);
} else {
// If there's no previous location while disconnecting we remove the location. We keep the
// coordinates to prevent the map from jumping around.
@@ -968,38 +960,13 @@ export default class AppRenderer {
}
break;
case 'connecting':
- this.setLocation(this.tunnelState.details?.location ?? this.getLocationFromConstraints());
- break;
case 'connected': {
- if (this.tunnelState.details?.location) {
- this.setLocation(this.tunnelState.details.location);
- }
- const location = await this.fetchLocation();
- if (location) {
- this.setLocation(location);
- }
+ this.setLocation(this.tunnelState.details?.location ?? this.getLocationFromConstraints());
break;
}
}
}
- private async fetchLocation(): Promise<ILocation | void> {
- try {
- // Fetch the new user location
- const getLocationPromise = IpcRendererEventChannel.location.get();
- this.getLocationPromise = getLocationPromise;
- const location = await getLocationPromise;
- // If the location is currently unavailable, do nothing! This only ever happens when a
- // custom relay is set or we are in a blocked state.
- if (location && getLocationPromise === this.getLocationPromise) {
- return location;
- }
- } catch (e) {
- const error = e as Error;
- log.error(`Failed to update the location: ${error.message}`);
- }
- }
-
private getLocationFromConstraints(): Partial<ILocation> {
const state = this.reduxStore.getState();
const coordinates = {
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 6977be4375..e83f9e5afc 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -181,10 +181,10 @@ export interface ITunnelStateRelayInfo {
}
export type TunnelState =
- | { state: 'disconnected' }
+ | { state: 'disconnected'; location?: Partial<ILocation> }
| { state: 'connecting'; details?: ITunnelStateRelayInfo }
| { state: 'connected'; details: ITunnelStateRelayInfo }
- | { state: 'disconnecting'; details: AfterDisconnect }
+ | { state: 'disconnecting'; details: AfterDisconnect; location?: Partial<ILocation> }
| { state: 'error'; details: ErrorState };
export interface RelayLocationCountry extends Partial<RelayLocationCustomList> {
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index 994bacdcd6..ecf34e93fb 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -15,7 +15,6 @@ import {
IDevice,
IDeviceRemoval,
IDnsOptions,
- ILocation,
IRelayListWithEndpointData,
ISettings,
ObfuscationSettings,
@@ -153,9 +152,6 @@ export const ipcSchema = {
showOpenDialog: invoke<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(),
showLaunchDaemonSettings: invoke<void, void>(),
},
- location: {
- get: invoke<void, ILocation>(),
- },
tunnel: {
'': notifyRenderer<TunnelState>(),
connect: invoke<void, void>(),
diff --git a/gui/test/e2e/setup/main.ts b/gui/test/e2e/setup/main.ts
index 430dfdb70d..e645e8b794 100644
--- a/gui/test/e2e/setup/main.ts
+++ b/gui/test/e2e/setup/main.ts
@@ -153,7 +153,7 @@ class ApplicationMain {
autoStart: false,
accountData: this.accountData,
accountHistory: undefined,
- tunnelState: { state: 'disconnected' },
+ tunnelState: { state: 'disconnected', location: this.location },
settings: this.settings,
isPerformingPostUpgrade: false,
deviceState: this.deviceState,
@@ -174,8 +174,6 @@ class ApplicationMain {
scrollPositions: {},
}));
- IpcMainEventChannel.location.handleGet(() => Promise.resolve(this.location));
-
IpcMainEventChannel.guiSettings.handleSetPreferredLocale((locale) => {
this.updateCurrentLocale(locale);
IpcMainEventChannel.guiSettings.notify?.(this.guiSettings);
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs
index a4a8add15e..5bed82b4c0 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -18,10 +18,6 @@ pub struct StatusArgs {
#[arg(long, short = 'v')]
verbose: bool,
- /// Print the current location and IP, based on GeoIP lookups
- #[arg(long, short = 'l')]
- location: bool,
-
/// Enable debug output
#[arg(long, short = 'd')]
debug: bool,
@@ -29,22 +25,29 @@ pub struct StatusArgs {
impl Status {
pub async fn listen(mut rpc: MullvadProxyClient, args: StatusArgs) -> Result<()> {
+ let mut previous_tunnel_state = None;
+
while let Some(event) = rpc.events_listen().await?.next().await {
match event? {
DaemonEvent::TunnelState(new_state) => {
if args.debug {
println!("New tunnel state: {new_state:#?}");
} else {
- format::print_state(&new_state, args.verbose);
- }
-
- match new_state {
- TunnelState::Connected { .. } | TunnelState::Disconnected => {
- if args.location {
- print_location(&mut rpc).await?;
- }
+ // When we enter the connected or disconnected state, am.i.mullvad.net will
+ // be polled to get IP information. When it arrives, we will get another
+ // tunnel state of the same enum type, but with the IP filled in. This
+ // match statement checks for duplicate tunnel states and skips the second
+ // print to avoid spamming the user.
+ match (&previous_tunnel_state, &new_state) {
+ (Some(TunnelState::Disconnected(_)), TunnelState::Disconnected(_))
+ | (
+ Some(TunnelState::Connected { .. }),
+ TunnelState::Connected { .. },
+ ) => continue,
+ _ => {}
}
- _ => {}
+ format::print_state(&new_state, args.verbose);
+ previous_tunnel_state = Some(new_state);
}
}
DaemonEvent::Settings(settings) => {
@@ -89,10 +92,7 @@ pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
println!("Tunnel state: {state:#?}");
} else {
format::print_state(&state, args.verbose);
- }
-
- if args.location {
- print_location(&mut rpc).await?;
+ format::print_location(&state);
}
if cmd == Some(Status::Listen) {
@@ -101,31 +101,6 @@ pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
Ok(())
}
-async fn print_location(rpc: &mut MullvadProxyClient) -> Result<()> {
- let location = match rpc.get_current_location().await {
- Ok(location) => location,
- Err(error) => match &error {
- mullvad_management_interface::Error::NoLocationData => {
- println!("Location data unavailable");
- return Ok(());
- }
- _ => return Err(error.into()),
- },
- };
- if let Some(ipv4) = location.ipv4 {
- println!("IPv4: {ipv4}");
- }
- if let Some(ipv6) = location.ipv6 {
- println!("IPv6: {ipv6}");
- }
-
- println!(
- "Position: {:.5}°N, {:.5}°W",
- location.latitude, location.longitude
- );
- Ok(())
-}
-
fn print_account_loggedout(state: &TunnelState, device: &DeviceState) {
match state {
TunnelState::Connecting { .. } | TunnelState::Connected { .. } | TunnelState::Error(_) => {
@@ -137,6 +112,6 @@ fn print_account_loggedout(state: &TunnelState, device: &DeviceState) {
DeviceState::LoggedIn(_) => (),
}
}
- TunnelState::Disconnected | TunnelState::Disconnecting(_) => (),
+ TunnelState::Disconnected(_) | TunnelState::Disconnecting(_) => (),
}
}
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs
index 2938842cef..512b632dc8 100644
--- a/mullvad-cli/src/format.rs
+++ b/mullvad-cli/src/format.rs
@@ -37,11 +37,34 @@ pub fn print_state(state: &TunnelState, verbose: bool) {
format_relay_connection(endpoint, location.as_ref(), verbose)
);
}
- Disconnected => println!("Disconnected"),
+ Disconnected(_) => {
+ println!("Disconnected");
+ }
Disconnecting(_) => println!("Disconnecting..."),
}
}
+pub fn print_location(state: &TunnelState) {
+ let location = match state {
+ TunnelState::Disconnected(location) => location,
+ TunnelState::Connected { location, .. } => location,
+ _ => return,
+ };
+ if let Some(location) = location {
+ print!("Your connection appears to be from: {}", location.country);
+ if let Some(city) = &location.city {
+ print!(", {}", city);
+ }
+ if let Some(ipv4) = location.ipv4 {
+ print!(". IPv4: {ipv4}");
+ }
+ if let Some(ipv6) = location.ipv6 {
+ print!(", IPv6: {ipv6}");
+ }
+ println!();
+ }
+}
+
fn format_relay_connection(
endpoint: &TunnelEndpoint,
location: Option<&GeoIpLocation>,
diff --git a/mullvad-daemon/src/geoip.rs b/mullvad-daemon/src/geoip.rs
index 02ee09d1c0..3874e206b8 100644
--- a/mullvad-daemon/src/geoip.rs
+++ b/mullvad-daemon/src/geoip.rs
@@ -3,16 +3,17 @@ use std::time::Duration;
use futures::join;
use mullvad_api::{
self,
- availability::ApiAvailabilityHandle,
rest::{Error, RequestServiceHandle},
};
-use mullvad_types::location::{AmIMullvad, GeoIpLocation};
+use mullvad_types::location::{AmIMullvad, GeoIpLocation, LocationEventData};
use once_cell::sync::Lazy;
-use talpid_core::future_retry::{retry_future, ConstantInterval};
+use talpid_core::{
+ future_retry::{retry_future, ExponentialBackoff, Jittered},
+ mpsc::Sender,
+};
use talpid_types::ErrorExt;
-/// Retry interval for fetching location
-const RETRY_LOCATION_STRATEGY: ConstantInterval = ConstantInterval::new(Duration::ZERO, Some(3));
+use crate::{DaemonEventSender, InternalDaemonEvent};
// Define the Mullvad connection checking api endpoint.
//
@@ -43,29 +44,72 @@ static MULLVAD_CONNCHECK_HOST: Lazy<String> = Lazy::new(|| {
host.to_string()
});
-/// Fetch the current `GeoIpLocation` with retrys
-pub async fn get_geo_location(
+const LOCATION_RETRY_STRATEGY: Jittered<ExponentialBackoff> =
+ Jittered::jitter(ExponentialBackoff::new(Duration::from_secs(1), 4));
+
+/// Handler for request to am.i.mullvad.net, manages in-flight request and validity of responses.
+pub(crate) struct GeoIpHandler {
+ /// Unique ID for each request. If the ID attached to the
+ /// [`InternalDaemonEvent::LocationEvent`] used by [`crate::Daemon::handle_location_event`] to
+ /// determine if the location belongs to the current tunnel state.
+ pub request_id: usize,
rest_service: RequestServiceHandle,
+ location_sender: DaemonEventSender,
+}
+
+impl GeoIpHandler {
+ pub fn new(rest_service: RequestServiceHandle, location_sender: DaemonEventSender) -> Self {
+ Self {
+ request_id: 0,
+ rest_service,
+ location_sender,
+ }
+ }
+
+ /// Send a location request to am.i.mullvad.net. When it arrives, send an
+ /// [`InternalDaemonEvent::LocationEvent`], which triggers an update of the current
+ /// tunnel state with the `ipv4` and/or `ipv6` fields filled in.
+ pub fn send_geo_location_request(&mut self, use_ipv6: bool) {
+ // Increment request ID
+ self.request_id = self.request_id.wrapping_add(1);
+
+ self.abort_current_request();
+
+ let request_id = self.request_id;
+ let rest_service = self.rest_service.clone();
+ let location_sender = self.location_sender.clone();
+ tokio::spawn(async move {
+ if let Ok(location) = get_geo_location_with_retry(use_ipv6, rest_service).await {
+ let _ =
+ location_sender.send(InternalDaemonEvent::LocationEvent(LocationEventData {
+ request_id,
+ location,
+ }));
+ }
+ });
+ }
+
+ /// Abort any ongoing call to am.i.mullvad.net
+ pub fn abort_current_request(&mut self) {
+ self.rest_service.reset();
+ }
+}
+
+/// Fetch the current `GeoIpLocation` from am.i.mullvad.net. Handles retries on network errors.
+async fn get_geo_location_with_retry(
use_ipv6: bool,
- api_handle: ApiAvailabilityHandle,
-) -> Option<GeoIpLocation> {
+ rest_service: RequestServiceHandle,
+) -> Result<GeoIpLocation, Error> {
log::debug!("Fetching GeoIpLocation");
- match retry_future(
+ retry_future(
move || send_location_request(rest_service.clone(), use_ipv6),
move |result| match result {
- Err(error) if error.is_network_error() => !api_handle.get_state().is_offline(),
+ Err(error) => error.is_network_error(),
_ => false,
},
- RETRY_LOCATION_STRATEGY,
+ LOCATION_RETRY_STRATEGY,
)
.await
- {
- Ok(loc) => Some(loc),
- Err(e) => {
- log::warn!("Unable to fetch GeoIP location: {}", e.display_chain());
- None
- }
- }
}
async fn send_location_request(
@@ -83,6 +127,7 @@ async fn send_location_request(
if use_ipv6 {
let uri_v6 = format!("https://ipv6.{}/json", *MULLVAD_CONNCHECK_HOST);
let location = send_location_request_internal(&uri_v6, v6_sender).await;
+ log::warn!("{location:?}");
Some(location.map(GeoIpLocation::from))
} else {
None
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 58069db4fa..19e1d02dfb 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -27,13 +27,14 @@ mod tunnel;
pub mod version;
mod version_check;
-use crate::{geoip::get_geo_location, target_state::PersistentTargetState};
+use crate::target_state::PersistentTargetState;
use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent};
use futures::{
channel::{mpsc, oneshot},
future::{abortable, AbortHandle, Future, LocalBoxFuture},
StreamExt,
};
+use geoip::GeoIpHandler;
use mullvad_relay_selector::{
updater::{RelayListUpdater, RelayListUpdaterHandle},
RelaySelector, SelectorConfig,
@@ -46,7 +47,7 @@ use mullvad_types::{
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
- location::GeoIpLocation,
+ location::{GeoIpLocation, LocationEventData},
relay_constraints::{
BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
},
@@ -80,7 +81,7 @@ use talpid_types::android::AndroidContext;
#[cfg(target_os = "windows")]
use talpid_types::split_tunnel::ExcludedProcess;
use talpid_types::{
- net::{TunnelEndpoint, TunnelType},
+ net::{IpVersion, TunnelEndpoint, TunnelType},
tunnel::{ErrorStateCause, TunnelStateTransition},
ErrorExt,
};
@@ -203,8 +204,6 @@ pub enum DaemonCommand {
Reconnect(oneshot::Sender<bool>),
/// Request the current state.
GetState(oneshot::Sender<TunnelState>),
- /// Get the current geographical location.
- GetCurrentLocation(oneshot::Sender<Option<GeoIpLocation>>),
CreateNewAccount(ResponseTx<String, Error>),
/// Request the metadata for an account.
GetAccountData(
@@ -371,6 +370,8 @@ pub(crate) enum InternalDaemonEvent {
DeviceEvent(AccountEvent),
/// Handles updates from versions without devices.
DeviceMigrationEvent(Result<PrivateAccountAndDevice, device::Error>),
+ /// A geographical location has has been received from am.i.mullvad.net
+ LocationEvent(LocationEventData),
/// The split tunnel paths or state were updated.
#[cfg(target_os = "windows")]
ExcludedPathsEvent(ExcludedPathsUpdate, oneshot::Sender<Result<(), Error>>),
@@ -420,7 +421,7 @@ impl DaemonExecutionState {
match self {
Running => {
match tunnel_state {
- TunnelState::Disconnected => mem::replace(self, Finished),
+ TunnelState::Disconnected(_) => mem::replace(self, Finished),
_ => mem::replace(self, Exiting),
};
}
@@ -617,6 +618,7 @@ pub struct Daemon<L: EventListener> {
tunnel_state_machine_handle: TunnelStateMachineHandle,
#[cfg(target_os = "windows")]
volume_update_tx: mpsc::UnboundedSender<()>,
+ location_handler: GeoIpHandler,
}
impl<L> Daemon<L>
@@ -830,8 +832,13 @@ where
// Attempt to download a fresh relay list
relay_list_updater.update().await;
+ let location_handler = GeoIpHandler::new(
+ api_runtime.rest_handle().await,
+ internal_event_tx.clone().to_specialized_sender(),
+ );
+
let daemon = Daemon {
- tunnel_state: TunnelState::Disconnected,
+ tunnel_state: TunnelState::Disconnected(None),
target_state,
state: DaemonExecutionState::Running,
#[cfg(target_os = "linux")]
@@ -857,6 +864,7 @@ where
tunnel_state_machine_handle,
#[cfg(target_os = "windows")]
volume_update_tx,
+ location_handler,
};
api_availability.unsuspend();
@@ -867,8 +875,16 @@ where
/// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a
/// shutdown event is received.
pub async fn run(mut self) -> Result<(), Error> {
- if *self.target_state == TargetState::Secured {
- self.connect_tunnel();
+ match *self.target_state {
+ TargetState::Secured => {
+ self.connect_tunnel();
+ }
+ TargetState::Unsecured => {
+ // Fetching GeoIpLocation is automatically done when connecting.
+ // If TargetState is Unsecured we will not connect on lauch and
+ // so we have to explicitly fetch this information.
+ self.fetch_am_i_mullvad()
+ }
}
while let Some(event) = self.rx.next().await {
@@ -946,6 +962,7 @@ where
}
DeviceEvent(event) => self.handle_device_event(event).await,
DeviceMigrationEvent(event) => self.handle_device_migration_event(event),
+ LocationEvent(location_data) => self.handle_location_event(location_data),
#[cfg(windows)]
ExcludedPathsEvent(update, tx) => self.handle_new_excluded_paths(update, tx).await,
}
@@ -960,15 +977,15 @@ where
.handle_state_transition(&tunnel_state_transition);
let tunnel_state = match tunnel_state_transition {
- TunnelStateTransition::Disconnected => TunnelState::Disconnected,
+ TunnelStateTransition::Disconnected => TunnelState::Disconnected(None),
TunnelStateTransition::Connecting(endpoint) => TunnelState::Connecting {
endpoint,
location: self.parameters_generator.get_last_location().await,
},
- TunnelStateTransition::Connected(endpoint) => TunnelState::Connected {
- endpoint,
- location: self.parameters_generator.get_last_location().await,
- },
+ TunnelStateTransition::Connected(endpoint) => {
+ let location = self.parameters_generator.get_last_location().await;
+ TunnelState::Connected { endpoint, location }
+ }
TunnelStateTransition::Disconnecting(after_disconnect) => {
TunnelState::Disconnecting(after_disconnect)
}
@@ -985,7 +1002,7 @@ where
log::debug!("New tunnel state: {:?}", tunnel_state);
match tunnel_state {
- TunnelState::Disconnected => {
+ TunnelState::Disconnected(_) => {
self.api_handle.availability.reset_inactivity_timer();
}
_ => {
@@ -994,7 +1011,7 @@ where
}
match &tunnel_state {
- TunnelState::Disconnected => self.state.disconnected(),
+ TunnelState::Disconnected(_) => self.state.disconnected(),
TunnelState::Connecting { .. } => {
log::debug!("Settings: {}", self.settings.summary());
}
@@ -1022,6 +1039,72 @@ where
self.tunnel_state = tunnel_state.clone();
self.event_listener.notify_new_state(tunnel_state);
+ self.fetch_am_i_mullvad();
+ }
+
+ /// Get the geographical location from am.i.mullvad.net. When it arrives,
+ /// update the "Out IP" field of the front ends by sending a
+ /// [`InternalDaemonEvent::LocationEvent`].
+ ///
+ /// See [`Daemon::handle_location_event()`]
+ fn fetch_am_i_mullvad(&mut self) {
+ // Always abort any ongoing request when entering a new tunnel state
+ self.location_handler.abort_current_request();
+
+ // Whether or not to poll for an IPv6 exit IP
+ let use_ipv6 = match &self.tunnel_state {
+ // If connected, refer to the tunnel setting
+ TunnelState::Connected { .. } => self.settings.tunnel_options.generic.enable_ipv6,
+ // If not connected, we have to guess whether the users local connection supports IPv6.
+ // The only thing we have to go on is the wireguard setting.
+ TunnelState::Disconnected(_) => {
+ if let RelaySettings::Normal(relay_constraints) = &self.settings.relay_settings {
+ // Note that `Constraint::Any` corresponds to just IPv4
+ matches!(
+ relay_constraints.wireguard_constraints.ip_version,
+ mullvad_types::relay_constraints::Constraint::Only(IpVersion::V6)
+ )
+ } else {
+ false
+ }
+ }
+ // Fetching IP from am.i.mullvad.net should only be done from a tunnel state where a
+ // connection is available. Otherwise we just exist.
+ _ => return,
+ };
+
+ self.location_handler.send_geo_location_request(use_ipv6);
+ }
+
+ /// Recieves and handles the geographical exit location received from am.i.mullvad.net, i.e. the
+ /// [`InternalDaemonEvent::LocationEvent`] event.
+ fn handle_location_event(&mut self, location_data: LocationEventData) {
+ let LocationEventData {
+ request_id,
+ location: fetched_location,
+ } = location_data;
+
+ if self.location_handler.request_id != request_id {
+ log::debug!("Location from am.i.mullvad.net belongs to an outdated tunnel state");
+ return;
+ }
+
+ match self.tunnel_state {
+ TunnelState::Disconnected(ref mut location) => *location = Some(fetched_location),
+ TunnelState::Connected {
+ ref mut location, ..
+ } => {
+ *location = Some(GeoIpLocation {
+ ipv4: fetched_location.ipv4,
+ ipv6: fetched_location.ipv6,
+ ..location.clone().unwrap_or(fetched_location)
+ })
+ }
+ _ => return,
+ };
+
+ self.event_listener
+ .notify_new_state(self.tunnel_state.clone());
}
fn reset_rpc_sockets_on_tunnel_state_transition(
@@ -1075,7 +1158,6 @@ where
SetTargetState(tx, state) => self.on_set_target_state(tx, state).await,
Reconnect(tx) => self.on_reconnect(tx),
GetState(tx) => self.on_get_state(tx),
- GetCurrentLocation(tx) => self.on_get_current_location(tx).await,
CreateNewAccount(tx) => self.on_create_new_account(tx),
GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token),
GetWwwAuthToken(tx) => self.on_get_www_auth_token(tx).await,
@@ -1326,52 +1408,6 @@ where
Self::oneshot_send(tx, performing_post_upgrade, "performing post upgrade");
}
- async fn on_get_current_location(&mut self, tx: oneshot::Sender<Option<GeoIpLocation>>) {
- use self::TunnelState::*;
-
- match &self.tunnel_state {
- Disconnected => self.update_and_send_geo_location(tx, None).await,
- Connecting { location, .. } => {
- Self::oneshot_send(tx, location.clone(), "current location")
- }
- Disconnecting(..) => Self::oneshot_send(
- tx,
- self.parameters_generator.get_last_location().await,
- "current location",
- ),
- Connected { location, .. } => {
- self.update_and_send_geo_location(tx, location.clone())
- .await
- }
- // We are not online at all at this stage so no location data is available.
- Error(_) => Self::oneshot_send(tx, None, "current location"),
- };
- }
-
- /// Fetch the current `GeoIpLocation` and send it on the return channel,
- /// in a non-blocking fashion. Optionally give a chached previous location.
- async fn update_and_send_geo_location(
- &mut self,
- tx: oneshot::Sender<Option<GeoIpLocation>>,
- current_relay_location: Option<GeoIpLocation>,
- ) {
- let rest_service = self.api_runtime.rest_handle().await;
- let use_ipv6 = self.settings.tunnel_options.generic.enable_ipv6;
- let api_handle = self.api_handle.availability.clone();
- tokio::spawn(async move {
- let new_location = get_geo_location(rest_service, use_ipv6, api_handle).await;
- Self::oneshot_send(
- tx,
- new_location.map(|fetched_location| GeoIpLocation {
- ipv4: fetched_location.ipv4,
- ipv6: fetched_location.ipv6,
- ..current_relay_location.unwrap_or(fetched_location)
- }),
- "current location",
- );
- });
- }
-
fn on_create_new_account(&mut self, tx: ResponseTx<String, Error>) {
let account_manager = self.account_manager.clone();
tokio::spawn(async move {
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index c194825a34..cf007e665d 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -191,17 +191,6 @@ impl ManagementService for ManagementServiceImpl {
.map(|relays| Response::new(types::RelayList::from(relays)))
}
- async fn get_current_location(&self, _: Request<()>) -> ServiceResult<types::GeoIpLocation> {
- log::debug!("get_current_location");
- let (tx, rx) = oneshot::channel();
- self.send_command_to_daemon(DaemonCommand::GetCurrentLocation(tx))?;
- let result = self.wait_for_result(rx).await?;
- match result {
- Some(geoip) => Ok(Response::new(types::GeoIpLocation::from(geoip))),
- None => Err(Status::not_found("no location was found")),
- }
- }
-
async fn set_bridge_settings(
&self,
request: Request<types::BridgeSettings>,
diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs
index 236c864a01..feea551557 100644
--- a/mullvad-jni/src/daemon_interface.rs
+++ b/mullvad-jni/src/daemon_interface.rs
@@ -3,7 +3,6 @@ use mullvad_daemon::{device, DaemonCommand, DaemonCommandSender};
use mullvad_types::{
account::{AccountData, AccountToken, PlayPurchase, VoucherSubmission},
device::{Device, DeviceState},
- location::GeoIpLocation,
relay_constraints::{ObfuscationSettings, RelaySettings},
relay_list::RelayList,
settings::{DnsOptions, Settings},
@@ -115,14 +114,6 @@ impl DaemonInterface {
.map_err(Error::from)
}
- pub fn get_current_location(&self) -> Result<Option<GeoIpLocation>> {
- let (tx, rx) = oneshot::channel();
-
- self.send_command(DaemonCommand::GetCurrentLocation(tx))?;
-
- block_on(rx).map_err(|_| Error::NoResponse)
- }
-
pub fn get_current_version(&self) -> Result<String> {
let (tx, rx) = oneshot::channel();
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index a2c3586431..8c51157896 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -762,34 +762,6 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_getWwwA
#[no_mangle]
#[allow(non_snake_case)]
-pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_getCurrentLocation<
- 'env,
->(
- env: JNIEnv<'env>,
- _: JObject<'_>,
- daemon_interface_address: jlong,
-) -> JObject<'env> {
- let env = JnixEnv::from(env);
-
- // SAFETY: The address points to an instance valid for the duration of this function call
- if let Some(daemon_interface) = unsafe { get_daemon_interface(daemon_interface_address) } {
- match daemon_interface.get_current_location() {
- Ok(location) => location.into_java(&env).forget(),
- Err(error) => {
- log::error!(
- "{}",
- error.display_chain_with_msg("Failed to get current location")
- );
- JObject::null()
- }
- }
- } else {
- JObject::null()
- }
-}
-
-#[no_mangle]
-#[allow(non_snake_case)]
pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_getCurrentVersion<'env>(
env: JNIEnv<'env>,
_: JObject<'_>,
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index d66707b79f..97507ca8a2 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -28,7 +28,6 @@ service ManagementService {
// Relays and tunnel constraints
rpc UpdateRelayLocations(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc GetRelayLocations(google.protobuf.Empty) returns (RelayList) {}
- rpc GetCurrentLocation(google.protobuf.Empty) returns (GeoIpLocation) {}
rpc SetRelaySettings(RelaySettings) returns (google.protobuf.Empty) {}
rpc SetBridgeSettings(BridgeSettings) returns (google.protobuf.Empty) {}
rpc SetBridgeState(BridgeState) returns (google.protobuf.Empty) {}
@@ -172,7 +171,7 @@ message ErrorState {
}
message TunnelState {
- message Disconnected {}
+ message Disconnected { GeoIpLocation disconnected_location = 1; }
message Connecting { TunnelStateRelayInfo relay_info = 1; }
message Connected { TunnelStateRelayInfo relay_info = 1; }
message Disconnecting { AfterDisconnect after_disconnect = 1; }
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 1c9d80b2e8..85048e29e7 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -7,7 +7,6 @@ use mullvad_types::{
account::{AccountData, AccountToken, VoucherSubmission},
custom_list::{CustomList, Id},
device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent},
- location::GeoIpLocation,
relay_constraints::{
BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
},
@@ -234,16 +233,6 @@ impl MullvadProxyClient {
Ok(())
}
- pub async fn get_current_location(&mut self) -> Result<GeoIpLocation> {
- let location = self
- .0
- .get_current_location(())
- .await
- .map_err(map_location_error)?
- .into_inner();
- GeoIpLocation::try_from(location).map_err(Error::InvalidResponse)
- }
-
pub async fn set_bridge_settings(&mut self, settings: BridgeSettings) -> Result<()> {
let settings = types::BridgeSettings::from(settings);
self.0
@@ -571,10 +560,9 @@ impl MullvadProxyClient {
/// Set the [`AccessMethod`] which [`ApiConnectionModeProvider`] should
/// pick.
///
- /// - `access_method`: If `Some(access_method)`, [`ApiConnectionModeProvider`] will skip
- /// ahead and return `access_method` when asked for a new access method.
- /// If `None`, [`ApiConnectionModeProvider`] will pick the next access
- /// method "randomly"
+ /// - `access_method`: If `Some(access_method)`, [`ApiConnectionModeProvider`] will skip ahead
+ /// and return `access_method` when asked for a new access method. If `None`,
+ /// [`ApiConnectionModeProvider`] will pick the next access method "randomly"
///
/// [`ApiConnectionModeProvider`]: mullvad_daemon::api::ApiConnectionModeProvider
pub async fn set_access_method(&mut self, api_access_method: access_method::Id) -> Result<()> {
@@ -696,13 +684,6 @@ fn map_device_error(status: Status) -> Error {
}
}
-fn map_location_error(status: Status) -> Error {
- match status.code() {
- Code::NotFound => Error::NoLocationData,
- _other => Error::Rpc(status),
- }
-}
-
fn map_custom_list_error(status: Status) -> Error {
match status.code() {
Code::NotFound => {
diff --git a/mullvad-management-interface/src/types/conversions/states.rs b/mullvad-management-interface/src/types/conversions/states.rs
index b742aa7b00..2541859e27 100644
--- a/mullvad-management-interface/src/types/conversions/states.rs
+++ b/mullvad-management-interface/src/types/conversions/states.rs
@@ -32,8 +32,10 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState {
};
let state = match state {
- MullvadTunnelState::Disconnected => {
- proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {})
+ MullvadTunnelState::Disconnected(disconnected_location) => {
+ proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {
+ disconnected_location: disconnected_location.map(proto::GeoIpLocation::from),
+ })
}
MullvadTunnelState::Connecting { endpoint, location } => {
proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
@@ -189,7 +191,13 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState {
use talpid_types::{net as talpid_net, tunnel as talpid_tunnel};
let state = match state.state {
- Some(proto::tunnel_state::State::Disconnected(_)) => MullvadState::Disconnected,
+ Some(proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {
+ disconnected_location,
+ })) => MullvadState::Disconnected(
+ disconnected_location
+ .map(mullvad_types::location::GeoIpLocation::try_from)
+ .transpose()?,
+ ),
Some(proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
relay_info:
Some(proto::TunnelStateRelayInfo {
diff --git a/mullvad-types/src/location.rs b/mullvad-types/src/location.rs
index 6453d17717..07e2b1e90c 100644
--- a/mullvad-types/src/location.rs
+++ b/mullvad-types/src/location.rs
@@ -181,6 +181,13 @@ impl From<AmIMullvad> for GeoIpLocation {
}
}
+pub struct LocationEventData {
+ /// Keep track of which request led to this event being triggered
+ pub request_id: usize,
+ /// New location information
+ pub location: GeoIpLocation,
+}
+
#[cfg(test)]
mod tests {
use super::Coordinates;
diff --git a/mullvad-types/src/states.rs b/mullvad-types/src/states.rs
index f6131711cb..4a266d1d6e 100644
--- a/mullvad-types/src/states.rs
+++ b/mullvad-types/src/states.rs
@@ -34,7 +34,7 @@ impl fmt::Display for TargetState {
#[cfg_attr(target_os = "android", derive(IntoJava))]
#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))]
pub enum TunnelState {
- Disconnected,
+ Disconnected(Option<GeoIpLocation>),
Connecting {
endpoint: TunnelEndpoint,
location: Option<GeoIpLocation>,
@@ -60,6 +60,6 @@ impl TunnelState {
/// Returns true if the tunnel state is in the disconnected state.
pub fn is_disconnected(&self) -> bool {
- matches!(self, TunnelState::Disconnected)
+ matches!(self, TunnelState::Disconnected(_))
}
}
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs
index 48dba20aac..3f64aef1be 100644
--- a/test/test-manager/src/tests/helpers.rs
+++ b/test/test-manager/src/tests/helpers.rs
@@ -229,7 +229,7 @@ pub async fn disconnect_and_wait(
.await
.map_err(|error| Error::DaemonError(format!("failed to begin disconnecting: {}", error)))?;
wait_for_tunnel_state(mullvad_client.clone(), |state| {
- matches!(state, TunnelState::Disconnected)
+ matches!(state, TunnelState::Disconnected(_))
})
.await?;
diff --git a/test/test-manager/src/tests/settings.rs b/test/test-manager/src/tests/settings.rs
index aea81028fe..d2bde57fcc 100644
--- a/test/test-manager/src/tests/settings.rs
+++ b/test/test-manager/src/tests/settings.rs
@@ -107,9 +107,8 @@ pub async fn test_lockdown(
let inet_destination: SocketAddr = "1.1.1.1:1337".parse().unwrap();
log::info!("Verify tunnel state: disconnected");
- assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected);
+ assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected(_));
- //
// Enable lockdown mode
//
mullvad_client
diff --git a/test/test-manager/src/tests/tunnel_state.rs b/test/test-manager/src/tests/tunnel_state.rs
index 9ae817c7ee..d8cce41b3a 100644
--- a/test/test-manager/src/tests/tunnel_state.rs
+++ b/test/test-manager/src/tests/tunnel_state.rs
@@ -32,9 +32,8 @@ pub async fn test_disconnected_state(
let inet_destination = "1.3.3.7:1337".parse().unwrap();
log::info!("Verify tunnel state: disconnected");
- assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected);
+ assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected(_));
- //
// Test whether outgoing packets can be observed
//
@@ -90,7 +89,7 @@ pub async fn test_connecting_state(
let lan_dns: SocketAddr = SocketAddr::new(IpAddr::V4(DUMMY_LAN_INTERFACE_IP), 53);
log::info!("Verify tunnel state: disconnected");
- assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected);
+ assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected(_));
let relay_settings = RelaySettings::CustomTunnelEndpoint(CustomTunnelEndpoint {
host: "1.3.3.7".to_owned(),
@@ -172,9 +171,8 @@ pub async fn test_error_state(
let lan_dns: SocketAddr = SocketAddr::new(IpAddr::V4(DUMMY_LAN_INTERFACE_IP), 53);
log::info!("Verify tunnel state: disconnected");
- assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected);
+ assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected(_));
- //
// Connect to non-existent location
//