From 2c0c6d2155a6aff41e85d9f676063d16a4a2786f Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 20 Mar 2024 15:37:18 +0100 Subject: [PATCH] Consider the status of IPv6 --- mullvad-daemon/src/tunnel.rs | 16 ++-- mullvad-relay-selector/src/lib.rs | 4 +- .../src/relay_selector/mod.rs | 76 ++++++++++++++++--- .../tests/relay_selector.rs | 5 +- .../tunnel_state_machine/connecting_state.rs | 2 +- talpid-core/src/tunnel_state_machine/mod.rs | 1 + talpid-types/src/net/mod.rs | 8 ++ 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/mullvad-daemon/src/tunnel.rs b/mullvad-daemon/src/tunnel.rs index 775e04d45c96..59943a786d08 100644 --- a/mullvad-daemon/src/tunnel.rs +++ b/mullvad-daemon/src/tunnel.rs @@ -8,10 +8,7 @@ use std::{ use tokio::sync::Mutex; -#[cfg(not(target_os = "android"))] -use mullvad_relay_selector::{GetRelay, RelaySelector, WireguardConfig}; -#[cfg(target_os = "android")] -use mullvad_relay_selector::{GetRelay, RelaySelector, WireguardConfig}; +use mullvad_relay_selector::{GetRelay, RelaySelector, RuntimeParameters, WireguardConfig}; use mullvad_types::{ endpoint::MullvadWireguardEndpoint, location::GeoIpLocation, relay_list::Relay, settings::TunnelOptions, @@ -143,11 +140,15 @@ impl ParametersGenerator { } impl InnerParametersGenerator { - async fn generate(&mut self, retry_attempt: u32) -> Result { + async fn generate( + &mut self, + retry_attempt: u32, + ipv6: bool, + ) -> Result { let data = self.device().await?; let selected_relay = self .relay_selector - .get_relay(retry_attempt as usize) + .get_relay_with_runtime_params(retry_attempt as usize, RuntimeParameters { ipv6 }) .map_err(|err| match err { mullvad_relay_selector::Error::NoBridge => Error::NoBridgeAvailable, _ => Error::NoRelayAvailable, @@ -277,12 +278,13 @@ impl TunnelParametersGenerator for ParametersGenerator { fn generate( &mut self, retry_attempt: u32, + ipv6: bool, ) -> Pin>>> { let generator = self.0.clone(); Box::pin(async move { let mut inner = generator.lock().await; inner - .generate(retry_attempt) + .generate(retry_attempt, ipv6) .await .map_err(|error| match error { Error::NoBridgeAvailable => ParameterGenerationError::NoMatchingBridgeRelay, diff --git a/mullvad-relay-selector/src/lib.rs b/mullvad-relay-selector/src/lib.rs index 17b781519a5d..9ac49d0b1f63 100644 --- a/mullvad-relay-selector/src/lib.rs +++ b/mullvad-relay-selector/src/lib.rs @@ -10,6 +10,6 @@ mod relay_selector; pub use error::Error; pub use relay_selector::detailer; pub use relay_selector::{ - query, GetRelay, RelaySelector, SelectedBridge, SelectedObfuscator, SelectorConfig, - WireguardConfig, RETRY_ORDER, + query, GetRelay, RelaySelector, RuntimeParameters, SelectedBridge, SelectedObfuscator, + SelectorConfig, WireguardConfig, RETRY_ORDER, }; diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index cf4830d2afc9..3b1e6494711e 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -107,6 +107,41 @@ pub struct SelectorConfig { pub bridge_settings: BridgeSettings, } +/// Values which affect the choice of relay but are only known at runtime. +#[derive(Clone, Debug)] +pub struct RuntimeParameters { + /// Whether IPv6 is available + pub ipv6: bool, +} + +impl RuntimeParameters { + /// Return whether a given [query][`RelayQuery`] is valid given the current runtime parameters + pub fn compatible(&self, query: &RelayQuery) -> bool { + if !self.ipv6 { + let must_use_ipv6 = matches!( + query.wireguard_constraints.ip_version, + Constraint::Only(talpid_types::net::IpVersion::V6) + ); + if must_use_ipv6 { + log::trace!( + "{query:?} is incompatible with {self:?} due to IPv6 not being available" + ); + return false; + } + } + true + } +} + +// Note: It is probably not a good idea to rely on derived default values to be correct for our use +// case. +#[allow(clippy::derivable_impls)] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters { ipv6: false } + } +} + /// This enum exists to separate the two types of [`SelectorConfig`] that exists. /// /// The first one is a "regular" config, where [`SelectorConfig::relay_settings`] is [`RelaySettings::Normal`]. @@ -429,7 +464,16 @@ impl RelaySelector { /// /// [`RETRY_ORDER`]: crate::RETRY_ORDER pub fn get_relay(&self, retry_attempt: usize) -> Result { - self.get_relay_with_order(&RETRY_ORDER, retry_attempt) + self.get_relay_with_custom_params(retry_attempt, &RETRY_ORDER, RuntimeParameters::default()) + } + + /// TODO: Document + pub fn get_relay_with_runtime_params( + &self, + retry_attempt: usize, + runtime_params: RuntimeParameters, + ) -> Result { + self.get_relay_with_custom_params(retry_attempt, &RETRY_ORDER, runtime_params) } /// Peek at which [`TunnelType`] that would be returned for a certain connection attempt for a given @@ -445,9 +489,10 @@ impl RelaySelector { SpecializedSelectorConfig::Custom(_) => None, SpecializedSelectorConfig::Normal(config) => Some( Self::pick_and_merge_query( + connection_attempt, &RETRY_ORDER, + RuntimeParameters::default(), RelayQuery::from(config), - connection_attempt, ) .tunnel_protocol .unwrap_or(TunnelType::Wireguard), @@ -456,11 +501,12 @@ impl RelaySelector { } /// Returns a random relay and relay endpoint matching the current constraints defined by - /// `retry_order` corresponsing to `retry_attempt`. - pub fn get_relay_with_order( + /// `retry_order` corresponding to `retry_attempt`. + pub fn get_relay_with_custom_params( &self, - retry_order: &[RelayQuery], retry_attempt: usize, + retry_order: &[RelayQuery], + runtime_params: RuntimeParameters, ) -> Result { let config_guard = self.config.lock().unwrap(); let config = SpecializedSelectorConfig::from(&*config_guard); @@ -475,8 +521,12 @@ impl RelaySelector { let parsed_relays = &self.parsed_relays.lock().unwrap(); // Merge user preferences with the relay selector's default preferences. let user_preferences = RelayQuery::from(normal_config.clone()); - let query = - Self::pick_and_merge_query(retry_order, user_preferences, retry_attempt); + let query = Self::pick_and_merge_query( + retry_attempt, + retry_order, + runtime_params, + user_preferences, + ); Self::get_relay_inner(&query, parsed_relays, &normal_config) } } @@ -488,15 +538,23 @@ impl RelaySelector { /// This algorithm will loop back to the start of `retry_order` if `retry_attempt < retry_order.len()`. /// If `user_preferences` is not compatible with any of the pre-defined queries in `retry_order`, `user_preferences` /// is returned. + /// + /// Runtime parameters may affect which of the default queries that are considered. For example, + /// queries which rely on IPv6 will not be considered if working IPv6 is not available at runtime. fn pick_and_merge_query( + retry_attempt: usize, retry_order: &[RelayQuery], + runtime_params: RuntimeParameters, user_preferences: RelayQuery, - retry_attempt: usize, ) -> RelayQuery { + log::trace!("Merging user preferences {user_preferences:?} with default retry strategy"); retry_order .iter() + // Remove candidate queries based on runtime parameters before trying to merge user + // settings + .filter(|query| runtime_params.compatible(query)) .cycle() - .filter_map(|constraint| constraint.clone().intersection(user_preferences.clone())) + .filter_map(|query| query.clone().intersection(user_preferences.clone())) .nth(retry_attempt) .unwrap_or(user_preferences) } diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index 4b66f4e4208c..5ee5c72bf536 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -12,7 +12,8 @@ use talpid_types::net::{ use mullvad_relay_selector::{ query::{builder::RelayQueryBuilder, BridgeQuery, OpenVpnRelayQuery}, - Error, GetRelay, RelaySelector, SelectorConfig, WireguardConfig, RETRY_ORDER, + Error, GetRelay, RelaySelector, RuntimeParameters, SelectorConfig, WireguardConfig, + RETRY_ORDER, }; use mullvad_types::{ constraints::Constraint, @@ -876,7 +877,7 @@ fn test_openvpn_auto_bridge() { .take(100 * retry_order.len()) { let relay = relay_selector - .get_relay_with_order(&retry_order, retry_attempt) + .get_relay_with_custom_params(retry_attempt, &retry_order, RuntimeParameters::default()) .unwrap(); match relay { GetRelay::OpenVpn { bridge, .. } => { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 71a88e64fb84..96800f8a0d7f 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -69,7 +69,7 @@ impl ConnectingState { match shared_values.runtime.block_on( shared_values .tunnel_parameters_generator - .generate(retry_attempt), + .generate(retry_attempt, shared_values.connectivity.has_ipv6()), ) { Err(err) => { ErrorState::enter(shared_values, ErrorStateCause::TunnelParameterError(err)) diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 164b1741ad57..02bd2bdbff76 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -417,6 +417,7 @@ pub trait TunnelParametersGenerator: Send + 'static { fn generate( &mut self, retry_attempt: u32, + ipv6: bool, ) -> Pin>>>; } diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 0c3a35743280..b37c26eb1b71 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -555,6 +555,14 @@ impl Connectivity { ) } + // TODO: Document + pub fn has_ipv6(&self) -> bool { + match self { + Connectivity::Status { ipv6, .. } => *ipv6, + _ => false, + } + } + /// If the host does not have configured IPv6 routes, we have no way of /// reaching the internet so we consider ourselves offline. #[cfg(target_os = "android")]