From 09f779cf14749e19cbc7c702ca3314376b06bcc3 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 12 Aug 2024 17:28:53 +0200 Subject: [PATCH 01/16] Add `use_anywhere` daita setting --- mullvad-cli/src/cmds/tunnel.rs | 7 ++- mullvad-daemon/src/lib.rs | 6 ++ .../proto/management_interface.proto | 5 +- .../src/types/conversions/wireguard.rs | 2 + .../src/relay_selector/mod.rs | 56 +++++++++++++++++++ .../src/relay_selector/query.rs | 18 +++--- mullvad-types/src/wireguard.rs | 3 + 7 files changed, 86 insertions(+), 11 deletions(-) diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 0937cc82299e..2464334cc63f 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -196,8 +196,11 @@ impl Tunnel { #[cfg(daita)] if let Some(daita) = daita { - rpc.set_daita_settings(DaitaSettings { enabled: *daita }) - .await?; + rpc.set_daita_settings(DaitaSettings { + enabled: *daita, + use_anywhere: true, /* TODO */ + }) + .await?; println!("DAITA setting has been updated"); } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index a25a83522791..4a437e6e242b 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -2931,8 +2931,14 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig { wireguard: AdditionalWireguardConstraints { #[cfg(daita)] daita: settings.tunnel_options.wireguard.daita.enabled, + #[cfg(daita)] + daita_use_anywhere: settings.tunnel_options.wireguard.daita.use_anywhere, + #[cfg(not(daita))] daita: false, + #[cfg(not(daita))] + daita_use_anywhere: false, + quantum_resistant: settings.tunnel_options.wireguard.quantum_resistant, }, }; diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index e705c6378898..4a7d46d003a7 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -541,7 +541,10 @@ message QuantumResistantState { State state = 1; } -message DaitaSettings { bool enabled = 1; } +message DaitaSettings { + bool enabled = 1; + bool use_anywhere = 2; +} message TunnelOptions { message OpenvpnOptions { optional uint32 mssfix = 1; } diff --git a/mullvad-management-interface/src/types/conversions/wireguard.rs b/mullvad-management-interface/src/types/conversions/wireguard.rs index b4e3bcaef722..cf30a8cc99cd 100644 --- a/mullvad-management-interface/src/types/conversions/wireguard.rs +++ b/mullvad-management-interface/src/types/conversions/wireguard.rs @@ -78,6 +78,7 @@ impl From for proto::DaitaSettings { fn from(settings: mullvad_types::wireguard::DaitaSettings) -> Self { proto::DaitaSettings { enabled: settings.enabled, + use_anywhere: settings.use_anywhere, } } } @@ -87,6 +88,7 @@ impl From for mullvad_types::wireguard::DaitaSettings { fn from(settings: proto::DaitaSettings) -> Self { mullvad_types::wireguard::DaitaSettings { enabled: settings.enabled, + use_anywhere: settings.use_anywhere, } } } diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index dc495b05aa1b..45268a8eae08 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -124,6 +124,11 @@ pub struct AdditionalWireguardConstraints { /// If true, select WireGuard relays that support DAITA. If false, select any /// server. pub daita: bool, + + /// If true and multihop is disabled, will set up multihop with an automatic entry relay if + /// DAITA is enabled. + pub daita_use_anywhere: bool, + /// If enabled, select relays that support PQ. pub quantum_resistant: QuantumResistantState, } @@ -345,6 +350,7 @@ impl<'a> TryFrom> for RelayQuery { } = wireguard_constraints; let AdditionalWireguardConstraints { daita, + daita_use_anywhere, quantum_resistant, } = additional_constraints; WireguardRelayQuery { @@ -354,6 +360,7 @@ impl<'a> TryFrom> for RelayQuery { entry_location, obfuscation: ObfuscationQuery::from(obfuscation_settings), daita: Constraint::Only(daita), + daita_use_anywhere: Constraint::Only(daita_use_anywhere), quantum_resistant, } } @@ -718,12 +725,61 @@ impl RelaySelector { parsed_relays: &ParsedRelays, ) -> Result { let candidates = filter_matching_relay_list(query, parsed_relays, custom_lists); + + // are we using daita? + let using_daita = || query.wireguard_constraints().daita == Constraint::Only(true); + + // is the `candidates` list empty because DAITA is enabled? + let no_relay_because_daita = || { + let mut query = query.clone(); + let mut wireguard_constraints = query.wireguard_constraints().clone(); + wireguard_constraints.daita = Constraint::Any; + query.set_wireguard_constraints(wireguard_constraints)?; + let candidates = filter_matching_relay_list(&query, parsed_relays, custom_lists); + Result::<_, Error>::Ok(!candidates.is_empty()) + }; + + // is `use_anywhere` enabled? + let use_anywhere = || { + query + .wireguard_constraints() + .daita_use_anywhere + .intersection(Constraint::Only(true)) + .is_some() + }; + + // if we found no matching relays because DAITA was enabled, and `use_anywhere` is enabled, + // try enabling multihop and connecting using an automatically selected entry relay. + if candidates.is_empty() && using_daita() && no_relay_because_daita()? && use_anywhere() { + return Self::get_wireguard_auto_multihop_config(query, custom_lists, parsed_relays); + } + helpers::pick_random_relay(&candidates) .cloned() .map(WireguardConfig::singlehop) .ok_or(Error::NoRelay) } + /// Select a valid Wireguard exit relay, together with with an automatically chosen entry relay. + /// + /// # Returns + /// * An `Err` if no entry/exit relay can be chosen + /// * `Ok(WireguardInner::Multihop)` otherwise + fn get_wireguard_auto_multihop_config( + query: &RelayQuery, + custom_lists: &CustomListsSettings, + parsed_relays: &ParsedRelays, + ) -> Result { + // Modify the query to enable multihop + let mut query = query.clone(); + let mut wireguard_constraints = query.wireguard_constraints().clone(); + wireguard_constraints.use_multihop = Constraint::Only(true); + wireguard_constraints.entry_location = Constraint::Any; // TODO: smarter location selection + query.set_wireguard_constraints(wireguard_constraints)?; + + Self::get_wireguard_multihop_config(&query, custom_lists, parsed_relays) + } + /// This function selects a valid entry and exit relay to be used in a multihop configuration. /// /// # Returns diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index 67fe43e2a354..cf9fc475ba30 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, Error}; +use crate::Error; use mullvad_types::{ constraints::Constraint, relay_constraints::{ @@ -261,6 +261,7 @@ pub struct WireguardRelayQuery { pub entry_location: Constraint, pub obfuscation: ObfuscationQuery, pub daita: Constraint, + pub daita_use_anywhere: Constraint, pub quantum_resistant: QuantumResistantState, } @@ -346,6 +347,7 @@ impl WireguardRelayQuery { entry_location: Constraint::Any, obfuscation: ObfuscationQuery::Auto, daita: Constraint::Any, + daita_use_anywhere: Constraint::Any, quantum_resistant: QuantumResistantState::Auto, } } @@ -367,14 +369,14 @@ impl Default for WireguardRelayQuery { } } -impl From for AdditionalWireguardConstraints { - /// The mapping from [`WireguardRelayQuery`] to [`AdditionalWireguardConstraints`]. +impl From for WireguardConstraints { + /// The mapping from [`WireguardRelayQuery`] to [`WireguardConstraints`]. fn from(value: WireguardRelayQuery) -> Self { - AdditionalWireguardConstraints { - daita: value - .daita - .unwrap_or(AdditionalWireguardConstraints::default().daita), - quantum_resistant: value.quantum_resistant, + WireguardConstraints { + port: value.port, + ip_version: value.ip_version, + entry_location: value.entry_location, + use_multihop: value.use_multihop.unwrap_or(false), } } } diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs index c920e9f0afbe..8f666575895c 100644 --- a/mullvad-types/src/wireguard.rs +++ b/mullvad-types/src/wireguard.rs @@ -80,6 +80,9 @@ pub struct QuantumResistantStateParseError; #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct DaitaSettings { pub enabled: bool, + + #[serde(default)] + pub use_anywhere: bool, } /// Contains account specific wireguard data From fb92bf539e21542989c5bd31142b265f33812e0d Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 12 Aug 2024 17:29:21 +0200 Subject: [PATCH 02/16] Add temporary gui toggle for `use_anywhere` --- gui/src/main/daemon-rpc.ts | 1 + .../renderer/components/WireguardSettings.tsx | 19 +++++++++++++++++-- .../select-location/RelayListContext.tsx | 5 ++++- gui/src/shared/daemon-rpc-types.ts | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index c85189cea740..754f0b8d47ea 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -589,6 +589,7 @@ export class DaemonRpc { public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise { const grpcDaitaSettings = new grpcTypes.DaitaSettings(); grpcDaitaSettings.setEnabled(daitaSettings.enabled); + grpcDaitaSettings.setUseAnywhere(daitaSettings.useAnywhere); await this.call( this.client.setDaitaSettings, grpcDaitaSettings, diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 1f4709159fd0..5beeab26c9c9 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -531,6 +531,7 @@ function MtuSetting() { function DaitaSettings() { const { setDaitaSettings } = useAppContext(); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); @@ -538,12 +539,16 @@ function DaitaSettings() { if (value) { showConfirmationDialog(); } else { - void setDaitaSettings({ enabled: value }); + void setDaitaSettings({ enabled: value, useAnywhere: useAnywhere }); } }, []); + const setUseAnywhere = useCallback((value: boolean) => { + void setDaitaSettings({ enabled: daita, useAnywhere: value }); + }, []); + const confirmDaita = useCallback(() => { - void setDaitaSettings({ enabled: true }); + void setDaitaSettings({ enabled: true, useAnywhere: useAnywhere }); hideConfirmationDialog(); }, []); @@ -579,6 +584,16 @@ function DaitaSettings() { + + + + The "Just make it work" Button + + + + + + state.settings.wireguard.daita?.enabled ?? false); + const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); + const fullRelayList = useSelector((state) => state.settings.relayLocations); const relaySettings = useNormalRelaySettings(); + const multihop = relaySettings?.wireguard.useMultihop ?? false; // Filters the relays to only keep the ones of the desired endpoint type, e.g. "wireguard", // "openvpn" or "bridge" @@ -76,7 +79,7 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { const relayListForDaita = useMemo(() => { return filterLocationsByDaita( relayListForEndpointType, - daita, + daita && (!useAnywhere || multihop), locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index b84ee8a72ed4..9cb3c15b5d43 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -551,6 +551,7 @@ export interface RelayOverride { export interface IDaitaSettings { enabled: boolean; + useAnywhere: boolean; } export function parseSocketAddress(socketAddrStr: string): ISocketAddress { From 3373ca2c3f0df02da4ade50815f5654bc5dd7abf Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 13 Aug 2024 12:43:41 +0200 Subject: [PATCH 03/16] Add `use_anywhere` feature indicator --- gui/src/main/daemon-rpc.ts | 2 ++ gui/src/renderer/components/main-view/FeatureIndicators.tsx | 2 ++ gui/src/shared/daemon-rpc-types.ts | 1 + mullvad-management-interface/proto/management_interface.proto | 1 + mullvad-management-interface/src/types/conversions/features.rs | 2 ++ mullvad-types/src/features.rs | 2 ++ 6 files changed, 10 insertions(+) diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 754f0b8d47ea..3ccfce85bfc4 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -1183,6 +1183,8 @@ function convertFromFeatureIndicator( return FeatureIndicator.customMssFix; case grpcTypes.FeatureIndicator.DAITA: return FeatureIndicator.daita; + case grpcTypes.FeatureIndicator.DAITA_USE_ANYWHERE: + return FeatureIndicator.daitaUseAnywhere; case grpcTypes.FeatureIndicator.SHADOWSOCKS: return FeatureIndicator.shadowsocks; } diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx index 68ee0ae5ae81..97ff92742b54 100644 --- a/gui/src/renderer/components/main-view/FeatureIndicators.tsx +++ b/gui/src/renderer/components/main-view/FeatureIndicators.tsx @@ -216,6 +216,8 @@ function getFeatureIndicatorLabel(indicator: FeatureIndicator) { switch (indicator) { case FeatureIndicator.daita: return strings.daita; + case FeatureIndicator.daitaUseAnywhere: + return messages.pgettext('wireguard-settings-view', 'DAITA: Use Anywhere'); case FeatureIndicator.udp2tcp: case FeatureIndicator.shadowsocks: return messages.pgettext('wireguard-settings-view', 'Obfuscation'); diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 9cb3c15b5d43..f888413047af 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -183,6 +183,7 @@ export interface ITunnelStateRelayInfo { // The order of the variants match the priority order and can be sorted on. export enum FeatureIndicator { daita, + daitaUseAnywhere, quantumResistance, multihop, bridgeMode, diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 4a7d46d003a7..595c8f35273b 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -262,6 +262,7 @@ enum FeatureIndicator { CUSTOM_MTU = 11; CUSTOM_MSS_FIX = 12; DAITA = 13; + DAITA_USE_ANYWHERE = 14; } message ObfuscationEndpoint { diff --git a/mullvad-management-interface/src/types/conversions/features.rs b/mullvad-management-interface/src/types/conversions/features.rs index ac235d81636c..971c2a01155f 100644 --- a/mullvad-management-interface/src/types/conversions/features.rs +++ b/mullvad-management-interface/src/types/conversions/features.rs @@ -18,6 +18,7 @@ impl From for proto::FeatureIndicator mullvad_types::features::FeatureIndicator::CustomMtu => CustomMtu, mullvad_types::features::FeatureIndicator::CustomMssFix => CustomMssFix, mullvad_types::features::FeatureIndicator::Daita => Daita, + mullvad_types::features::FeatureIndicator::DaitaUseAnywhere => DaitaUseAnywhere, } } } @@ -39,6 +40,7 @@ impl From for mullvad_types::features::FeatureIndicator proto::FeatureIndicator::CustomMtu => Self::CustomMtu, proto::FeatureIndicator::CustomMssFix => Self::CustomMssFix, proto::FeatureIndicator::Daita => Self::Daita, + proto::FeatureIndicator::DaitaUseAnywhere => Self::DaitaUseAnywhere, } } } diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs index f847c94af057..0a87262666f1 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -66,6 +66,7 @@ pub enum FeatureIndicator { CustomMtu, CustomMssFix, Daita, + DaitaUseAnywhere, } impl FeatureIndicator { @@ -85,6 +86,7 @@ impl FeatureIndicator { FeatureIndicator::CustomMtu => "Custom MTU", FeatureIndicator::CustomMssFix => "Custom MSS", FeatureIndicator::Daita => "DAITA", + FeatureIndicator::DaitaUseAnywhere => "Use Anywhere (DAITA)", } } } From 7b19fda8f280378a8237ed60e63c266188266711 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 13 Aug 2024 17:16:08 +0200 Subject: [PATCH 04/16] Remove Option from Relay::location --- mullvad-api/src/relay_list.rs | 2 +- mullvad-cli/src/cmds/relay.rs | 23 +++++-------- mullvad-daemon/src/tunnel.rs | 4 +-- .../src/types/conversions/relay_list.rs | 34 +++++++++++-------- .../src/relay_selector/mod.rs | 7 ++-- .../src/relay_selector/parsed_relays.rs | 4 +-- .../tests/relay_selector.rs | 30 ++++++++++------ mullvad-types/src/relay_constraints.rs | 21 +++++------- mullvad-types/src/relay_list.rs | 11 ++++-- test/test-manager/src/tests/dns.rs | 2 +- test/test-manager/src/tests/helpers.rs | 27 +++++---------- .../src/tests/relay_ip_overrides.rs | 7 +--- test/test-manager/src/tests/tunnel.rs | 5 ++- 13 files changed, 89 insertions(+), 88 deletions(-) diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs index afe71c829d1a..5f2b2d6d8180 100644 --- a/mullvad-api/src/relay_list.rs +++ b/mullvad-api/src/relay_list.rs @@ -162,7 +162,7 @@ fn into_mullvad_relay( provider: relay.provider, weight: relay.weight, endpoint_data, - location: Some(location), + location, } } diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index 68292c0ad1b8..eaaa2834111b 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::{ constraints::{Constraint, Match}, - location::{CountryCode, Location}, + location::CountryCode, relay_constraints::{ GeographicLocationConstraint, LocationConstraint, LocationConstraintFormatter, OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraints, RelayOverride, @@ -921,15 +921,11 @@ fn parse_transport_port( fn relay_to_geographical_constraint( relay: mullvad_types::relay_list::Relay, -) -> Option { - relay.location.map( - |Location { - country_code, - city_code, - .. - }| { - GeographicLocationConstraint::Hostname(country_code, city_code, relay.hostname) - }, +) -> GeographicLocationConstraint { + GeographicLocationConstraint::Hostname( + relay.location.country_code, + relay.location.city_code, + relay.hostname, ) } @@ -952,10 +948,9 @@ pub async fn resolve_location_constraint( .find(|relay| relay.hostname.to_lowercase() == location_constraint_args.country) { if relay_filter(&matching_relay) { - Ok(Constraint::Only( - relay_to_geographical_constraint(matching_relay) - .context("Selected relay did not contain a valid location")?, - )) + Ok(Constraint::Only(relay_to_geographical_constraint( + matching_relay, + ))) } else { bail!( "The relay `{}` is not valid for this operation", diff --git a/mullvad-daemon/src/tunnel.rs b/mullvad-daemon/src/tunnel.rs index 72ec5087fc8d..73bda59635c7 100644 --- a/mullvad-daemon/src/tunnel.rs +++ b/mullvad-daemon/src/tunnel.rs @@ -128,7 +128,7 @@ impl ParametersGenerator { hostname = exit.hostname.clone(); obfuscator_hostname = take_hostname(obfuscator); bridge_hostname = None; - location = exit.location.as_ref().cloned().unwrap(); + location = exit.location.clone(); } #[cfg(not(target_os = "android"))] LastSelectedRelays::OpenVpn { relay, bridge, .. } => { @@ -136,7 +136,7 @@ impl ParametersGenerator { bridge_hostname = take_hostname(bridge); entry_hostname = None; obfuscator_hostname = None; - location = relay.location.as_ref().cloned().unwrap(); + location = relay.location.clone(); } }; diff --git a/mullvad-management-interface/src/types/conversions/relay_list.rs b/mullvad-management-interface/src/types/conversions/relay_list.rs index a8b69d87fafe..11718973c138 100644 --- a/mullvad-management-interface/src/types/conversions/relay_list.rs +++ b/mullvad-management-interface/src/types/conversions/relay_list.rs @@ -144,13 +144,13 @@ impl From for proto::Relay { )), _ => None, }, - location: relay.location.map(|location| proto::Location { - country: location.country, - country_code: location.country_code, - city: location.city, - city_code: location.city_code, - latitude: location.latitude, - longitude: location.longitude, + location: Some(proto::Location { + country: relay.location.country, + country_code: relay.location.country_code, + city: relay.location.city, + city_code: relay.location.city_code, + latitude: relay.location.latitude, + longitude: relay.location.longitude, }), } } @@ -299,14 +299,18 @@ impl TryFrom for mullvad_types::relay_list::Relay { provider: relay.provider, weight: relay.weight, endpoint_data, - location: relay.location.map(|location| MullvadLocation { - country: location.country, - country_code: location.country_code, - city: location.city, - city_code: location.city_code, - latitude: location.latitude, - longitude: location.longitude, - }), + location: relay + .location + .map(|location| MullvadLocation { + country: location.country, + country_code: location.country_code, + city: location.city, + city_code: location.city_code, + latitude: location.latitude, + longitude: location.longitude, + }) + .ok_or("missing relay location") + .map_err(FromProtobufTypeError::InvalidArgument)?, }) } } diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index 45268a8eae08..f822f2045dfb 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -994,10 +994,9 @@ impl RelaySelector { Err(Error::NoBridge) } TransportProtocol::Tcp => { - let location = relay.location.as_ref().ok_or(Error::NoRelay)?; Self::get_bridge_for( bridge_query, - location, + &relay.location, // FIXME: This is temporary while talpid-core only supports TCP proxies TransportProtocol::Tcp, parsed_relays, @@ -1077,7 +1076,7 @@ impl RelaySelector { let matching_bridges: Vec = relays .into_iter() .map(|relay| RelayWithDistance { - distance: relay.location.as_ref().unwrap().distance_from(&location), + distance: relay.location.distance_from(&location), relay, }) .sorted_unstable_by_key(|relay| relay.distance as usize) @@ -1115,7 +1114,7 @@ impl RelaySelector { let matching_locations: Vec = filter_matching_relay_list(query, parsed_relays, custom_lists) .into_iter() - .filter_map(|relay| relay.location) + .map(|relay| relay.location) .unique_by(|location| location.city.clone()) .collect(); diff --git a/mullvad-relay-selector/src/relay_selector/parsed_relays.rs b/mullvad-relay-selector/src/relay_selector/parsed_relays.rs index 35c78a52aaa8..3a1663d9dd47 100644 --- a/mullvad-relay-selector/src/relay_selector/parsed_relays.rs +++ b/mullvad-relay-selector/src/relay_selector/parsed_relays.rs @@ -168,14 +168,14 @@ impl ParsedRelays { for city in &mut country.cities { for relay in &mut city.relays { // Append location data - relay.location = Some(Location { + relay.location = Location { country: country.name.clone(), country_code: country.code.clone(), city: city.name.clone(), city_code: city.code.clone(), latitude: city.latitude, longitude: city.longitude, - }); + }; // Append overrides if let Some(overrides) = remaining_overrides.remove(&relay.hostname) { diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index bdfd8e19d0ed..1349b5af2611 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -21,6 +21,7 @@ use mullvad_relay_selector::{ use mullvad_types::{ constraints::Constraint, endpoint::MullvadEndpoint, + location::Location, relay_constraints::{ BridgeConstraints, BridgeState, GeographicLocationConstraint, Ownership, Providers, RelayOverride, TransportPort, @@ -32,6 +33,15 @@ use mullvad_types::{ }, }; +static DUMMY_LOCATION: LazyLock = LazyLock::new(|| Location { + country: "Sweden".to_string(), + country_code: "se".to_string(), + city: "Gothenburg".to_string(), + city_code: "got".to_string(), + latitude: 57.71, + longitude: 11.97, +}); + static RELAYS: LazyLock = LazyLock::new(|| RelayList { etag: None, countries: vec![RelayListCountry { @@ -62,7 +72,7 @@ static RELAYS: LazyLock = LazyLock::new(|| RelayList { daita: true, shadowsocks_extra_addr_in: vec![], }), - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se10-wireguard".to_string(), @@ -83,7 +93,7 @@ static RELAYS: LazyLock = LazyLock::new(|| RelayList { daita: false, shadowsocks_extra_addr_in: vec![], }), - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se-got-001".to_string(), @@ -97,7 +107,7 @@ static RELAYS: LazyLock = LazyLock::new(|| RelayList { provider: "provider2".to_string(), weight: 1, endpoint_data: RelayEndpointData::Openvpn, - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se-got-002".to_string(), @@ -111,7 +121,7 @@ static RELAYS: LazyLock = LazyLock::new(|| RelayList { provider: "provider0".to_string(), weight: 1, endpoint_data: RelayEndpointData::Openvpn, - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se-got-br-001".to_string(), @@ -125,7 +135,7 @@ static RELAYS: LazyLock = LazyLock::new(|| RelayList { provider: "provider3".to_string(), weight: 1, endpoint_data: RelayEndpointData::Bridge, - location: None, + location: DUMMY_LOCATION.clone(), }, SHADOWSOCKS_RELAY.clone(), ], @@ -209,7 +219,7 @@ static SHADOWSOCKS_RELAY: LazyLock = LazyLock::new(|| Relay { daita: false, shadowsocks_extra_addr_in: SHADOWSOCKS_RELAY_EXTRA_ADDRS.to_vec(), }), - location: None, + location: DUMMY_LOCATION.clone(), }); const SHADOWSOCKS_RELAY_IPV4: Ipv4Addr = Ipv4Addr::new(123, 123, 123, 1); const SHADOWSOCKS_RELAY_IPV6: Ipv6Addr = Ipv6Addr::new(0x123, 0, 0, 0, 0, 0, 0, 2); @@ -497,7 +507,7 @@ fn test_wireguard_entry() { daita: false, shadowsocks_extra_addr_in: vec![], }), - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se10-wireguard".to_string(), @@ -518,7 +528,7 @@ fn test_wireguard_entry() { daita: false, shadowsocks_extra_addr_in: vec![], }), - location: None, + location: DUMMY_LOCATION.clone(), }, ], }], @@ -1169,7 +1179,7 @@ fn test_include_in_country() { shadowsocks_extra_addr_in: vec![], daita: false, }), - location: None, + location: DUMMY_LOCATION.clone(), }, Relay { hostname: "se10-wireguard".to_string(), @@ -1190,7 +1200,7 @@ fn test_include_in_country() { shadowsocks_extra_addr_in: vec![], daita: false, }), - location: None, + location: DUMMY_LOCATION.clone(), }, ], }], diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index f3e38adc7943..2b3d5099d249 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -210,21 +210,18 @@ impl GeographicLocationConstraint { impl Match for GeographicLocationConstraint { fn matches(&self, relay: &Relay) -> bool { match self { - GeographicLocationConstraint::Country(ref country) => relay - .location - .as_ref() - .map_or(false, |loc| loc.country_code == *country), + GeographicLocationConstraint::Country(ref country) => { + relay.location.country_code == *country + } GeographicLocationConstraint::City(ref country, ref city) => { - relay.location.as_ref().map_or(false, |loc| { - loc.country_code == *country && loc.city_code == *city - }) + let loc = &relay.location; + loc.country_code == *country && loc.city_code == *city } GeographicLocationConstraint::Hostname(ref country, ref city, ref hostname) => { - relay.location.as_ref().map_or(false, |loc| { - loc.country_code == *country - && loc.city_code == *city - && relay.hostname == *hostname - }) + let loc = &relay.location; + loc.country_code == *country + && loc.city_code == *city + && relay.hostname == *hostname } } } diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs index 1693720d4af1..afe8ba6378d3 100644 --- a/mullvad-types/src/relay_list.rs +++ b/mullvad-types/src/relay_list.rs @@ -90,7 +90,7 @@ pub struct Relay { pub provider: String, pub weight: u64, pub endpoint_data: RelayEndpointData, - pub location: Option, + pub location: Location, } impl Relay { @@ -134,7 +134,14 @@ impl PartialEq for Relay { /// # daita: false, /// # shadowsocks_extra_addr_in: vec![], /// # }), - /// # location: None, + /// # location: mullvad_types::location::Location { + /// # country: "Sweden".to_string(), + /// # country_code: "se".to_string(), + /// # city: "Gothenburg".to_string(), + /// # city_code: "got".to_string(), + /// # latitude: 57.71, + /// # longitude: 11.97, + /// # }, /// }; /// /// let mut different_relay = relay.clone(); diff --git a/test/test-manager/src/tests/dns.rs b/test/test-manager/src/tests/dns.rs index d7ea3d021d6a..69f98450caf1 100644 --- a/test/test-manager/src/tests/dns.rs +++ b/test/test-manager/src/tests/dns.rs @@ -642,7 +642,7 @@ async fn connect_local_wg_relay(mullvad_client: &mut MullvadProxyClient) -> Resu .set_quantum_resistant_tunnel(QuantumResistantState::Off) .await?; mullvad_client - .set_daita_settings(DaitaSettings { enabled: false }) + .set_daita_settings(DaitaSettings::default()) .await?; let peer_addr: SocketAddr = SocketAddr::new( diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs index 6c51b1f18599..3eb0be0cc238 100644 --- a/test/test-manager/src/tests/helpers.rs +++ b/test/test-manager/src/tests/helpers.rs @@ -17,7 +17,6 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ constraints::Constraint, - location::Location, relay_constraints::{ GeographicLocationConstraint, LocationConstraint, RelayConstraints, RelaySettings, }, @@ -710,7 +709,7 @@ pub async fn constrain_to_relay( .. } | GetRelay::OpenVpn { exit, .. } => { - let location = into_constraint(&exit)?; + let location = into_constraint(&exit); let (mut relay_constraints, ..) = query.into_settings(); relay_constraints.location = location; Ok((exit, relay_constraints)) @@ -736,22 +735,14 @@ pub async fn constrain_to_relay( /// # Panics /// /// The relay must have a location set. -pub fn into_constraint(relay: &Relay) -> anyhow::Result> { - relay - .location - .as_ref() - .map( - |Location { - country_code, - city_code, - .. - }| { - GeographicLocationConstraint::hostname(country_code, city_code, &relay.hostname) - }, - ) - .map(LocationConstraint::Location) - .map(Constraint::Only) - .ok_or(anyhow!("relay is missing location")) +pub fn into_constraint(relay: &Relay) -> Constraint { + let constraint = GeographicLocationConstraint::hostname( + relay.location.country_code.clone(), + relay.location.city_code.clone(), + &relay.hostname, + ); + + Constraint::Only(LocationConstraint::Location(constraint)) } /// Ping monitoring made easy! diff --git a/test/test-manager/src/tests/relay_ip_overrides.rs b/test/test-manager/src/tests/relay_ip_overrides.rs index 48df8ff0e4b6..3805412ee92a 100644 --- a/test/test-manager/src/tests/relay_ip_overrides.rs +++ b/test/test-manager/src/tests/relay_ip_overrides.rs @@ -298,12 +298,7 @@ async fn pick_a_relay( let relay_ip = relay.ipv4_addr_in; let hostname = relay.hostname.clone(); - let city = relay - .location - .as_ref() - .ok_or(anyhow!("Got Relay with an unknown location"))? - .city_code - .clone(); + let city = relay.location.city_code.clone(); log::info!("selected {hostname} ({relay_ip})"); let location = GeographicLocationConstraint::Hostname(country, city, hostname.clone()).into(); diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index 575339c5a815..f5d8f6c95960 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -410,7 +410,10 @@ pub async fn test_daita( .await?; mullvad_client - .set_daita_settings(wireguard::DaitaSettings { enabled: true }) + .set_daita_settings(wireguard::DaitaSettings { + enabled: true, + use_anywhere: false, + }) .await .context("Failed to enable daita")?; From 4892d4a4f34509b9af21bd1349e9e5e964bf9846 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 13 Aug 2024 15:59:54 +0200 Subject: [PATCH 05/16] Pick DAITA use_anywhere relays based on distance to selected location --- .../src/relay_selector/mod.rs | 77 +++++++++++++------ mullvad-types/src/location.rs | 5 +- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index f822f2045dfb..f889cac48efb 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -770,14 +770,42 @@ impl RelaySelector { custom_lists: &CustomListsSettings, parsed_relays: &ParsedRelays, ) -> Result { - // Modify the query to enable multihop - let mut query = query.clone(); - let mut wireguard_constraints = query.wireguard_constraints().clone(); - wireguard_constraints.use_multihop = Constraint::Only(true); - wireguard_constraints.entry_location = Constraint::Any; // TODO: smarter location selection - query.set_wireguard_constraints(wireguard_constraints)?; + let mut exit_relay_query = query.clone(); + + // DAITA should only be enabled for the entry relay + let mut wireguard_constraints = exit_relay_query.wireguard_constraints().clone(); + wireguard_constraints.daita = Constraint::Only(false); + exit_relay_query.set_wireguard_constraints(wireguard_constraints)?; + + let exit_candidates = + filter_matching_relay_list(&exit_relay_query, parsed_relays, custom_lists); + let exit = helpers::pick_random_relay(&exit_candidates).ok_or(Error::NoRelay)?; + + // generate a list of potential entry relays, disregarding any location constraint + let mut entry_query = query.clone(); + entry_query.set_location(Constraint::Any)?; + let mut entry_candidates = + filter_matching_relay_list(&entry_query, parsed_relays, custom_lists) + .into_iter() + .map(|entry| RelayWithDistance::new_with_distance_from(entry, &exit.location)) + .collect_vec(); + + // sort entry relay candidates by distance, and pick one from those that are closest + entry_candidates.sort_unstable_by(|a, b| a.distance.total_cmp(&b.distance)); + let smallest_distance = entry_candidates.first().map(|relay| relay.distance); + let smallest_distance = smallest_distance.unwrap_or_default(); + let entry_candidates = entry_candidates + .into_iter() + // only consider the relay(s) with the smallest distance. note that the list is sorted. + // NOTE: we could relax this requirement, but since so few relays support DAITA + // (and this function is only used for daita) we might end up picking relays that are + // needlessly far away. Consider making this closure configurable if needed. + .take_while(|relay| relay.distance <= smallest_distance) + .map(|relay_with_distance| relay_with_distance.relay) + .collect_vec(); + let entry = pick_random_excluding(&entry_candidates, &exit).ok_or(Error::NoRelay)?; - Self::get_wireguard_multihop_config(&query, custom_lists, parsed_relays) + Ok(WireguardConfig::multihop(exit.clone(), entry.clone())) } /// This function selects a valid entry and exit relay to be used in a multihop configuration. @@ -813,11 +841,6 @@ impl RelaySelector { let entry_candidates = filter_matching_relay_list(&entry_relay_query, parsed_relays, custom_lists); - fn pick_random_excluding<'a>(list: &'a [Relay], exclude: &'a Relay) -> Option<&'a Relay> { - list.iter() - .filter(|&a| a != exclude) - .choose(&mut thread_rng()) - } // We avoid picking the same relay for entry and exit by choosing one and excluding it when // choosing the other. let (exit, entry) = match (exit_candidates.as_slice(), entry_candidates.as_slice()) { @@ -1066,19 +1089,10 @@ impl RelaySelector { const MIN_BRIDGE_COUNT: usize = 5; let location = location.into(); - #[derive(Clone)] - struct RelayWithDistance { - relay: Relay, - distance: f64, - } - // Filter out all candidate bridges. let matching_bridges: Vec = relays .into_iter() - .map(|relay| RelayWithDistance { - distance: relay.location.distance_from(&location), - relay, - }) + .map(|relay| RelayWithDistance::new_with_distance_from(relay, location)) .sorted_unstable_by_key(|relay| relay.distance as usize) .take(MIN_BRIDGE_COUNT) .collect(); @@ -1139,3 +1153,22 @@ impl RelaySelector { helpers::pick_random_relay(&candidates).cloned() } } + +fn pick_random_excluding<'a>(list: &'a [Relay], exclude: &'a Relay) -> Option<&'a Relay> { + list.iter() + .filter(|&a| a != exclude) + .choose(&mut thread_rng()) +} + +#[derive(Clone)] +struct RelayWithDistance { + distance: f64, + relay: Relay, +} + +impl RelayWithDistance { + fn new_with_distance_from(relay: Relay, from: impl Into) -> Self { + let distance = relay.location.distance_from(from); + RelayWithDistance { relay, distance } + } +} diff --git a/mullvad-types/src/location.rs b/mullvad-types/src/location.rs index 7807b65d6a91..d8a47176c190 100644 --- a/mullvad-types/src/location.rs +++ b/mullvad-types/src/location.rs @@ -19,7 +19,8 @@ pub struct Location { const RAIDUS_OF_EARTH: f64 = 6372.8; impl Location { - pub fn distance_from(&self, other: &Coordinates) -> f64 { + pub fn distance_from(&self, other: impl Into) -> f64 { + let other: Coordinates = other.into(); haversine_dist_deg( self.latitude, self.longitude, @@ -33,7 +34,7 @@ impl Location { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Coordinates { pub latitude: f64, pub longitude: f64, From 3fac6f5f6d84ee65ba015f900ab5436e0f48cc01 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 19 Aug 2024 14:47:35 +0200 Subject: [PATCH 06/16] Add daita.enabled and daita.use_anywhere rpc calls --- gui/src/main/daemon-rpc.ts | 15 ++--- gui/src/main/settings.ts | 7 ++- gui/src/renderer/app.tsx | 7 ++- .../renderer/components/WireguardSettings.tsx | 8 +-- gui/src/shared/ipc-schema.ts | 4 +- mullvad-cli/src/cmds/tunnel.rs | 25 ++++++--- mullvad-daemon/src/lib.rs | 56 +++++++++++++++++++ mullvad-daemon/src/management_interface.rs | 30 ++++++++++ .../proto/management_interface.proto | 2 + mullvad-management-interface/src/client.rs | 15 +++++ .../src/relay_selector/mod.rs | 2 +- 11 files changed, 142 insertions(+), 29 deletions(-) diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 3ccfce85bfc4..9f1f2d760c1f 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -38,7 +38,6 @@ import { IAppVersionInfo, IBridgeConstraints, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -586,14 +585,12 @@ export class DaemonRpc { await this.callBool(this.client.prepareRestartV2, quit); } - public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise { - const grpcDaitaSettings = new grpcTypes.DaitaSettings(); - grpcDaitaSettings.setEnabled(daitaSettings.enabled); - grpcDaitaSettings.setUseAnywhere(daitaSettings.useAnywhere); - await this.call( - this.client.setDaitaSettings, - grpcDaitaSettings, - ); + public async setEnableDaita(value: boolean): Promise { + await this.callBool(this.client.setEnableDaita, value); + } + + public async setDaitaUseAnywhere(value: boolean): Promise { + await this.callBool(this.client.setDaitaUseAnywhere, value); } public async listDevices(accountToken: AccountToken): Promise> { diff --git a/gui/src/main/settings.ts b/gui/src/main/settings.ts index 22238c72c459..6ec4e0b09d2f 100644 --- a/gui/src/main/settings.ts +++ b/gui/src/main/settings.ts @@ -107,8 +107,11 @@ export default class Settings implements Readonly { const settings = await fs.readFile(path); return this.daemonRpc.applyJsonSettings(settings.toString()); }); - IpcMainEventChannel.settings.handleSetDaitaSettings((daitaSettings) => { - return this.daemonRpc.setDaitaSettings(daitaSettings); + IpcMainEventChannel.settings.handleSetEnableDaita((value) => { + return this.daemonRpc.setEnableDaita(value); + }); + IpcMainEventChannel.settings.handleSetDaitaUseAnywhere((value) => { + return this.daemonRpc.setDaitaUseAnywhere(value); }); IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => { diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index a4c76aa2d1a4..168e88dcb6f6 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -19,7 +19,6 @@ import { IAccountData, IAppVersionInfo, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -345,8 +344,10 @@ export default class AppRenderer { IpcRendererEventChannel.splitTunneling.forgetManuallyAddedApplication(application); public setObfuscationSettings = (obfuscationSettings: ObfuscationSettings) => IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings); - public setDaitaSettings = (daitaSettings: IDaitaSettings) => - IpcRendererEventChannel.settings.setDaitaSettings(daitaSettings); + public setEnableDaita = (value: boolean) => + IpcRendererEventChannel.settings.setEnableDaita(value); + public setDaitaUseAnywhere = (value: boolean) => + IpcRendererEventChannel.settings.setDaitaUseAnywhere(value); public collectProblemReport = (toRedact: string | undefined) => IpcRendererEventChannel.problemReport.collectLogs(toRedact); public viewLog = (path: string) => IpcRendererEventChannel.problemReport.viewLog(path); diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 5beeab26c9c9..edcef77ea8f8 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -529,7 +529,7 @@ function MtuSetting() { } function DaitaSettings() { - const { setDaitaSettings } = useAppContext(); + const { setEnableDaita, setDaitaUseAnywhere } = useAppContext(); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); @@ -539,16 +539,16 @@ function DaitaSettings() { if (value) { showConfirmationDialog(); } else { - void setDaitaSettings({ enabled: value, useAnywhere: useAnywhere }); + void setEnableDaita(value); } }, []); const setUseAnywhere = useCallback((value: boolean) => { - void setDaitaSettings({ enabled: daita, useAnywhere: value }); + void setDaitaUseAnywhere(value); }, []); const confirmDaita = useCallback(() => { - void setDaitaSettings({ enabled: true, useAnywhere: useAnywhere }); + void setEnableDaita(true); hideConfirmationDialog(); }, []); diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index acbb6366d262..78acc9fae99b 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -14,7 +14,6 @@ import { IAccountData, IAppVersionInfo, ICustomList, - IDaitaSettings, IDevice, IDeviceRemoval, IDnsOptions, @@ -195,7 +194,8 @@ export const ipcSchema = { testApiAccessMethodById: invoke(), testCustomApiAccessMethod: invoke(), clearAllRelayOverrides: invoke(), - setDaitaSettings: invoke(), + setEnableDaita: invoke(), + setDaitaUseAnywhere: invoke(), }, guiSettings: { '': notifyRenderer(), diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 2464334cc63f..911c2f010bbd 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -1,8 +1,6 @@ use anyhow::Result; use clap::Subcommand; use mullvad_management_interface::MullvadProxyClient; -#[cfg(daita)] -use mullvad_types::wireguard::DaitaSettings; use mullvad_types::{ constraints::Constraint, wireguard::{QuantumResistantState, RotationInterval, DEFAULT_ROTATION_INTERVAL}, @@ -44,6 +42,10 @@ pub enum TunnelOptions { #[cfg(daita)] #[arg(long)] daita: Option, + /// Configure whether to enable DAITA "use anywhere" + #[cfg(daita)] + #[arg(long)] + daita_use_anywhere: Option, /// The key rotation interval. Number of hours, or 'any' #[arg(long)] rotation_interval: Option>, @@ -140,6 +142,8 @@ impl Tunnel { quantum_resistant, #[cfg(daita)] daita, + #[cfg(daita)] + daita_use_anywhere, rotation_interval, rotate_key, } => { @@ -148,6 +152,8 @@ impl Tunnel { quantum_resistant, #[cfg(daita)] daita, + #[cfg(daita)] + daita_use_anywhere, rotation_interval, rotate_key, ) @@ -179,6 +185,7 @@ impl Tunnel { mtu: Option>, quantum_resistant: Option, #[cfg(daita)] daita: Option, + #[cfg(daita)] daita_use_anywhere: Option, rotation_interval: Option>, rotate_key: Option, ) -> Result<()> { @@ -195,12 +202,14 @@ impl Tunnel { } #[cfg(daita)] - if let Some(daita) = daita { - rpc.set_daita_settings(DaitaSettings { - enabled: *daita, - use_anywhere: true, /* TODO */ - }) - .await?; + if let Some(enable_daita) = daita { + rpc.set_enable_daita(*enable_daita).await?; + println!("DAITA setting has been updated"); + } + + #[cfg(daita)] + if let Some(daita_use_anywhere) = daita_use_anywhere { + rpc.set_daita_use_anywhere(*daita_use_anywhere).await?; println!("DAITA setting has been updated"); } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 4a437e6e242b..7a0dc2df2360 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -264,6 +264,10 @@ pub enum DaemonCommand { SetQuantumResistantTunnel(ResponseTx<(), settings::Error>, QuantumResistantState), /// Set DAITA settings for the tunnel #[cfg(daita)] + SetEnableDaita(ResponseTx<(), settings::Error>, bool), + #[cfg(daita)] + SetDaitaUseAnywhere(ResponseTx<(), settings::Error>, bool), + #[cfg(daita)] SetDaitaSettings(ResponseTx<(), settings::Error>, DaitaSettings), /// Set DNS options or servers to use SetDnsOptions(ResponseTx<(), settings::Error>, DnsOptions), @@ -1255,6 +1259,10 @@ impl Daemon { .await } #[cfg(daita)] + SetEnableDaita(tx, value) => self.on_set_daita_enabled(tx, value).await, + #[cfg(daita)] + SetDaitaUseAnywhere(tx, value) => self.on_set_daita_use_anywhere(tx, value).await, + #[cfg(daita)] SetDaitaSettings(tx, daita_settings) => { self.on_set_daita_settings(tx, daita_settings).await } @@ -2323,6 +2331,54 @@ impl Daemon { } } + #[cfg(daita)] + async fn on_set_daita_enabled(&mut self, tx: ResponseTx<(), settings::Error>, value: bool) { + match self + .settings + .update(|settings| settings.tunnel_options.wireguard.daita.enabled = value) + .await + { + Ok(settings_changed) => { + Self::oneshot_send(tx, Ok(()), "set_daita_enabled response"); + if settings_changed && self.get_target_tunnel_type() != Some(TunnelType::OpenVpn) { + log::info!("Reconnecting because DAITA settings changed"); + self.reconnect_tunnel(); + } + } + Err(e) => { + log::error!("{}", e.display_chain_with_msg("Unable to save settings")); + Self::oneshot_send(tx, Err(e), "set_daita_enabled response"); + } + } + } + + #[cfg(daita)] + async fn on_set_daita_use_anywhere( + &mut self, + tx: ResponseTx<(), settings::Error>, + value: bool, + ) { + match self + .settings + .update(|settings| settings.tunnel_options.wireguard.daita.use_anywhere = value) + .await + { + Ok(settings_changed) => { + Self::oneshot_send(tx, Ok(()), "set_daita_use_anywhere response"); + + // TODO: don't reconnect if multihop is enabled + if settings_changed && self.get_target_tunnel_type() != Some(TunnelType::OpenVpn) { + log::info!("Reconnecting because DAITA settings changed"); + self.reconnect_tunnel(); + } + } + Err(e) => { + log::error!("{}", e.display_chain_with_msg("Unable to save settings")); + Self::oneshot_send(tx, Err(e), "set_daita_use_anywhere response"); + } + } + } + #[cfg(daita)] async fn on_set_daita_settings( &mut self, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 4d0f558a97f4..594fd7a19e63 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -341,6 +341,26 @@ impl ManagementService for ManagementServiceImpl { Ok(Response::new(())) } + #[cfg(daita)] + async fn set_enable_daita(&self, request: Request) -> ServiceResult<()> { + let value = request.into_inner(); + log::debug!("set_enable_daita({value})"); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetEnableDaita(tx, value))?; + self.wait_for_result(rx).await?.map(Response::new)?; + Ok(Response::new(())) + } + + #[cfg(daita)] + async fn set_daita_use_anywhere(&self, request: Request) -> ServiceResult<()> { + let value = request.into_inner(); + log::debug!("set_daita_use_anywhere({value})"); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetDaitaUseAnywhere(tx, value))?; + self.wait_for_result(rx).await?.map(Response::new)?; + Ok(Response::new(())) + } + #[cfg(daita)] async fn set_daita_settings( &self, @@ -355,6 +375,16 @@ impl ManagementService for ManagementServiceImpl { Ok(Response::new(())) } + #[cfg(not(daita))] + async fn set_enable_daita(&self, _: Request) -> ServiceResult<()> { + Ok(Response::new(())) + } + + #[cfg(not(daita))] + async fn set_daita_use_anywhere(&self, _: Request) -> ServiceResult<()> { + Ok(Response::new(())) + } + #[cfg(not(daita))] async fn set_daita_settings(&self, _: Request) -> ServiceResult<()> { Ok(Response::new(())) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 595c8f35273b..29acf34923cd 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -48,6 +48,8 @@ service ManagementService { rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetQuantumResistantTunnel(QuantumResistantState) returns (google.protobuf.Empty) {} + rpc SetEnableDaita(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetDaitaUseAnywhere(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetDaitaSettings(DaitaSettings) returns (google.protobuf.Empty) {} rpc SetDnsOptions(DnsOptions) returns (google.protobuf.Empty) {} rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 3b2cabc33ea7..14676ceeb962 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -378,6 +378,21 @@ impl MullvadProxyClient { Ok(()) } + #[cfg(daita)] + pub async fn set_enable_daita(&mut self, value: bool) -> Result<()> { + self.0.set_enable_daita(value).await.map_err(Error::Rpc)?; + Ok(()) + } + + #[cfg(daita)] + pub async fn set_daita_use_anywhere(&mut self, value: bool) -> Result<()> { + self.0 + .set_daita_use_anywhere(value) + .await + .map_err(Error::Rpc)?; + Ok(()) + } + #[cfg(daita)] pub async fn set_daita_settings(&mut self, settings: DaitaSettings) -> Result<()> { let settings = types::DaitaSettings::from(settings); diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index f889cac48efb..1635de88570c 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -803,7 +803,7 @@ impl RelaySelector { .take_while(|relay| relay.distance <= smallest_distance) .map(|relay_with_distance| relay_with_distance.relay) .collect_vec(); - let entry = pick_random_excluding(&entry_candidates, &exit).ok_or(Error::NoRelay)?; + let entry = pick_random_excluding(&entry_candidates, exit).ok_or(Error::NoRelay)?; Ok(WireguardConfig::multihop(exit.clone(), entry.clone())) } From 31a215bee9954194db9b5b0d90c285fa62d7b6ea Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 19 Aug 2024 17:11:11 +0200 Subject: [PATCH 07/16] Implement daita use_anywhere feature indicator --- mullvad-types/src/features.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs index 0a87262666f1..592d2382e2af 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -163,6 +163,18 @@ pub fn compute_feature_indicators( #[cfg(daita)] let daita = endpoint.daita; + #[cfg(daita)] + let daita_use_anywhere = + if let crate::relay_constraints::RelaySettings::Normal(constraints) = + &settings.relay_settings + { + // Detect whether we're using "use_anywhere" by checking if multihop is + // in use but not explicitly enabled. + daita && multihop && !constraints.wireguard_constraints.use_multihop + } else { + false + }; + vec![ (quantum_resistant, FeatureIndicator::QuantumResistance), (multihop, FeatureIndicator::Multihop), @@ -171,6 +183,8 @@ pub fn compute_feature_indicators( (mtu, FeatureIndicator::CustomMtu), #[cfg(daita)] (daita, FeatureIndicator::Daita), + #[cfg(daita)] + (daita_use_anywhere, FeatureIndicator::DaitaUseAnywhere), ] } }; @@ -192,6 +206,8 @@ mod tests { Endpoint, ObfuscationEndpoint, TransportProtocol, }; + use crate::relay_constraints::RelaySettings; + use super::*; #[test] @@ -304,6 +320,9 @@ mod tests { address: SocketAddr::from(([1, 2, 3, 4], 443)), protocol: TransportProtocol::Tcp, }); + if let RelaySettings::Normal(constraints) = &mut settings.relay_settings { + constraints.wireguard_constraints.use_multihop = true; + }; expected_indicators.0.insert(FeatureIndicator::Multihop); assert_eq!( compute_feature_indicators(&settings, &endpoint, false), @@ -345,6 +364,18 @@ mod tests { compute_feature_indicators(&settings, &endpoint, false), expected_indicators ); + + if let RelaySettings::Normal(constraints) = &mut settings.relay_settings { + constraints.wireguard_constraints.use_multihop = false; + }; + expected_indicators + .0 + .insert(FeatureIndicator::DaitaUseAnywhere); + assert_eq!( + compute_feature_indicators(&settings, &endpoint, false), + expected_indicators, + "DaitaUseAnywhere should be enable" + ); } // NOTE: If this match statement fails to compile, it means that a new feature indicator has @@ -364,6 +395,7 @@ mod tests { FeatureIndicator::CustomMtu => {} FeatureIndicator::CustomMssFix => {} FeatureIndicator::Daita => {} + FeatureIndicator::DaitaUseAnywhere => {} } } } From e141580b842834161a362a227b59fc233b6659de Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 19 Aug 2024 17:15:58 +0200 Subject: [PATCH 08/16] Remove daita cfg from mullvad-cli --- mullvad-cli/build.rs | 7 ------- mullvad-cli/src/cmds/relay.rs | 1 - mullvad-cli/src/cmds/tunnel.rs | 13 ++----------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/mullvad-cli/build.rs b/mullvad-cli/build.rs index ee547da265b5..de110d3a76b0 100644 --- a/mullvad-cli/build.rs +++ b/mullvad-cli/build.rs @@ -15,11 +15,4 @@ fn main() { )); res.compile().expect("Unable to generate windows resources"); } - let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set"); - - // Enable DAITA by default on desktop - println!("cargo::rustc-check-cfg=cfg(daita)"); - if let "linux" | "windows" | "macos" = target_os.as_str() { - println!(r#"cargo::rustc-cfg=daita"#); - } } diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index eaaa2834111b..4fe11b2a8f95 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -542,7 +542,6 @@ impl Relay { allowed_ips: all_of_the_internet(), endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port), psk: None, - #[cfg(daita)] constant_packet_size: false, }, exit_peer: None, diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 911c2f010bbd..c17877b438da 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -39,11 +39,9 @@ pub enum TunnelOptions { #[arg(long)] quantum_resistant: Option, /// Configure whether to enable DAITA - #[cfg(daita)] #[arg(long)] daita: Option, /// Configure whether to enable DAITA "use anywhere" - #[cfg(daita)] #[arg(long)] daita_use_anywhere: Option, /// The key rotation interval. Number of hours, or 'any' @@ -103,7 +101,6 @@ impl Tunnel { tunnel_options.wireguard.quantum_resistant, ); - #[cfg(daita)] print_option!("DAITA", tunnel_options.wireguard.daita.enabled); let key = rpc.get_wireguard_key().await?; @@ -140,9 +137,7 @@ impl Tunnel { TunnelOptions::Wireguard { mtu, quantum_resistant, - #[cfg(daita)] daita, - #[cfg(daita)] daita_use_anywhere, rotation_interval, rotate_key, @@ -150,9 +145,7 @@ impl Tunnel { Self::handle_wireguard( mtu, quantum_resistant, - #[cfg(daita)] daita, - #[cfg(daita)] daita_use_anywhere, rotation_interval, rotate_key, @@ -184,8 +177,8 @@ impl Tunnel { async fn handle_wireguard( mtu: Option>, quantum_resistant: Option, - #[cfg(daita)] daita: Option, - #[cfg(daita)] daita_use_anywhere: Option, + daita: Option, + daita_use_anywhere: Option, rotation_interval: Option>, rotate_key: Option, ) -> Result<()> { @@ -201,13 +194,11 @@ impl Tunnel { println!("Quantum resistant setting has been updated"); } - #[cfg(daita)] if let Some(enable_daita) = daita { rpc.set_enable_daita(*enable_daita).await?; println!("DAITA setting has been updated"); } - #[cfg(daita)] if let Some(daita_use_anywhere) = daita_use_anywhere { rpc.set_daita_use_anywhere(*daita_use_anywhere).await?; println!("DAITA setting has been updated"); From 52cc8aa95eb78917eed7e496ba213c2c6982563e Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 20 Aug 2024 10:40:39 +0200 Subject: [PATCH 09/16] Fix relay_selector daita tests --- .../src/relay_selector/query.rs | 10 ++++++ .../tests/relay_selector.rs | 32 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index cf9fc475ba30..bdf6843d41f3 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -662,6 +662,16 @@ pub mod builder { } } + // impl-block for after DAITA is set + impl RelayQueryBuilder> { + /// Enable DAITA use_anywhere. + pub fn daita_use_anywhere(mut self, constraint: impl Into>) -> Self { + self.query.wireguard_constraints.daita_use_anywhere = constraint.into(); + self + } + } + + impl RelayQueryBuilder> { /// Enable PQ support. pub fn quantum_resistant( diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index 1349b5af2611..2ac05c8a7c49 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -1448,7 +1448,11 @@ fn test_daita() { let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone()); // Only pick relays that support DAITA - let query = RelayQueryBuilder::new().wireguard().daita().build(); + let query = RelayQueryBuilder::new() + .wireguard() + .daita() + .daita_use_anywhere(false) + .build(); let relay = unwrap_entry_relay(relay_selector.get_relay_by_query(query).unwrap()); assert!( supports_daita(&relay), @@ -1459,12 +1463,36 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() + .daita_use_anywhere(false) .location(NON_DAITA_RELAY_LOCATION.clone()) .build(); relay_selector .get_relay_by_query(query) .expect_err("Expected to find no matching relay"); + // Should be able to connect to non-DAITA relay with use_anywhere + let query = RelayQueryBuilder::new() + .wireguard() + .daita() + .daita_use_anywhere(true) + .location(NON_DAITA_RELAY_LOCATION.clone()) + .build(); + let relay = relay_selector + .get_relay_by_query(query) + .expect("Expected to find a relay with daita_use_anywhere"); + match relay { + GetRelay::Wireguard { + inner: WireguardConfig::Multihop { exit, entry }, + .. + } => { + assert!(supports_daita(&entry), "entry relay must support DAITA"); + assert!(!supports_daita(&exit), "exit relay must not support DAITA"); + } + wrong_relay => panic!( + "Relay selector should have picked a Wireguard relay, instead chose {wrong_relay:?}" + ), + } + // DAITA-supporting relays can be picked even when it is disabled let query = RelayQueryBuilder::new() .wireguard() @@ -1487,6 +1515,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() + .daita_use_anywhere(false) .multihop() .build(); let relay = relay_selector.get_relay_by_query(query).unwrap(); @@ -1506,6 +1535,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() + .daita_use_anywhere(false) .multihop() .location(NON_DAITA_RELAY_LOCATION.clone()) .build(); From 800575568a26fedfacb012a759e988c5b5773a99 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 20 Aug 2024 11:20:05 +0200 Subject: [PATCH 10/16] Disable use_anywhere toggle if DAITA is disabled --- .../renderer/components/WireguardSettings.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index edcef77ea8f8..95bedbb33a9b 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -585,10 +585,23 @@ function DaitaSettings() { - + - The "Just make it work" Button + {messages.gettext('Use anywhere')} + + + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + '¯\\_(ツ)_/¯', // TODO + ), + { + daita: strings.daita, + }, + )} + + From 83b5d571c20c359e027cab30c4f44294a075623a Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 20 Aug 2024 16:15:45 +0200 Subject: [PATCH 11/16] Remove daita filter notice when using use_anywhere --- .../components/select-location/RelayListContext.tsx | 4 ++-- .../renderer/components/select-location/SelectLocation.tsx | 2 ++ gui/src/renderer/lib/filter-locations.ts | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/gui/src/renderer/components/select-location/RelayListContext.tsx b/gui/src/renderer/components/select-location/RelayListContext.tsx index 13156dd04537..ba86709080b7 100644 --- a/gui/src/renderer/components/select-location/RelayListContext.tsx +++ b/gui/src/renderer/components/select-location/RelayListContext.tsx @@ -66,7 +66,6 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { const fullRelayList = useSelector((state) => state.settings.relayLocations); const relaySettings = useNormalRelaySettings(); - const multihop = relaySettings?.wireguard.useMultihop ?? false; // Filters the relays to only keep the ones of the desired endpoint type, e.g. "wireguard", // "openvpn" or "bridge" @@ -79,7 +78,8 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { const relayListForDaita = useMemo(() => { return filterLocationsByDaita( relayListForEndpointType, - daita && (!useAnywhere || multihop), + daita, + useAnywhere, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index f6cf772019a2..36a69667da65 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -68,8 +68,10 @@ export default function SelectLocation() { const providers = relaySettings?.providers ?? []; const filteredProviders = useFilteredProviders(providers, ownership); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); const showDaitaFilter = daitaFilterActive( daita, + useAnywhere, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/lib/filter-locations.ts b/gui/src/renderer/lib/filter-locations.ts index 78126f1fb417..675a4fc88bc3 100644 --- a/gui/src/renderer/lib/filter-locations.ts +++ b/gui/src/renderer/lib/filter-locations.ts @@ -37,17 +37,19 @@ export function filterLocationsByEndPointType( export function filterLocationsByDaita( locations: IRelayLocationCountryRedux[], daita: boolean, + useAnywhere: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, ): IRelayLocationCountryRedux[] { - return daitaFilterActive(daita, locationType, tunnelProtocol, multihop) + return daitaFilterActive(daita, useAnywhere, locationType, tunnelProtocol, multihop) ? filterLocationsImpl(locations, (relay: IRelayLocationRelayRedux) => relay.daita) : locations; } export function daitaFilterActive( daita: boolean, + useAnywhere: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, @@ -55,7 +57,7 @@ export function daitaFilterActive( const isEntry = multihop ? locationType === LocationType.entry : locationType === LocationType.exit; - return daita && isEntry && tunnelProtocol !== 'openvpn'; + return daita && (!useAnywhere || multihop) && isEntry && tunnelProtocol !== 'openvpn'; } export function filterLocations( From 39bc888ca4fa4d74ed6113a7406da4061afec260 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Wed, 21 Aug 2024 14:24:01 +0200 Subject: [PATCH 12/16] Add unit test for daita_use_anywhere with singlehop --- .../tests/relay_selector.rs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index 2ac05c8a7c49..fc08ee23c18f 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -1489,7 +1489,29 @@ fn test_daita() { assert!(!supports_daita(&exit), "exit relay must not support DAITA"); } wrong_relay => panic!( - "Relay selector should have picked a Wireguard relay, instead chose {wrong_relay:?}" + "Relay selector should have picked two Wireguard relays, instead chose {wrong_relay:?}" + ), + } + + // Should be able to connect to DAITA relay with use_anywhere + let query = RelayQueryBuilder::new() + .wireguard() + .daita() + .daita_use_anywhere(true) + .location(DAITA_RELAY_LOCATION.clone()) + .build(); + let relay = relay_selector + .get_relay_by_query(query) + .expect("Expected to find a relay with daita_use_anywhere"); + match relay { + GetRelay::Wireguard { + inner: WireguardConfig::Singlehop { exit }, + .. + } => { + assert!(supports_daita(&exit), "entry relay must support DAITA"); + } + wrong_relay => panic!( + "Relay selector should have picked a single Wireguard relay, instead chose {wrong_relay:?}" ), } From db82d8936172a82ef82c206f2bcf47ef099c302f Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Thu, 22 Aug 2024 16:56:26 +0200 Subject: [PATCH 13/16] Fix daita rpc should-reconnect-check --- mullvad-daemon/src/lib.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 7a0dc2df2360..e6b4698c7384 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -2333,6 +2333,8 @@ impl Daemon { #[cfg(daita)] async fn on_set_daita_enabled(&mut self, tx: ResponseTx<(), settings::Error>, value: bool) { + use mullvad_types::{constraints::Constraint, Intersection}; + match self .settings .update(|settings| settings.tunnel_options.wireguard.daita.enabled = value) @@ -2340,7 +2342,16 @@ impl Daemon { { Ok(settings_changed) => { Self::oneshot_send(tx, Ok(()), "set_daita_enabled response"); - if settings_changed && self.get_target_tunnel_type() != Some(TunnelType::OpenVpn) { + let RelaySettings::Normal(constraints) = &self.settings.relay_settings else { + return; // DAITA is not supported for custom relays + }; + + let wireguard_enabled = constraints + .tunnel_protocol + .intersection(Constraint::Only(TunnelType::Wireguard)) + .is_some(); + + if settings_changed && wireguard_enabled { log::info!("Reconnecting because DAITA settings changed"); self.reconnect_tunnel(); } @@ -2358,6 +2369,8 @@ impl Daemon { tx: ResponseTx<(), settings::Error>, value: bool, ) { + use mullvad_types::{constraints::Constraint, Intersection}; + match self .settings .update(|settings| settings.tunnel_options.wireguard.daita.use_anywhere = value) @@ -2366,8 +2379,19 @@ impl Daemon { Ok(settings_changed) => { Self::oneshot_send(tx, Ok(()), "set_daita_use_anywhere response"); - // TODO: don't reconnect if multihop is enabled - if settings_changed && self.get_target_tunnel_type() != Some(TunnelType::OpenVpn) { + let RelaySettings::Normal(constraints) = &self.settings.relay_settings else { + return; // DAITA is not supported for custom relays + }; + + let wireguard_enabled = constraints + .tunnel_protocol + .intersection(Constraint::Only(TunnelType::Wireguard)) + .is_some(); + + let multihop_enabled = constraints.wireguard_constraints.use_multihop; + let daita_enabled = self.settings.tunnel_options.wireguard.daita.enabled; + + if settings_changed && wireguard_enabled && daita_enabled && !multihop_enabled { log::info!("Reconnecting because DAITA settings changed"); self.reconnect_tunnel(); } From cd78ee2663ad8effcd639a07050e948cabee2416 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 9 Sep 2024 10:20:31 +0200 Subject: [PATCH 14/16] Align Smart routing GUI with design spec --- gui/locales/messages.pot | 38 +++- gui/src/main/daemon-rpc.ts | 8 +- gui/src/main/settings.ts | 4 +- gui/src/renderer/app.tsx | 4 +- gui/src/renderer/components/AppRouter.tsx | 2 + gui/src/renderer/components/DaitaSettings.tsx | 193 ++++++++++++++++++ .../renderer/components/WireguardSettings.tsx | 118 +---------- .../main-view/FeatureIndicators.tsx | 88 ++++++-- .../select-location/RelayListContext.tsx | 6 +- .../select-location/SelectLocation.tsx | 6 +- gui/src/renderer/lib/filter-locations.ts | 8 +- gui/src/renderer/lib/routes.ts | 1 + gui/src/shared/daemon-rpc-types.ts | 4 +- gui/src/shared/ipc-schema.ts | 2 +- mullvad-cli/src/cmds/tunnel.rs | 17 +- mullvad-daemon/src/lib.rs | 32 +-- mullvad-daemon/src/management_interface.rs | 8 +- .../proto/management_interface.proto | 6 +- mullvad-management-interface/src/client.rs | 4 +- .../src/types/conversions/features.rs | 4 +- .../src/types/conversions/wireguard.rs | 4 +- .../src/relay_selector/mod.rs | 16 +- .../src/relay_selector/query.rs | 15 +- .../tests/relay_selector.rs | 20 +- mullvad-types/src/features.rs | 52 +++-- mullvad-types/src/wireguard.rs | 2 +- 26 files changed, 434 insertions(+), 228 deletions(-) create mode 100644 gui/src/renderer/components/DaitaSettings.tsx diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 5969e4ffd36b..730b75aacaa7 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -5,6 +5,11 @@ msgstr "" msgid "%(amount)d more..." msgstr "" +#. This refers to the Smart Routing setting in the VPN settings view. +#. This is displayed when both Smart Routing and DAITA features are on. +msgid "%(daita)s: Smart routing" +msgstr "" + msgid "%(duration)s was added, account paid until %(expiry)s." msgstr "" @@ -125,6 +130,9 @@ msgstr "" msgid "Disable" msgstr "" +msgid "Disable anyway" +msgstr "" + msgid "Disconnect" msgstr "" @@ -245,6 +253,9 @@ msgstr "" msgid "Settings" msgstr "" +msgid "Smart routing" +msgstr "" + msgid "System default" msgstr "" @@ -1934,6 +1945,10 @@ msgctxt "vpn-settings-view" msgid "IPv4 is always enabled and the majority of websites and applications use this protocol. We do not recommend enabling IPv6 unless you know you need it." msgstr "" +msgctxt "vpn-settings-view" +msgid "Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency." +msgstr "" + msgctxt "vpn-settings-view" msgid "It does this by allowing network communication outside the tunnel to local multicast and broadcast ranges as well as to and from these private IP ranges:" msgstr "" @@ -2065,7 +2080,7 @@ msgid "%(wireguard)s settings" msgstr "" msgctxt "wireguard-settings-view" -msgid "Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s." +msgid "Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage." msgstr "" msgctxt "wireguard-settings-view" @@ -2080,6 +2095,15 @@ msgctxt "wireguard-settings-view" msgid "MTU" msgstr "" +#. Warning text in a dialog that is displayed after a setting is toggled. +msgctxt "wireguard-settings-view" +msgid "Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing." +msgstr "" + +msgctxt "wireguard-settings-view" +msgid "Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first." +msgstr "" + msgctxt "wireguard-settings-view" msgid "Obfuscation" msgstr "" @@ -2134,11 +2158,6 @@ msgctxt "wireguard-settings-view" msgid "This allows access to %(wireguard)s for devices that only support IPv6." msgstr "" -#. Warning text in a dialog that is displayed after a setting is toggled. -msgctxt "wireguard-settings-view" -msgid "This feature isn’t available on all servers. You might need to change location after enabling." -msgstr "" - msgctxt "wireguard-settings-view" msgid "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers." msgstr "" @@ -2151,6 +2170,10 @@ msgctxt "wireguard-settings-view" msgid "UDP-over-TCP port" msgstr "" +msgctxt "wireguard-settings-view" +msgid "Use Smart routing" +msgstr "" + #. Text describing the valid port range for a port selector. msgctxt "wireguard-settings-view" msgid "Valid range: %(min)s - %(max)s" @@ -2541,6 +2564,9 @@ msgstr "" msgid "This address has already been entered." msgstr "" +msgid "This feature isn’t available on all servers. You might need to change location after enabling." +msgstr "" + msgid "This field is required" msgstr "" diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 9f1f2d760c1f..31f6ddcfabfb 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -589,8 +589,8 @@ export class DaemonRpc { await this.callBool(this.client.setEnableDaita, value); } - public async setDaitaUseAnywhere(value: boolean): Promise { - await this.callBool(this.client.setDaitaUseAnywhere, value); + public async setDaitaSmartRouting(value: boolean): Promise { + await this.callBool(this.client.setDaitaSmartRouting, value); } public async listDevices(accountToken: AccountToken): Promise> { @@ -1180,8 +1180,8 @@ function convertFromFeatureIndicator( return FeatureIndicator.customMssFix; case grpcTypes.FeatureIndicator.DAITA: return FeatureIndicator.daita; - case grpcTypes.FeatureIndicator.DAITA_USE_ANYWHERE: - return FeatureIndicator.daitaUseAnywhere; + case grpcTypes.FeatureIndicator.DAITA_SMART_ROUTING: + return FeatureIndicator.daitaSmartRouting; case grpcTypes.FeatureIndicator.SHADOWSOCKS: return FeatureIndicator.shadowsocks; } diff --git a/gui/src/main/settings.ts b/gui/src/main/settings.ts index 6ec4e0b09d2f..03537ba581a6 100644 --- a/gui/src/main/settings.ts +++ b/gui/src/main/settings.ts @@ -110,8 +110,8 @@ export default class Settings implements Readonly { IpcMainEventChannel.settings.handleSetEnableDaita((value) => { return this.daemonRpc.setEnableDaita(value); }); - IpcMainEventChannel.settings.handleSetDaitaUseAnywhere((value) => { - return this.daemonRpc.setDaitaUseAnywhere(value); + IpcMainEventChannel.settings.handleSetDaitaSmartRouting((value) => { + return this.daemonRpc.setDaitaSmartRouting(value); }); IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => { diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 168e88dcb6f6..a4d5fc2fada4 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -346,8 +346,8 @@ export default class AppRenderer { IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings); public setEnableDaita = (value: boolean) => IpcRendererEventChannel.settings.setEnableDaita(value); - public setDaitaUseAnywhere = (value: boolean) => - IpcRendererEventChannel.settings.setDaitaUseAnywhere(value); + public setDaitaSmartRouting = (value: boolean) => + IpcRendererEventChannel.settings.setDaitaSmartRouting(value); public collectProblemReport = (toRedact: string | undefined) => IpcRendererEventChannel.problemReport.collectLogs(toRedact); public viewLog = (path: string) => IpcRendererEventChannel.problemReport.viewLog(path); diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index e90729507c16..75b8df936c3b 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -8,6 +8,7 @@ import { ITransitionSpecification, transitions, useHistory } from '../lib/histor import { RoutePath } from '../lib/routes'; import Account from './Account'; import ApiAccessMethods from './ApiAccessMethods'; +import DaitaSettings from './DaitaSettings'; import Debug from './Debug'; import { DeviceRevokedView } from './DeviceRevokedView'; import { EditApiAccessMethod } from './EditApiAccessMethod'; @@ -85,6 +86,7 @@ export default function AppRouter() { + diff --git a/gui/src/renderer/components/DaitaSettings.tsx b/gui/src/renderer/components/DaitaSettings.tsx new file mode 100644 index 000000000000..3176192ad10d --- /dev/null +++ b/gui/src/renderer/components/DaitaSettings.tsx @@ -0,0 +1,193 @@ +import { useCallback } from 'react'; +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; + +import { strings } from '../../config.json'; +import { messages } from '../../shared/gettext'; +import { useAppContext } from '../context'; +import { useHistory } from '../lib/history'; +import { useBoolean } from '../lib/utilityHooks'; +import { useSelector } from '../redux/store'; +import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; +import * as Cell from './cell'; +import InfoButton from './InfoButton'; +import { BackAction } from './KeyboardNavigation'; +import { Layout, SettingsContainer } from './Layout'; +import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; +import { + NavigationBar, + NavigationContainer, + NavigationInfoButton, + NavigationItems, + NavigationScrollbars, + TitleBarItem, +} from './NavigationBar'; +import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader'; +import { SmallButton, SmallButtonColor } from './SmallButton'; + +const StyledContent = styled.div({ + display: 'flex', + flexDirection: 'column', + flex: 1, + marginBottom: '2px', +}); + +export default function DaitaSettings() { + const { pop } = useHistory(); + + return ( + + + + + + + {strings.daita} + + + + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.', + ), + { daita: strings.daita, daitaFull: strings.daitaFull }, + )} + + + + + + + + {strings.daita} + + {messages.pgettext( + 'wireguard-settings-view', + 'Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage.', + )} + + + + + + + + + + + + + + ); +} + +function DaitaToggle() { + const { setEnableDaita, setDaitaSmartRouting } = useAppContext(); + const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); + + const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); + + const setDaita = useCallback((value: boolean) => { + void setEnableDaita(value); + }, []); + + const setSmartRouting = useCallback((value: boolean) => { + if (value) { + void setDaitaSmartRouting(value); + } else { + showConfirmationDialog(); + } + }, []); + + const confirmDisableSmartRouting = useCallback(() => { + void setDaitaSmartRouting(false); + hideConfirmationDialog(); + }, []); + + return ( + <> + + + + {messages.gettext('Enable')} + + + + + + + + + + {messages.gettext('Smart routing')} + + + + + + + + + + + + {sprintf( + messages.pgettext( + 'vpn-settings-view', + 'Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency.', + ), + { daita: strings.daita }, + )} + + + + + + {messages.gettext('Disable anyway')} + , + + {messages.pgettext('wireguard-settings-view', 'Use Smart routing')} + , + ]} + close={hideConfirmationDialog}> + + {sprintf( + // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled. + messages.pgettext( + 'wireguard-settings-view', + 'Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing.', + ), + { daita: strings.daita }, + )} + + + + ); +} + +export function SmartRoutingModalMessage() { + return ( + + {sprintf( + messages.pgettext( + 'wireguard-settings-view', + 'Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first.', + ), + { + daita: strings.daita, + }, + )} + + ); +} diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 95bedbb33a9b..8f2329d77f05 100644 --- a/gui/src/renderer/components/WireguardSettings.tsx +++ b/gui/src/renderer/components/WireguardSettings.tsx @@ -22,7 +22,6 @@ import * as AppButton from './AppButton'; import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup'; import * as Cell from './cell'; import Selector, { SelectorItem, SelectorWithCustomItem } from './cell/Selector'; -import InfoButton from './InfoButton'; import { BackAction } from './KeyboardNavigation'; import { Layout, SettingsContainer } from './Layout'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; @@ -98,7 +97,7 @@ export default function WireguardSettings() { - + @@ -528,117 +527,16 @@ function MtuSetting() { ); } -function DaitaSettings() { - const { setEnableDaita, setDaitaUseAnywhere } = useAppContext(); +function DaitaButton() { + const history = useHistory(); + const navigate = useCallback(() => history.push(RoutePath.daitaSettings), [history]); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); - const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); - - const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); - - const setDaita = useCallback((value: boolean) => { - if (value) { - showConfirmationDialog(); - } else { - void setEnableDaita(value); - } - }, []); - - const setUseAnywhere = useCallback((value: boolean) => { - void setDaitaUseAnywhere(value); - }, []); - - const confirmDaita = useCallback(() => { - void setEnableDaita(true); - hideConfirmationDialog(); - }, []); return ( - <> - - - - {strings.daita} - - - - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.', - ), - { daita: strings.daita, daitaFull: strings.daitaFull }, - )} - - - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.', - ), - { daita: strings.daita }, - )} - - - - - - - - - - - {messages.gettext('Use anywhere')} - - - - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - '¯\\_(ツ)_/¯', // TODO - ), - { - daita: strings.daita, - }, - )} - - - - - - - - - {messages.gettext('Enable anyway')} - , - - {messages.gettext('Back')} - , - ]} - close={hideConfirmationDialog}> - - { - // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled. - messages.pgettext( - 'wireguard-settings-view', - 'This feature isn’t available on all servers. You might need to change location after enabling.', - ) - } - - - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.', - ), - { daita: strings.daita }, - )} - - - + + {strings.daita} + {daita ? messages.gettext('On') : messages.gettext('Off')} + ); } diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx index 97ff92742b54..4d189e712c5d 100644 --- a/gui/src/renderer/components/main-view/FeatureIndicators.tsx +++ b/gui/src/renderer/components/main-view/FeatureIndicators.tsx @@ -5,9 +5,13 @@ import styled from 'styled-components'; import { colors, strings } from '../../../config.json'; import { FeatureIndicator } from '../../../shared/daemon-rpc-types'; import { messages } from '../../../shared/gettext'; -import { useStyledRef } from '../../lib/utilityHooks'; +import { useBoolean, useStyledRef } from '../../lib/utilityHooks'; import { useSelector } from '../../redux/store'; import { tinyText } from '../common-styles'; +import { SmartRoutingModalMessage } from '../DaitaSettings'; +import { InfoIcon } from '../InfoButton'; +import { ModalAlert, ModalAlertType } from '../Modal'; +import { SmallButton, SmallButtonColor } from '../SmallButton'; import { ConnectionPanelAccordion } from './styles'; const LINE_HEIGHT = 22; @@ -43,8 +47,9 @@ const StyledFeatureIndicatorsWrapper = styled.div<{ $expanded: boolean }>((props })); const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText, (props) => ({ - display: 'inline', - padding: '2px 8px', + display: 'flex', + gap: '4px', + padding: '1px 7px', justifyContent: 'center', alignItems: 'center', borderRadius: '4px', @@ -53,6 +58,15 @@ const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText fontWeight: 400, whiteSpace: 'nowrap', visibility: props.$expanded ? 'visible' : 'hidden', + + // Style clickable feature indicators with a border and on-hover effect + boxSizing: 'border-box', // make border act as padding rather than margin + border: 'solid 1px', + borderColor: props.onClick ? colors.blue : colors.darkerBlue, + transition: 'background ease-in-out 300ms', + '&&:hover': { + background: props.onClick ? colors.blue60 : undefined, + }, })); const StyledBaseEllipsis = styled.span<{ $display: boolean }>(tinyText, (props) => ({ @@ -88,6 +102,11 @@ interface FeatureIndicatorsProps { // we can count those and add another ellipsis element which is visible and place it after the last // visible indicator. export default function FeatureIndicators(props: FeatureIndicatorsProps) { + const [ + daitaSmartRoutingDialogueVisible, + showDaitaSmartRoutingDialogue, + hideDaitaSmartRoutingDialogue, + ] = useBoolean(); const tunnelState = useSelector((state) => state.connection.status); const ellipsisRef = useStyledRef(); const ellipsisSpacerRef = useStyledRef(); @@ -106,6 +125,16 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { const ellipsis = messages.gettext('%(amount)d more...'); + // Returns an optional callback for clickable feature indicators, or undefined. + const getFeatureIndicatorOnClick = (indicator: FeatureIndicator) => { + switch (indicator) { + case FeatureIndicator.daitaSmartRouting: + return showDaitaSmartRoutingDialogue; + default: + return undefined; + } + }; + useEffect(() => { // We need to defer the visibility logic one painting cycle to make sure the elements are // rendered and available. @@ -164,7 +193,7 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { return ( 0}> - + {messages.pgettext('connect-view', 'Active features')} @@ -172,16 +201,20 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { - {sortedIndicators.map((indicator) => ( - - {getFeatureIndicatorLabel(indicator)} - - ))} + {sortedIndicators.map((indicator) => { + const onClick = getFeatureIndicatorOnClick(indicator); + return ( + + {getFeatureIndicatorLabel(indicator)} + {onClick ? : null} + + ); + })} - { // Mock amount for the spacer ellipsis. This needs to be wider than the real @@ -189,8 +222,28 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { sprintf(ellipsis, { amount: 222 }) } + + + + {messages.gettext('Got it!')} + , + ]} + close={hideDaitaSmartRoutingDialogue}> + + ); } @@ -216,8 +269,13 @@ function getFeatureIndicatorLabel(indicator: FeatureIndicator) { switch (indicator) { case FeatureIndicator.daita: return strings.daita; - case FeatureIndicator.daitaUseAnywhere: - return messages.pgettext('wireguard-settings-view', 'DAITA: Use Anywhere'); + case FeatureIndicator.daitaSmartRouting: + return sprintf( + // TRANSLATORS: This refers to the Smart Routing setting in the VPN settings view. + // TRANSLATORS: This is displayed when both Smart Routing and DAITA features are on. + messages.gettext('%(daita)s: Smart routing'), + { daita: strings.daita }, + ); case FeatureIndicator.udp2tcp: case FeatureIndicator.shadowsocks: return messages.pgettext('wireguard-settings-view', 'Obfuscation'); diff --git a/gui/src/renderer/components/select-location/RelayListContext.tsx b/gui/src/renderer/components/select-location/RelayListContext.tsx index ba86709080b7..241d11fbdcf0 100644 --- a/gui/src/renderer/components/select-location/RelayListContext.tsx +++ b/gui/src/renderer/components/select-location/RelayListContext.tsx @@ -62,7 +62,9 @@ interface RelayListContextProviderProps { export function RelayListContextProvider(props: RelayListContextProviderProps) { const { locationType, searchTerm } = useSelectLocationContext(); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); - const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); const fullRelayList = useSelector((state) => state.settings.relayLocations); const relaySettings = useNormalRelaySettings(); @@ -79,7 +81,7 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { return filterLocationsByDaita( relayListForEndpointType, daita, - useAnywhere, + smartRouting, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index 36a69667da65..9cd66f98f435 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -68,10 +68,12 @@ export default function SelectLocation() { const providers = relaySettings?.providers ?? []; const filteredProviders = useFilteredProviders(providers, ownership); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); - const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); const showDaitaFilter = daitaFilterActive( daita, - useAnywhere, + smartRouting, locationType, relaySettings?.tunnelProtocol ?? 'any', relaySettings?.wireguard.useMultihop ?? false, diff --git a/gui/src/renderer/lib/filter-locations.ts b/gui/src/renderer/lib/filter-locations.ts index 675a4fc88bc3..661d8ecc2950 100644 --- a/gui/src/renderer/lib/filter-locations.ts +++ b/gui/src/renderer/lib/filter-locations.ts @@ -37,19 +37,19 @@ export function filterLocationsByEndPointType( export function filterLocationsByDaita( locations: IRelayLocationCountryRedux[], daita: boolean, - useAnywhere: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, ): IRelayLocationCountryRedux[] { - return daitaFilterActive(daita, useAnywhere, locationType, tunnelProtocol, multihop) + return daitaFilterActive(daita, smartRouting, locationType, tunnelProtocol, multihop) ? filterLocationsImpl(locations, (relay: IRelayLocationRelayRedux) => relay.daita) : locations; } export function daitaFilterActive( daita: boolean, - useAnywhere: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, @@ -57,7 +57,7 @@ export function daitaFilterActive( const isEntry = multihop ? locationType === LocationType.entry : locationType === LocationType.exit; - return daita && (!useAnywhere || multihop) && isEntry && tunnelProtocol !== 'openvpn'; + return daita && (!smartRouting || multihop) && isEntry && tunnelProtocol !== 'openvpn'; } export function filterLocations( diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts index 0ccc3679ff81..5204dc666c61 100644 --- a/gui/src/renderer/lib/routes.ts +++ b/gui/src/renderer/lib/routes.ts @@ -15,6 +15,7 @@ export enum RoutePath { userInterfaceSettings = '/settings/interface', vpnSettings = '/settings/vpn', wireguardSettings = '/settings/advanced/wireguard', + daitaSettings = '/settings/advanced/wireguard/daita', udpOverTcp = '/settings/advanced/wireguard/udp-over-tcp', shadowsocks = '/settings/advanced/shadowsocks', openVpnSettings = '/settings/advanced/openvpn', diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index f888413047af..33ff6bb8ce3d 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -183,7 +183,7 @@ export interface ITunnelStateRelayInfo { // The order of the variants match the priority order and can be sorted on. export enum FeatureIndicator { daita, - daitaUseAnywhere, + daitaSmartRouting, quantumResistance, multihop, bridgeMode, @@ -552,7 +552,7 @@ export interface RelayOverride { export interface IDaitaSettings { enabled: boolean; - useAnywhere: boolean; + smartRouting: boolean; } export function parseSocketAddress(socketAddrStr: string): ISocketAddress { diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts index 78acc9fae99b..4e08e7109d47 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -195,7 +195,7 @@ export const ipcSchema = { testCustomApiAccessMethod: invoke(), clearAllRelayOverrides: invoke(), setEnableDaita: invoke(), - setDaitaUseAnywhere: invoke(), + setDaitaSmartRouting: invoke(), }, guiSettings: { '': notifyRenderer(), diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index c17877b438da..da66cac5d505 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -41,9 +41,9 @@ pub enum TunnelOptions { /// Configure whether to enable DAITA #[arg(long)] daita: Option, - /// Configure whether to enable DAITA "use anywhere" + /// Configure whether to enable DAITA smart routing #[arg(long)] - daita_use_anywhere: Option, + daita_smart_routing: Option, /// The key rotation interval. Number of hours, or 'any' #[arg(long)] rotation_interval: Option>, @@ -138,7 +138,7 @@ impl Tunnel { mtu, quantum_resistant, daita, - daita_use_anywhere, + daita_smart_routing, rotation_interval, rotate_key, } => { @@ -146,7 +146,7 @@ impl Tunnel { mtu, quantum_resistant, daita, - daita_use_anywhere, + daita_smart_routing, rotation_interval, rotate_key, ) @@ -178,7 +178,7 @@ impl Tunnel { mtu: Option>, quantum_resistant: Option, daita: Option, - daita_use_anywhere: Option, + daita_smart_routing: Option, rotation_interval: Option>, rotate_key: Option, ) -> Result<()> { @@ -197,11 +197,12 @@ impl Tunnel { if let Some(enable_daita) = daita { rpc.set_enable_daita(*enable_daita).await?; println!("DAITA setting has been updated"); + println!("Smart routing setting has been updated"); } - if let Some(daita_use_anywhere) = daita_use_anywhere { - rpc.set_daita_use_anywhere(*daita_use_anywhere).await?; - println!("DAITA setting has been updated"); + if let Some(daita_smart_routing) = daita_smart_routing { + rpc.set_daita_smart_routing(*daita_smart_routing).await?; + println!("Smart routing setting has been updated"); } if let Some(interval) = rotation_interval { diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index e6b4698c7384..097dcc6bf65d 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -266,7 +266,7 @@ pub enum DaemonCommand { #[cfg(daita)] SetEnableDaita(ResponseTx<(), settings::Error>, bool), #[cfg(daita)] - SetDaitaUseAnywhere(ResponseTx<(), settings::Error>, bool), + SetDaitaSmartRouting(ResponseTx<(), settings::Error>, bool), #[cfg(daita)] SetDaitaSettings(ResponseTx<(), settings::Error>, DaitaSettings), /// Set DNS options or servers to use @@ -1261,7 +1261,7 @@ impl Daemon { #[cfg(daita)] SetEnableDaita(tx, value) => self.on_set_daita_enabled(tx, value).await, #[cfg(daita)] - SetDaitaUseAnywhere(tx, value) => self.on_set_daita_use_anywhere(tx, value).await, + SetDaitaSmartRouting(tx, value) => self.on_set_daita_smart_routing(tx, value).await, #[cfg(daita)] SetDaitaSettings(tx, daita_settings) => { self.on_set_daita_settings(tx, daita_settings).await @@ -2335,11 +2335,19 @@ impl Daemon { async fn on_set_daita_enabled(&mut self, tx: ResponseTx<(), settings::Error>, value: bool) { use mullvad_types::{constraints::Constraint, Intersection}; - match self + let result = self .settings - .update(|settings| settings.tunnel_options.wireguard.daita.enabled = value) - .await - { + .update(|settings| { + settings.tunnel_options.wireguard.daita.enabled = value; + + // enable smart-routing automatically with daita + if cfg!(not(target_os = "android")) { + settings.tunnel_options.wireguard.daita.smart_routing = value + } + }) + .await; + + match result { Ok(settings_changed) => { Self::oneshot_send(tx, Ok(()), "set_daita_enabled response"); let RelaySettings::Normal(constraints) = &self.settings.relay_settings else { @@ -2364,7 +2372,7 @@ impl Daemon { } #[cfg(daita)] - async fn on_set_daita_use_anywhere( + async fn on_set_daita_smart_routing( &mut self, tx: ResponseTx<(), settings::Error>, value: bool, @@ -2373,11 +2381,11 @@ impl Daemon { match self .settings - .update(|settings| settings.tunnel_options.wireguard.daita.use_anywhere = value) + .update(|settings| settings.tunnel_options.wireguard.daita.smart_routing = value) .await { Ok(settings_changed) => { - Self::oneshot_send(tx, Ok(()), "set_daita_use_anywhere response"); + Self::oneshot_send(tx, Ok(()), "set_daita_smart_routing response"); let RelaySettings::Normal(constraints) = &self.settings.relay_settings else { return; // DAITA is not supported for custom relays @@ -2398,7 +2406,7 @@ impl Daemon { } Err(e) => { log::error!("{}", e.display_chain_with_msg("Unable to save settings")); - Self::oneshot_send(tx, Err(e), "set_daita_use_anywhere response"); + Self::oneshot_send(tx, Err(e), "set_daita_smart_routing response"); } } } @@ -3012,12 +3020,12 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig { #[cfg(daita)] daita: settings.tunnel_options.wireguard.daita.enabled, #[cfg(daita)] - daita_use_anywhere: settings.tunnel_options.wireguard.daita.use_anywhere, + daita_smart_routing: settings.tunnel_options.wireguard.daita.smart_routing, #[cfg(not(daita))] daita: false, #[cfg(not(daita))] - daita_use_anywhere: false, + daita_smart_routing: false, quantum_resistant: settings.tunnel_options.wireguard.quantum_resistant, }, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 594fd7a19e63..331754d1f25f 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -352,11 +352,11 @@ impl ManagementService for ManagementServiceImpl { } #[cfg(daita)] - async fn set_daita_use_anywhere(&self, request: Request) -> ServiceResult<()> { + async fn set_daita_smart_routing(&self, request: Request) -> ServiceResult<()> { let value = request.into_inner(); - log::debug!("set_daita_use_anywhere({value})"); + log::debug!("set_daita_smart_routing({value})"); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetDaitaUseAnywhere(tx, value))?; + self.send_command_to_daemon(DaemonCommand::SetDaitaSmartRouting(tx, value))?; self.wait_for_result(rx).await?.map(Response::new)?; Ok(Response::new(())) } @@ -381,7 +381,7 @@ impl ManagementService for ManagementServiceImpl { } #[cfg(not(daita))] - async fn set_daita_use_anywhere(&self, _: Request) -> ServiceResult<()> { + async fn set_daita_smart_routing(&self, _: Request) -> ServiceResult<()> { Ok(Response::new(())) } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 29acf34923cd..d701fc8e4f9b 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -49,7 +49,7 @@ service ManagementService { rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetQuantumResistantTunnel(QuantumResistantState) returns (google.protobuf.Empty) {} rpc SetEnableDaita(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} - rpc SetDaitaUseAnywhere(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetDaitaSmartRouting(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetDaitaSettings(DaitaSettings) returns (google.protobuf.Empty) {} rpc SetDnsOptions(DnsOptions) returns (google.protobuf.Empty) {} rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {} @@ -264,7 +264,7 @@ enum FeatureIndicator { CUSTOM_MTU = 11; CUSTOM_MSS_FIX = 12; DAITA = 13; - DAITA_USE_ANYWHERE = 14; + DAITA_SMART_ROUTING = 14; } message ObfuscationEndpoint { @@ -546,7 +546,7 @@ message QuantumResistantState { message DaitaSettings { bool enabled = 1; - bool use_anywhere = 2; + bool smart_routing = 2; } message TunnelOptions { diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 14676ceeb962..f782d3ab3242 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -385,9 +385,9 @@ impl MullvadProxyClient { } #[cfg(daita)] - pub async fn set_daita_use_anywhere(&mut self, value: bool) -> Result<()> { + pub async fn set_daita_smart_routing(&mut self, value: bool) -> Result<()> { self.0 - .set_daita_use_anywhere(value) + .set_daita_smart_routing(value) .await .map_err(Error::Rpc)?; Ok(()) diff --git a/mullvad-management-interface/src/types/conversions/features.rs b/mullvad-management-interface/src/types/conversions/features.rs index 971c2a01155f..85c85b8b7786 100644 --- a/mullvad-management-interface/src/types/conversions/features.rs +++ b/mullvad-management-interface/src/types/conversions/features.rs @@ -18,7 +18,7 @@ impl From for proto::FeatureIndicator mullvad_types::features::FeatureIndicator::CustomMtu => CustomMtu, mullvad_types::features::FeatureIndicator::CustomMssFix => CustomMssFix, mullvad_types::features::FeatureIndicator::Daita => Daita, - mullvad_types::features::FeatureIndicator::DaitaUseAnywhere => DaitaUseAnywhere, + mullvad_types::features::FeatureIndicator::DaitaSmartRouting => DaitaSmartRouting, } } } @@ -40,7 +40,7 @@ impl From for mullvad_types::features::FeatureIndicator proto::FeatureIndicator::CustomMtu => Self::CustomMtu, proto::FeatureIndicator::CustomMssFix => Self::CustomMssFix, proto::FeatureIndicator::Daita => Self::Daita, - proto::FeatureIndicator::DaitaUseAnywhere => Self::DaitaUseAnywhere, + proto::FeatureIndicator::DaitaSmartRouting => Self::DaitaSmartRouting, } } } diff --git a/mullvad-management-interface/src/types/conversions/wireguard.rs b/mullvad-management-interface/src/types/conversions/wireguard.rs index cf30a8cc99cd..9e40b4b52600 100644 --- a/mullvad-management-interface/src/types/conversions/wireguard.rs +++ b/mullvad-management-interface/src/types/conversions/wireguard.rs @@ -78,7 +78,7 @@ impl From for proto::DaitaSettings { fn from(settings: mullvad_types::wireguard::DaitaSettings) -> Self { proto::DaitaSettings { enabled: settings.enabled, - use_anywhere: settings.use_anywhere, + smart_routing: settings.smart_routing, } } } @@ -88,7 +88,7 @@ impl From for mullvad_types::wireguard::DaitaSettings { fn from(settings: proto::DaitaSettings) -> Self { mullvad_types::wireguard::DaitaSettings { enabled: settings.enabled, - use_anywhere: settings.use_anywhere, + smart_routing: settings.smart_routing, } } } diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index 1635de88570c..fb3c37064c52 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -127,7 +127,7 @@ pub struct AdditionalWireguardConstraints { /// If true and multihop is disabled, will set up multihop with an automatic entry relay if /// DAITA is enabled. - pub daita_use_anywhere: bool, + pub daita_smart_routing: bool, /// If enabled, select relays that support PQ. pub quantum_resistant: QuantumResistantState, @@ -350,7 +350,7 @@ impl<'a> TryFrom> for RelayQuery { } = wireguard_constraints; let AdditionalWireguardConstraints { daita, - daita_use_anywhere, + daita_smart_routing, quantum_resistant, } = additional_constraints; WireguardRelayQuery { @@ -360,7 +360,7 @@ impl<'a> TryFrom> for RelayQuery { entry_location, obfuscation: ObfuscationQuery::from(obfuscation_settings), daita: Constraint::Only(daita), - daita_use_anywhere: Constraint::Only(daita_use_anywhere), + daita_smart_routing: Constraint::Only(daita_smart_routing), quantum_resistant, } } @@ -739,18 +739,18 @@ impl RelaySelector { Result::<_, Error>::Ok(!candidates.is_empty()) }; - // is `use_anywhere` enabled? - let use_anywhere = || { + // is `smart_routing` enabled? + let smart_routing = || { query .wireguard_constraints() - .daita_use_anywhere + .daita_smart_routing .intersection(Constraint::Only(true)) .is_some() }; - // if we found no matching relays because DAITA was enabled, and `use_anywhere` is enabled, + // if we found no matching relays because DAITA was enabled, and `smart_routing` is enabled, // try enabling multihop and connecting using an automatically selected entry relay. - if candidates.is_empty() && using_daita() && no_relay_because_daita()? && use_anywhere() { + if candidates.is_empty() && using_daita() && no_relay_because_daita()? && smart_routing() { return Self::get_wireguard_auto_multihop_config(query, custom_lists, parsed_relays); } diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index bdf6843d41f3..4a9cd7c12b2c 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -261,7 +261,7 @@ pub struct WireguardRelayQuery { pub entry_location: Constraint, pub obfuscation: ObfuscationQuery, pub daita: Constraint, - pub daita_use_anywhere: Constraint, + pub daita_smart_routing: Constraint, pub quantum_resistant: QuantumResistantState, } @@ -347,7 +347,7 @@ impl WireguardRelayQuery { entry_location: Constraint::Any, obfuscation: ObfuscationQuery::Auto, daita: Constraint::Any, - daita_use_anywhere: Constraint::Any, + daita_smart_routing: Constraint::Any, quantum_resistant: QuantumResistantState::Auto, } } @@ -663,15 +663,16 @@ pub mod builder { } // impl-block for after DAITA is set - impl RelayQueryBuilder> { - /// Enable DAITA use_anywhere. - pub fn daita_use_anywhere(mut self, constraint: impl Into>) -> Self { - self.query.wireguard_constraints.daita_use_anywhere = constraint.into(); + impl + RelayQueryBuilder> + { + /// Enable DAITA smart routing. + pub fn daita_smart_routing(mut self, constraint: impl Into>) -> Self { + self.query.wireguard_constraints.daita_smart_routing = constraint.into(); self } } - impl RelayQueryBuilder> { /// Enable PQ support. pub fn quantum_resistant( diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs index fc08ee23c18f..dc05d04eb2aa 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -1451,7 +1451,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .build(); let relay = unwrap_entry_relay(relay_selector.get_relay_by_query(query).unwrap()); assert!( @@ -1463,23 +1463,23 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .location(NON_DAITA_RELAY_LOCATION.clone()) .build(); relay_selector .get_relay_by_query(query) .expect_err("Expected to find no matching relay"); - // Should be able to connect to non-DAITA relay with use_anywhere + // Should be able to connect to non-DAITA relay with smart_routing let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(true) + .daita_smart_routing(true) .location(NON_DAITA_RELAY_LOCATION.clone()) .build(); let relay = relay_selector .get_relay_by_query(query) - .expect("Expected to find a relay with daita_use_anywhere"); + .expect("Expected to find a relay with daita_smart_routing"); match relay { GetRelay::Wireguard { inner: WireguardConfig::Multihop { exit, entry }, @@ -1493,16 +1493,16 @@ fn test_daita() { ), } - // Should be able to connect to DAITA relay with use_anywhere + // Should be able to connect to DAITA relay with smart_routing let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(true) + .daita_smart_routing(true) .location(DAITA_RELAY_LOCATION.clone()) .build(); let relay = relay_selector .get_relay_by_query(query) - .expect("Expected to find a relay with daita_use_anywhere"); + .expect("Expected to find a relay with daita_smart_routing"); match relay { GetRelay::Wireguard { inner: WireguardConfig::Singlehop { exit }, @@ -1537,7 +1537,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .multihop() .build(); let relay = relay_selector.get_relay_by_query(query).unwrap(); @@ -1557,7 +1557,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .multihop() .location(NON_DAITA_RELAY_LOCATION.clone()) .build(); diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs index 592d2382e2af..ba40e42d1800 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -65,8 +65,14 @@ pub enum FeatureIndicator { ServerIpOverride, CustomMtu, CustomMssFix, + + /// Whether DAITA (without smart routing) is in use. + /// Mutually exclusive with [FeatureIndicator::DaitaSmartRouting]. Daita, - DaitaUseAnywhere, + + /// Whether DAITA (with smart routing) is in use. + /// Mutually exclusive with [FeatureIndicator::Daita]. + DaitaSmartRouting, } impl FeatureIndicator { @@ -86,7 +92,7 @@ impl FeatureIndicator { FeatureIndicator::CustomMtu => "Custom MTU", FeatureIndicator::CustomMssFix => "Custom MSS", FeatureIndicator::Daita => "DAITA", - FeatureIndicator::DaitaUseAnywhere => "Use Anywhere (DAITA)", + FeatureIndicator::DaitaSmartRouting => "DAITA: Smart Routing", } } } @@ -146,7 +152,6 @@ pub fn compute_feature_indicators( } TunnelType::Wireguard => { let quantum_resistant = endpoint.quantum_resistant; - let multihop = endpoint.entry_endpoint.is_some(); let udp_tcp = endpoint .obfuscation .as_ref() @@ -160,20 +165,28 @@ pub fn compute_feature_indicators( let mtu = settings.tunnel_options.wireguard.mtu.is_some(); - #[cfg(daita)] - let daita = endpoint.daita; + let mut daita_smart_routing = false; + let mut multihop = false; - #[cfg(daita)] - let daita_use_anywhere = - if let crate::relay_constraints::RelaySettings::Normal(constraints) = - &settings.relay_settings + if let crate::relay_constraints::RelaySettings::Normal(constraints) = + &settings.relay_settings + { + multihop = endpoint.entry_endpoint.is_some() + && constraints.wireguard_constraints.use_multihop; + + #[cfg(daita)] { - // Detect whether we're using "use_anywhere" by checking if multihop is + // Detect whether we're using "smart_routing" by checking if multihop is // in use but not explicitly enabled. - daita && multihop && !constraints.wireguard_constraints.use_multihop - } else { - false - }; + daita_smart_routing = endpoint.daita + && endpoint.entry_endpoint.is_some() + && !constraints.wireguard_constraints.use_multihop + } + }; + + // Daita is mutually exclusive with DaitaSmartRouting + #[cfg(daita)] + let daita = endpoint.daita && !daita_smart_routing; vec![ (quantum_resistant, FeatureIndicator::QuantumResistance), @@ -183,8 +196,7 @@ pub fn compute_feature_indicators( (mtu, FeatureIndicator::CustomMtu), #[cfg(daita)] (daita, FeatureIndicator::Daita), - #[cfg(daita)] - (daita_use_anywhere, FeatureIndicator::DaitaUseAnywhere), + (daita_smart_routing, FeatureIndicator::DaitaSmartRouting), ] } }; @@ -370,11 +382,13 @@ mod tests { }; expected_indicators .0 - .insert(FeatureIndicator::DaitaUseAnywhere); + .insert(FeatureIndicator::DaitaSmartRouting); + expected_indicators.0.remove(&FeatureIndicator::Daita); + expected_indicators.0.remove(&FeatureIndicator::Multihop); assert_eq!( compute_feature_indicators(&settings, &endpoint, false), expected_indicators, - "DaitaUseAnywhere should be enable" + "DaitaSmartRouting should be enabled" ); } @@ -395,7 +409,7 @@ mod tests { FeatureIndicator::CustomMtu => {} FeatureIndicator::CustomMssFix => {} FeatureIndicator::Daita => {} - FeatureIndicator::DaitaUseAnywhere => {} + FeatureIndicator::DaitaSmartRouting => {} } } } diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs index 8f666575895c..2850e7f10c38 100644 --- a/mullvad-types/src/wireguard.rs +++ b/mullvad-types/src/wireguard.rs @@ -82,7 +82,7 @@ pub struct DaitaSettings { pub enabled: bool, #[serde(default)] - pub use_anywhere: bool, + pub smart_routing: bool, } /// Contains account specific wireguard data From 311bc57a05d4de0eaa1aafa9453599a870471644 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Wed, 28 Aug 2024 14:48:44 +0200 Subject: [PATCH 15/16] Add DAITA smart routing e2e tests --- .../src/relay_selector/query.rs | 11 +- test/test-manager/src/tests/daita.rs | 200 ++++++++++++++++++ test/test-manager/src/tests/mod.rs | 6 +- test/test-manager/src/tests/tunnel.rs | 35 --- 4 files changed, 214 insertions(+), 38 deletions(-) create mode 100644 test/test-manager/src/tests/daita.rs diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index 4a9cd7c12b2c..2dc81b083386 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -34,8 +34,8 @@ use mullvad_types::{ relay_constraints::{ BridgeConstraints, BridgeSettings, BridgeState, BridgeType, LocationConstraint, ObfuscationSettings, OpenVpnConstraints, Ownership, Providers, RelayConstraints, - SelectedObfuscation, ShadowsocksSettings, TransportPort, Udp2TcpObfuscationSettings, - WireguardConstraints, + RelaySettings, SelectedObfuscation, ShadowsocksSettings, TransportPort, + Udp2TcpObfuscationSettings, WireguardConstraints, }, wireguard::QuantumResistantState, Intersection, @@ -244,6 +244,13 @@ impl Default for RelayQuery { } } +impl From for RelaySettings { + fn from(query: RelayQuery) -> Self { + let (relay_constraints, ..) = query.into_settings(); + RelaySettings::from(relay_constraints) + } +} + /// A query for a relay with Wireguard-specific properties, such as `multihop` and [wireguard /// obfuscation][`SelectedObfuscation`]. /// diff --git a/test/test-manager/src/tests/daita.rs b/test/test-manager/src/tests/daita.rs new file mode 100644 index 000000000000..912c811eccc2 --- /dev/null +++ b/test/test-manager/src/tests/daita.rs @@ -0,0 +1,200 @@ +use anyhow::{anyhow, bail, ensure, Context}; +use futures::StreamExt; +use mullvad_management_interface::{client::DaemonEvent, MullvadProxyClient}; +use mullvad_relay_selector::query::builder::RelayQueryBuilder; +use mullvad_types::{ + relay_constraints::GeographicLocationConstraint, relay_list::RelayEndpointData, + states::TunnelState, +}; +use talpid_types::{net::TunnelEndpoint, tunnel::ErrorStateCause}; +use test_macro::test_function; +use test_rpc::ServiceClient; + +use super::{helpers, Error, TestContext}; + +/// Test that daita and daita_smart_routing works by connecting +/// - to a non-DAITA relay with singlehop (should block) +/// - to a DAITA relay with singlehop +/// - to a DAITA relay with auto-multihop using smart_routing +/// - to a DAITA relay with explicit multihop +/// - to a non-DAITA relay with multihop (should block) +/// +/// # Limitations +/// +/// The test does not analyze any traffic, nor verify that DAITA is in use in any way except +/// by looking at [TunnelEndpoint::daita]. +#[test_function] +pub async fn test_daita( + _ctx: TestContext, + _rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> anyhow::Result<()> { + let relay_list = mullvad_client.get_relay_locations().await?; + let wg_relays = relay_list + .relays() + .flat_map(|relay| match &relay.endpoint_data { + RelayEndpointData::Wireguard(wireguard) => Some((relay, wireguard)), + _ => None, + }); + + // Select two relays to use for the test, one with DAITA and one without. + let daita_relay = wg_relays + .clone() + .find(|(_relay, wireguard_data)| wireguard_data.daita) + .map(|(relay, _)| relay) + .context("Failed to find a daita wireguard relay")?; + log::info!("Selected daita relay: {}", daita_relay.hostname); + let daita_relay_location = GeographicLocationConstraint::hostname( + &daita_relay.location.country_code, + &daita_relay.location.city_code, + &daita_relay.hostname, + ); + + let non_daita_relay = wg_relays + .clone() + .find(|(_relay, wireguard_data)| !wireguard_data.daita) + .map(|(relay, _)| relay) + .context("Failed to find a non-daita wireguard relay")?; + let non_daita_relay_location = GeographicLocationConstraint::hostname( + &non_daita_relay.location.country_code, + &non_daita_relay.location.city_code, + &non_daita_relay.hostname, + ); + log::info!("Selected non-daita relay: {}", non_daita_relay.hostname); + + let non_daita_location_query = RelayQueryBuilder::new() + .wireguard() + .location(non_daita_relay_location.clone()) + .build(); + + let daita_location_query = RelayQueryBuilder::new() + .wireguard() + .location(daita_relay_location.clone()) + .build(); + + let daita_to_non_daita_multihop_query = RelayQueryBuilder::new() + .wireguard() + .multihop() + .entry(daita_relay_location.clone()) + .location(non_daita_relay_location.clone()) + .build(); + + let non_daita_multihop_query = RelayQueryBuilder::new() + .wireguard() + .multihop() + .entry(non_daita_relay_location.clone()) + .build(); + + let mut events = mullvad_client + .events_listen() + .await? + .inspect(|event| log::debug!("New daemon event: {event:?}")); + + log::info!("Connecting to non-daita relay with DAITA smart routing"); + { + helpers::set_relay_settings(&mut mullvad_client, non_daita_location_query.clone()).await?; + mullvad_client.set_enable_daita(true).await?; + mullvad_client.connect_tunnel().await?; + let state = wait_for_daemon_reconnect(&mut events) + .await + .context("Failed to connect with smart_routing enabled")?; + + let endpoint: &TunnelEndpoint = state.endpoint().ok_or(anyhow!("No endpoint"))?; + ensure!(endpoint.daita, "DAITA must be used"); + ensure!(endpoint.entry_endpoint.is_some(), "multihop must be used"); + + log::info!("Successfully multihopped with use smart_routing"); + } + + log::info!("Connecting to non-daita relay with DAITA but no smart routing"); + { + mullvad_client.set_daita_smart_routing(false).await?; + + let result = wait_for_daemon_reconnect(&mut events).await; + let Err(Error::UnexpectedErrorState(state)) = result else { + bail!("Connection failed unsuccessfully, reason: {:?}", result); + }; + let ErrorStateCause::TunnelParameterError(_) = state.cause() else { + bail!("Connection failed unsuccessfully, cause: {}", state.cause()); + }; + + log::info!("Failed to connect, this is expected!"); + } + + log::info!("Connecting to daita relay with smart_routing"); + { + helpers::set_relay_settings(&mut mullvad_client, daita_location_query).await?; + + let state = wait_for_daemon_reconnect(&mut events) + .await + .context("Failed to connect to daita location with smart_routing enabled")?; + + let endpoint = state.endpoint().context("No endpoint")?; + ensure!(endpoint.daita, "DAITA must be used"); + ensure!( + endpoint.entry_endpoint.is_none(), + "multihop must not be used" + ); + + log::info!("Successfully singlehopped with smart_routing"); + } + + log::info!("Connecting to daita relay with multihop"); + { + helpers::set_relay_settings(&mut mullvad_client, daita_to_non_daita_multihop_query).await?; + let state = wait_for_daemon_reconnect(&mut events) + .await + .context("Failed to connect via daita location with multihop enabled")?; + + let endpoint = state.endpoint().context("No endpoint")?; + ensure!(endpoint.daita, "DAITA must be used"); + ensure!(endpoint.entry_endpoint.is_some(), "multihop must be used"); + + log::info!("Successfully connected with multihop"); + } + + log::info!("Connecting to non_daita relay with multihop"); + { + helpers::set_relay_settings(&mut mullvad_client, non_daita_multihop_query).await?; + let result = wait_for_daemon_reconnect(&mut events).await; + let Err(Error::UnexpectedErrorState(state)) = result else { + bail!("Connection failed unsuccessfully, reason: {:?}", result); + }; + let ErrorStateCause::TunnelParameterError(_) = state.cause() else { + bail!("Connection failed unsuccessfully, cause: {}", state.cause()); + }; + + log::info!("Failed to connect, this is expected!"); + } + + Ok(()) +} + +async fn wait_for_daemon_reconnect( + mut event_stream: impl futures::Stream> + + Unpin, +) -> Result { + // wait until the daemon informs us that it's trying to connect + helpers::find_daemon_event(&mut event_stream, |event| match event { + DaemonEvent::TunnelState(state) => Some(match state { + TunnelState::Connecting { .. } => Ok(state), + TunnelState::Connected { .. } => return None, + TunnelState::Disconnecting { .. } => return None, + TunnelState::Disconnected { .. } => Err(Error::UnexpectedTunnelState(Box::new(state))), + TunnelState::Error(state) => Err(Error::UnexpectedErrorState(state)), + }), + _ => None, + }) + .await??; + + // then wait until the daemon informs us that it connected (or failed) + helpers::find_daemon_event(&mut event_stream, |event| match event { + DaemonEvent::TunnelState(state) => match state { + TunnelState::Connecting { .. } => None, + TunnelState::Connected { .. } => Some(Ok(state)), + _ => Some(Err(Error::UnexpectedTunnelState(Box::new(state)))), + }, + _ => None, + }) + .await? +} diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs index 7e4cbc9eb6a5..bc17a7f3f64a 100644 --- a/test/test-manager/src/tests/mod.rs +++ b/test/test-manager/src/tests/mod.rs @@ -2,6 +2,7 @@ mod access_methods; mod account; pub mod config; mod cve_2019_14899; +mod daita; mod dns; mod helpers; mod install; @@ -57,7 +58,10 @@ pub enum Error { #[error("The daemon returned an error: {0}")] Daemon(String), - #[error("The daemon ended up in the error state")] + #[error("The daemon ended up in the the wrong tunnel-state: {0:?}")] + UnexpectedTunnelState(Box), + + #[error("The daemon ended up in the error state: {0:?}")] UnexpectedErrorState(talpid_types::tunnel::ErrorState), #[error("The gRPC client ran into an error: {0}")] diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index f5d8f6c95960..6fb57adc1498 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -390,41 +390,6 @@ pub async fn test_wireguard_autoconnect( Ok(()) } -/// Test connecting to a WireGuard relay using DAITA. -/// -/// # Limitations -/// -/// The test does not analyze any traffic, nor verify that DAITA is in use. -#[test_function] -pub async fn test_daita( - _: TestContext, - rpc: ServiceClient, - mut mullvad_client: MullvadProxyClient, -) -> anyhow::Result<()> { - log::info!("Connecting to relay with DAITA"); - - apply_settings_from_relay_query( - &mut mullvad_client, - RelayQueryBuilder::new().wireguard().build(), - ) - .await?; - - mullvad_client - .set_daita_settings(wireguard::DaitaSettings { - enabled: true, - use_anywhere: false, - }) - .await - .context("Failed to enable daita")?; - - connect_and_wait(&mut mullvad_client).await?; - - log::info!("Check that the connection works"); - let _ = helpers::geoip_lookup_with_retries(&rpc).await?; - - Ok(()) -} - /// Test whether the daemon automatically connects on reboot when using /// OpenVPN. /// From d77bd4d9d9412aab5705d949570f419f03415734 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Mon, 16 Sep 2024 16:30:25 +0200 Subject: [PATCH 16/16] Mark Smart Routing feature indicator as unsupported on android --- .../net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index 1ff297312bec..a1020b71d0d8 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -609,6 +609,7 @@ internal fun ManagementInterface.FeatureIndicator.toDomain() = FeatureIndicator.SERVER_IP_OVERRIDE ManagementInterface.FeatureIndicator.CUSTOM_MTU -> FeatureIndicator.CUSTOM_MTU ManagementInterface.FeatureIndicator.DAITA -> FeatureIndicator.DAITA + ManagementInterface.FeatureIndicator.DAITA_SMART_ROUTING, ManagementInterface.FeatureIndicator.LOCKDOWN_MODE, ManagementInterface.FeatureIndicator.SHADOWSOCKS, ManagementInterface.FeatureIndicator.MULTIHOP,