From 5940861458d273b7ff2879fcb0e5d4fc15e69d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Fri, 23 Aug 2024 11:41:51 +0200 Subject: [PATCH] Filter out OpenVPN relays when a core privacy feature is enabled Core privacy features currently include PQ, multihop, and DAITA --- mullvad-daemon/src/lib.rs | 1 + mullvad-relay-selector/src/error.rs | 3 + .../src/relay_selector/matcher.rs | 12 +- .../src/relay_selector/mod.rs | 79 +++-- .../src/relay_selector/query.rs | 292 +++++++++++++++--- .../tests/relay_selector.rs | 260 ++++++++-------- mullvad-types/src/wireguard.rs | 33 +- test/test-manager/src/tests/helpers.rs | 8 +- 8 files changed, 482 insertions(+), 206 deletions(-) diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index c52972c4e12e..16a455ed799a 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 aefd931da633..dea6f81c3ee9 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 7f5607dfe50c..21376e9485d4 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 { 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 6e82252f14c6..d7efa89b5f39 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> for RelayQuery { +impl<'a> TryFrom> for RelayQuery { + type Error = crate::Error; + /// Map user settings to [`RelayQuery`]. - fn from(value: NormalSelectorConfig<'a>) -> Self { + fn try_from(value: NormalSelectorConfig<'a>) -> Result { /// Map the Wireguard-specific bits of `value` to [`WireguradRelayQuery`] fn wireguard_constraints( wireguard_constraints: WireguardConstraints, @@ -338,7 +343,10 @@ impl<'a> From> 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> for RelayQuery { entry_location, obfuscation: ObfuscationQuery::from(obfuscation_settings), daita: Constraint::Only(daita), + quantum_resistant, } } @@ -382,14 +391,14 @@ impl<'a> From> 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 { - 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 { - 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 { - // 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 { 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 { 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 { - 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 { 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, 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 { 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 0faa9c1ca951..08e75d782a92 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, - pub providers: Constraint, - pub ownership: Constraint, - pub tunnel_protocol: Constraint, - pub wireguard_constraints: WireguardRelayQuery, - pub openvpn_constraints: OpenVpnRelayQuery, + location: Constraint, + providers: Constraint, + ownership: Constraint, + tunnel_protocol: Constraint, + 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, + providers: Constraint, + ownership: Constraint, + tunnel_protocol: Constraint, + wireguard_constraints: WireguardRelayQuery, + openvpn_constraints: OpenVpnRelayQuery, + ) -> Result { + 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 { + &self.location + } + + pub fn set_location(&mut self, location: Constraint) -> Result<(), Error> { + self.set_if_valid(|query| query.location = location) + } + + pub fn providers(&self) -> &Constraint { + &self.providers + } + + pub fn ownership(&self) -> Constraint { + self.ownership + } + + pub fn tunnel_protocol(&self) -> Constraint { + self.tunnel_protocol + } + + pub fn set_tunnel_protocol( + &mut self, + tunnel_protocol: Constraint, + ) -> 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 for RelayConstraints { /// The mapping from [`RelayQuery`] to [`RelayConstraints`]. fn from(value: RelayQuery) -> Self { @@ -152,6 +244,7 @@ pub struct WireguardRelayQuery { pub entry_location: Constraint, pub obfuscation: ObfuscationQuery, pub daita: Constraint, + 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 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 { + pub fn new() -> RelayQueryBuilder { RelayQueryBuilder { - query: RelayQuery::new(), + query: RelayQuery::default(), protocol: Any, } } /// Set the VPN protocol for this [`RelayQueryBuilder`] to Wireguard. - pub fn wireguard(mut self) -> RelayQueryBuilder> { + pub fn wireguard(mut self) -> RelayQueryBuilder> { 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 { + pub struct Wireguard { multihop: Multihop, obfuscation: Obfuscation, daita: Daita, + quantum_resistant: QuantumResistant, } // This impl-block is quantified over all configurations - impl RelayQueryBuilder> { + impl + RelayQueryBuilder> + { /// 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 RelayQueryBuilder> { + impl + RelayQueryBuilder> + { /// Enable DAITA support. - pub fn daita(mut self) -> RelayQueryBuilder> { + pub fn daita( + mut self, + ) -> RelayQueryBuilder> { 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 RelayQueryBuilder> { + impl RelayQueryBuilder> { + /// Enable PQ support. + pub fn quantum_resistant( + mut self, + ) -> RelayQueryBuilder> { + 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 + RelayQueryBuilder> + { /// Enable multihop. /// /// To configure the entry relay, see [`RelayQueryBuilder::entry`]. - pub fn multihop(mut self) -> RelayQueryBuilder> { + pub fn multihop( + mut self, + ) -> RelayQueryBuilder> { 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 RelayQueryBuilder> { + impl + RelayQueryBuilder> + { /// 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 RelayQueryBuilder> { + impl + RelayQueryBuilder> + { /// Enable `UDP2TCP` obufscation. This will in turn enable the option to configure the /// `UDP2TCP` port. pub fn udp2tcp( mut self, - ) -> RelayQueryBuilder> { + ) -> RelayQueryBuilder< + Wireguard, + > { 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> { + ) -> RelayQueryBuilder> + { 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 RelayQueryBuilder> { + impl + RelayQueryBuilder> + { /// 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 9e7eb9c31ca7..51a395f6d5f8 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 = 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 = LazyLock::new(|| RelayList { }, }); +static DAITA_RELAY_LOCATION: LazyLock = + LazyLock::new(|| GeographicLocationConstraint::hostname("se", "got", "se9-wireguard")); +static NON_DAITA_RELAY_LOCATION: LazyLock = + LazyLock::new(|| GeographicLocationConstraint::hostname("se", "got", "se10-wireguard")); + /// A Shadowsocks relay with additional addresses static SHADOWSOCKS_RELAY: LazyLock = 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 1af50abadc4c..c920e9f0afbe 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 { + 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 24d7e33babfa..4c9da00a168e 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))