diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | Cargo.lock | 23 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | docs/relay-selector.md | 11 | ||||
| -rw-r--r-- | mullvad-api/src/rest.rs | 7 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/api.rs | 137 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 429 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/mod.rs | 43 | ||||
| -rw-r--r-- | mullvad-relay-selector/Cargo.toml | 29 | ||||
| -rw-r--r-- | mullvad-relay-selector/src/lib.rs (renamed from mullvad-daemon/src/relays/mod.rs) | 397 | ||||
| -rw-r--r-- | mullvad-relay-selector/src/matcher.rs (renamed from mullvad-daemon/src/relays/matcher.rs) | 0 | ||||
| -rw-r--r-- | mullvad-relay-selector/src/updater.rs (renamed from mullvad-daemon/src/relays/updater.rs) | 27 |
13 files changed, 584 insertions, 522 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e0c9bbe91..7179694398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Line wrap the file at 100 chars. Th by updating `udp-over-tcp` to 0.2. - Parse old account history formats correctly when they are empty. - Use suspend-aware timers for relay list updates and version checks on all platforms. +- Don't attempt to use bridges when using OpenVPN over UDP and bridge mode is set to auto. #### Windows - Fix "Open Mullvad VPN" tray context menu item not working after toggling unpinned window setting. diff --git a/Cargo.lock b/Cargo.lock index 664020ac7e..cbd5f6981c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1713,6 +1713,7 @@ dependencies = [ "mullvad-api", "mullvad-management-interface", "mullvad-paths", + "mullvad-relay-selector", "mullvad-types", "nix 0.23.1", "parking_lot 0.11.2", @@ -1818,6 +1819,28 @@ dependencies = [ ] [[package]] +name = "mullvad-relay-selector" +version = "0.1.0" +dependencies = [ + "chrono", + "err-derive", + "futures", + "ipnetwork", + "lazy_static", + "log", + "mullvad-api", + "mullvad-types", + "parking_lot 0.11.2", + "rand 0.7.3", + "serde", + "serde_json", + "talpid-core", + "talpid-types", + "tokio", + "tokio-stream", +] + +[[package]] name = "mullvad-setup" version = "2022.1.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index f40610b778..75bbc7d0ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "mullvad-problem-report", "mullvad-jni", "mullvad-paths", + "mullvad-relay-selector", "mullvad-types", "mullvad-api", "mullvad-exclude", diff --git a/docs/relay-selector.md b/docs/relay-selector.md index 8da8ea69a9..ab6a808d0f 100644 --- a/docs/relay-selector.md +++ b/docs/relay-selector.md @@ -75,13 +75,10 @@ relay location will be used. ### Selecting a bridge endpoint between filtered relays When filtering bridge endpoints by location, if multiple bridge endpoints match the specified -constraints then currently the one which is geographically closest to the selected tunnel relay -would be selected. Ideally, rather than always picking the closest one given the same constraints -and tunnel endpoint, a different but still geographically close bridge endpoint would be selected if -the daemon failed to connect to the first ones initially. If bridge state is set to _On_, then a -bridge is always selected and used. If it's set to _auto_, a bridge will only be tried after 3 -failed attempts at connecting without a bridge and only if the relay constraints allow for a bridge -to be selected. +constraints then endpoints which are geographically closer to the selected tunnel relay are more +likely to be selected. If bridge state is set to _On_, then a bridge is always selected and used. +If it's set to _auto_, a bridge will only be tried after 3 failed attempts at connecting without a +bridge and only if the relay constraints allow for a bridge to be selected. ### Bridge caveats diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index 4297bd92ed..517e0b3a03 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -256,6 +256,13 @@ impl RequestServiceHandle { .map_err(|_| Error::SendError)?; completion_rx.await.map_err(|_| Error::ReceiveError)? } + + /// Forcibly update the connection mode. + pub async fn next_api_endpoint(&self) -> Result<()> { + self.tx + .unbounded_send(RequestCommand::NextApiConfig) + .map_err(|_| Error::SendError) + } } #[derive(Debug)] diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index f970250385..81518397f9 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -30,6 +30,7 @@ tokio-stream = "0.1" uuid = { version = "0.8", features = ["v4"] } mullvad-paths = { path = "../mullvad-paths" } +mullvad-relay-selector = { path = "../mullvad-relay-selector" } mullvad-types = { path = "../mullvad-types" } mullvad-api = { path = "../mullvad-api" } talpid-core = { path = "../talpid-core" } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 99d614a741..563a53fab4 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -1,66 +1,113 @@ -use crate::DaemonEventSender; use futures::{ channel::{mpsc, oneshot}, - stream, Stream, StreamExt, + Future, Stream, }; -use mullvad_api::{proxy::ApiConnectionMode, ApiEndpointUpdateCallback}; +use mullvad_api::{ + proxy::{ApiConnectionMode, ProxyConfig}, + ApiEndpointUpdateCallback, +}; +use mullvad_relay_selector::RelaySelector; use std::{ net::SocketAddr, + path::PathBuf, + pin::Pin, sync::{Arc, Mutex, Weak}, + task::Poll, }; -use talpid_core::{mpsc::Sender, tunnel_state_machine::TunnelCommand}; +use talpid_core::tunnel_state_machine::TunnelCommand; use talpid_types::{ - net::{AllowedEndpoint, Endpoint, TransportProtocol}, + net::{openvpn::ProxySettings, AllowedEndpoint, Endpoint, TransportProtocol}, ErrorExt, }; -pub(crate) struct ApiConnectionModeRequest { - pub response_tx: oneshot::Sender<ApiConnectionMode>, - pub retry_attempt: u32, +/// A stream that returns the next API connection mode to use for reaching the API. +/// +/// When `mullvad-api` fails to contact the API, it requests a new connection mode. +/// The API can be connected to either directly (i.e., [`ApiConnectionMode::Direct`]) +/// or from a bridge ([`ApiConnectionMode::Proxied`]). +/// +/// * Every 3rd attempt returns [`ApiConnectionMode::Direct`]. +/// * Any other attempt returns a configuration for the bridge that is closest to the selected relay +/// location and matches all bridge constraints. +/// * When no matching bridge is found, e.g. if the selected hosting providers don't match any +/// bridge, [`ApiConnectionMode::Direct`] is returned. +pub struct ApiConnectionModeProvider { + cache_dir: PathBuf, + + relay_selector: RelaySelector, + retry_attempt: u32, + + current_task: Option<Pin<Box<dyn Future<Output = ApiConnectionMode> + Send>>>, } -/// Returns a stream that returns the next API bridge to try. -/// `initial_config` refers to the first config returned by the stream. The daemon is not notified -/// of this. -pub(crate) fn create_api_config_provider( - daemon_sender: DaemonEventSender<ApiConnectionModeRequest>, - initial_config: ApiConnectionMode, -) -> impl Stream<Item = ApiConnectionMode> + Unpin { - struct Context { - attempt: u32, - daemon_sender: DaemonEventSender<ApiConnectionModeRequest>, - } +impl Stream for ApiConnectionModeProvider { + type Item = ApiConnectionMode; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll<Option<Self::Item>> { + // Poll the current task + if let Some(task) = self.current_task.as_mut() { + return match task.as_mut().poll(cx) { + Poll::Ready(mode) => { + self.current_task = None; + Poll::Ready(Some(mode)) + } + Poll::Pending => Poll::Pending, + }; + } + + // Create a new task. + let config = if Self::should_use_bridge(self.retry_attempt) { + self.relay_selector + .get_bridge_forced() + .map(|settings| match settings { + ProxySettings::Shadowsocks(ss_settings) => { + ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(ss_settings)) + } + _ => { + log::error!("Received unexpected proxy settings type"); + ApiConnectionMode::Direct + } + }) + .unwrap_or(ApiConnectionMode::Direct) + } else { + ApiConnectionMode::Direct + }; + + self.retry_attempt = self.retry_attempt.wrapping_add(1); - let ctx = Context { - attempt: 1, - daemon_sender, - }; + let cache_dir = self.cache_dir.clone(); + self.current_task = Some(Box::pin(async move { + if let Err(error) = config.save(&cache_dir).await { + log::debug!( + "{}", + error.display_chain_with_msg("Failed to save API endpoint") + ); + } + config + })); + + return self.poll_next(cx); + } +} - Box::pin( - stream::once(async move { initial_config }).chain(stream::unfold( - ctx, - |mut ctx| async move { - ctx.attempt = ctx.attempt.wrapping_add(1); - let (response_tx, response_rx) = oneshot::channel(); +impl ApiConnectionModeProvider { + pub(crate) fn new(cache_dir: PathBuf, relay_selector: RelaySelector) -> Self { + Self { + cache_dir, - let _ = ctx.daemon_sender.send(ApiConnectionModeRequest { - response_tx, - retry_attempt: ctx.attempt, - }); + relay_selector, + retry_attempt: 0, - let new_config = response_rx.await.unwrap_or_else(|error| { - log::error!( - "{}", - error.display_chain_with_msg("Failed to receive API proxy config") - ); - // Fall back on unbridged connection - ApiConnectionMode::Direct - }); + current_task: None, + } + } - Some((new_config, ctx)) - }, - )), - ) + fn should_use_bridge(retry_attempt: u32) -> bool { + retry_attempt % 3 > 0 + } } /// Notifies the tunnel state machine that the API (real or proxied) endpoint has diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 8313d79373..19f17e3e45 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -15,7 +15,6 @@ pub mod logging; #[cfg(not(target_os = "android"))] pub mod management_interface; mod migrations; -mod relays; #[cfg(not(target_os = "android"))] pub mod rpc_uniqueness_check; pub mod runtime; @@ -31,19 +30,17 @@ use futures::{ future::{abortable, AbortHandle, Future}, StreamExt, }; -use mullvad_api::{ - availability::ApiAvailabilityHandle, - proxy::{ApiConnectionMode, ProxyConfig}, +use mullvad_api::availability::ApiAvailabilityHandle; +use mullvad_relay_selector::{ + updater::{RelayListUpdater, RelayListUpdaterHandle}, + RelaySelector, SelectedBridge, SelectedObfuscator, SelectedRelay, SelectorConfig, }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, device::{Device, DeviceConfig, DeviceData, DeviceEvent, DeviceId, RemoveDeviceEvent}, endpoint::MullvadEndpoint, - location::{Coordinates, GeoIpLocation}, - relay_constraints::{ - BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, ObfuscationSettings, - RelaySettings, RelaySettingsUpdate, SelectedObfuscation, - }, + location::GeoIpLocation, + relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, relay_list::{Relay, RelayList}, settings::{DnsOptions, DnsState, Settings}, states::{TargetState, TunnelState}, @@ -77,10 +74,7 @@ use talpid_types::android::AndroidContext; #[cfg(not(target_os = "android"))] use talpid_types::net::openvpn; use talpid_types::{ - net::{ - openvpn::ProxySettings, wireguard, TransportProtocol, TunnelEndpoint, TunnelParameters, - TunnelType, - }, + net::{wireguard, TunnelEndpoint, TunnelParameters, TunnelType}, tunnel::{ErrorStateCause, ParameterGenerationError, TunnelStateTransition}, ErrorExt, }; @@ -162,9 +156,6 @@ pub enum Error { #[error(display = "No bridge available")] NoBridgeAvailable, - #[error(display = "Failed to select a compatible obfuscator")] - NoObfuscator, - #[error(display = "No matching entry relay was found")] NoEntryRelayAvailable, @@ -358,8 +349,6 @@ pub(crate) enum InternalDaemonEvent { TriggerShutdown, /// The background job fetching new `AppVersionInfo`s got a new info object. NewAppVersionInfo(AppVersionInfo), - /// Request from REST client to use a different API endpoint. - GenerateApiConnectionMode(api::ApiConnectionModeRequest), /// Sent when a device is updated in any way (key rotation, login, logout, etc.). DeviceEvent(InnerDeviceEvent), /// Handles updates from versions without devices. @@ -393,12 +382,6 @@ impl From<AppVersionInfo> for InternalDaemonEvent { } } -impl From<api::ApiConnectionModeRequest> for InternalDaemonEvent { - fn from(request: api::ApiConnectionModeRequest) -> Self { - InternalDaemonEvent::GenerateApiConnectionMode(request) - } -} - impl From<InnerDeviceEvent> for InternalDaemonEvent { fn from(event: InnerDeviceEvent) -> Self { InternalDaemonEvent::DeviceEvent(event) @@ -592,12 +575,12 @@ pub struct Daemon<L: EventListener> { api_runtime: mullvad_api::Runtime, api_handle: mullvad_api::rest::MullvadRestHandle, version_updater_handle: version_check::VersionUpdaterHandle, - relay_selector: relays::RelaySelector, + relay_selector: RelaySelector, + relay_list_updater: RelayListUpdaterHandle, last_generated_relays: Option<LastSelectedRelays>, app_version_info: Option<AppVersionInfo>, shutdown_tasks: Vec<Pin<Box<dyn Future<Output = ()>>>>, tunnel_state_machine_handle: tunnel_state_machine::JoinHandle, - cache_dir: PathBuf, #[cfg(target_os = "windows")] volume_update_tx: mpsc::UnboundedSender<()>, } @@ -639,32 +622,34 @@ where let endpoint_updater = api::ApiEndpointUpdaterHandle::new(); - let proxy_provider = api::create_api_config_provider( - internal_event_tx.to_specialized_sender(), - ApiConnectionMode::Direct, - ); + let migration_data = migrations::migrate_all(&cache_dir, &settings_dir) + .await + .unwrap_or_else(|error| { + log::error!( + "{}", + error.display_chain_with_msg("Failed to migrate settings or cache") + ); + None + }); + let settings = SettingsPersister::load(&settings_dir).await; + + let initial_selector_config = new_selector_config(&settings); + let relay_selector = RelaySelector::new(initial_selector_config, &resource_dir, &cache_dir); + + let proxy_provider = + api::ApiConnectionModeProvider::new(cache_dir.clone(), relay_selector.clone()); let api_handle = api_runtime .mullvad_rest_handle(proxy_provider, endpoint_updater.callback()) .await; - let migration_complete = migrations::migrate_all( - &cache_dir, - &settings_dir, - api_handle.clone(), - internal_event_tx.clone(), - ) - .await - .unwrap_or_else(|error| { - log::error!( - "{}", - error.display_chain_with_msg("Failed to migrate settings or cache") - ); + let migration_complete = if let Some(migration_data) = migration_data { + migrations::migrate_device( + migration_data, + api_handle.clone(), + internal_event_tx.clone(), + ) + } else { migrations::MigrationComplete::new(true) - }); - let settings = SettingsPersister::load(&settings_dir).await; - - let tunnel_parameters_generator = MullvadTunnelParametersGenerator { - tx: internal_event_tx.clone(), }; let account_manager = device::AccountManager::spawn( @@ -716,7 +701,9 @@ where let initial_api_endpoint = api::get_allowed_endpoint(api_runtime.address_cache.get_address().await); - + let tunnel_parameters_generator = MullvadTunnelParametersGenerator { + tx: internal_event_tx.clone(), + }; let (offline_state_tx, offline_state_rx) = mpsc::unbounded(); #[cfg(target_os = "windows")] let (volume_update_tx, volume_update_rx) = mpsc::unbounded(); @@ -754,12 +741,11 @@ where relay_list_listener.notify_relay_list(relay_list.clone()); }; - let relay_selector = relays::RelaySelector::new( + let mut relay_list_updater = RelayListUpdater::new( + relay_selector.clone(), api_handle.clone(), - on_relay_list_update, - &resource_dir, &cache_dir, - api_availability.clone(), + on_relay_list_update, ); let app_version_info = version_check::load_cache(&cache_dir).await; @@ -774,7 +760,7 @@ where tokio::spawn(version_updater.run()); // Attempt to download a fresh relay list - relay_selector.update().await; + relay_list_updater.update().await; let daemon = Daemon { tunnel_command_tx, @@ -796,11 +782,11 @@ where api_handle, version_updater_handle, relay_selector, + relay_list_updater, last_generated_relays: None, app_version_info, shutdown_tasks: vec![], tunnel_state_machine_handle, - cache_dir, #[cfg(target_os = "windows")] volume_update_tx, }; @@ -944,9 +930,6 @@ where NewAppVersionInfo(app_version_info) => { self.handle_new_app_version_info(app_version_info) } - GenerateApiConnectionMode(request) => { - self.handle_generate_api_connection_mode(request).await - } DeviceEvent(event) => self.handle_device_event(event).await, DeviceMigrationEvent(event) => self.handle_device_migration_event(event).await, #[cfg(windows)] @@ -1033,69 +1016,54 @@ where >, retry_attempt: u32, ) { - if let Ok(Some(device)) = self.account_manager.data().await { - let result = match self.settings.get_relay_settings() { - RelaySettings::CustomTunnelEndpoint(custom_relay) => { - custom_relay - // TODO(emilsp): generate proxy settings for custom tunnels - .to_tunnel_parameters(self.settings.tunnel_options.clone(), None) - .map_err(|e| { - log::error!("Failed to resolve hostname for custom tunnel config: {}", e); - ParameterGenerationError::CustomTunnelHostResultionError - }) - } - RelaySettings::Normal(constraints) => { - let endpoint = self - .relay_selector - .get_tunnel_endpoint( - &constraints, - self.settings.get_bridge_state(), - retry_attempt, - ) - .ok(); - if let Some(relays::RelaySelectorResult { - exit_relay, - entry_relay, - endpoint, - }) = endpoint - { - let result = self - .create_tunnel_parameters( - &exit_relay, - &entry_relay, - endpoint, - device.token, - retry_attempt, - ) - .await; - match result { - Ok(result) => Ok(result), - Err(Error::NoKeyAvailable) => { - Err(ParameterGenerationError::NoWireguardKey) - } - Err(Error::NoBridgeAvailable) => { - Err(ParameterGenerationError::NoMatchingBridgeRelay) - } - Err(err) => { - log::error!( - "{}", - err.display_chain_with_msg( - "Failed to generate tunnel parameters" - ) - ); - Err(ParameterGenerationError::NoMatchingRelay) - } - } - } else { - Err(ParameterGenerationError::NoMatchingRelay) + let data = match self.account_manager.data().await { + Ok(Some(data)) => data, + _ => { + log::error!("No account token configured"); + return; + } + }; + + let result = match self.relay_selector.get_relay(retry_attempt) { + Ok((SelectedRelay::Custom(custom_relay), _bridge, _obfsucator)) => { + custom_relay + // TODO(emilsp): generate proxy settings for custom tunnels + .to_tunnel_parameters(self.settings.tunnel_options.clone(), None) + .map_err(|e| { + log::error!("Failed to resolve hostname for custom tunnel config: {}", e); + ParameterGenerationError::CustomTunnelHostResultionError + }) + } + Ok((SelectedRelay::Normal(constraints), bridge, obfuscator)) => { + let result = self + .create_tunnel_parameters( + &constraints.exit_relay, + &constraints.entry_relay, + constraints.endpoint, + bridge, + obfuscator, + data, + ) + .await; + result.map_err(|error| match error { + Error::NoKeyAvailable => ParameterGenerationError::NoWireguardKey, + Error::NoBridgeAvailable => ParameterGenerationError::NoMatchingBridgeRelay, + error => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to generate tunnel parameters") + ); + ParameterGenerationError::NoMatchingRelay } - } - }; - if tunnel_parameters_tx.send(result).is_err() { - log::error!("Failed to send tunnel parameters"); + }) } - } else { - log::error!("No account token configured"); + Err(mullvad_relay_selector::Error::NoBridge) => { + Err(ParameterGenerationError::NoMatchingBridgeRelay) + } + Err(_error) => Err(ParameterGenerationError::NoMatchingRelay), + }; + if tunnel_parameters_tx.send(result).is_err() { + log::error!("Failed to send tunnel parameters"); } } @@ -1105,16 +1073,21 @@ where relay: &Relay, entry_relay: &Option<Relay>, endpoint: MullvadEndpoint, - account_token: String, - retry_attempt: u32, + bridge: Option<SelectedBridge>, + obfuscator: Option<SelectedObfuscator>, + device: DeviceData, ) -> Result<TunnelParameters, Error> { let tunnel_options = self.settings.tunnel_options.clone(); - let location = relay.location.as_ref().expect("Relay has no location set"); match endpoint { #[cfg(not(target_os = "android"))] MullvadEndpoint::OpenVpn(endpoint) => { - let (bridge_settings, bridge_relay) = - self.generate_bridge_parameters(&location, retry_attempt)?; + let (bridge_settings, bridge_relay) = match bridge { + Some(SelectedBridge::Normal(bridge)) => { + (Some(bridge.settings), Some(bridge.relay)) + } + Some(SelectedBridge::Custom(settings)) => (Some(settings), None), + None => (None, None), + }; self.last_generated_relays = Some(LastSelectedRelays::OpenVpn { relay: relay.clone(), @@ -1122,11 +1095,7 @@ where }); Ok(openvpn::TunnelParameters { - config: openvpn::ConnectionConfig::new( - endpoint, - account_token, - "-".to_string(), - ), + config: openvpn::ConnectionConfig::new(endpoint, device.token, "-".to_string()), options: tunnel_options.openvpn, generic_options: tunnel_options.generic, proxy: bridge_settings, @@ -1138,46 +1107,16 @@ where unreachable!("OpenVPN is not supported on Android"); } MullvadEndpoint::Wireguard(endpoint) => { - let wg_data = self - .account_manager - .data() - .await - .map_err(|_| Error::NoKeyAvailable)? - .map(|device| device.wg_data) - .ok_or(Error::NoKeyAvailable)?; let tunnel = wireguard::TunnelConfig { - private_key: wg_data.private_key, + private_key: device.wg_data.private_key, addresses: vec![ - wg_data.addresses.ipv4_address.ip().into(), - wg_data.addresses.ipv6_address.ip().into(), + device.wg_data.addresses.ipv4_address.ip().into(), + device.wg_data.addresses.ipv6_address.ip().into(), ], }; - let selected_obfuscator = - match self.settings.obfuscation_settings.selected_obfuscation { - SelectedObfuscation::Off => None, - SelectedObfuscation::Auto - if !self - .relay_selector - .should_use_auto_obfuscator(retry_attempt) => - { - None - } - _ => { - let (obfuscator_config, obfuscator_relay) = self - .relay_selector - .get_obfuscator( - &self.settings.obfuscation_settings, - entry_relay.as_ref().unwrap_or(relay), - &endpoint, - retry_attempt, - ) - .ok_or(Error::NoObfuscator)?; - Some((obfuscator_config, obfuscator_relay)) - } - }; - let (obfuscation, obfuscator_relay) = match selected_obfuscator { - Some((obfuscation, relay)) => (Some(obfuscation), Some(relay)), + let (obfuscator_relay, obfuscator_config) = match obfuscator { + Some(obfuscator) => (Some(obfuscator.relay), Some(obfuscator.config)), None => (None, None), }; @@ -1197,67 +1136,13 @@ where }, options: tunnel_options.wireguard.options, generic_options: tunnel_options.generic, - obfuscation, + obfuscation: obfuscator_config, } .into()) } } } - /// Generates bridge parameters for the chosen OpenVpn tunnel relay - #[cfg(not(target_os = "android"))] - fn generate_bridge_parameters( - &self, - location: &mullvad_types::location::Location, - retry_attempt: u32, - ) -> Result<(Option<ProxySettings>, Option<Relay>), Error> { - let result = match &self.settings.bridge_settings { - BridgeSettings::Normal(settings) => { - let bridge_constraints = InternalBridgeConstraints { - location: settings.location.clone(), - providers: settings.providers.clone(), - // FIXME: This is temporary while talpid-core only supports TCP proxies - transport_protocol: Constraint::Only(TransportProtocol::Tcp), - }; - match self.settings.get_bridge_state() { - BridgeState::On => { - let (bridge_settings, bridge_relay) = self - .relay_selector - .get_proxy_settings(&bridge_constraints, Some(location)) - .ok_or(Error::NoBridgeAvailable)?; - (Some(bridge_settings), Some(bridge_relay)) - } - BridgeState::Auto => { - if let Some((bridge_settings, bridge_relay)) = - self.relay_selector.get_auto_proxy_settings( - &bridge_constraints, - Some(location), - retry_attempt, - ) - { - (Some(bridge_settings), Some(bridge_relay)) - } else { - (None, None) - } - } - BridgeState::Off => (None, None), - } - } - BridgeSettings::Custom(bridge_settings) => match self.settings.get_bridge_state() { - BridgeState::On => (Some(bridge_settings.clone()), None), - BridgeState::Auto => { - if self.relay_selector.should_use_bridge(retry_attempt) { - (Some(bridge_settings.clone()), None) - } else { - (None, None) - } - } - BridgeState::Off => (None, None), - }, - }; - Ok(result) - } - fn schedule_reconnect(&mut self, delay: Duration) { self.unschedule_reconnect(); @@ -1370,77 +1255,6 @@ where self.event_listener.notify_app_version(app_version_info); } - /// Returns the next API connection mode to use for reaching the API. - /// - /// When `mullvad-api` fails to contact the API, it requests a new connection mode - /// from this function, which will be used for future requests. The API can be - /// connected to either directly (i.e., [`ApiConnectionMode::Direct`]) or from - /// a bridge ([`ApiConnectionMode::Proxied`]). - /// - /// * Every 3rd attempt returns [`ApiConnectionMode::Direct`] (i.e., no bridge). - /// * For any other attempt, this function returns a configuration for the bridge that is - /// closest to the selected relay location[^note] and matches all bridge constraints. - /// * When no matching bridge is found, e.g. if the selected hosting providers don't match any - /// bridge, [`ApiConnectionMode::Direct`] is returned. - /// - /// [^note]: The "selected relay location" is the location of the last relay that - /// the daemon connected to, or, if no relay was connected to, the "midpoint" of - /// all relays that match the selected relay location constraint. - async fn handle_generate_api_connection_mode( - &mut self, - request: api::ApiConnectionModeRequest, - ) { - let location = self - .last_generated_relays - .as_ref() - .and_then(LastSelectedRelays::first_hop_coordinates) - .or_else(|| { - if let RelaySettings::Normal(settings) = self.settings.get_relay_settings() { - self.relay_selector.get_relay_midpoint(&settings) - } else { - None - } - }); - let bridge = if request.retry_attempt % 3 > 0 { - let constraints = match &self.settings.bridge_settings { - BridgeSettings::Normal(settings) => InternalBridgeConstraints { - location: settings.location.clone(), - providers: settings.providers.clone(), - transport_protocol: Constraint::Only(TransportProtocol::Tcp), - }, - _ => InternalBridgeConstraints { - location: Constraint::Any, - providers: Constraint::Any, - transport_protocol: Constraint::Only(TransportProtocol::Tcp), - }, - }; - self.relay_selector - .get_proxy_settings(&constraints, location) - } else { - None - }; - let config = match bridge { - Some((settings, _relay)) => match settings { - ProxySettings::Shadowsocks(ss_settings) => { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(ss_settings)) - } - _ => { - log::error!("Received unexpected proxy settings type"); - ApiConnectionMode::Direct - } - }, - None => ApiConnectionMode::Direct, - }; - - if let Err(error) = config.save(&self.cache_dir).await { - log::debug!( - "{}", - error.display_chain_with_msg("Failed to save API endpoint") - ); - } - let _ = request.response_tx.send(config); - } - async fn handle_device_event(&mut self, event: InnerDeviceEvent) { match &event { InnerDeviceEvent::Login(device) => { @@ -1740,7 +1554,7 @@ where } async fn on_update_relay_locations(&mut self) { - self.relay_selector.update().await; + self.relay_list_updater.update().await; } fn on_login_account(&mut self, tx: ResponseTx<(), Error>, account_token: String) { @@ -2177,6 +1991,8 @@ where if settings_changed { self.event_listener .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); log::info!("Initiating tunnel restart because the relay settings changed"); self.reconnect_tunnel(); } @@ -2314,6 +2130,11 @@ where if settings_changes { self.event_listener .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + if let Err(error) = self.api_handle.service().next_api_endpoint().await { + log::error!("Failed to rotate API endpoint: {}", error); + } self.reconnect_tunnel(); }; Self::oneshot_send(tx, Ok(()), "set_bridge_settings"); @@ -2339,6 +2160,8 @@ where if settings_changed { self.event_listener .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); self.reconnect_tunnel(); } Self::oneshot_send(tx, Ok(()), "set_obfuscation_settings"); @@ -2363,6 +2186,8 @@ where if settings_changed { self.event_listener .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); log::info!("Initiating tunnel restart because bridge state changed"); self.reconnect_tunnel(); } @@ -2758,24 +2583,12 @@ enum LastSelectedRelays { OpenVpn { relay: Relay, bridge: Option<Relay> }, } -impl LastSelectedRelays { - fn first_hop_coordinates(&self) -> Option<Coordinates> { - let first_hop_location = match &self { - Self::WireGuard { - wg_entry: entry, - wg_exit: exit, - obfuscator, - } => { - let first_hop = obfuscator.as_ref().or(entry.as_ref()).unwrap_or(exit); - first_hop.location.clone() - } - #[cfg(not(target_os = "android"))] - Self::OpenVpn { relay, bridge } => { - let first_hop = bridge.as_ref().unwrap_or(relay); - first_hop.location.clone() - } - }; - first_hop_location.map(Coordinates::from) +fn new_selector_config(settings: &Settings) -> SelectorConfig { + SelectorConfig { + relay_settings: settings.get_relay_settings(), + bridge_state: settings.get_bridge_state(), + bridge_settings: settings.bridge_settings.clone(), + obfuscation_settings: settings.obfuscation_settings.clone(), } } diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs index a280d55af5..bb1e9d1ba0 100644 --- a/mullvad-daemon/src/migrations/mod.rs +++ b/mullvad-daemon/src/migrations/mod.rs @@ -93,8 +93,7 @@ pub enum Error { pub type Result<T> = std::result::Result<T, Error>; -/// Returns whether there is any background work remaining -/// after `migrate_all` has returned. +/// Returns whether there is any background work remaining. #[derive(Clone)] pub(crate) struct MigrationComplete(Arc<AtomicBool>); @@ -112,12 +111,13 @@ impl MigrationComplete { } } +/// Contains discarded data that may be useful for later work. +pub(crate) type MigrationData = v5::MigrationData; + pub(crate) async fn migrate_all( cache_dir: &Path, settings_dir: &Path, - rest_handle: mullvad_api::rest::MullvadRestHandle, - daemon_tx: crate::DaemonEventSender, -) -> Result<MigrationComplete> { +) -> Result<Option<MigrationData>> { #[cfg(windows)] windows::migrate_after_windows_update(settings_dir) .await @@ -126,7 +126,7 @@ pub(crate) async fn migrate_all( let path = settings_dir.join(SETTINGS_FILE); if !path.is_file() { - return Ok(MigrationComplete::new(true)); + return Ok(None); } let settings_bytes = fs::read(&path).await.map_err(Error::ReadError)?; @@ -149,22 +149,10 @@ pub(crate) async fn migrate_all( account_history::migrate_formats(settings_dir, &mut settings).await?; let migration_data = v5::migrate(&mut settings).await?; - let mut migration_complete = MigrationComplete::new(false); - - if let Some(migration_data) = migration_data { - device::generate_device( - migration_data, - migration_complete.clone(), - rest_handle, - daemon_tx, - ); - } else { - migration_complete.set_complete(); - } if settings == old_settings { // Nothing changed - return Ok(migration_complete); + return Ok(migration_data); } let buffer = serde_json::to_string_pretty(&settings).map_err(Error::SerializeError)?; @@ -188,7 +176,22 @@ pub(crate) async fn migrate_all( log::debug!("Migrated settings. Wrote settings to {}", path.display()); - Ok(migration_complete) + Ok(migration_data) +} + +pub(crate) fn migrate_device( + migration_data: MigrationData, + rest_handle: mullvad_api::rest::MullvadRestHandle, + daemon_tx: crate::DaemonEventSender, +) -> MigrationComplete { + let migration_complete = MigrationComplete::new(false); + device::generate_device( + migration_data, + migration_complete.clone(), + rest_handle, + daemon_tx, + ); + migration_complete } #[cfg(windows)] diff --git a/mullvad-relay-selector/Cargo.toml b/mullvad-relay-selector/Cargo.toml new file mode 100644 index 0000000000..24e8491c3d --- /dev/null +++ b/mullvad-relay-selector/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "mullvad-relay-selector" +version = "0.1.0" +authors = ["Mullvad VPN"] +description = "Mullvad VPN relay selector" +license = "GPL-3.0" +edition = "2021" +publish = false + +[dependencies] +chrono = "0.4.19" +err-derive = "0.3.1" +futures = "0.3" +ipnetwork = "0.16" +log = "0.4" +parking_lot = "0.11" +rand = "0.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.8", features = ["fs", "io-util", "time"] } +tokio-stream = "0.1" + +talpid-core = { path = "../talpid-core" } +talpid-types = { path = "../talpid-types" } +mullvad-api = { path = "../mullvad-api" } +mullvad-types = { path = "../mullvad-types" } + +[dev-dependencies] +lazy_static = "1.0" diff --git a/mullvad-daemon/src/relays/mod.rs b/mullvad-relay-selector/src/lib.rs index 1aa8001ba0..48464f5616 100644 --- a/mullvad-daemon/src/relays/mod.rs +++ b/mullvad-relay-selector/src/lib.rs @@ -3,18 +3,18 @@ use chrono::{DateTime, Local}; use ipnetwork::IpNetwork; -use mullvad_api::{availability::ApiAvailabilityHandle, rest::MullvadRestHandle}; use mullvad_types::{ endpoint::{MullvadEndpoint, MullvadWireguardEndpoint}, location::{Coordinates, Location}, relay_constraints::{ - BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint, Match, - ObfuscationSettings, OpenVpnConstraints, Providers, RelayConstraints, SelectedObfuscation, - Set, TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints, + BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint, + Match, ObfuscationSettings, OpenVpnConstraints, Providers, RelayConstraints, RelaySettings, + SelectedObfuscation, Set, TransportPort, Udp2TcpObfuscationSettings, WireguardConstraints, }, relay_list::{Relay, RelayList, Udp2TcpEndpointData}, + CustomTunnelEndpoint, }; -use parking_lot::Mutex; +use parking_lot::{Mutex, MutexGuard}; use rand::{self, seq::SliceRandom, Rng}; use std::{ io, @@ -31,15 +31,10 @@ use talpid_types::{ ErrorExt, }; -use crate::relays::updater::RelayListUpdater; - -use self::{ - matcher::{RelayMatcher, TunnelMatcher, WireguardMatcher}, - updater::RelayListUpdaterHandle, -}; +use self::matcher::{RelayMatcher, TunnelMatcher, WireguardMatcher}; mod matcher; -mod updater; +pub mod updater; const DATE_TIME_FORMAT_STR: &str = "%Y-%m-%d %H:%M:%S%.3f"; const RELAYS_FILENAME: &str = "relays.json"; @@ -70,6 +65,12 @@ pub enum Error { #[error(display = "No relays matching current constraints")] NoRelay, + #[error(display = "No bridges matching current constraints")] + NoBridge, + + #[error(display = "No obfuscators matching current constraints")] + NoObfuscator, + #[error(display = "Failure in serialization of the relay list")] Serialize(#[error(source)] serde_json::Error), @@ -207,21 +208,23 @@ impl ParsedRelays { } } +#[derive(Clone)] +pub struct SelectorConfig { + pub relay_settings: RelaySettings, + pub bridge_state: BridgeState, + pub bridge_settings: BridgeSettings, + pub obfuscation_settings: ObfuscationSettings, +} + +#[derive(Clone)] pub struct RelaySelector { + config: Arc<Mutex<SelectorConfig>>, parsed_relays: Arc<Mutex<ParsedRelays>>, - updater: Option<RelayListUpdaterHandle>, } impl RelaySelector { - /// Returns a new `RelaySelector` backed by relays cached on disk. Use the `update` method - /// to refresh the relay list from the internet. - pub fn new( - api_handle: MullvadRestHandle, - on_update: impl Fn(&RelayList) + Send + 'static, - resource_dir: &Path, - cache_dir: &Path, - api_availability: ApiAvailabilityHandle, - ) -> Self { + /// Returns a new `RelaySelector` backed by relays cached on disk. + pub fn new(config: SelectorConfig, resource_dir: &Path, cache_dir: &Path) -> Self { let cache_path = cache_dir.join(RELAYS_FILENAME); let resource_path = resource_dir.join(RELAYS_FILENAME); let unsynchronized_parsed_relays = Self::read_relays_from_disk(&cache_path, &resource_path) @@ -238,34 +241,15 @@ impl RelaySelector { DateTime::<Local>::from(unsynchronized_parsed_relays.last_updated()) .format(DATE_TIME_FORMAT_STR) ); - let parsed_relays = Arc::new(Mutex::new(unsynchronized_parsed_relays)); - - let updater = RelayListUpdater::new( - api_handle, - cache_path, - parsed_relays.clone(), - Box::new(on_update), - api_availability, - ); RelaySelector { - parsed_relays, - updater: Some(updater), + config: Arc::new(Mutex::new(config)), + parsed_relays: Arc::new(Mutex::new(unsynchronized_parsed_relays)), } } - /// Download the newest relay list. - pub async fn update(&self) { - if let Some(mut updater) = self.updater.clone() { - if let Err(err) = updater.update_relay_list().await { - log::error!( - "{}", - err.display_chain_with_msg( - "Unable to send update command to relay list updater" - ) - ); - } - } + pub fn set_config(&mut self, config: SelectorConfig) { + *self.config.lock() = config; } /// Returns all countries and cities. The cities in the object returned does not have any @@ -274,14 +258,65 @@ impl RelaySelector { self.parsed_relays.lock().locations().clone() } + /// Returns a random relay and relay endpoint matching the current constraints. + pub fn get_relay( + &self, + retry_attempt: u32, + ) -> Result< + ( + SelectedRelay, + Option<SelectedBridge>, + Option<SelectedObfuscator>, + ), + Error, + > { + let config = self.config.lock(); + match &config.relay_settings { + RelaySettings::CustomTunnelEndpoint(custom_relay) => { + Ok((SelectedRelay::Custom(custom_relay.clone()), None, None)) + } + RelaySettings::Normal(constraints) => { + let relay = + self.get_tunnel_endpoint(&constraints, config.bridge_state, retry_attempt)?; + let bridge = match relay.endpoint { + MullvadEndpoint::OpenVpn(endpoint) + if endpoint.protocol == TransportProtocol::Tcp => + { + let location = relay + .exit_relay + .location + .as_ref() + .expect("Relay has no location set"); + self.get_bridge_for(&config, location, retry_attempt)? + } + _ => None, + }; + let obfuscator = match relay.endpoint { + MullvadEndpoint::Wireguard(ref endpoint) => { + let obfuscator_relay = + relay.entry_relay.as_ref().unwrap_or(&relay.exit_relay); + self.get_obfuscator_inner( + &config, + obfuscator_relay, + &endpoint, + retry_attempt, + )? + } + _ => None, + }; + Ok((SelectedRelay::Normal(relay), bridge, obfuscator)) + } + } + } + /// Returns a random relay and relay endpoint matching the given constraints and with /// preferences applied. - pub fn get_tunnel_endpoint( + fn get_tunnel_endpoint( &self, relay_constraints: &RelayConstraints, bridge_state: BridgeState, retry_attempt: u32, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { match relay_constraints.tunnel_protocol { Constraint::Only(TunnelType::OpenVpn) => self.get_openvpn_endpoint( &relay_constraints.location, @@ -340,7 +375,7 @@ impl RelaySelector { openvpn_constraints: OpenVpnConstraints, bridge_state: BridgeState, retry_attempt: u32, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { let mut relay_matcher = RelayMatcher { location: location.clone(), providers: providers.clone(), @@ -390,7 +425,7 @@ impl RelaySelector { &self, mut entry_matcher: RelayMatcher<WireguardMatcher>, exit_location: Constraint<LocationConstraint>, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { let mut exit_matcher = RelayMatcher { location: exit_location, tunnel: WIREGUARD_EXIT_CONSTRAINTS.clone().into(), @@ -430,7 +465,7 @@ impl RelaySelector { exit_relay.hostname, exit_endpoint.to_endpoint().address.ip(), ); - let result = RelaySelectorResult::wireguard_multihop_endpoint( + let result = NormalSelectedRelay::wireguard_multihop_endpoint( exit_relay, entry_endpoint, entry_relay, @@ -446,7 +481,7 @@ impl RelaySelector { providers: &Constraint<Providers>, wireguard_constraints: &WireguardConstraints, retry_attempt: u32, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { let mut entry_relay_matcher = RelayMatcher { location: location.clone(), providers: providers.clone(), @@ -480,7 +515,7 @@ impl RelaySelector { relay_constraints: &RelayConstraints, bridge_state: BridgeState, retry_attempt: u32, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { let preferred_constraints = self.preferred_constraints(&relay_constraints, bridge_state, retry_attempt); let original_matcher: RelayMatcher<_> = relay_constraints.clone().into(); @@ -650,27 +685,75 @@ impl RelaySelector { entry_endpoint.exit_peer = Some(exit_peer.clone()); } - #[cfg(not(target_os = "android"))] - pub fn get_auto_proxy_settings<T: Into<Coordinates>>( + fn get_bridge_for( &self, - bridge_constraints: &InternalBridgeConstraints, - location: Option<T>, + config: &MutexGuard<'_, SelectorConfig>, + location: &mullvad_types::location::Location, retry_attempt: u32, - ) -> Option<(ProxySettings, Relay)> { - if !self.should_use_bridge(retry_attempt) { - return None; + ) -> Result<Option<SelectedBridge>, Error> { + match &config.bridge_settings { + BridgeSettings::Normal(settings) => { + let bridge_constraints = InternalBridgeConstraints { + location: settings.location.clone(), + providers: settings.providers.clone(), + // FIXME: This is temporary while talpid-core only supports TCP proxies + transport_protocol: Constraint::Only(TransportProtocol::Tcp), + }; + match config.bridge_state { + BridgeState::On => { + let (settings, relay) = self + .get_proxy_settings(&bridge_constraints, Some(location)) + .ok_or(Error::NoBridge)?; + Ok(Some(SelectedBridge::Normal(NormalSelectedBridge { + settings, + relay, + }))) + } + BridgeState::Auto if Self::should_use_bridge(retry_attempt) => Ok(self + .get_proxy_settings(&bridge_constraints, Some(location)) + .map(|(settings, relay)| { + SelectedBridge::Normal(NormalSelectedBridge { settings, relay }) + })), + BridgeState::Auto | BridgeState::Off => Ok(None), + } + } + BridgeSettings::Custom(bridge_settings) => match config.bridge_state { + BridgeState::On => Ok(Some(SelectedBridge::Custom(bridge_settings.clone()))), + BridgeState::Auto if Self::should_use_bridge(retry_attempt) => { + Ok(Some(SelectedBridge::Custom(bridge_settings.clone()))) + } + BridgeState::Auto | BridgeState::Off => Ok(None), + }, } + } - // For now, only TCP tunnels are supported. - if let Constraint::Only(TransportProtocol::Udp) = bridge_constraints.transport_protocol { - return None; - } + /// Returns a bridge based on the relay and bridge constraints, ignoring the bridge state. + pub fn get_bridge_forced(&self) -> Option<ProxySettings> { + let config = self.config.lock(); - self.get_proxy_settings(bridge_constraints, location) + let near_location = match &config.relay_settings { + RelaySettings::Normal(settings) => self.get_relay_midpoint(settings), + _ => None, + }; + + let constraints = match &config.bridge_settings { + BridgeSettings::Normal(settings) => InternalBridgeConstraints { + location: settings.location.clone(), + providers: settings.providers.clone(), + transport_protocol: Constraint::Only(TransportProtocol::Tcp), + }, + BridgeSettings::Custom(_bridge_settings) => InternalBridgeConstraints { + location: Constraint::Any, + providers: Constraint::Any, + transport_protocol: Constraint::Only(TransportProtocol::Tcp), + }, + }; + + self.get_proxy_settings(&constraints, near_location) + .map(|(settings, _relay)| settings) } - #[cfg(not(target_os = "android"))] - pub fn should_use_bridge(&self, retry_attempt: u32) -> bool { + fn should_use_bridge(retry_attempt: u32) -> bool { // shouldn't use a bridge for the first 3 times retry_attempt > 3 && // i.e. 4th and 5th with bridge, 6th & 7th without @@ -680,7 +763,7 @@ impl RelaySelector { (retry_attempt % 4) < 2 } - pub fn get_proxy_settings<T: Into<Coordinates>>( + fn get_proxy_settings<T: Into<Coordinates>>( &self, constraints: &InternalBridgeConstraints, location: Option<T>, @@ -720,22 +803,37 @@ impl RelaySelector { pub fn get_obfuscator( &self, - obfuscation_settings: &ObfuscationSettings, relay: &Relay, endpoint: &MullvadWireguardEndpoint, retry_attempt: u32, - ) -> Option<(ObfuscatorConfig, Relay)> { - match obfuscation_settings.selected_obfuscation { - SelectedObfuscation::Auto => { - self.get_auto_obfuscator(obfuscation_settings, relay, endpoint, retry_attempt) - } - SelectedObfuscation::Off => None, - SelectedObfuscation::Udp2Tcp => self.get_udp2tcp_obfuscator( - &obfuscation_settings.udp2tcp, + ) -> Result<Option<SelectedObfuscator>, Error> { + self.get_obfuscator_inner(&self.config.lock(), relay, endpoint, retry_attempt) + } + + fn get_obfuscator_inner( + &self, + config: &MutexGuard<'_, SelectorConfig>, + relay: &Relay, + endpoint: &MullvadWireguardEndpoint, + retry_attempt: u32, + ) -> Result<Option<SelectedObfuscator>, Error> { + match &config.obfuscation_settings.selected_obfuscation { + SelectedObfuscation::Auto => Ok(self.get_auto_obfuscator( + &config.obfuscation_settings, relay, endpoint, retry_attempt, - ), + )), + SelectedObfuscation::Off => Ok(None), + SelectedObfuscation::Udp2Tcp => Ok(Some( + self.get_udp2tcp_obfuscator( + &config.obfuscation_settings.udp2tcp, + relay, + endpoint, + retry_attempt, + ) + .ok_or(Error::NoObfuscator)?, + )), } } @@ -745,7 +843,7 @@ impl RelaySelector { relay: &Relay, endpoint: &MullvadWireguardEndpoint, retry_attempt: u32, - ) -> Option<(ObfuscatorConfig, Relay)> { + ) -> Option<SelectedObfuscator> { if !self.should_use_auto_obfuscator(retry_attempt) { return None; } @@ -761,7 +859,7 @@ impl RelaySelector { ) } - pub fn should_use_auto_obfuscator(&self, retry_attempt: u32) -> bool { + fn should_use_auto_obfuscator(&self, retry_attempt: u32) -> bool { self.get_auto_obfuscator_retry_attempt(retry_attempt) .is_some() } @@ -779,7 +877,7 @@ impl RelaySelector { relay: &Relay, _endpoint: &MullvadWireguardEndpoint, retry_attempt: u32, - ) -> Option<(ObfuscatorConfig, Relay)> { + ) -> Option<SelectedObfuscator> { let udp2tcp_endpoint = if obfuscation_settings.port.is_only() { relay .obfuscators @@ -792,14 +890,14 @@ impl RelaySelector { .udp2tcp .get(retry_attempt as usize % relay.obfuscators.udp2tcp.len()) }; - udp2tcp_endpoint.map(|udp2tcp_endpoint| { - ( - ObfuscatorConfig::Udp2Tcp { - endpoint: SocketAddr::new(relay.ipv4_addr_in.into(), udp2tcp_endpoint.port), - }, - relay.clone(), - ) - }) + udp2tcp_endpoint + .map(|udp2tcp_endpoint| ObfuscatorConfig::Udp2Tcp { + endpoint: SocketAddr::new(relay.ipv4_addr_in.into(), udp2tcp_endpoint.port), + }) + .map(|config| SelectedObfuscator { + config, + relay: relay.clone(), + }) } /// Returns preferred constraints @@ -876,15 +974,16 @@ impl RelaySelector { // Prefer UDP by default. But if that has failed a couple of times, then try TCP port // 443, which works for many with UDP problems. After that, just alternate // between protocols. - // If the tunnel type constraint is set OpenVpn, from the 4th attempt onwards, every two - // retry attempts OpenVpn constraints should be set to TCP as a bridge will be used, - // and to UDP for the next two attempts. If the tunnel type is specified to be _Any_ + // If the tunnel type constraint is set OpenVpn, from the 4th attempt onwards, the first + // two retry attempts OpenVpn constraints should be set to TCP as a bridge will be used, + // and to UDP or TCP for the next two attempts. If the tunnel type is specified to be _Any_ // and on not-Windows, the first two tries are used for WireGuard and don't // affect counting here. match retry_attempt { 0 | 1 => (Constraint::Any, TransportProtocol::Udp), 2 | 3 => (Constraint::Only(443), TransportProtocol::Tcp), - attempt if attempt % 2 == 0 => (Constraint::Any, TransportProtocol::Udp), + attempt if attempt % 4 < 2 => (Constraint::Any, TransportProtocol::Tcp), + attempt if attempt % 4 == 2 => (Constraint::Any, TransportProtocol::Udp), _ => (Constraint::Any, TransportProtocol::Tcp), } } @@ -893,7 +992,7 @@ impl RelaySelector { fn get_tunnel_endpoint_internal<T: TunnelMatcher>( &self, matcher: &RelayMatcher<T>, - ) -> Result<RelaySelectorResult, Error> { + ) -> Result<NormalSelectedRelay, Error> { let matching_relays: Vec<Relay> = self .parsed_relays .lock() @@ -911,7 +1010,7 @@ impl RelaySelector { .map(|endpoint| endpoint.to_endpoint().address.ip()) .unwrap_or(IpAddr::from(selected_relay.ipv4_addr_in)); log::info!("Selected relay {} at {}", selected_relay.hostname, addr_in); - endpoint.map(|endpoint| RelaySelectorResult::new(endpoint, selected_relay.clone())) + endpoint.map(|endpoint| NormalSelectedRelay::new(endpoint, selected_relay.clone())) }) .ok_or(Error::NoRelay) } @@ -1029,13 +1128,37 @@ impl RelaySelector { } #[derive(Debug)] -pub struct RelaySelectorResult { +pub enum SelectedBridge { + Normal(NormalSelectedBridge), + Custom(ProxySettings), +} + +#[derive(Debug)] +pub struct NormalSelectedBridge { + pub settings: ProxySettings, + pub relay: Relay, +} + +#[derive(Debug)] +pub enum SelectedRelay { + Normal(NormalSelectedRelay), + Custom(CustomTunnelEndpoint), +} + +#[derive(Debug)] +pub struct NormalSelectedRelay { pub exit_relay: Relay, pub endpoint: MullvadEndpoint, pub entry_relay: Option<Relay>, } -impl RelaySelectorResult { +#[derive(Debug)] +pub struct SelectedObfuscator { + pub config: ObfuscatorConfig, + pub relay: Relay, +} + +impl NormalSelectedRelay { fn new(endpoint: MullvadEndpoint, exit_relay: Relay) -> Self { Self { exit_relay, @@ -1061,7 +1184,7 @@ impl RelaySelectorResult { mod test { use super::*; use mullvad_types::{ - relay_constraints::RelayConstraints, + relay_constraints::{BridgeConstraints, RelayConstraints}, relay_list::{ OpenVpnEndpointData, Relay, RelayBridges, RelayListCity, RelayListCountry, RelayObfuscators, RelayTunnels, WireguardEndpointData, @@ -1239,7 +1362,18 @@ mod test { RELAYS.clone(), SystemTime::now(), ))), - updater: None, + config: Arc::new(Mutex::new(SelectorConfig { + relay_settings: RelaySettings::Normal(RelayConstraints { + location: Constraint::Only(LocationConstraint::Country("se".to_owned())), + ..Default::default() + }), + bridge_settings: BridgeSettings::Normal(BridgeConstraints::default()), + obfuscation_settings: ObfuscationSettings { + selected_obfuscation: SelectedObfuscation::Off, + ..Default::default() + }, + bridge_state: BridgeState::Auto, + })), } } @@ -1395,7 +1529,7 @@ mod test { relay_constraints.wireguard_constraints.entry_location = Constraint::Only(location_general); // The entry must not equal the exit - let RelaySelectorResult { + let NormalSelectedRelay { exit_relay, endpoint, .. @@ -1585,21 +1719,23 @@ mod test { assert!(result.entry_relay.is_none()); assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard { .. })); - let obfs_settings = ObfuscationSettings { + relay_selector.config.lock().obfuscation_settings = ObfuscationSettings { selected_obfuscation: SelectedObfuscation::Udp2Tcp, ..ObfuscationSettings::default() }; - let (obfs_config, _obfs_relay) = relay_selector - .get_obfuscator( - &obfs_settings, - &result.exit_relay, - result.endpoint.unwrap_wireguard(), - 0, - ) + let obfs_config = relay_selector + .get_obfuscator(&result.exit_relay, result.endpoint.unwrap_wireguard(), 0) + .unwrap() .unwrap(); - assert!(matches!(obfs_config, ObfuscatorConfig::Udp2Tcp { .. })); + assert!(matches!( + obfs_config, + SelectedObfuscator { + config: ObfuscatorConfig::Udp2Tcp { .. }, + .. + } + )); } #[test] @@ -1612,36 +1748,24 @@ mod test { assert!(result.entry_relay.is_none()); assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard { .. })); - let obfs_settings = ObfuscationSettings { + relay_selector.config.lock().obfuscation_settings = ObfuscationSettings { selected_obfuscation: SelectedObfuscation::Auto, ..ObfuscationSettings::default() }; assert!(relay_selector - .get_obfuscator( - &obfs_settings, - &result.exit_relay, - result.endpoint.unwrap_wireguard(), - 0, - ) + .get_obfuscator(&result.exit_relay, result.endpoint.unwrap_wireguard(), 0,) + .unwrap() .is_none()); assert!(relay_selector - .get_obfuscator( - &obfs_settings, - &result.exit_relay, - result.endpoint.unwrap_wireguard(), - 1, - ) + .get_obfuscator(&result.exit_relay, result.endpoint.unwrap_wireguard(), 1,) + .unwrap() .is_none()); assert!(relay_selector - .get_obfuscator( - &obfs_settings, - &result.exit_relay, - result.endpoint.unwrap_wireguard(), - 2, - ) + .get_obfuscator(&result.exit_relay, result.endpoint.unwrap_wireguard(), 2,) + .unwrap() .is_some()); } @@ -1651,7 +1775,7 @@ mod test { const TCP2UDP_PORTS: [u16; 3] = [80, 443, 5001]; - let obfs_settings = ObfuscationSettings { + relay_selector.config.lock().obfuscation_settings = ObfuscationSettings { selected_obfuscation: SelectedObfuscation::Udp2Tcp, ..ObfuscationSettings::default() }; @@ -1662,18 +1786,27 @@ mod test { .expect("Failed to select a WireGuard relay"); assert!(result.entry_relay.is_none()); - let (obfs_config, _obfs_relay) = relay_selector + let obfs_config = relay_selector .get_obfuscator( - &obfs_settings, &result.exit_relay, result.endpoint.unwrap_wireguard(), attempt, ) + .unwrap() .expect("Failed to get Tcp2Udp endpoint"); - assert!(matches!(obfs_config, ObfuscatorConfig::Udp2Tcp { .. })); + assert!(matches!( + obfs_config, + SelectedObfuscator { + config: ObfuscatorConfig::Udp2Tcp { .. }, + .. + } + )); - let ObfuscatorConfig::Udp2Tcp { endpoint } = obfs_config; + let SelectedObfuscator { + config: ObfuscatorConfig::Udp2Tcp { endpoint }, + .. + } = obfs_config; assert!(TCP2UDP_PORTS.contains(&endpoint.port())); } } diff --git a/mullvad-daemon/src/relays/matcher.rs b/mullvad-relay-selector/src/matcher.rs index 7d75141b16..7d75141b16 100644 --- a/mullvad-daemon/src/relays/matcher.rs +++ b/mullvad-relay-selector/src/matcher.rs diff --git a/mullvad-daemon/src/relays/updater.rs b/mullvad-relay-selector/src/updater.rs index 35f431125a..fe5088704d 100644 --- a/mullvad-daemon/src/relays/updater.rs +++ b/mullvad-relay-selector/src/updater.rs @@ -32,11 +32,18 @@ pub struct RelayListUpdaterHandle { } impl RelayListUpdaterHandle { - pub async fn update_relay_list(&mut self) -> Result<(), Error> { - self.tx + pub async fn update(&mut self) { + if let Err(error) = self + .tx .send(()) .await .map_err(|_| Error::DownloaderShutDown) + { + log::error!( + "{}", + error.display_chain_with_msg("Unable to send update command to relay list updater") + ); + } } } @@ -50,20 +57,20 @@ pub struct RelayListUpdater { } impl RelayListUpdater { - pub(super) fn new( + pub fn new( + selector: super::RelaySelector, api_handle: MullvadRestHandle, - cache_path: PathBuf, - parsed_relays: Arc<Mutex<ParsedRelays>>, - on_update: Box<dyn Fn(&RelayList) + Send + 'static>, - api_availability: ApiAvailabilityHandle, + cache_dir: &Path, + on_update: impl Fn(&RelayList) + Send + 'static, ) -> RelayListUpdaterHandle { let (tx, cmd_rx) = mpsc::channel(1); + let api_availability = api_handle.availability.clone(); let api_client = RelayListProxy::new(api_handle); let updater = RelayListUpdater { api_client, - cache_path, - parsed_relays, - on_update, + cache_path: cache_dir.join(super::RELAYS_FILENAME), + parsed_relays: selector.parsed_relays.clone(), + on_update: Box::new(on_update), last_check: UNIX_EPOCH, api_availability, }; |
