Skip to content

Commit

Permalink
Filter out OpenVPN relays when a core privacy feature is enabled
Browse files Browse the repository at this point in the history
Core privacy features currently include PQ, multihop, and
DAITA
  • Loading branch information
dlon committed Aug 28, 2024
1 parent f0b6d29 commit 5940861
Show file tree
Hide file tree
Showing 8 changed files with 482 additions and 206 deletions.
1 change: 1 addition & 0 deletions mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2937,6 +2937,7 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig {
daita: settings.tunnel_options.wireguard.daita.enabled,
#[cfg(not(daita))]
daita: false,
quantum_resistant: settings.tunnel_options.wireguard.quantum_resistant,
},
};

Expand Down
3 changes: 3 additions & 0 deletions mullvad-relay-selector/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub enum Error {
#[error("Failed to write relay cache file to disk")]
WriteRelayCache(#[source] std::io::Error),

#[error("The combination of relay constraints is invalid")]
InvalidConstraints,

#[error("No relays matching current constraints")]
NoRelay,

Expand Down
12 changes: 6 additions & 6 deletions mullvad-relay-selector/src/relay_selector/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ pub fn filter_matching_relay_list(
) -> Vec<Relay> {
let relays = relay_list.relays();

let locations = ResolvedLocationConstraint::from_constraint(&query.location, custom_lists);
let locations = ResolvedLocationConstraint::from_constraint(query.location(), custom_lists);
let shortlist = relays
// Filter on tunnel type
.filter(|relay| filter_tunnel_type(&query.tunnel_protocol, relay))
.filter(|relay| filter_tunnel_type(&query.tunnel_protocol(), relay))
// Filter on active relays
.filter(|relay| filter_on_active(relay))
// Filter by location
.filter(|relay| filter_on_location(&locations, relay))
// Filter by ownership
.filter(|relay| filter_on_ownership(&query.ownership, relay))
.filter(|relay| filter_on_ownership(&query.ownership(), relay))
// Filter by providers
.filter(|relay| filter_on_providers(&query.providers, relay))
.filter(|relay| filter_on_providers(query.providers(), relay))
// Filter by DAITA support
.filter(|relay| filter_on_daita(&query.wireguard_constraints.daita, relay))
.filter(|relay| filter_on_daita(&query.wireguard_constraints().daita, relay))
// Filter by obfuscation support
.filter(|relay| filter_on_obfuscation(&query.wireguard_constraints, relay_list, relay));
.filter(|relay| filter_on_obfuscation(query.wireguard_constraints(), relay_list, relay));

// The last filtering to be done is on the `include_in_country` attribute found on each
// relay. When the location constraint is based on country, a relay which has
Expand Down
79 changes: 49 additions & 30 deletions mullvad-relay-selector/src/relay_selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use mullvad_types::{
},
relay_list::{Relay, RelayEndpointData, RelayList},
settings::Settings,
wireguard::QuantumResistantState,
CustomTunnelEndpoint, Intersection,
};
use talpid_types::{
Expand Down Expand Up @@ -123,6 +124,8 @@ pub struct AdditionalWireguardConstraints {
/// If true, select WireGuard relays that support DAITA. If false, select any
/// server.
pub daita: bool,
/// If enabled, select relays that support PQ.
pub quantum_resistant: QuantumResistantState,
}

/// Values which affect the choice of relay but are only known at runtime.
Expand All @@ -137,7 +140,7 @@ impl RuntimeParameters {
pub fn compatible(&self, query: &RelayQuery) -> bool {
if !self.ipv6 {
let must_use_ipv6 = matches!(
query.wireguard_constraints.ip_version,
query.wireguard_constraints().ip_version,
Constraint::Only(talpid_types::net::IpVersion::V6)
);
if must_use_ipv6 {
Expand Down Expand Up @@ -323,9 +326,11 @@ impl<'a> From<&'a SelectorConfig> for SpecializedSelectorConfig<'a> {
}
}

impl<'a> From<NormalSelectorConfig<'a>> for RelayQuery {
impl<'a> TryFrom<NormalSelectorConfig<'a>> for RelayQuery {
type Error = crate::Error;

/// Map user settings to [`RelayQuery`].
fn from(value: NormalSelectorConfig<'a>) -> Self {
fn try_from(value: NormalSelectorConfig<'a>) -> Result<Self, Self::Error> {
/// Map the Wireguard-specific bits of `value` to [`WireguradRelayQuery`]
fn wireguard_constraints(
wireguard_constraints: WireguardConstraints,
Expand All @@ -338,14 +343,18 @@ impl<'a> From<NormalSelectorConfig<'a>> for RelayQuery {
use_multihop,
entry_location,
} = wireguard_constraints;
let AdditionalWireguardConstraints { daita } = additional_constraints;
let AdditionalWireguardConstraints {
daita,
quantum_resistant,
} = additional_constraints;
WireguardRelayQuery {
port,
ip_version,
use_multihop: Constraint::Only(use_multihop),
entry_location,
obfuscation: ObfuscationQuery::from(obfuscation_settings),
daita: Constraint::Only(daita),
quantum_resistant,
}
}

Expand Down Expand Up @@ -382,14 +391,14 @@ impl<'a> From<NormalSelectorConfig<'a>> for RelayQuery {
*value.bridge_state,
value.bridge_settings.clone(),
);
RelayQuery {
location: value.user_preferences.location.clone(),
providers: value.user_preferences.providers.clone(),
ownership: value.user_preferences.ownership,
tunnel_protocol: value.user_preferences.tunnel_protocol,
RelayQuery::new(
value.user_preferences.location.clone(),
value.user_preferences.providers.clone(),
value.user_preferences.ownership,
value.user_preferences.tunnel_protocol,
wireguard_constraints,
openvpn_constraints,
}
)
}
}

Expand Down Expand Up @@ -473,10 +482,11 @@ impl RelaySelector {
let specialized_config = SpecializedSelectorConfig::from(&*config);

let near_location = match specialized_config {
SpecializedSelectorConfig::Normal(config) => {
let user_preferences = RelayQuery::from(config.clone());
Self::get_relay_midpoint(&user_preferences, parsed_relays, config.custom_lists)
}
SpecializedSelectorConfig::Normal(config) => RelayQuery::try_from(config.clone())
.ok()
.and_then(|user_preferences| {
Self::get_relay_midpoint(&user_preferences, parsed_relays, config.custom_lists)
}),
SpecializedSelectorConfig::Custom(_) => None,
};

Expand Down Expand Up @@ -583,7 +593,7 @@ impl RelaySelector {
user_config: &NormalSelectorConfig<'_>,
parsed_relays: &ParsedRelays,
) -> Result<RelayQuery, Error> {
let user_query = RelayQuery::from(user_config.clone());
let user_query = RelayQuery::try_from(user_config.clone())?;
log::trace!("Merging user preferences {user_query:?} with default retry strategy");
retry_order
.iter()
Expand Down Expand Up @@ -618,7 +628,7 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
custom_lists: &CustomListsSettings,
) -> Result<GetRelay, Error> {
match query.tunnel_protocol {
match query.tunnel_protocol() {
Constraint::Only(TunnelType::Wireguard) => {
Self::get_wireguard_relay(query, custom_lists, parsed_relays)
}
Expand All @@ -629,7 +639,9 @@ impl RelaySelector {
// Try Wireguard, then OpenVPN, then fail
for tunnel_type in [TunnelType::Wireguard, TunnelType::OpenVpn] {
let mut new_query = query.clone();
new_query.tunnel_protocol = Constraint::Only(tunnel_type);
new_query
.set_tunnel_protocol(Constraint::Only(tunnel_type))
.expect("unreachable since tunnel constraint is 'any', not wg");
// If a suitable relay is found, short-circuit and return it
if let Ok(relay) =
Self::get_relay_inner(&new_query, parsed_relays, custom_lists)
Expand All @@ -648,12 +660,12 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
custom_lists: &CustomListsSettings,
) -> Result<GetRelay, Error> {
// FIXME: A bit of defensive programming - calling `get_wiregurad_relay` with a query that
// FIXME: A bit of defensive programming - calling `get_wireguard_relay` with a query that
// doesn't specify Wireguard as the desired tunnel type is not valid and will lead
// to unwanted behavior. This should be seen as a workaround, and it would be nicer
// to lift this invariant to be checked by the type system instead.
let mut query = query.clone();
query.tunnel_protocol = Constraint::Only(TunnelType::Wireguard);
query.set_tunnel_protocol(Constraint::Only(TunnelType::Wireguard))?;
Self::get_wireguard_relay(&query, custom_lists, parsed_relays)
}

Expand All @@ -676,10 +688,10 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
) -> Result<GetRelay, Error> {
assert_eq!(
query.tunnel_protocol,
query.tunnel_protocol(),
Constraint::Only(TunnelType::Wireguard)
);
let inner = if !query.wireguard_constraints.multihop() {
let inner = if !query.wireguard_constraints().multihop() {
Self::get_wireguard_singlehop_config(query, custom_lists, parsed_relays)?
} else {
Self::get_wireguard_multihop_config(query, custom_lists, parsed_relays)?
Expand Down Expand Up @@ -729,13 +741,17 @@ impl RelaySelector {
// exception that the location is different. It is simply the location as dictated by
// the query's multihop constraint.
let mut entry_relay_query = query.clone();
entry_relay_query.location = query.wireguard_constraints.entry_location.clone();
entry_relay_query.set_location(query.wireguard_constraints().entry_location.clone())?;
// After we have our two queries (one for the exit relay & one for the entry relay),
// we can query for all exit & entry candidates! All candidates are needed for the next
// step.
let mut exit_relay_query = query.clone();
// DAITA should only be enabled for the entry relay
exit_relay_query.wireguard_constraints.daita = Constraint::Only(false);

let mut wg_constraints = exit_relay_query.wireguard_constraints().clone();
wg_constraints.daita = Constraint::Only(false);
exit_relay_query.set_wireguard_constraints(wg_constraints)?;

let exit_candidates =
filter_matching_relay_list(&exit_relay_query, parsed_relays, custom_lists);
let entry_candidates =
Expand Down Expand Up @@ -775,7 +791,7 @@ impl RelaySelector {
relay: &WireguardConfig,
) -> Result<MullvadWireguardEndpoint, Error> {
wireguard_endpoint(
&query.wireguard_constraints,
query.wireguard_constraints(),
&parsed_relays.parsed_list().wireguard,
relay,
)
Expand All @@ -797,7 +813,7 @@ impl RelaySelector {
};
let box_obfsucation_error = |error: helpers::Error| Error::NoObfuscator(Box::new(error));

match &query.wireguard_constraints.obfuscation {
match &query.wireguard_constraints().obfuscation {
ObfuscationQuery::Off | ObfuscationQuery::Auto => Ok(None),
ObfuscationQuery::Udp2tcp(settings) => {
let udp2tcp_ports = &parsed_relays.parsed_list().wireguard.udp2tcp_ports;
Expand Down Expand Up @@ -843,7 +859,10 @@ impl RelaySelector {
custom_lists: &CustomListsSettings,
parsed_relays: &ParsedRelays,
) -> Result<GetRelay, Error> {
assert_eq!(query.tunnel_protocol, Constraint::Only(TunnelType::OpenVpn));
assert_eq!(
query.tunnel_protocol(),
Constraint::Only(TunnelType::OpenVpn)
);
let exit =
Self::choose_openvpn_relay(query, custom_lists, parsed_relays).ok_or(Error::NoRelay)?;
let endpoint = Self::get_openvpn_endpoint(query, &exit, parsed_relays)?;
Expand Down Expand Up @@ -874,7 +893,7 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
) -> Result<Endpoint, Error> {
openvpn_endpoint(
&query.openvpn_constraints,
query.openvpn_constraints(),
&parsed_relays.parsed_list().openvpn,
relay,
)
Expand Down Expand Up @@ -908,10 +927,10 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
custom_lists: &CustomListsSettings,
) -> Result<Option<SelectedBridge>, Error> {
if !BridgeQuery::should_use_bridge(&query.openvpn_constraints.bridge_settings) {
if !BridgeQuery::should_use_bridge(&query.openvpn_constraints().bridge_settings) {
Ok(None)
} else {
let bridge_query = &query.openvpn_constraints.bridge_settings.clone().unwrap();
let bridge_query = &query.openvpn_constraints().bridge_settings.clone().unwrap();
let custom_lists = &custom_lists;
match protocol {
TransportProtocol::Udp => {
Expand Down Expand Up @@ -1033,7 +1052,7 @@ impl RelaySelector {
custom_lists: &CustomListsSettings,
) -> Option<Coordinates> {
use std::ops::Not;
if query.location.is_any() {
if query.location().is_any() {
return None;
}

Expand Down
Loading

0 comments on commit 5940861

Please sign in to comment.