summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-08-23 11:41:51 +0200
committerDavid Lönnhager <david.l@mullvad.net>2024-08-28 14:17:12 +0200
commit5940861458d273b7ff2879fcb0e5d4fc15e69d54 (patch)
tree93fc6ff02348b7d35f9a83acf5f96b95b6545862
parentf0b6d290d7c84fa69253a459d83396a74ecc6250 (diff)
downloadmullvadvpn-5940861458d273b7ff2879fcb0e5d4fc15e69d54.tar.xz
mullvadvpn-5940861458d273b7ff2879fcb0e5d4fc15e69d54.zip
Filter out OpenVPN relays when a core privacy feature is enabled
Core privacy features currently include PQ, multihop, and DAITA
-rw-r--r--mullvad-daemon/src/lib.rs1
-rw-r--r--mullvad-relay-selector/src/error.rs3
-rw-r--r--mullvad-relay-selector/src/relay_selector/matcher.rs12
-rw-r--r--mullvad-relay-selector/src/relay_selector/mod.rs79
-rw-r--r--mullvad-relay-selector/src/relay_selector/query.rs292
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs260
-rw-r--r--mullvad-types/src/wireguard.rs33
-rw-r--r--test/test-manager/src/tests/helpers.rs8
8 files changed, 482 insertions, 206 deletions
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index c52972c4e1..16a455ed79 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -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,
},
};
diff --git a/mullvad-relay-selector/src/error.rs b/mullvad-relay-selector/src/error.rs
index aefd931da6..dea6f81c3e 100644
--- a/mullvad-relay-selector/src/error.rs
+++ b/mullvad-relay-selector/src/error.rs
@@ -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,
diff --git a/mullvad-relay-selector/src/relay_selector/matcher.rs b/mullvad-relay-selector/src/relay_selector/matcher.rs
index 7f5607dfe5..21376e9485 100644
--- a/mullvad-relay-selector/src/relay_selector/matcher.rs
+++ b/mullvad-relay-selector/src/relay_selector/matcher.rs
@@ -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
diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs
index 6e82252f14..d7efa89b5f 100644
--- a/mullvad-relay-selector/src/relay_selector/mod.rs
+++ b/mullvad-relay-selector/src/relay_selector/mod.rs
@@ -28,6 +28,7 @@ use mullvad_types::{
},
relay_list::{Relay, RelayEndpointData, RelayList},
settings::Settings,
+ wireguard::QuantumResistantState,
CustomTunnelEndpoint, Intersection,
};
use talpid_types::{
@@ -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.
@@ -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 {
@@ -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,
@@ -338,7 +343,10 @@ 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,
@@ -346,6 +354,7 @@ impl<'a> From<NormalSelectorConfig<'a>> for RelayQuery {
entry_location,
obfuscation: ObfuscationQuery::from(obfuscation_settings),
daita: Constraint::Only(daita),
+ quantum_resistant,
}
}
@@ -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,
- }
+ )
}
}
@@ -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,
};
@@ -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()
@@ -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)
}
@@ -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)
@@ -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)
}
@@ -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)?
@@ -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 =
@@ -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,
)
@@ -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;
@@ -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)?;
@@ -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,
)
@@ -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 => {
@@ -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;
}
diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs
index 0faa9c1ca9..08e75d782a 100644
--- a/mullvad-relay-selector/src/relay_selector/query.rs
+++ b/mullvad-relay-selector/src/relay_selector/query.rs
@@ -28,7 +28,7 @@
//! queries and ensure that queries are built in a type-safe manner, reducing the risk
//! of runtime errors and improving code readability.
-use crate::AdditionalWireguardConstraints;
+use crate::{AdditionalWireguardConstraints, Error};
use mullvad_types::{
constraints::Constraint,
relay_constraints::{
@@ -36,6 +36,7 @@ use mullvad_types::{
Providers, RelayConstraints, RelaySettings, SelectedObfuscation, ShadowsocksSettings,
TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints,
},
+ wireguard::QuantumResistantState,
Intersection,
};
use talpid_types::net::{proxy::CustomProxy, IpVersion, TunnelType};
@@ -74,30 +75,127 @@ use talpid_types::net::{proxy::CustomProxy, IpVersion, TunnelType};
/// See [`builder`] for more info on how to construct queries.
#[derive(Debug, Clone, Eq, PartialEq, Intersection)]
pub struct RelayQuery {
- pub location: Constraint<LocationConstraint>,
- pub providers: Constraint<Providers>,
- pub ownership: Constraint<Ownership>,
- pub tunnel_protocol: Constraint<TunnelType>,
- pub wireguard_constraints: WireguardRelayQuery,
- pub openvpn_constraints: OpenVpnRelayQuery,
+ location: Constraint<LocationConstraint>,
+ providers: Constraint<Providers>,
+ ownership: Constraint<Ownership>,
+ tunnel_protocol: Constraint<TunnelType>,
+ wireguard_constraints: WireguardRelayQuery,
+ openvpn_constraints: OpenVpnRelayQuery,
}
impl RelayQuery {
+ /// Create a new [`RelayQuery`], and fail if the combination of constraints is invalid.
+ pub fn new(
+ location: Constraint<LocationConstraint>,
+ providers: Constraint<Providers>,
+ ownership: Constraint<Ownership>,
+ tunnel_protocol: Constraint<TunnelType>,
+ wireguard_constraints: WireguardRelayQuery,
+ openvpn_constraints: OpenVpnRelayQuery,
+ ) -> Result<RelayQuery, Error> {
+ let mut query = RelayQuery {
+ location,
+ providers,
+ ownership,
+ tunnel_protocol,
+ wireguard_constraints,
+ openvpn_constraints,
+ };
+ query.validate()?;
+ Ok(query)
+ }
+
+ fn validate(&mut self) -> Result<(), Error> {
+ if self.core_privacy_feature_enabled() {
+ if self.tunnel_protocol == Constraint::Only(TunnelType::OpenVpn) {
+ log::error!("Cannot use OpenVPN with a core privacy feature enabled (DAITA = {}, PQ = {}, or multihop = {})", self.wireguard_constraints.daita, self.wireguard_constraints.quantum_resistant, self.wireguard_constraints.multihop());
+ return Err(Error::InvalidConstraints);
+ }
+ self.tunnel_protocol = Constraint::Only(TunnelType::Wireguard);
+ }
+ Ok(())
+ }
+
+ fn core_privacy_feature_enabled(&self) -> bool {
+ self.wireguard_constraints.daita == Constraint::Only(true)
+ || self.wireguard_constraints.multihop()
+ || self.wireguard_constraints.quantum_resistant == QuantumResistantState::On
+ }
+
+ pub fn location(&self) -> &Constraint<LocationConstraint> {
+ &self.location
+ }
+
+ pub fn set_location(&mut self, location: Constraint<LocationConstraint>) -> Result<(), Error> {
+ self.set_if_valid(|query| query.location = location)
+ }
+
+ pub fn providers(&self) -> &Constraint<Providers> {
+ &self.providers
+ }
+
+ pub fn ownership(&self) -> Constraint<Ownership> {
+ self.ownership
+ }
+
+ pub fn tunnel_protocol(&self) -> Constraint<TunnelType> {
+ self.tunnel_protocol
+ }
+
+ pub fn set_tunnel_protocol(
+ &mut self,
+ tunnel_protocol: Constraint<TunnelType>,
+ ) -> Result<(), Error> {
+ self.set_if_valid(|query| query.tunnel_protocol = tunnel_protocol)
+ }
+
+ pub fn openvpn_constraints(&self) -> &OpenVpnRelayQuery {
+ &self.openvpn_constraints
+ }
+
+ pub fn set_openvpn_constraints(
+ &mut self,
+ openvpn_constraints: OpenVpnRelayQuery,
+ ) -> Result<(), Error> {
+ self.set_if_valid(|query| query.openvpn_constraints = openvpn_constraints)
+ }
+
+ pub fn wireguard_constraints(&self) -> &WireguardRelayQuery {
+ &self.wireguard_constraints
+ }
+
+ pub fn set_wireguard_constraints(
+ &mut self,
+ wireguard_constraints: WireguardRelayQuery,
+ ) -> Result<(), Error> {
+ self.set_if_valid(|query| query.wireguard_constraints = wireguard_constraints)
+ }
+
+ fn set_if_valid(&mut self, set_fn: impl FnOnce(&mut Self)) -> Result<(), Error> {
+ let mut new = self.clone();
+ (set_fn)(&mut new);
+ new.validate()?;
+ *self = new;
+
+ Ok(())
+ }
+}
+
+impl Default for RelayQuery {
/// Create a new [`RelayQuery`] with no opinionated defaults. This query matches every relay
- /// with any configuration by setting each of its fields to [`Constraint::Any`]. Should be the
- /// const equivalent to [`Default::default`].
+ /// with any configuration by setting each of its fields to [`Constraint::Any`].
///
/// Note that the following identity applies for any `other_query`:
/// ```rust
/// # use mullvad_relay_selector::query::RelayQuery;
/// # use mullvad_types::Intersection;
///
- /// # let other_query = RelayQuery::new();
- /// assert_eq!(RelayQuery::new().intersection(other_query.clone()), Some(other_query));
- /// # let other_query = RelayQuery::new();
- /// assert_eq!(other_query.clone().intersection(RelayQuery::new()), Some(other_query));
+ /// # let other_query = RelayQuery::default();
+ /// assert_eq!(RelayQuery::default().intersection(other_query.clone()), Some(other_query));
+ /// # let other_query = RelayQuery::default();
+ /// assert_eq!(other_query.clone().intersection(RelayQuery::default()), Some(other_query));
/// ```
- pub const fn new() -> RelayQuery {
+ fn default() -> Self {
RelayQuery {
location: Constraint::Any,
providers: Constraint::Any,
@@ -109,12 +207,6 @@ impl RelayQuery {
}
}
-impl Default for RelayQuery {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl From<RelayQuery> for RelayConstraints {
/// The mapping from [`RelayQuery`] to [`RelayConstraints`].
fn from(value: RelayQuery) -> Self {
@@ -152,6 +244,7 @@ pub struct WireguardRelayQuery {
pub entry_location: Constraint<LocationConstraint>,
pub obfuscation: ObfuscationQuery,
pub daita: Constraint<bool>,
+ pub quantum_resistant: QuantumResistantState,
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
@@ -211,6 +304,7 @@ impl WireguardRelayQuery {
entry_location: Constraint::Any,
obfuscation: ObfuscationQuery::Auto,
daita: Constraint::Any,
+ quantum_resistant: QuantumResistantState::Auto,
}
}
}
@@ -240,6 +334,7 @@ impl From<WireguardRelayQuery> for AdditionalWireguardConstraints {
daita: value
.daita
.unwrap_or(AdditionalWireguardConstraints::default().daita),
+ quantum_resistant: value.quantum_resistant,
}
}
}
@@ -347,6 +442,7 @@ pub mod builder {
BridgeConstraints, LocationConstraint, RelayConstraints, SelectedObfuscation,
ShadowsocksSettings, TransportPort, Udp2TcpObfuscationSettings,
},
+ wireguard::QuantumResistantState,
};
use talpid_types::net::TunnelType;
@@ -398,7 +494,8 @@ pub mod builder {
/// Assemble the final [`RelayQuery`] that has been configured
/// through `self`.
- pub fn build(self) -> RelayQuery {
+ pub fn build(mut self) -> RelayQuery {
+ debug_assert!(self.query.validate().is_ok());
self.query
}
@@ -414,18 +511,19 @@ pub mod builder {
/// which is used to guide the [`RelaySelector`]
///
/// [`RelaySelector`]: crate::RelaySelector
- pub const fn new() -> RelayQueryBuilder<Any> {
+ pub fn new() -> RelayQueryBuilder<Any> {
RelayQueryBuilder {
- query: RelayQuery::new(),
+ query: RelayQuery::default(),
protocol: Any,
}
}
/// Set the VPN protocol for this [`RelayQueryBuilder`] to Wireguard.
- pub fn wireguard(mut self) -> RelayQueryBuilder<Wireguard<Any, Any, Any>> {
+ pub fn wireguard(mut self) -> RelayQueryBuilder<Wireguard<Any, Any, Any, Any>> {
let protocol = Wireguard {
multihop: Any,
obfuscation: Any,
daita: Any,
+ quantum_resistant: Any,
};
self.query.tunnel_protocol = Constraint::Only(TunnelType::Wireguard);
// Update the type state
@@ -464,14 +562,17 @@ pub mod builder {
/// enabled, the builder should expose an option to select entry point.
///
/// [`WireguardRelayQuery`]: super::WireguardRelayQuery
- pub struct Wireguard<Multihop, Obfuscation, Daita> {
+ pub struct Wireguard<Multihop, Obfuscation, Daita, QuantumResistant> {
multihop: Multihop,
obfuscation: Obfuscation,
daita: Daita,
+ quantum_resistant: QuantumResistant,
}
// This impl-block is quantified over all configurations
- impl<Multihop, Obfuscation, Daita> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Daita>> {
+ impl<Multihop, Obfuscation, Daita, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Daita, QuantumResistant>>
+ {
/// Specify the port to ues when connecting to the selected
/// Wireguard relay.
pub const fn port(mut self, port: u16) -> Self {
@@ -487,9 +588,13 @@ pub mod builder {
}
}
- impl<Multihop, Obfuscation> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Any>> {
+ impl<Multihop, Obfuscation, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Any, QuantumResistant>>
+ {
/// Enable DAITA support.
- pub fn daita(mut self) -> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, bool>> {
+ pub fn daita(
+ mut self,
+ ) -> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, bool, QuantumResistant>> {
self.query.wireguard_constraints.daita = Constraint::Only(true);
// Update the type state
RelayQueryBuilder {
@@ -498,16 +603,40 @@ pub mod builder {
multihop: self.protocol.multihop,
obfuscation: self.protocol.obfuscation,
daita: true,
+ quantum_resistant: self.protocol.quantum_resistant,
},
}
}
}
- impl<Obfuscation, Daita> RelayQueryBuilder<Wireguard<Any, Obfuscation, Daita>> {
+ impl<Multihop, Obfuscation, Daita> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Daita, Any>> {
+ /// Enable PQ support.
+ pub fn quantum_resistant(
+ mut self,
+ ) -> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Daita, bool>> {
+ self.query.wireguard_constraints.quantum_resistant = QuantumResistantState::On;
+ // Update the type state
+ RelayQueryBuilder {
+ query: self.query,
+ protocol: Wireguard {
+ multihop: self.protocol.multihop,
+ obfuscation: self.protocol.obfuscation,
+ daita: self.protocol.daita,
+ quantum_resistant: true,
+ },
+ }
+ }
+ }
+
+ impl<Obfuscation, Daita, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Any, Obfuscation, Daita, QuantumResistant>>
+ {
/// Enable multihop.
///
/// To configure the entry relay, see [`RelayQueryBuilder::entry`].
- pub fn multihop(mut self) -> RelayQueryBuilder<Wireguard<bool, Obfuscation, Daita>> {
+ pub fn multihop(
+ mut self,
+ ) -> RelayQueryBuilder<Wireguard<bool, Obfuscation, Daita, QuantumResistant>> {
self.query.wireguard_constraints.use_multihop = Constraint::Only(true);
// Update the type state
RelayQueryBuilder {
@@ -516,12 +645,15 @@ pub mod builder {
multihop: true,
obfuscation: self.protocol.obfuscation,
daita: self.protocol.daita,
+ quantum_resistant: self.protocol.quantum_resistant,
},
}
}
}
- impl<Obfuscation, Daita> RelayQueryBuilder<Wireguard<bool, Obfuscation, Daita>> {
+ impl<Obfuscation, Daita, QuantumResistant>
+ RelayQueryBuilder<Wireguard<bool, Obfuscation, Daita, QuantumResistant>>
+ {
/// Set the entry location in a multihop configuration. This requires
/// multihop to be enabled.
pub fn entry(mut self, location: GeographicLocationConstraint) -> Self {
@@ -531,12 +663,16 @@ pub mod builder {
}
}
- impl<Multihop, Daita> RelayQueryBuilder<Wireguard<Multihop, Any, Daita>> {
+ impl<Multihop, Daita, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Multihop, Any, Daita, QuantumResistant>>
+ {
/// Enable `UDP2TCP` obufscation. This will in turn enable the option to configure the
/// `UDP2TCP` port.
pub fn udp2tcp(
mut self,
- ) -> RelayQueryBuilder<Wireguard<Multihop, Udp2TcpObfuscationSettings, Daita>> {
+ ) -> RelayQueryBuilder<
+ Wireguard<Multihop, Udp2TcpObfuscationSettings, Daita, QuantumResistant>,
+ > {
let obfuscation = Udp2TcpObfuscationSettings {
port: Constraint::Any,
};
@@ -544,6 +680,7 @@ pub mod builder {
multihop: self.protocol.multihop,
obfuscation: obfuscation.clone(),
daita: self.protocol.daita,
+ quantum_resistant: self.protocol.quantum_resistant,
};
self.query.wireguard_constraints.obfuscation = ObfuscationQuery::Udp2tcp(obfuscation);
RelayQueryBuilder {
@@ -556,7 +693,8 @@ pub mod builder {
/// port.
pub fn shadowsocks(
mut self,
- ) -> RelayQueryBuilder<Wireguard<Multihop, ShadowsocksSettings, Daita>> {
+ ) -> RelayQueryBuilder<Wireguard<Multihop, ShadowsocksSettings, Daita, QuantumResistant>>
+ {
let obfuscation = ShadowsocksSettings {
port: Constraint::Any,
};
@@ -564,6 +702,7 @@ pub mod builder {
multihop: self.protocol.multihop,
obfuscation: obfuscation.clone(),
daita: self.protocol.daita,
+ quantum_resistant: self.protocol.quantum_resistant,
};
self.query.wireguard_constraints.obfuscation =
ObfuscationQuery::Shadowsocks(obfuscation);
@@ -574,7 +713,9 @@ pub mod builder {
}
}
- impl<Multihop, Daita> RelayQueryBuilder<Wireguard<Multihop, Udp2TcpObfuscationSettings, Daita>> {
+ impl<Multihop, Daita, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Multihop, Udp2TcpObfuscationSettings, Daita, QuantumResistant>>
+ {
/// Set the `UDP2TCP` port. This is the TCP port which the `UDP2TCP` obfuscation
/// protocol should use to connect to a relay.
pub fn udp2tcp_port(mut self, port: u16) -> Self {
@@ -707,8 +848,9 @@ mod test {
},
};
use proptest::prelude::*;
+ use talpid_types::net::TunnelType;
- use super::{Intersection, ObfuscationQuery};
+ use super::{builder::RelayQueryBuilder, Intersection, ObfuscationQuery, RelayQuery};
// Define proptest combinators for the `Constraint` type.
@@ -802,4 +944,82 @@ mod test {
assert_eq!(query, ObfuscationQuery::Auto);
}
}
+
+ /// Test whether the default relay query is valid
+ #[test]
+ fn test_relay_query_default_valid() {
+ RelayQuery::default().validate().unwrap();
+ }
+
+ /// OpenVPN queries with DAITA enabled are invalid
+ /// DAITA is a core privacy feature.
+ #[test]
+ fn test_relay_query_daita_openvpn() {
+ let mut query = RelayQueryBuilder::new().wireguard().daita().build();
+ query
+ .set_tunnel_protocol(Constraint::Only(TunnelType::OpenVpn))
+ .expect_err("expected query to be invalid for OpenVPN");
+ }
+
+ /// OpenVPN queries with multihop enabled are invalid
+ /// Multihop is a core privacy feature.
+ #[test]
+ fn test_relay_query_multihop_openvpn() {
+ let mut query = RelayQueryBuilder::new().wireguard().multihop().build();
+ query
+ .set_tunnel_protocol(Constraint::Only(TunnelType::OpenVpn))
+ .expect_err("expected query to be invalid for OpenVPN");
+ }
+
+ /// OpenVPN queries with PQ enabled are invalid
+ /// PQ is a core privacy feature.
+ #[test]
+ fn test_relay_query_quantum_resistant_openvpn() {
+ let mut query = RelayQueryBuilder::new()
+ .wireguard()
+ .quantum_resistant()
+ .build();
+ query
+ .set_tunnel_protocol(Constraint::Only(TunnelType::OpenVpn))
+ .expect_err("expected query to be invalid for OpenVPN");
+ }
+
+ /// The tunnel protocol should be constrained to Wireguard when enabling DAITA
+ /// DAITA is a core privacy feature
+ #[test]
+ fn test_relay_query_daita_wireguard() {
+ let mut query = RelayQueryBuilder::new().wireguard().daita().build();
+ query.set_tunnel_protocol(Constraint::Any).unwrap();
+ assert_eq!(
+ query.tunnel_protocol(),
+ Constraint::Only(TunnelType::Wireguard)
+ );
+ }
+
+ /// The tunnel protocol should be constrained to Wireguard when enabling multihop
+ /// Multihop is a core privacy feature
+ #[test]
+ fn test_relay_query_multihop_wireguard() {
+ let mut query = RelayQueryBuilder::new().wireguard().multihop().build();
+ query.set_tunnel_protocol(Constraint::Any).unwrap();
+ assert_eq!(
+ query.tunnel_protocol(),
+ Constraint::Only(TunnelType::Wireguard)
+ );
+ }
+
+ /// The tunnel protocol should be constrained to Wireguard when enabling PQ
+ /// PQ is a core privacy feature
+ #[test]
+ fn test_relay_query_quantum_resistant_wireguard() {
+ let mut query = RelayQueryBuilder::new()
+ .wireguard()
+ .quantum_resistant()
+ .build();
+ query.set_tunnel_protocol(Constraint::Any).unwrap();
+ assert_eq!(
+ query.tunnel_protocol(),
+ Constraint::Only(TunnelType::Wireguard)
+ );
+ }
}
diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs
index 9e7eb9c31c..51a395f6d5 100644
--- a/mullvad-relay-selector/tests/relay_selector.rs
+++ b/mullvad-relay-selector/tests/relay_selector.rs
@@ -22,8 +22,8 @@ use mullvad_types::{
constraints::Constraint,
endpoint::MullvadEndpoint,
relay_constraints::{
- BridgeConstraints, BridgeState, GeographicLocationConstraint, LocationConstraint,
- Ownership, Providers, RelayOverride, TransportPort,
+ BridgeConstraints, BridgeState, GeographicLocationConstraint, Ownership, Providers,
+ RelayOverride, TransportPort,
},
relay_list::{
BridgeEndpointData, OpenVpnEndpoint, OpenVpnEndpointData, Relay, RelayEndpointData,
@@ -59,7 +59,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
"BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
)
.unwrap(),
- daita: false,
+ daita: true,
shadowsocks_extra_addr_in: vec![],
}),
location: None,
@@ -184,6 +184,11 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
},
});
+static DAITA_RELAY_LOCATION: LazyLock<GeographicLocationConstraint> =
+ LazyLock::new(|| GeographicLocationConstraint::hostname("se", "got", "se9-wireguard"));
+static NON_DAITA_RELAY_LOCATION: LazyLock<GeographicLocationConstraint> =
+ LazyLock::new(|| GeographicLocationConstraint::hostname("se", "got", "se10-wireguard"));
+
/// A Shadowsocks relay with additional addresses
static SHADOWSOCKS_RELAY: LazyLock<Relay> = LazyLock::new(|| Relay {
hostname: SHADOWSOCKS_RELAY_LOCATION
@@ -340,7 +345,7 @@ fn test_retry_order() {
let tunnel_type = tunnel_type(&unwrap_relay(relay.clone()));
assert_eq!(
tunnel_type,
- query.tunnel_protocol.unwrap_or(TunnelType::Wireguard),
+ query.tunnel_protocol().unwrap_or(TunnelType::Wireguard),
"Retry attempt {retry_attempt} yielded an unexpected tunnel type"
);
// Then perform some protocol-specific probing as well.
@@ -351,17 +356,17 @@ fn test_retry_order() {
..
} => {
assert!(query
- .wireguard_constraints
+ .wireguard_constraints()
.ip_version
.matches_eq(&match endpoint.peer.endpoint.ip() {
std::net::IpAddr::V4(_) => talpid_types::net::IpVersion::V4,
std::net::IpAddr::V6(_) => talpid_types::net::IpVersion::V6,
}));
assert!(query
- .wireguard_constraints
+ .wireguard_constraints()
.port
.matches_eq(&endpoint.peer.endpoint.port()));
- assert!(match &query.wireguard_constraints.obfuscation {
+ assert!(match &query.wireguard_constraints().obfuscation {
ObfuscationQuery::Auto => true,
ObfuscationQuery::Off => obfuscator.is_none(),
ObfuscationQuery::Udp2tcp(_) | ObfuscationQuery::Shadowsocks(_) =>
@@ -371,25 +376,25 @@ fn test_retry_order() {
GetRelay::OpenVpn {
endpoint, bridge, ..
} => {
- if BridgeQuery::should_use_bridge(&query.openvpn_constraints.bridge_settings) {
+ if BridgeQuery::should_use_bridge(&query.openvpn_constraints().bridge_settings) {
assert!(bridge.is_some(), "Relay selector should have selected a bridge for query {query:?}, but bridge was `None`");
};
assert!(query
- .openvpn_constraints
+ .openvpn_constraints()
.port
.map(|transport_port| transport_port.port.matches_eq(&endpoint.address.port()))
.unwrap_or(true),
"The query {query:?} defined a port to use, but the chosen relay endpoint did not match that port number.
Expected: {expected}
Actual: {actual}",
- expected = query.openvpn_constraints.port.unwrap().port.unwrap(), actual = endpoint.address.port()
+ expected = query.openvpn_constraints().port.unwrap().port.unwrap(), actual = endpoint.address.port()
);
- assert!(query.openvpn_constraints.port.map(|transport_port| transport_port.protocol == endpoint.protocol).unwrap_or(true),
+ assert!(query.openvpn_constraints().port.map(|transport_port| transport_port.protocol == endpoint.protocol).unwrap_or(true),
"The query {query:?} defined a transport protocol to use, but the chosen relay endpoint did not match that transport protocol.
Expected: {expected}
Actual: {actual}",
- expected = query.openvpn_constraints.port.unwrap().protocol, actual = endpoint.protocol
+ expected = query.openvpn_constraints().port.unwrap().protocol, actual = endpoint.protocol
);
}
GetRelay::Custom(_) => unreachable!(),
@@ -720,7 +725,7 @@ fn test_openvpn_constraints() {
} else {
match relay.expect("Expected to find a relay") {
GetRelay::OpenVpn { endpoint, .. } => {
- matches_constraints(endpoint, &query.openvpn_constraints);
+ matches_constraints(endpoint, query.openvpn_constraints());
},
wrong_relay => panic!("Relay selector should have picked an OpenVPN relay, instead chose {wrong_relay:?}")
};
@@ -754,7 +759,7 @@ fn test_selecting_wireguard_location_will_consider_multihop() {
fn test_selecting_any_relay_will_consider_multihop() {
let relay_selector = default_relay_selector();
let mut query = RelayQueryBuilder::new().wireguard().multihop().build();
- query.tunnel_protocol = Constraint::Any;
+ query.set_tunnel_protocol(Constraint::Any).unwrap();
for _ in 0..100 {
let relay = relay_selector.get_relay_by_query(query.clone()).unwrap();
@@ -770,8 +775,8 @@ fn test_selecting_any_relay_will_consider_multihop() {
fn test_selecting_wireguard_over_shadowsocks() {
let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
- let mut query = RelayQueryBuilder::new().wireguard().shadowsocks().build();
- query.wireguard_constraints.use_multihop = Constraint::Only(false);
+ let query = RelayQueryBuilder::new().wireguard().shadowsocks().build();
+ assert!(!query.wireguard_constraints().multihop());
let relay = relay_selector.get_relay_by_query(query).unwrap();
match relay {
@@ -796,11 +801,12 @@ fn test_selecting_wireguard_over_shadowsocks() {
fn test_selecting_wireguard_over_shadowsocks_extra_ips() {
let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
- let mut query = RelayQueryBuilder::new().wireguard().shadowsocks().build();
- query.wireguard_constraints.use_multihop = Constraint::Only(false);
- query.location = Constraint::Only(LocationConstraint::Location(
- SHADOWSOCKS_RELAY_LOCATION.clone(),
- ));
+ let query = RelayQueryBuilder::new()
+ .location(SHADOWSOCKS_RELAY_LOCATION.clone())
+ .wireguard()
+ .shadowsocks()
+ .build();
+ assert!(!query.wireguard_constraints().multihop());
let relay = relay_selector.get_relay_by_query(query).unwrap();
match relay {
@@ -838,12 +844,13 @@ fn test_selecting_wireguard_ignore_extra_ips_override_v4() {
let relay_selector = RelaySelector::from_list(config, RELAYS.clone());
- let mut query_v4 = RelayQueryBuilder::new().wireguard().shadowsocks().build();
- query_v4.wireguard_constraints.use_multihop = Constraint::Only(false);
- query_v4.location = Constraint::Only(LocationConstraint::Location(
- SHADOWSOCKS_RELAY_LOCATION.clone(),
- ));
- query_v4.wireguard_constraints.ip_version = Constraint::Only(IpVersion::V4);
+ let query_v4 = RelayQueryBuilder::new()
+ .location(SHADOWSOCKS_RELAY_LOCATION.clone())
+ .wireguard()
+ .ip_version(IpVersion::V4)
+ .shadowsocks()
+ .build();
+ assert!(!query_v4.wireguard_constraints().multihop());
let relay = relay_selector.get_relay_by_query(query_v4).unwrap();
match relay {
@@ -881,12 +888,13 @@ fn test_selecting_wireguard_ignore_extra_ips_override_v6() {
let relay_selector = RelaySelector::from_list(config, RELAYS.clone());
- let mut query_v6 = RelayQueryBuilder::new().wireguard().shadowsocks().build();
- query_v6.wireguard_constraints.use_multihop = Constraint::Only(false);
- query_v6.location = Constraint::Only(LocationConstraint::Location(
- SHADOWSOCKS_RELAY_LOCATION.clone(),
- ));
- query_v6.wireguard_constraints.ip_version = Constraint::Only(IpVersion::V6);
+ let query_v6 = RelayQueryBuilder::new()
+ .location(SHADOWSOCKS_RELAY_LOCATION.clone())
+ .wireguard()
+ .ip_version(IpVersion::V6)
+ .shadowsocks()
+ .build();
+ assert!(!query_v6.wireguard_constraints().multihop());
let relay = relay_selector.get_relay_by_query(query_v6).unwrap();
match relay {
@@ -911,8 +919,8 @@ fn test_selecting_wireguard_ignore_extra_ips_override_v6() {
#[test]
fn test_selecting_wireguard_endpoint_with_udp2tcp_obfuscation() {
let relay_selector = default_relay_selector();
- let mut query = RelayQueryBuilder::new().wireguard().udp2tcp().build();
- query.wireguard_constraints.use_multihop = Constraint::Only(false);
+ let query = RelayQueryBuilder::new().wireguard().udp2tcp().build();
+ assert!(!query.wireguard_constraints().multihop());
let relay = relay_selector.get_relay_by_query(query).unwrap();
match relay {
@@ -941,8 +949,11 @@ fn test_selecting_wireguard_endpoint_with_udp2tcp_obfuscation() {
fn test_selecting_wireguard_endpoint_with_auto_obfuscation() {
let relay_selector = default_relay_selector();
- let mut query = RelayQueryBuilder::new().wireguard().build();
- query.wireguard_constraints.obfuscation = ObfuscationQuery::Auto;
+ let query = RelayQueryBuilder::new().wireguard().build();
+ assert_eq!(
+ query.wireguard_constraints().obfuscation,
+ ObfuscationQuery::Auto
+ );
for _ in 0..100 {
let relay = relay_selector.get_relay_by_query(query.clone()).unwrap();
@@ -1047,12 +1058,12 @@ fn test_load_balancing() {
assert!(
ports.len() > 1,
"expected more than 1 port, got {ports:?}, for tunnel protocol {tunnel_protocol:?}",
- tunnel_protocol = query.tunnel_protocol,
+ tunnel_protocol = query.tunnel_protocol(),
);
assert!(
ips.len() > 1,
"expected more than 1 server, got {ips:?}, for tunnel protocol {tunnel_protocol:?}",
- tunnel_protocol = query.tunnel_protocol,
+ tunnel_protocol = query.tunnel_protocol(),
);
}
}
@@ -1110,7 +1121,7 @@ fn test_openvpn_auto_bridge() {
.unwrap();
match relay {
GetRelay::OpenVpn { bridge, .. } => {
- if BridgeQuery::should_use_bridge(&query.openvpn_constraints.bridge_settings) {
+ if BridgeQuery::should_use_bridge(&query.openvpn_constraints().bridge_settings) {
assert!(bridge.is_some())
} else {
assert!(bridge.is_none())
@@ -1279,16 +1290,25 @@ fn openvpn_handle_bridge_settings() {
}
// Tweaking the query just slightly to try to enable bridge mode, while sill using UDP,
// should fail.
- query.openvpn_constraints.bridge_settings =
- Constraint::Only(BridgeQuery::Normal(BridgeConstraints::default()));
+ query
+ .set_openvpn_constraints(OpenVpnRelayQuery {
+ bridge_settings: Constraint::Only(BridgeQuery::Normal(BridgeConstraints::default())),
+ ..query.openvpn_constraints().clone()
+ })
+ .unwrap();
let relay = relay_selector.get_relay_by_query(query.clone());
assert!(relay.is_err());
// Correcting the query to use TCP, the relay selector should yield a valid relay + bridge
- query.openvpn_constraints.port = Constraint::Only(TransportPort {
- protocol: Tcp,
- port: Constraint::default(),
- });
+ query
+ .set_openvpn_constraints(OpenVpnRelayQuery {
+ port: Constraint::Only(TransportPort {
+ protocol: Tcp,
+ port: Constraint::default(),
+ }),
+ ..query.openvpn_constraints().clone()
+ })
+ .unwrap();
let relay = relay_selector.get_relay_by_query(query.clone()).unwrap();
match relay {
GetRelay::OpenVpn {
@@ -1320,7 +1340,12 @@ fn openvpn_bridge_with_automatic_transport_protocol() {
let mut query = RelayQueryBuilder::new().openvpn().bridge().build();
// Forcefully modify the transport protocol, as the builder will ensure that the transport
// protocol is set to TCP.
- query.openvpn_constraints.port = Constraint::Any;
+ query
+ .set_openvpn_constraints(OpenVpnRelayQuery {
+ port: Constraint::Any,
+ ..query.openvpn_constraints().clone()
+ })
+ .unwrap();
for _ in 0..100 {
let relay = relay_selector.get_relay_by_query(query.clone()).unwrap();
@@ -1348,86 +1373,69 @@ fn openvpn_bridge_with_automatic_transport_protocol() {
}
}
+/// Always select a WireGuard relay when DAITA is enabled
+/// DAITA is a core privacy feature
+#[test]
+fn test_daita_any_tunnel_protocol() {
+ let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
+ let mut query = RelayQueryBuilder::new().wireguard().daita().build();
+ query
+ .set_tunnel_protocol(Constraint::Any)
+ .expect("expected query to be valid for any tunnel protocol");
+
+ let relay = relay_selector.get_relay_by_query(query);
+
+ assert!(
+ matches!(relay, Ok(GetRelay::Wireguard { .. })),
+ "expected wg relay, got {relay:?}"
+ );
+}
+
+/// Always select a WireGuard relay when multihop is enabled
+/// Multihop is a core privacy feature
+#[test]
+fn test_multihop_any_tunnel_protocol() {
+ let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
+ let mut query = RelayQueryBuilder::new().wireguard().multihop().build();
+ query
+ .set_tunnel_protocol(Constraint::Any)
+ .expect("expected query to be valid for any tunnel protocol");
+
+ let relay = relay_selector.get_relay_by_query(query);
+
+ assert!(
+ matches!(relay, Ok(GetRelay::Wireguard { .. })),
+ "expected wg relay, got {relay:?}"
+ );
+}
+
+/// Always select a WireGuard relay when quantum resistance is enabled
+/// PQ is a core privacy feature
+#[test]
+fn test_quantum_resistant_any_tunnel_protocol() {
+ let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
+ let mut query = RelayQueryBuilder::new()
+ .wireguard()
+ .quantum_resistant()
+ .build();
+ query
+ .set_tunnel_protocol(Constraint::Any)
+ .expect("expected query to be valid for any tunnel protocol");
+
+ let relay = relay_selector.get_relay_by_query(query);
+
+ assert!(
+ matches!(relay, Ok(GetRelay::Wireguard { .. })),
+ "expected wg relay, got {relay:?}"
+ );
+}
+
/// Return only entry relays that support DAITA when DAITA filtering is enabled. All relays that
/// support DAITA also support NOT DAITA. Thus, disabling it should not cause any WireGuard relays
/// to be filtered out.
#[test]
fn test_daita() {
- let relay_list = RelayList {
- etag: None,
- countries: vec![RelayListCountry {
- name: "Sweden".to_string(),
- code: "se".to_string(),
- cities: vec![RelayListCity {
- name: "Gothenburg".to_string(),
- code: "got".to_string(),
- latitude: 57.70887,
- longitude: 11.97456,
- relays: vec![
- Relay {
- hostname: "se9-wireguard".to_string(),
- ipv4_addr_in: "185.213.154.68".parse().unwrap(),
- ipv6_addr_in: Some("2a03:1b20:5:f011::a09f".parse().unwrap()),
- overridden_ipv4: false,
- overridden_ipv6: false,
- include_in_country: true,
- active: true,
- owned: true,
- provider: "31173".to_string(),
- weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- shadowsocks_extra_addr_in: vec![],
- daita: false,
- }),
- location: None,
- },
- Relay {
- hostname: "se10-wireguard".to_string(),
- ipv4_addr_in: "185.213.154.69".parse().unwrap(),
- ipv6_addr_in: Some("2a03:1b20:5:f011::a10f".parse().unwrap()),
- overridden_ipv4: false,
- overridden_ipv6: false,
- include_in_country: true,
- active: true,
- owned: false,
- provider: "31173".to_string(),
- weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- shadowsocks_extra_addr_in: vec![],
- daita: true,
- }),
- location: None,
- },
- ],
- }],
- }],
- openvpn: OpenVpnEndpointData { ports: vec![] },
- bridge: BridgeEndpointData {
- shadowsocks: vec![],
- },
- wireguard: WireguardEndpointData {
- port_ranges: vec![53..=53, 4000..=33433, 33565..=51820, 52000..=60000],
- ipv4_gateway: "10.64.0.1".parse().unwrap(),
- ipv6_gateway: "fc00:bbbb:bbbb:bb01::1".parse().unwrap(),
- shadowsocks_port_ranges: vec![],
- udp2tcp_ports: vec![],
- },
- };
-
- let daita_supporting_relay =
- GeographicLocationConstraint::hostname("se", "got", "se10-wireguard");
- let nondaita_supporting_relay =
- GeographicLocationConstraint::hostname("se", "got", "se9-wireguard");
-
- let relay_selector = RelaySelector::from_list(SelectorConfig::default(), relay_list);
+ let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
// Only pick relays that support DAITA
let query = RelayQueryBuilder::new().wireguard().daita().build();
@@ -1441,7 +1449,7 @@ fn test_daita() {
let query = RelayQueryBuilder::new()
.wireguard()
.daita()
- .location(nondaita_supporting_relay.clone())
+ .location(NON_DAITA_RELAY_LOCATION.clone())
.build();
relay_selector
.get_relay_by_query(query)
@@ -1450,7 +1458,7 @@ fn test_daita() {
// DAITA-supporting relays can be picked even when it is disabled
let query = RelayQueryBuilder::new()
.wireguard()
- .location(daita_supporting_relay.clone())
+ .location(DAITA_RELAY_LOCATION.clone())
.build();
relay_selector
.get_relay_by_query(query)
@@ -1459,7 +1467,7 @@ fn test_daita() {
// Non DAITA-supporting relays can be picked when it is disabled
let query = RelayQueryBuilder::new()
.wireguard()
- .location(nondaita_supporting_relay.clone())
+ .location(NON_DAITA_RELAY_LOCATION.clone())
.build();
relay_selector
.get_relay_by_query(query)
@@ -1489,7 +1497,7 @@ fn test_daita() {
.wireguard()
.daita()
.multihop()
- .location(nondaita_supporting_relay)
+ .location(NON_DAITA_RELAY_LOCATION.clone())
.build();
let relay = relay_selector.get_relay_by_query(query).unwrap();
match relay {
diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs
index 1af50abadc..c920e9f0af 100644
--- a/mullvad-types/src/wireguard.rs
+++ b/mullvad-types/src/wireguard.rs
@@ -4,6 +4,8 @@ use serde::{Deserialize, Deserializer, Serialize};
use std::{fmt, str::FromStr, time::Duration};
use talpid_types::net::wireguard;
+use crate::Intersection;
+
pub const MIN_ROTATION_INTERVAL: Duration = Duration::from_secs(1 * 24 * 60 * 60);
pub const MAX_ROTATION_INTERVAL: Duration = Duration::from_secs(30 * 24 * 60 * 60);
pub const DEFAULT_ROTATION_INTERVAL: Duration = MAX_ROTATION_INTERVAL;
@@ -13,15 +15,38 @@ pub const DEFAULT_ROTATION_INTERVAL: Duration = MAX_ROTATION_INTERVAL;
/// but disabled on all other platforms.
const QUANTUM_RESISTANT_AUTO_STATE: bool = cfg!(any(target_os = "linux", target_os = "macos"));
-#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Default, Copy, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum QuantumResistantState {
+ #[default]
Auto,
On,
Off,
}
+impl QuantumResistantState {
+ pub fn enabled(&self) -> bool {
+ match self {
+ QuantumResistantState::Auto => QUANTUM_RESISTANT_AUTO_STATE,
+ QuantumResistantState::Off => false,
+ QuantumResistantState::On => true,
+ }
+ }
+}
+
+impl Intersection for QuantumResistantState {
+ fn intersection(self, other: Self) -> Option<Self> {
+ match (self, other) {
+ (QuantumResistantState::Auto, other) | (other, QuantumResistantState::Auto) => {
+ Some(other)
+ }
+ (val0, val1) if val0 == val1 => Some(val0),
+ _ => None,
+ }
+ }
+}
+
impl fmt::Display for QuantumResistantState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -218,11 +243,7 @@ impl TunnelOptions {
pub fn into_talpid_tunnel_options(self) -> wireguard::TunnelOptions {
wireguard::TunnelOptions {
mtu: self.mtu,
- quantum_resistant: match self.quantum_resistant {
- QuantumResistantState::Auto => QUANTUM_RESISTANT_AUTO_STATE,
- QuantumResistantState::On => true,
- QuantumResistantState::Off => false,
- },
+ quantum_resistant: self.quantum_resistant.enabled(),
#[cfg(daita)]
daita: self.daita.enabled,
}
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs
index 24d7e33bab..4c9da00a16 100644
--- a/test/test-manager/src/tests/helpers.rs
+++ b/test/test-manager/src/tests/helpers.rs
@@ -700,8 +700,12 @@ pub async fn constrain_to_relay(
let location = into_constraint(&exit)?;
let relay_constraints = RelayConstraints {
location,
- wireguard_constraints: WireguardConstraints::from(query.wireguard_constraints),
- openvpn_constraints: OpenVpnConstraints::from(query.openvpn_constraints),
+ wireguard_constraints: WireguardConstraints::from(
+ query.wireguard_constraints().clone(),
+ ),
+ openvpn_constraints: OpenVpnConstraints::from(
+ query.openvpn_constraints().clone(),
+ ),
..Default::default()
};
Ok((exit, relay_constraints))