summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml1
-rw-r--r--docs/relay-selector.md11
-rw-r--r--mullvad-api/src/rest.rs7
-rw-r--r--mullvad-daemon/Cargo.toml1
-rw-r--r--mullvad-daemon/src/api.rs137
-rw-r--r--mullvad-daemon/src/lib.rs429
-rw-r--r--mullvad-daemon/src/migrations/mod.rs43
-rw-r--r--mullvad-relay-selector/Cargo.toml29
-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,
};