diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-16 15:07:57 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-22 13:06:17 +0200 |
| commit | 01e2ade8ba9c8ea4363eeca65a0d8ed989de4d2f (patch) | |
| tree | fb55d756049f478fa4be40de48b239f2059393c9 | |
| parent | 4d3129808552e247a591e074a944d95ea9916a27 (diff) | |
| download | mullvadvpn-01e2ade8ba9c8ea4363eeca65a0d8ed989de4d2f.tar.xz mullvadvpn-01e2ade8ba9c8ea4363eeca65a0d8ed989de4d2f.zip | |
Add entry and exit no relay errors
The old no relay error is still kept for single hop
16 files changed, 125 insertions, 65 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/inappnotification/TunnelStateNotificationUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/inappnotification/TunnelStateNotificationUseCase.kt index 8e58c58a36..442e562bac 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/inappnotification/TunnelStateNotificationUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/inappnotification/TunnelStateNotificationUseCase.kt @@ -76,9 +76,11 @@ class TunnelStateNotificationUseCase( } else this private fun ErrorState.isPossiblePortError(): Boolean = - cause is ErrorStateCause.TunnelParameterError && - (cause as ErrorStateCause.TunnelParameterError).error == - ParameterGenerationError.NoMatchingRelay + (cause as? ErrorStateCause.TunnelParameterError)?.error?.let { + it == ParameterGenerationError.NoMatchingRelayEntry || + it == ParameterGenerationError.NoMatchingRelayExit || + it == ParameterGenerationError.NoMatchingRelay + } ?: false private fun Constraint<Port>.invalidPortOrNull(availablePortRanges: List<PortRange>): Port? = getOrNull()?.takeIf { !it.inAnyOf(availablePortRanges) } 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 f4c460d880..d10feff8ca 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 @@ -246,8 +246,4 @@ class ConnectViewModel( data class PermissionDenied(val systemVpnSettingsAvailable: Boolean) : ConnectError } } - - companion object { - const val UI_STATE_DEBOUNCE_DURATION_MILLIS: Long = 200 - } } diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index 26207c5d57..b60e2d8444 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -307,6 +307,10 @@ internal fun ManagementInterface.ErrorState.FirewallPolicyError.toDomain(): internal fun ManagementInterface.ErrorState.GenerationError.toDomain(): ParameterGenerationError = when (this) { + ManagementInterface.ErrorState.GenerationError.NO_MATCHING_RELAY_ENTRY -> + ParameterGenerationError.NoMatchingRelayEntry + ManagementInterface.ErrorState.GenerationError.NO_MATCHING_RELAY_EXIT -> + ParameterGenerationError.NoMatchingRelayExit ManagementInterface.ErrorState.GenerationError.NO_MATCHING_RELAY -> ParameterGenerationError.NoMatchingRelay ManagementInterface.ErrorState.GenerationError.NO_MATCHING_BRIDGE_RELAY -> diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt index 5683dc08b1..dd4e65a03a 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ParameterGenerationError.kt @@ -1,6 +1,8 @@ package net.mullvad.mullvadvpn.lib.model enum class ParameterGenerationError { + NoMatchingRelayEntry, + NoMatchingRelayExit, NoMatchingRelay, NoMatchingBridgeRelay, NoWireguardKey, diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index b03c4d7d36..1aac455e0c 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -462,4 +462,6 @@ <string name="send_email">Send email</string> <string name="no_email_app_available">No email app available on the device</string> <string name="include_account_token_checkbox_text">This is a question about account or payments (include account information)</string> + <string name="no_matching_relay_entry">No entry server match your settings, try changing server or other settings.</string> + <string name="no_matching_relay_exit">No exit server match your settings, try changing server or other settings.</string> </resources> diff --git a/android/lib/ui/component/build.gradle.kts b/android/lib/ui/component/build.gradle.kts index 56ec9a63ef..54cf81bdcb 100644 --- a/android/lib/ui/component/build.gradle.kts +++ b/android/lib/ui/component/build.gradle.kts @@ -24,6 +24,7 @@ android { compilerOptions { jvmTarget = JvmTarget.fromTarget(libs.versions.jvm.target.get()) allWarningsAsErrors = true + freeCompilerArgs = listOf("-XXLanguage:+WhenGuards") } } diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/AnimatedNotificationBanner.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/AnimatedNotificationBanner.kt index b9799fab33..2ed3ca4b0f 100644 --- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/AnimatedNotificationBanner.kt +++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/AnimatedNotificationBanner.kt @@ -80,14 +80,14 @@ fun AnimatedNotificationBanner( modifier = notificationModifier, visibleNotification.toNotificationData( isPlayBuild = isPlayBuild, - openAppListing, - onClickShowAccount, - onClickShowChangelog, - onClickShowAndroid16UpgradeInfo, - onClickDismissChangelog, - onClickDismissNewDevice, - onClickShowWireguardPortSettings, - onClickDismissAndroid16UpgradeWarning, + openAppListing = openAppListing, + onClickShowAccount = onClickShowAccount, + onClickShowChangelog = onClickShowChangelog, + onClickShowAndroid16UpgradeInfo = onClickShowAndroid16UpgradeInfo, + onClickDismissChangelog = onClickDismissChangelog, + onClickDismissNewDevice = onClickDismissNewDevice, + onClickShowWireguardPortSettings = onClickShowWireguardPortSettings, + onClickDismissAndroid16UpgradeWarning = onClickDismissAndroid16UpgradeWarning, ), ) } diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/NotificationData.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/NotificationData.kt index 9f4c17dbd9..53c56549b1 100644 --- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/NotificationData.kt +++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/NotificationData.kt @@ -288,6 +288,12 @@ private fun ParameterGenerationError.errorMessageId(): Int = ParameterGenerationError.NoMatchingBridgeRelay -> { R.string.no_matching_relay } + ParameterGenerationError.NoMatchingRelayExit -> { + R.string.no_matching_relay_exit + } + ParameterGenerationError.NoMatchingRelayEntry -> { + R.string.no_matching_relay_entry + } ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key ParameterGenerationError.CustomTunnelHostResolutionError -> R.string.custom_tunnel_host_resolution_error diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot index efeecc4761..2317b5923a 100644 --- a/desktop/packages/mullvad-vpn/locales/messages.pot +++ b/desktop/packages/mullvad-vpn/locales/messages.pot @@ -3264,6 +3264,12 @@ msgstr "" msgid "No email app available on the device" msgstr "" +msgid "No entry server match your settings, try changing server or other settings." +msgstr "" + +msgid "No exit server match your settings, try changing server or other settings." +msgstr "" + msgid "No internet connection" msgstr "" diff --git a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts index f29979c520..0387219756 100644 --- a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts +++ b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts @@ -329,6 +329,8 @@ function convertFromParameterError( ): TunnelParameterError { switch (error) { case grpcTypes.ErrorState.GenerationError.NO_MATCHING_RELAY: + case grpcTypes.ErrorState.GenerationError.NO_MATCHING_RELAY_ENTRY: + case grpcTypes.ErrorState.GenerationError.NO_MATCHING_RELAY_EXIT: return TunnelParameterError.noMatchingRelay; case grpcTypes.ErrorState.GenerationError.NO_MATCHING_BRIDGE_RELAY: return TunnelParameterError.noMatchingBridgeRelay; diff --git a/mullvad-daemon/src/tunnel.rs b/mullvad-daemon/src/tunnel.rs index 4c35ee2be8..27c89020f5 100644 --- a/mullvad-daemon/src/tunnel.rs +++ b/mullvad-daemon/src/tunnel.rs @@ -303,6 +303,12 @@ impl From<Error> for ParameterGenerationError { Error::SelectRelay(mullvad_relay_selector::Error::IpVersionUnavailable { family }) => { ParameterGenerationError::IpVersionUnavailable { family } } + Error::SelectRelay(mullvad_relay_selector::Error::NoRelayEntry(_)) => { + ParameterGenerationError::NoMatchingRelayEntry + } + Error::SelectRelay(mullvad_relay_selector::Error::NoRelayExit(_)) => { + ParameterGenerationError::NoMatchingRelayExit + } Error::NoAuthDetails | Error::SelectRelay(_) | Error::Device(_) => { ParameterGenerationError::NoMatchingRelay } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 25e0321ce4..928b704b41 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -222,12 +222,14 @@ message ErrorState { } enum GenerationError { - NO_MATCHING_RELAY = 0; - NO_MATCHING_BRIDGE_RELAY = 1; - NO_WIREGUARD_KEY = 2; - CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR = 3; - NETWORK_IPV4_UNAVAILABLE = 4; - NETWORK_IPV6_UNAVAILABLE = 5; + NO_MATCHING_RELAY_ENTRY = 0; + NO_MATCHING_RELAY_EXIT = 1; + NO_MATCHING_RELAY = 2; + NO_MATCHING_BRIDGE_RELAY = 3; + NO_WIREGUARD_KEY = 4; + CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR = 5; + NETWORK_IPV4_UNAVAILABLE = 6; + NETWORK_IPV6_UNAVAILABLE = 7; } message FirewallPolicyError { diff --git a/mullvad-management-interface/src/types/conversions/states.rs b/mullvad-management-interface/src/types/conversions/states.rs index bbd6c500f2..002aa6ea69 100644 --- a/mullvad-management-interface/src/types/conversions/states.rs +++ b/mullvad-management-interface/src/types/conversions/states.rs @@ -179,9 +179,15 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState { error_state.cause() { match reason { - talpid_tunnel::ParameterGenerationError::NoMatchingRelay => { - i32::from(GenerationError::NoMatchingRelay) + talpid_tunnel::ParameterGenerationError::NoMatchingRelayEntry => { + i32::from(GenerationError::NoMatchingRelayEntry) } + talpid_tunnel::ParameterGenerationError::NoMatchingRelayExit => { + i32::from(GenerationError::NoMatchingRelayExit) + } + talpid_tunnel::ParameterGenerationError::NoMatchingRelay => { + i32::from(GenerationError::NoMatchingRelay) + } talpid_tunnel::ParameterGenerationError::NoMatchingBridgeRelay => { i32::from(GenerationError::NoMatchingBridgeRelay) } @@ -391,10 +397,12 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState { let parameter_error = match proto::error_state::GenerationError::try_from(parameter_error) { Ok(proto::error_state::GenerationError::CustomTunnelHostResolutionError) => talpid_tunnel::ParameterGenerationError::CustomTunnelHostResolutionError, Ok(proto::error_state::GenerationError::NoMatchingBridgeRelay) => talpid_tunnel::ParameterGenerationError::NoMatchingBridgeRelay, - Ok(proto::error_state::GenerationError::NoMatchingRelay) => talpid_tunnel::ParameterGenerationError::NoMatchingRelay, + Ok(proto::error_state::GenerationError::NoMatchingRelayEntry) => talpid_tunnel::ParameterGenerationError::NoMatchingRelayEntry, + Ok(proto::error_state::GenerationError::NoMatchingRelayExit) => talpid_tunnel::ParameterGenerationError::NoMatchingRelayExit, Ok(proto::error_state::GenerationError::NoWireguardKey) => talpid_tunnel::ParameterGenerationError::NoWireguardKey, Ok(proto::error_state::GenerationError::NetworkIpv4Unavailable) => talpid_tunnel::ParameterGenerationError::IpVersionUnavailable { family: IpVersion::V4 }, Ok(proto::error_state::GenerationError::NetworkIpv6Unavailable) => talpid_tunnel::ParameterGenerationError::IpVersionUnavailable { family: IpVersion::V6 }, + Ok(proto::error_state::GenerationError::NoMatchingRelay) => talpid_tunnel::ParameterGenerationError::NoMatchingRelay, _ => return Err(FromProtobufTypeError::InvalidArgument( "invalid parameter error", )), diff --git a/mullvad-relay-selector/src/error.rs b/mullvad-relay-selector/src/error.rs index 5dc7747f6c..0d5acf4ae8 100644 --- a/mullvad-relay-selector/src/error.rs +++ b/mullvad-relay-selector/src/error.rs @@ -16,6 +16,12 @@ pub enum Error { #[error("The combination of relay constraints is invalid")] InvalidConstraints, + #[error("No relays matching current entry constraints: {0:?}")] + NoRelayEntry(Box<RelayQuery>), + + #[error("No relays matching current exit constraints: {0:?}")] + NoRelayExit(Box<RelayQuery>), + #[error("No relays matching current constraints: {0:?}")] NoRelay(Box<RelayQuery>), diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index d5087f43a3..16e0013ffd 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -612,20 +612,20 @@ impl RelaySelector { // Select a relay using the user's preferences merged with the nth compatible query // in `retry_order`, looping back to the start of `retry_order` if // necessary. - retry_order + let maybe_relay = retry_order .iter() .filter_map(|query| query.clone().intersection(user_query.clone())) .filter_map(|query| { Self::get_relay_inner(&query, &parsed_relays, custom_lists).ok() }) .cycle() // If the above filters remove all relays, cycle will also return an empty iterator - .nth(retry_attempt) + .nth(retry_attempt); + match maybe_relay { + Some(v) => Ok(v), // If none of the queries in `retry_order` merged with `user_preferences` yield any relays, // attempt to only consider the user's preferences. - .or_else(|| { - Self::get_relay_inner(&user_query, &parsed_relays, custom_lists).ok() - }) - .ok_or_else(|| Error::NoRelay(Box::new(user_query))) + None => Self::get_relay_inner(&user_query, &parsed_relays, custom_lists), + } } } } @@ -789,7 +789,7 @@ impl RelaySelector { let exit_candidates = filter_matching_relay_list(&exit_relay_query, parsed_relays, custom_lists); let exit = helpers::pick_random_relay(&exit_candidates) - .ok_or_else(|| Error::NoRelay(Box::new(exit_relay_query)))?; + .ok_or_else(|| Error::NoRelayExit(Box::new(exit_relay_query)))?; // generate a list of potential entry relays, disregarding any location constraint let mut entry_query = query.clone(); @@ -814,7 +814,7 @@ impl RelaySelector { .map(|relay_with_distance| relay_with_distance.relay) .collect_vec(); let entry = helpers::pick_random_relay_excluding(&entry_candidates, exit) - .ok_or_else(|| Error::NoRelay(Box::new(entry_query)))?; + .ok_or_else(|| Error::NoRelayEntry(Box::new(entry_query)))?; Ok(Multihop::new(entry.clone(), exit.clone())) } @@ -856,55 +856,66 @@ impl RelaySelector { let entry_candidates = filter_matching_relay_list(&entry_relay_query, parsed_relays, custom_lists); - match Self::pick_working_entry_exit_combo( + Self::pick_working_entry_exit_combo( + query, exit_candidates.as_slice(), entry_candidates.as_slice(), - ) { - Some((exit, entry)) => Ok(Multihop::new(entry.clone(), exit.clone())), - None => { - // Sometimes, the set of relays is too small to consider the `include_in_country` - // flag. It might just be that if we disregard the `include_in_country` flag, we - // manage to find candidate relays. This is rather unlikely, but it might just - // happen. - let exit_candidates = filter_matching_relay_list_include_all( - &exit_relay_query, - parsed_relays, - custom_lists, - ); - let entry_candidates = filter_matching_relay_list_include_all( - &entry_relay_query, - parsed_relays, - custom_lists, - ); - let (exit, entry) = Self::pick_working_entry_exit_combo( - exit_candidates.as_slice(), - entry_candidates.as_slice(), - ) - .ok_or_else(|| Error::NoRelay(Box::new(query.clone())))?; - Ok(Multihop::new(entry.clone(), exit.clone())) - } - } + ) + .map(|(exit, entry)| Multihop::new(entry.clone(), exit.clone())) + .or_else(|_e| { + // Sometimes, the set of relays is too small to consider the `include_in_country` + // flag. It might just be that if we disregard the `include_in_country` flag, we + // manage to find candidate relays. This is rather unlikely, but it might just + // happen. + let exit_candidates = filter_matching_relay_list_include_all( + &exit_relay_query, + parsed_relays, + custom_lists, + ); + let entry_candidates = filter_matching_relay_list_include_all( + &entry_relay_query, + parsed_relays, + custom_lists, + ); + Self::pick_working_entry_exit_combo( + query, + exit_candidates.as_slice(), + entry_candidates.as_slice(), + ) + .map(|(exit, entry)| Multihop::new(entry.clone(), exit.clone())) + }) } /// Avoid picking the same relay for entry and exit by choosing one and excluding it when /// choosing the other. fn pick_working_entry_exit_combo<'a>( + query: &RelayQuery, exit_candidates: &'a [Relay], entry_candidates: &'a [Relay], - ) -> Option<(&'a Relay, &'a Relay)> { + ) -> Result<(&'a Relay, &'a Relay), Error> { match (exit_candidates, entry_candidates) { // In the case where there is only one entry to choose from, we have to pick it before // the exit (exits, [entry]) if exits.contains(entry) => { - helpers::pick_random_relay_excluding(exits, entry).map(|exit| (exit, entry)) + helpers::pick_random_relay_excluding(exits, entry) + .map(|exit| (exit, entry)) + .ok_or_else(|| Error::NoRelayExit(Box::new(query.clone()))) } // Vice versa for the case of only one exit ([exit], entries) if entries.contains(exit) => { - helpers::pick_random_relay_excluding(entries, exit).map(|entry| (exit, entry)) + helpers::pick_random_relay_excluding(entries, exit) + .map(|entry| (exit, entry)) + .ok_or_else(|| Error::NoRelayEntry(Box::new(query.clone()))) + } + (exits, entries) => { + let exit = helpers::pick_random_relay(exits); + match exit { + None => Err(Error::NoRelayExit(Box::new(query.clone()))), + Some(exit) => helpers::pick_random_relay_excluding(entries, exit) + .map(|entry| (exit, entry)) + .ok_or_else(|| Error::NoRelayEntry(Box::new(query.clone()))), + } } - (exits, entries) => helpers::pick_random_relay(exits).and_then(|exit| { - helpers::pick_random_relay_excluding(entries, exit).map(|entry| (exit, entry)) - }), } } diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs index 3dc7bdc308..768c40a600 100644 --- a/talpid-types/src/tunnel.rs +++ b/talpid-types/src/tunnel.rs @@ -121,7 +121,13 @@ impl ErrorStateCause { #[derive(thiserror::Error, Debug, Serialize, Clone, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ParameterGenerationError { - /// Failure to select a matching tunnel relay + /// Failure to select a matching entry tunnel relay + #[error("Failure to select a matching entry tunnel relay")] + NoMatchingRelayEntry, + /// Failure to select a matching exit tunnel relay + #[error("Failure to select a matching exit tunnel relay")] + NoMatchingRelayExit, + /// Failure to select a matching tunnel relay, but we do not know if it is an entry or an exit #[error("Failure to select a matching tunnel relay")] NoMatchingRelay, /// Failure to select a matching bridge relay |
