Skip to content

Commit

Permalink
Consider the status of IPv6
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusPettersson98 committed Mar 20, 2024
1 parent 27cdc18 commit 2c0c6d2
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 21 deletions.
16 changes: 9 additions & 7 deletions mullvad-daemon/src/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -143,11 +140,15 @@ impl ParametersGenerator {
}

impl InnerParametersGenerator {
async fn generate(&mut self, retry_attempt: u32) -> Result<TunnelParameters, Error> {
async fn generate(
&mut self,
retry_attempt: u32,
ipv6: bool,
) -> Result<TunnelParameters, Error> {
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,
Expand Down Expand Up @@ -277,12 +278,13 @@ impl TunnelParametersGenerator for ParametersGenerator {
fn generate(
&mut self,
retry_attempt: u32,
ipv6: bool,
) -> Pin<Box<dyn Future<Output = Result<TunnelParameters, ParameterGenerationError>>>> {
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,
Expand Down
4 changes: 2 additions & 2 deletions mullvad-relay-selector/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
76 changes: 67 additions & 9 deletions mullvad-relay-selector/src/relay_selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down Expand Up @@ -429,7 +464,16 @@ impl RelaySelector {
///
/// [`RETRY_ORDER`]: crate::RETRY_ORDER
pub fn get_relay(&self, retry_attempt: usize) -> Result<GetRelay, Error> {
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<GetRelay, Error> {
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
Expand All @@ -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),
Expand All @@ -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<GetRelay, Error> {
let config_guard = self.config.lock().unwrap();
let config = SpecializedSelectorConfig::from(&*config_guard);
Expand All @@ -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)
}
}
Expand All @@ -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)
}
Expand Down
5 changes: 3 additions & 2 deletions mullvad-relay-selector/tests/relay_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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, .. } => {
Expand Down
2 changes: 1 addition & 1 deletion talpid-core/src/tunnel_state_machine/connecting_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions talpid-core/src/tunnel_state_machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ pub trait TunnelParametersGenerator: Send + 'static {
fn generate(
&mut self,
retry_attempt: u32,
ipv6: bool,
) -> Pin<Box<dyn Future<Output = Result<TunnelParameters, ParameterGenerationError>>>>;
}

Expand Down
8 changes: 8 additions & 0 deletions talpid-types/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down

0 comments on commit 2c0c6d2

Please sign in to comment.