diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-03-05 16:12:14 +0100 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-03-17 16:51:38 +0100 |
| commit | 73da8abf9b25e1b1327e017c83a0ab2c519c632e (patch) | |
| tree | 222c94f350f56ef3824fd7179cb9d2a5b1d73d68 | |
| parent | bcb1e6654923612ef08d1df45ab8515a90745d49 (diff) | |
| download | mullvadvpn-73da8abf9b25e1b1327e017c83a0ab2c519c632e.tar.xz mullvadvpn-73da8abf9b25e1b1327e017c83a0ab2c519c632e.zip | |
Move the api client from the daemon to mullvad-api
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | mullvad-api/Cargo.toml | 21 | ||||
| -rw-r--r-- | mullvad-api/src/api.rs | 667 | ||||
| -rw-r--r-- | mullvad-api/src/lib.rs | 1 | ||||
| -rw-r--r-- | mullvad-api/src/proxy.rs | 6 | ||||
| -rw-r--r-- | mullvad-daemon/src/access_method.rs | 17 | ||||
| -rw-r--r-- | mullvad-daemon/src/api.rs | 736 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 68 |
8 files changed, 792 insertions, 725 deletions
diff --git a/Cargo.lock b/Cargo.lock index f6b3c2c335..d1a29c00c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,6 +2614,7 @@ dependencies = [ "mockito", "mullvad-encrypted-dns-proxy", "mullvad-fs", + "mullvad-relay-selector", "mullvad-types", "rustls-pemfile 2.1.3", "serde", diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml index db248daf63..566a78f1c1 100644 --- a/mullvad-api/Cargo.toml +++ b/mullvad-api/Cargo.toml @@ -30,18 +30,18 @@ log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = [ - "macros", - "time", - "rt-multi-thread", - "net", - "io-std", - "io-util", - "fs", + "macros", + "time", + "rt-multi-thread", + "net", + "io-std", + "io-util", + "fs", ] } tokio-rustls = { version = "0.26.0", features = [ - "logging", - "tls12", - "ring", + "logging", + "tls12", + "ring", ], default-features = false } tokio-socks = "0.5.1" rustls-pemfile = "2.1.3" @@ -52,6 +52,7 @@ mullvad-fs = { path = "../mullvad-fs" } mullvad-types = { path = "../mullvad-types" } talpid-types = { path = "../talpid-types" } talpid-time = { path = "../talpid-time" } +mullvad-relay-selector = { path = "../mullvad-relay-selector" } shadowsocks = { workspace = true, features = ["stream-cipher"] } diff --git a/mullvad-api/src/api.rs b/mullvad-api/src/api.rs new file mode 100644 index 0000000000..c1123eba4b --- /dev/null +++ b/mullvad-api/src/api.rs @@ -0,0 +1,667 @@ +//! This module is responsible for enabling custom [`AccessMethodSetting`]s to +//! be used when connecting to the Mullvad API. In practice this means +//! converting [`AccessMethodSetting`]s to connection details as encoded by +//! [`ApiConnectionMode`], which in turn is used by `mullvad-api` for +//! establishing connections when performing API requests. + +use crate::{ + proxy::{AllowedClientsProvider, ApiConnectionMode, ConnectionModeProvider, ProxyConfig}, + AddressCache, +}; +use futures::{ + channel::{mpsc, oneshot}, + StreamExt, +}; +#[cfg(feature = "api-override")] +use mullvad_api::ApiEndpoint; +use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState; +use mullvad_relay_selector::RelaySelector; +use mullvad_types::access_method::{ + AccessMethod, AccessMethodSetting, BuiltInAccessMethod, Id, Settings, +}; +use std::{net::SocketAddr, path::PathBuf}; +use talpid_types::net::{ + proxy::CustomProxy, AllowedEndpoint, Endpoint, TransportProtocol, +}; + +pub enum Message { + Get(ResponseTx<ResolvedConnectionMode>), + Use(ResponseTx<()>, Id), + Rotate(ResponseTx<ApiConnectionMode>), + Update(ResponseTx<()>, Settings), + Resolve( + ResponseTx<Option<ResolvedConnectionMode>>, + AccessMethodSetting, + ), +} + +/// Calling [`AccessMethodEvent::send`] will cause a +/// [`crate::InternalDaemonEvent::AccessMethodEvent`] being sent to the daemon, +/// which in turn will handle updating the firewall and notifying clients as +/// applicable. +pub enum AccessMethodEvent { + /// A [`AccessMethodEvent::New`] event is emitted when the active access + /// method changes. + /// + /// This happens when a [`mullvad_api::rest::RequestService`] requests a new + /// [`ApiConnectionMode`] from the running [`AccessModeSelector`]. + New { + /// The new active [`AccessMethodSetting`]. + setting: AccessMethodSetting, + /// The endpoint which represents how to connect to the Mullvad API and + /// which clients are allowed to initiate such a connection. + #[cfg(not(target_os = "android"))] + endpoint: AllowedEndpoint, + }, + /// Emitted when the the firewall should be updated. + /// + /// This is useful for example when testing if some [`AccessMethodSetting`] + /// can be used to reach the Mullvad API. In this scenario, the currently + /// active access method will temporarily change (approximately for the + /// duration of 1 API call). Since this is just an internal test which + /// should be opaque to any client, it should not produce any unwanted noise + /// and as such it is *not* broadcasted after the daemon is done processing + /// this [`AccessMethodEvent::Allow`]. + #[cfg(not(target_os = "android"))] + Allow { endpoint: AllowedEndpoint }, +} + +impl AccessMethodEvent { + pub async fn send( + self, + daemon_event_sender: mpsc::UnboundedSender<(AccessMethodEvent, oneshot::Sender<()>)>, + ) -> Result<()> { + // It is up to the daemon to actually allow traffic to/from `api_endpoint` + // by updating the firewall. This [`oneshot::Sender`] allows the daemon to + // communicate when that action is done. + let (update_finished_tx, update_finished_rx) = oneshot::channel(); + let _ = daemon_event_sender.unbounded_send((self, update_finished_tx)); + // Wait for the daemon to finish processing `event`. + update_finished_rx.await.map_err(Error::NotRunning) + } +} + +/// This struct represent a concrete API endpoint (in the form of an +/// [`ApiConnectionMode`] and [`AllowedEndpoint`]) which has been derived from +/// some [`AccessMethodSetting`] (most likely the currently active access +/// method). These logically related values are sometimes useful to group +/// together into one value, which is encoded by [`ResolvedConnectionMode`]. +#[derive(Clone)] +pub struct ResolvedConnectionMode { + /// The connection strategy to be used by the `mullvad-api` crate when + /// initializing API requests. + pub connection_mode: ApiConnectionMode, + /// The actual endpoint of the Mullvad API and which clients should be + /// allowed to initialize a connection to this endpoint. + pub endpoint: AllowedEndpoint, + /// This is the [`AccessMethodSetting`] which resolved into + /// `connection_mode` and `endpoint`. + pub setting: AccessMethodSetting, +} + +/// Describes all the ways the daemon service which handles access methods can +/// fail. +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("No access methods were provided.")] + NoAccessMethods, + #[error("Could not resolve access method {access_method:#?}")] + Resolve { access_method: AccessMethod }, + #[error("AccessModeSelector is not receiving any messages.")] + SendFailed(#[from] mpsc::TrySendError<Message>), + #[error("AccessModeSelector is not receiving any messages.")] + OneshotSendFailed, + #[error("AccessModeSelector is not responding.")] + NotRunning(#[from] oneshot::Canceled), +} + +impl std::fmt::Display for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Message::Get(_) => f.write_str("Get"), + Message::Use(..) => f.write_str("Set"), + Message::Rotate(_) => f.write_str("Rotate"), + Message::Update(..) => f.write_str("Update"), + Message::Resolve(..) => f.write_str("Resolve"), + } + } +} + +impl Error { + /// Check if this error implies that the currenly running + /// [`AccessModeSelector`] can not continue to operate properly. + /// + /// To recover from this kind of error, the daemon will probably have to + /// intervene. + fn is_critical_error(&self) -> bool { + matches!( + self, + Error::SendFailed(..) | Error::OneshotSendFailed | Error::NotRunning(..) + ) + } +} + +type ResponseTx<T> = oneshot::Sender<Result<T>>; +type Result<T> = std::result::Result<T, Error>; + +/// A channel for sending [`Message`] commands to a running +/// [`AccessModeSelector`]. +#[derive(Clone)] +pub struct AccessModeSelectorHandle { + cmd_tx: mpsc::UnboundedSender<Message>, +} + +impl AccessModeSelectorHandle { + async fn send_command<T>(&self, make_cmd: impl FnOnce(ResponseTx<T>) -> Message) -> Result<T> { + let (tx, rx) = oneshot::channel(); + self.cmd_tx.unbounded_send(make_cmd(tx))?; + rx.await.map_err(Error::NotRunning)? + } + + pub async fn get_current(&self) -> Result<ResolvedConnectionMode> { + self.send_command(Message::Get).await.inspect_err(|_| { + log::debug!("Failed to get current access method!"); + }) + } + + pub async fn use_access_method(&self, value: Id) -> Result<()> { + self.send_command(|tx| Message::Use(tx, value)) + .await + .inspect_err(|_| { + log::debug!("Failed to set new access method!"); + }) + } + + pub async fn update_access_methods(&self, access_methods: Settings) -> Result<()> { + self.send_command(|tx| Message::Update(tx, access_methods)) + .await + .inspect_err(|_| { + log::debug!("Failed to switch to a new set of access methods"); + }) + } + + /// Try to resolve an access method into a set of connection details. + /// + /// This might fail if the underlying store/cache where `setting` is the key is empty. + /// In that case, `Ok(None)` is returned. + pub async fn resolve( + &self, + setting: AccessMethodSetting, + ) -> Result<Option<ResolvedConnectionMode>> { + self.send_command(|tx| Message::Resolve(tx, setting)) + .await + .inspect_err(|_| { + log::error!("Failed to update new access methods!"); + }) + } + + pub async fn rotate(&self) -> Result<ApiConnectionMode> { + self.send_command(Message::Rotate).await.inspect_err(|_| { + log::debug!("Failed while getting the next access method"); + }) + } +} + +pub struct AccessModeConnectionModeProvider { + initial: ApiConnectionMode, + handle: AccessModeSelectorHandle, + change_rx: mpsc::UnboundedReceiver<ApiConnectionMode>, +} + +impl AccessModeConnectionModeProvider { + fn new( + handle: AccessModeSelectorHandle, + initial_connection_mode: ApiConnectionMode, + change_rx: mpsc::UnboundedReceiver<ApiConnectionMode>, + ) -> Result<Self> { + Ok(Self { + initial: initial_connection_mode, + handle, + change_rx, + }) + } +} + +impl ConnectionModeProvider for AccessModeConnectionModeProvider { + fn initial(&self) -> ApiConnectionMode { + self.initial.clone() + } + + fn receive(&mut self) -> impl std::future::Future<Output = Option<ApiConnectionMode>> + Send { + self.change_rx.next() + } + + fn rotate(&self) -> impl std::future::Future<Output = ()> + Send { + let handle = self.handle.clone(); + async move { + handle.rotate().await.ok(); + } + } +} + +/// A small actor which takes care of handling the logic around rotating +/// connection modes to be used for Mullvad API request. +/// +/// When `mullvad-api` fails to contact the API, it will request a new +/// connection mode. The API can be connected to either directly (i.e., +/// [`ApiConnectionMode::Direct`]) via a bridge ([`ApiConnectionMode::Proxied`]) +/// or via any supported custom proxy protocol +/// ([`talpid_types::net::proxy::CustomProxy`]). +pub struct AccessModeSelector { + #[cfg(feature = "api-override")] + api_endpoint: ApiEndpoint, + cmd_rx: mpsc::UnboundedReceiver<Message>, + cache_dir: PathBuf, + /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. + relay_selector: RelaySelector, + /// Used for selecting a config for the 'Encrypted DNS proxy' access method. + encrypted_dns_proxy_cache: EncryptedDnsProxyState, + access_method_settings: Settings, + address_cache: AddressCache, + access_method_event_sender: mpsc::UnboundedSender<(AccessMethodEvent, oneshot::Sender<()>)>, + connection_mode_provider_sender: mpsc::UnboundedSender<ApiConnectionMode>, + current: ResolvedConnectionMode, + /// `index` is used to keep track of the [`AccessMethodSetting`] to use. + index: usize, + provider: Box<dyn AllowedClientsProvider>, +} + +impl AccessModeSelector { + pub async fn spawn( + cache_dir: PathBuf, + relay_selector: RelaySelector, + #[cfg_attr(not(feature = "api-override"), allow(unused_mut))] + mut access_method_settings: Settings, + #[cfg(feature = "api-override")] api_endpoint: ApiEndpoint, + access_method_event_sender: mpsc::UnboundedSender<(AccessMethodEvent, oneshot::Sender<()>)>, + address_cache: AddressCache, + provider: Box<dyn AllowedClientsProvider>, + ) -> Result<(AccessModeSelectorHandle, AccessModeConnectionModeProvider)> { + let (cmd_tx, cmd_rx) = mpsc::unbounded(); + + #[cfg(feature = "api-override")] + { + if api_endpoint.force_direct { + access_method_settings + .update(|setting| setting.is_direct(), |setting| setting.enable()); + } + } + + // Initialize the Encrypted DNS cache + let mut encrypted_dns_proxy_cache = EncryptedDnsProxyState::default(); + + // Always start looking from the position of `Direct`. + let (index, next) = Self::find_next_active(0, &access_method_settings); + let initial_connection_mode = Self::resolve_inner_with_default( + &next, + &relay_selector, + &mut encrypted_dns_proxy_cache, + &address_cache, + &*provider, + ) + .await; + + let (change_tx, change_rx) = mpsc::unbounded(); + + let api_connection_mode = initial_connection_mode.connection_mode.clone(); + + let selector = AccessModeSelector { + cmd_rx, + cache_dir, + relay_selector, + encrypted_dns_proxy_cache, + access_method_settings, + address_cache, + access_method_event_sender, + connection_mode_provider_sender: change_tx, + current: initial_connection_mode, + index, + provider, + #[cfg(feature = "api-override")] + api_endpoint, + }; + + tokio::spawn(selector.into_future()); + + let handle = AccessModeSelectorHandle { cmd_tx }; + + let connection_mode_provider = + AccessModeConnectionModeProvider::new(handle.clone(), api_connection_mode, change_rx)?; + + Ok((handle, connection_mode_provider)) + } + + async fn into_future(mut self) { + while let Some(cmd) = self.cmd_rx.next().await { + log::trace!("Processing {cmd} command"); + let execution = match cmd { + Message::Get(tx) => self.on_get_access_method(tx), + Message::Use(tx, id) => self.on_use_access_method(tx, id).await, + Message::Rotate(tx) => self.on_next_connection_mode(tx).await, + Message::Update(tx, values) => self.on_update_access_methods(tx, values).await, + Message::Resolve(tx, setting) => self.on_resolve_access_method(tx, setting).await, + }; + match execution { + Ok(_) => (), + Err(error) if error.is_critical_error() => { + log::error!( + "AccessModeSelector failed due to an internal error and won't be able to recover without a restart. {error}" + ); + break; + } + Err(error) => { + log::debug!("AccessModeSelector failed processing command due to {error}"); + } + } + } + } + + fn reply<T>(&self, tx: ResponseTx<T>, value: T) -> Result<()> { + tx.send(Ok(value)).map_err(|_| Error::OneshotSendFailed)?; + Ok(()) + } + + fn on_get_access_method(&mut self, tx: ResponseTx<ResolvedConnectionMode>) -> Result<()> { + self.reply(tx, self.current.clone()) + } + + async fn on_use_access_method(&mut self, tx: ResponseTx<()>, id: Id) -> Result<()> { + self.use_access_method(id).await; + self.reply(tx, ()) + } + + /// Set and announce the specified access method as the current one. + async fn use_access_method(&mut self, id: Id) { + #[cfg(feature = "api-override")] + { + if self.api_endpoint.force_direct { + log::debug!("API proxies are disabled"); + return; + } + } + + let Some((index, method)) = self + .access_method_settings + .iter() + .enumerate() + .find(|(_, access_method)| access_method.get_id() == id) + else { + return; + }; + + self.index = index; + self.set_current(method.to_owned()).await; + } + + async fn on_next_connection_mode(&mut self, tx: ResponseTx<ApiConnectionMode>) -> Result<()> { + let next = self.next_connection_mode().await?; + self.reply(tx, next) + } + + async fn next_connection_mode(&mut self) -> Result<ApiConnectionMode> { + #[cfg(feature = "api-override")] + { + if self.api_endpoint.force_direct { + log::debug!("API proxies are disabled"); + return Ok(ApiConnectionMode::Direct); + } + + log::debug!( + "The `api-override` feature is enabled, but a direct connection \ + is not enforced. Selecting API access methods as normal" + ); + } + + let (next_index, next) = + Self::find_next_active(self.index + 1, &self.access_method_settings); + self.index = next_index; + self.set_current(next).await; + Ok(self.current.connection_mode.clone()) + } + + async fn set_current(&mut self, access_method: AccessMethodSetting) { + let resolved = self.resolve_with_default(access_method).await; + + // Note: If the daemon is busy waiting for a call to this function + // to complete while we wait for the daemon to fully handle this + // `NewAccessMethodEvent`, then we find ourselves in a deadlock. + // This can happen during daemon startup when spawning a new + // `MullvadRestHandle`, which will call and await `next` on a Stream + // created from this `AccessModeSelector` instance. As such, the + // completion channel is discarded in this instance. + let setting = resolved.setting.clone(); + #[cfg(not(target_os = "android"))] + let endpoint = resolved.endpoint.clone(); + let daemon_sender = self.access_method_event_sender.clone(); + tokio::spawn(async move { + let _ = AccessMethodEvent::New { + setting, + #[cfg(not(target_os = "android"))] + endpoint, + } + .send(daemon_sender) + .await; + }); + + // Save the new connection mode to cache! + let cache_dir = self.cache_dir.clone(); + let connection_mode = resolved.connection_mode.clone(); + tokio::spawn(async move { + if connection_mode.save(&cache_dir).await.is_err() { + log::warn!("Failed to save {connection_mode:#?} to cache") + } + }); + + // Notify REST client + let _ = self + .connection_mode_provider_sender + .unbounded_send(resolved.connection_mode.clone()); + + self.current = resolved; + + log::info!( + "A new API access method has been selected: {name}", + name = self.current.setting.name + ); + } + + /// Find the next access method to use. + /// + /// * `start`: From which point in `access_methods` to start the search. + /// * `access_methods`: The search space. + fn find_next_active(start: usize, access_methods: &Settings) -> (usize, AccessMethodSetting) { + access_methods + .iter() + .cloned() + .enumerate() + .cycle() + .skip(start) + .take(access_methods.cardinality()) + .find(|(_index, access_method)| access_method.enabled()) + .unwrap_or_else(|| (0, access_methods.direct().clone())) + } + + async fn on_update_access_methods( + &mut self, + tx: ResponseTx<()>, + access_methods: Settings, + ) -> Result<()> { + self.update_access_methods(access_methods).await?; + self.reply(tx, ()) + } + + async fn update_access_methods(&mut self, access_methods: Settings) -> Result<()> { + self.access_method_settings = access_methods; + + let new_current = self + .access_method_settings + .iter() + .enumerate() + .find(|(_, access_method)| access_method.get_id() == self.current.setting.get_id()); + + match new_current { + Some((index, new_current)) => { + // If the current method was modified, announce changes + self.index = index; + if self.current.setting != *new_current { + if new_current.enabled() { + self.set_current(new_current.to_owned()).await; + } else { + self.next_connection_mode().await?; + } + } + } + None => { + // Current method was removed: A new access method will suddenly have the same index + // as the one we are removing, but we want it to still be a + // candidate. A minor hack to achieve this is to simply decrement + // the current index. + self.index = self.index.saturating_sub(1); + self.next_connection_mode().await?; + } + } + Ok(()) + } + + pub async fn on_resolve_access_method( + &mut self, + tx: ResponseTx<Option<ResolvedConnectionMode>>, + setting: AccessMethodSetting, + ) -> Result<()> { + let reply = self.resolve(setting).await; + self.reply(tx, reply) + } + + async fn resolve( + &mut self, + access_method: AccessMethodSetting, + ) -> Option<ResolvedConnectionMode> { + Self::resolve_inner( + &access_method, + &self.relay_selector, + &mut self.encrypted_dns_proxy_cache, + &self.address_cache, + &*self.provider, + ) + .await + } + + async fn resolve_inner( + access_method: &AccessMethodSetting, + relay_selector: &RelaySelector, + encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, + address_cache: &AddressCache, + provider: &dyn AllowedClientsProvider, + ) -> Option<ResolvedConnectionMode> { + let connection_mode = + Self::resolve_connection_mode(access_method, relay_selector, encrypted_dns_proxy_cache) + .await?; + let endpoint = resolve_allowed_endpoint( + &connection_mode, + address_cache.get_address().await, + provider, + ); + Some(ResolvedConnectionMode { + connection_mode, + endpoint, + setting: access_method.clone(), + }) + } + + /// Resolve an access method into a set of connection details - fall back to + /// [`ApiConnectionMode::Direct`] in case `access_method` does not yield anything. + async fn resolve_with_default( + &mut self, + access_method: AccessMethodSetting, + ) -> ResolvedConnectionMode { + Self::resolve_inner_with_default( + &access_method, + &self.relay_selector, + &mut self.encrypted_dns_proxy_cache, + &self.address_cache, + &*self.provider, + ) + .await + } + + async fn resolve_inner_with_default( + access_method: &AccessMethodSetting, + relay_selector: &RelaySelector, + encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, + address_cache: &AddressCache, + provider: &dyn AllowedClientsProvider, + ) -> ResolvedConnectionMode { + match Self::resolve_inner( + access_method, + relay_selector, + encrypted_dns_proxy_cache, + address_cache, + provider, + ) + .await + { + Some(resolved) => resolved, + None => { + log::trace!("Defaulting to direct API connection"); + ResolvedConnectionMode { + connection_mode: ApiConnectionMode::Direct, + endpoint: resolve_allowed_endpoint( + &ApiConnectionMode::Direct, + address_cache.get_address().await, + provider, + ), + setting: access_method.clone(), + } + } + } + } + + async fn resolve_connection_mode( + access_method: &AccessMethodSetting, + relay_selector: &RelaySelector, + encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, + ) -> Option<ApiConnectionMode> { + let connection_mode = { + match &access_method.access_method { + AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct, + AccessMethod::BuiltIn(BuiltInAccessMethod::Bridge) => { + let Some(bridge) = relay_selector.get_bridge_forced() else { + log::warn!("Could not select a Mullvad bridge"); + log::debug!("The relay list might be empty"); + return None; + }; + let proxy = CustomProxy::Shadowsocks(bridge); + ApiConnectionMode::Proxied(ProxyConfig::from(proxy)) + } + AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => { + if let Err(error) = encrypted_dns_proxy_cache.fetch_configs("frakta.eu").await { + log::warn!("Failed to fetch new Encrypted DNS Proxy configurations"); + log::debug!("{error:#?}"); + } + let Some(edp) = encrypted_dns_proxy_cache.next_configuration() else { + log::warn!("Could not select next Encrypted DNS proxy config"); + return None; + }; + ApiConnectionMode::Proxied(ProxyConfig::from(edp)) + } + AccessMethod::Custom(config) => { + ApiConnectionMode::Proxied(ProxyConfig::from(config.clone())) + } + } + }; + Some(connection_mode) + } +} + +pub fn resolve_allowed_endpoint( + connection_mode: &ApiConnectionMode, + fallback: SocketAddr, + provider: &dyn AllowedClientsProvider, +) -> AllowedEndpoint { + let endpoint = match connection_mode.get_endpoint() { + Some(endpoint) => endpoint, + None => Endpoint::from_socket_address(fallback, TransportProtocol::Tcp), + }; + let clients = provider.allowed_clients(connection_mode); + AllowedEndpoint { endpoint, clients } +} diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 950ce10345..6954c185c4 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -25,6 +25,7 @@ use availability::ApiAvailability; pub mod rest; mod abortable_stream; +pub mod api; mod https_client_with_sni; pub mod proxy; mod tls_stream; diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 106449bb30..9b67270f01 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -8,7 +8,7 @@ use std::{ task::{self, Poll}, }; use talpid_types::{ - net::{proxy, Endpoint, TransportProtocol}, + net::{proxy, AllowedClients, Endpoint, TransportProtocol}, ErrorExt, }; use tokio::{ @@ -53,6 +53,10 @@ impl ConnectionModeProvider for StaticConnectionModeProvider { } } +pub trait AllowedClientsProvider: Send + Sync { + fn allowed_clients(&self, connection_mode: &ApiConnectionMode) -> AllowedClients; +} + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ApiConnectionMode { /// Connect directly to the target. diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index 94f126a115..cad5d9bdd8 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -1,5 +1,6 @@ -use crate::{api, settings, Daemon}; -use mullvad_api::{proxy::ApiConnectionMode, rest, ApiProxy}; +use crate::{settings, Daemon}; +use mullvad_api::{api, rest}; +use mullvad_api::{proxy::ApiConnectionMode, ApiProxy}; use mullvad_types::{ access_method::{self, AccessMethod, AccessMethodSetting}, settings::Settings, @@ -153,9 +154,9 @@ impl Daemon { #[cfg(not(target_os = "android"))] pub(crate) async fn test_access_method( proxy: talpid_types::net::AllowedEndpoint, - access_method_selector: api::AccessModeSelectorHandle, + access_method_selector: mullvad_api::api::AccessModeSelectorHandle, daemon_event_sender: crate::DaemonEventSender<( - api::AccessMethodEvent, + mullvad_api::api::AccessMethodEvent, futures::channel::oneshot::Sender<()>, )>, api_proxy: ApiProxy, @@ -165,14 +166,14 @@ impl Daemon { .await .map(|connection_mode| connection_mode.endpoint)?; - api::AccessMethodEvent::Allow { endpoint: proxy } - .send(daemon_event_sender.clone()) + mullvad_api::api::AccessMethodEvent::Allow { endpoint: proxy } + .send(daemon_event_sender.to_unbounded_sender()) .await?; let result = Self::perform_api_request(api_proxy).await; - api::AccessMethodEvent::Allow { endpoint: reset } - .send(daemon_event_sender) + mullvad_api::api::AccessMethodEvent::Allow { endpoint: reset } + .send(daemon_event_sender.to_unbounded_sender()) .await?; result diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index e07a5a7077..cc3eb16d59 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -1,683 +1,61 @@ -//! This module is responsible for enabling custom [`AccessMethodSetting`]s to -//! be used when connecting to the Mullvad API. In practice this means -//! converting [`AccessMethodSetting`]s to connection details as encoded by -//! [`ApiConnectionMode`], which in turn is used by `mullvad-api` for -//! establishing connections when performing API requests. #[cfg(target_os = "android")] use crate::DaemonCommand; -use crate::DaemonEventSender; -use futures::{ - channel::{mpsc, oneshot}, - StreamExt, -}; -#[cfg(feature = "api-override")] -use mullvad_api::ApiEndpoint; -use mullvad_api::{ - availability::ApiAvailability, - proxy::{ApiConnectionMode, ConnectionModeProvider, ProxyConfig}, - AddressCache, -}; -use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState; -use mullvad_relay_selector::RelaySelector; -use mullvad_types::access_method::{ - AccessMethod, AccessMethodSetting, BuiltInAccessMethod, Id, Settings, -}; -use std::{net::SocketAddr, path::PathBuf}; -use talpid_core::mpsc::Sender; -use talpid_types::net::{ - proxy::CustomProxy, AllowedClients, AllowedEndpoint, Connectivity, Endpoint, TransportProtocol, -}; +use futures::channel::mpsc; +use futures::StreamExt; +use mullvad_api::availability::ApiAvailability; +use mullvad_api::proxy::AllowedClientsProvider; +use mullvad_api::proxy::ApiConnectionMode; +use mullvad_api::proxy::ProxyConfig; +use talpid_types::net::AllowedClients; +use talpid_types::net::Connectivity; -pub enum Message { - Get(ResponseTx<ResolvedConnectionMode>), - Use(ResponseTx<()>, Id), - Rotate(ResponseTx<ApiConnectionMode>), - Update(ResponseTx<()>, Settings), - Resolve( - ResponseTx<Option<ResolvedConnectionMode>>, - AccessMethodSetting, - ), -} - -/// Calling [`AccessMethodEvent::send`] will cause a -/// [`crate::InternalDaemonEvent::AccessMethodEvent`] being sent to the daemon, -/// which in turn will handle updating the firewall and notifying clients as -/// applicable. -pub enum AccessMethodEvent { - /// A [`AccessMethodEvent::New`] event is emitted when the active access - /// method changes. - /// - /// This happens when a [`mullvad_api::rest::RequestService`] requests a new - /// [`ApiConnectionMode`] from the running [`AccessModeSelector`]. - New { - /// The new active [`AccessMethodSetting`]. - setting: AccessMethodSetting, - /// The endpoint which represents how to connect to the Mullvad API and - /// which clients are allowed to initiate such a connection. - #[cfg(not(target_os = "android"))] - endpoint: AllowedEndpoint, - }, - /// Emitted when the the firewall should be updated. - /// - /// This is useful for example when testing if some [`AccessMethodSetting`] - /// can be used to reach the Mullvad API. In this scenario, the currently - /// active access method will temporarily change (approximately for the - /// duration of 1 API call). Since this is just an internal test which - /// should be opaque to any client, it should not produce any unwanted noise - /// and as such it is *not* broadcasted after the daemon is done processing - /// this [`AccessMethodEvent::Allow`]. - #[cfg(not(target_os = "android"))] - Allow { endpoint: AllowedEndpoint }, -} - -impl AccessMethodEvent { - pub(crate) async fn send( - self, - daemon_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, - ) -> Result<()> { - // It is up to the daemon to actually allow traffic to/from `api_endpoint` - // by updating the firewall. This [`oneshot::Sender`] allows the daemon to - // communicate when that action is done. - let (update_finished_tx, update_finished_rx) = oneshot::channel(); - let _ = daemon_event_sender.send((self, update_finished_tx)); - // Wait for the daemon to finish processing `event`. - update_finished_rx.await.map_err(Error::NotRunning) - } -} - -/// This struct represent a concrete API endpoint (in the form of an -/// [`ApiConnectionMode`] and [`AllowedEndpoint`]) which has been derived from -/// some [`AccessMethodSetting`] (most likely the currently active access -/// method). These logically related values are sometimes useful to group -/// together into one value, which is encoded by [`ResolvedConnectionMode`]. -#[derive(Clone)] -pub struct ResolvedConnectionMode { - /// The connection strategy to be used by the `mullvad-api` crate when - /// initializing API requests. - pub connection_mode: ApiConnectionMode, - /// The actual endpoint of the Mullvad API and which clients should be - /// allowed to initialize a connection to this endpoint. - pub endpoint: AllowedEndpoint, - /// This is the [`AccessMethodSetting`] which resolved into - /// `connection_mode` and `endpoint`. - pub setting: AccessMethodSetting, -} - -/// Describes all the ways the daemon service which handles access methods can -/// fail. -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("No access methods were provided.")] - NoAccessMethods, - #[error("Could not resolve access method {access_method:#?}")] - Resolve { access_method: AccessMethod }, - #[error("AccessModeSelector is not receiving any messages.")] - SendFailed(#[from] mpsc::TrySendError<Message>), - #[error("AccessModeSelector is not receiving any messages.")] - OneshotSendFailed, - #[error("AccessModeSelector is not responding.")] - NotRunning(#[from] oneshot::Canceled), -} - -impl std::fmt::Display for Message { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Message::Get(_) => f.write_str("Get"), - Message::Use(..) => f.write_str("Set"), - Message::Rotate(_) => f.write_str("Rotate"), - Message::Update(..) => f.write_str("Update"), - Message::Resolve(..) => f.write_str("Resolve"), - } - } -} - -impl Error { - /// Check if this error implies that the currenly running - /// [`AccessModeSelector`] can not continue to operate properly. - /// - /// To recover from this kind of error, the daemon will probably have to - /// intervene. - fn is_critical_error(&self) -> bool { - matches!( - self, - Error::SendFailed(..) | Error::OneshotSendFailed | Error::NotRunning(..) - ) - } -} - -type ResponseTx<T> = oneshot::Sender<Result<T>>; -type Result<T> = std::result::Result<T, Error>; - -/// A channel for sending [`Message`] commands to a running -/// [`AccessModeSelector`]. -#[derive(Clone)] -pub struct AccessModeSelectorHandle { - cmd_tx: mpsc::UnboundedSender<Message>, -} - -impl AccessModeSelectorHandle { - async fn send_command<T>(&self, make_cmd: impl FnOnce(ResponseTx<T>) -> Message) -> Result<T> { - let (tx, rx) = oneshot::channel(); - self.cmd_tx.unbounded_send(make_cmd(tx))?; - rx.await.map_err(Error::NotRunning)? - } - - pub async fn get_current(&self) -> Result<ResolvedConnectionMode> { - self.send_command(Message::Get).await.inspect_err(|_| { - log::debug!("Failed to get current access method!"); - }) - } - - pub async fn use_access_method(&self, value: Id) -> Result<()> { - self.send_command(|tx| Message::Use(tx, value)) - .await - .inspect_err(|_| { - log::debug!("Failed to set new access method!"); - }) - } - - pub async fn update_access_methods(&self, access_methods: Settings) -> Result<()> { - self.send_command(|tx| Message::Update(tx, access_methods)) - .await - .inspect_err(|_| { - log::debug!("Failed to switch to a new set of access methods"); - }) - } - - /// Try to resolve an access method into a set of connection details. - /// - /// This might fail if the underlying store/cache where `setting` is the key is empty. - /// In that case, `Ok(None)` is returned. - pub async fn resolve( - &self, - setting: AccessMethodSetting, - ) -> Result<Option<ResolvedConnectionMode>> { - self.send_command(|tx| Message::Resolve(tx, setting)) - .await - .inspect_err(|_| { - log::error!("Failed to update new access methods!"); - }) - } - - pub async fn rotate(&self) -> Result<ApiConnectionMode> { - self.send_command(Message::Rotate).await.inspect_err(|_| { - log::debug!("Failed while getting the next access method"); - }) - } -} - -pub struct AccessModeConnectionModeProvider { - initial: ApiConnectionMode, - handle: AccessModeSelectorHandle, - change_rx: mpsc::UnboundedReceiver<ApiConnectionMode>, -} - -impl AccessModeConnectionModeProvider { - fn new( - handle: AccessModeSelectorHandle, - initial_connection_mode: ApiConnectionMode, - change_rx: mpsc::UnboundedReceiver<ApiConnectionMode>, - ) -> Result<Self> { - Ok(Self { - initial: initial_connection_mode, - handle, - change_rx, - }) - } -} - -impl ConnectionModeProvider for AccessModeConnectionModeProvider { - fn initial(&self) -> ApiConnectionMode { - self.initial.clone() - } - - fn receive(&mut self) -> impl std::future::Future<Output = Option<ApiConnectionMode>> + Send { - self.change_rx.next() - } - - fn rotate(&self) -> impl std::future::Future<Output = ()> + Send { - let handle = self.handle.clone(); - async move { - handle.rotate().await.ok(); - } - } -} - -/// A small actor which takes care of handling the logic around rotating -/// connection modes to be used for Mullvad API request. -/// -/// When `mullvad-api` fails to contact the API, it will request a new -/// connection mode. The API can be connected to either directly (i.e., -/// [`ApiConnectionMode::Direct`]) via a bridge ([`ApiConnectionMode::Proxied`]) -/// or via any supported custom proxy protocol -/// ([`talpid_types::net::proxy::CustomProxy`]). -pub struct AccessModeSelector { - #[cfg(feature = "api-override")] - api_endpoint: ApiEndpoint, - cmd_rx: mpsc::UnboundedReceiver<Message>, - cache_dir: PathBuf, - /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. - relay_selector: RelaySelector, - /// Used for selecting a config for the 'Encrypted DNS proxy' access method. - encrypted_dns_proxy_cache: EncryptedDnsProxyState, - access_method_settings: Settings, - address_cache: AddressCache, - access_method_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, - connection_mode_provider_sender: mpsc::UnboundedSender<ApiConnectionMode>, - current: ResolvedConnectionMode, - /// `index` is used to keep track of the [`AccessMethodSetting`] to use. - index: usize, -} - -impl AccessModeSelector { - pub(crate) async fn spawn( - cache_dir: PathBuf, - relay_selector: RelaySelector, - #[cfg_attr(not(feature = "api-override"), allow(unused_mut))] - mut access_method_settings: Settings, - #[cfg(feature = "api-override")] api_endpoint: ApiEndpoint, - access_method_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>, - address_cache: AddressCache, - ) -> Result<(AccessModeSelectorHandle, AccessModeConnectionModeProvider)> { - let (cmd_tx, cmd_rx) = mpsc::unbounded(); - - #[cfg(feature = "api-override")] - { - if api_endpoint.force_direct { - access_method_settings - .update(|setting| setting.is_direct(), |setting| setting.enable()); - } - } - - // Initialize the Encrypted DNS cache - let mut encrypted_dns_proxy_cache = EncryptedDnsProxyState::default(); - - // Always start looking from the position of `Direct`. - let (index, next) = Self::find_next_active(0, &access_method_settings); - let initial_connection_mode = Self::resolve_inner_with_default( - &next, - &relay_selector, - &mut encrypted_dns_proxy_cache, - &address_cache, - ) - .await; - - let (change_tx, change_rx) = mpsc::unbounded(); - - let api_connection_mode = initial_connection_mode.connection_mode.clone(); - - let selector = AccessModeSelector { - cmd_rx, - cache_dir, - relay_selector, - encrypted_dns_proxy_cache, - access_method_settings, - address_cache, - access_method_event_sender, - connection_mode_provider_sender: change_tx, - current: initial_connection_mode, - index, - #[cfg(feature = "api-override")] - api_endpoint, - }; - - tokio::spawn(selector.into_future()); - - let handle = AccessModeSelectorHandle { cmd_tx }; - - let connection_mode_provider = - AccessModeConnectionModeProvider::new(handle.clone(), api_connection_mode, change_rx)?; - - Ok((handle, connection_mode_provider)) - } - - async fn into_future(mut self) { - while let Some(cmd) = self.cmd_rx.next().await { - log::trace!("Processing {cmd} command"); - let execution = match cmd { - Message::Get(tx) => self.on_get_access_method(tx), - Message::Use(tx, id) => self.on_use_access_method(tx, id).await, - Message::Rotate(tx) => self.on_next_connection_mode(tx).await, - Message::Update(tx, values) => self.on_update_access_methods(tx, values).await, - Message::Resolve(tx, setting) => self.on_resolve_access_method(tx, setting).await, - }; - match execution { - Ok(_) => (), - Err(error) if error.is_critical_error() => { - log::error!( - "AccessModeSelector failed due to an internal error and won't be able to recover without a restart. {error}" - ); - break; - } - Err(error) => { - log::debug!("AccessModeSelector failed processing command due to {error}"); - } - } - } - } - - fn reply<T>(&self, tx: ResponseTx<T>, value: T) -> Result<()> { - tx.send(Ok(value)).map_err(|_| Error::OneshotSendFailed)?; - Ok(()) - } - - fn on_get_access_method(&mut self, tx: ResponseTx<ResolvedConnectionMode>) -> Result<()> { - self.reply(tx, self.current.clone()) - } - - async fn on_use_access_method(&mut self, tx: ResponseTx<()>, id: Id) -> Result<()> { - self.use_access_method(id).await; - self.reply(tx, ()) - } - - /// Set and announce the specified access method as the current one. - async fn use_access_method(&mut self, id: Id) { - #[cfg(feature = "api-override")] - { - if self.api_endpoint.force_direct { - log::debug!("API proxies are disabled"); - return; - } - } - - let Some((index, method)) = self - .access_method_settings - .iter() - .enumerate() - .find(|(_, access_method)| access_method.get_id() == id) - else { - return; - }; - - self.index = index; - self.set_current(method.to_owned()).await; - } - - async fn on_next_connection_mode(&mut self, tx: ResponseTx<ApiConnectionMode>) -> Result<()> { - let next = self.next_connection_mode().await?; - self.reply(tx, next) - } - - async fn next_connection_mode(&mut self) -> Result<ApiConnectionMode> { - #[cfg(feature = "api-override")] - { - if self.api_endpoint.force_direct { - log::debug!("API proxies are disabled"); - return Ok(ApiConnectionMode::Direct); - } - - log::debug!( - "The `api-override` feature is enabled, but a direct connection \ - is not enforced. Selecting API access methods as normal" - ); - } - - let (next_index, next) = - Self::find_next_active(self.index + 1, &self.access_method_settings); - self.index = next_index; - self.set_current(next).await; - Ok(self.current.connection_mode.clone()) - } - - async fn set_current(&mut self, access_method: AccessMethodSetting) { - let resolved = self.resolve_with_default(access_method).await; - - // Note: If the daemon is busy waiting for a call to this function - // to complete while we wait for the daemon to fully handle this - // `NewAccessMethodEvent`, then we find ourselves in a deadlock. - // This can happen during daemon startup when spawning a new - // `MullvadRestHandle`, which will call and await `next` on a Stream - // created from this `AccessModeSelector` instance. As such, the - // completion channel is discarded in this instance. - let setting = resolved.setting.clone(); - #[cfg(not(target_os = "android"))] - let endpoint = resolved.endpoint.clone(); - let daemon_sender = self.access_method_event_sender.clone(); - tokio::spawn(async move { - let _ = AccessMethodEvent::New { - setting, - #[cfg(not(target_os = "android"))] - endpoint, - } - .send(daemon_sender) - .await; - }); - - // Save the new connection mode to cache! - let cache_dir = self.cache_dir.clone(); - let connection_mode = resolved.connection_mode.clone(); - tokio::spawn(async move { - if connection_mode.save(&cache_dir).await.is_err() { - log::warn!("Failed to save {connection_mode:#?} to cache") - } - }); - - // Notify REST client - let _ = self - .connection_mode_provider_sender - .unbounded_send(resolved.connection_mode.clone()); - - self.current = resolved; - - log::info!( - "A new API access method has been selected: {name}", - name = self.current.setting.name - ); - } - - /// Find the next access method to use. - /// - /// * `start`: From which point in `access_methods` to start the search. - /// * `access_methods`: The search space. - fn find_next_active(start: usize, access_methods: &Settings) -> (usize, AccessMethodSetting) { - access_methods - .iter() - .cloned() - .enumerate() - .cycle() - .skip(start) - .take(access_methods.cardinality()) - .find(|(_index, access_method)| access_method.enabled()) - .unwrap_or_else(|| (0, access_methods.direct().clone())) - } - - async fn on_update_access_methods( - &mut self, - tx: ResponseTx<()>, - access_methods: Settings, - ) -> Result<()> { - self.update_access_methods(access_methods).await?; - self.reply(tx, ()) - } - - async fn update_access_methods(&mut self, access_methods: Settings) -> Result<()> { - self.access_method_settings = access_methods; - - let new_current = self - .access_method_settings - .iter() - .enumerate() - .find(|(_, access_method)| access_method.get_id() == self.current.setting.get_id()); - - match new_current { - Some((index, new_current)) => { - // If the current method was modified, announce changes - self.index = index; - if self.current.setting != *new_current { - if new_current.enabled() { - self.set_current(new_current.to_owned()).await; - } else { - self.next_connection_mode().await?; - } - } - } - None => { - // Current method was removed: A new access method will suddenly have the same index - // as the one we are removing, but we want it to still be a - // candidate. A minor hack to achieve this is to simply decrement - // the current index. - self.index = self.index.saturating_sub(1); - self.next_connection_mode().await?; +#[cfg(target_os = "android")] +pub(crate) fn create_bypass_tx( + event_sender: &DaemonEventSender, +) -> Option<mpsc::Sender<mullvad_api::SocketBypassRequest>> { + let (bypass_tx, mut bypass_rx) = mpsc::channel(1); + let daemon_tx = event_sender.to_specialized_sender(); + tokio::spawn(async move { + while let Some((raw_fd, done_tx)) = bypass_rx.next().await { + if daemon_tx + .send(DaemonCommand::BypassSocket(raw_fd, done_tx)) + .is_err() + { + log::error!("Can't send socket bypass request to daemon"); + break; } } - Ok(()) - } - - pub async fn on_resolve_access_method( - &mut self, - tx: ResponseTx<Option<ResolvedConnectionMode>>, - setting: AccessMethodSetting, - ) -> Result<()> { - let reply = self.resolve(setting).await; - self.reply(tx, reply) - } - - async fn resolve( - &mut self, - access_method: AccessMethodSetting, - ) -> Option<ResolvedConnectionMode> { - Self::resolve_inner( - &access_method, - &self.relay_selector, - &mut self.encrypted_dns_proxy_cache, - &self.address_cache, - ) - .await - } - - async fn resolve_inner( - access_method: &AccessMethodSetting, - relay_selector: &RelaySelector, - encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, - address_cache: &AddressCache, - ) -> Option<ResolvedConnectionMode> { - let connection_mode = - Self::resolve_connection_mode(access_method, relay_selector, encrypted_dns_proxy_cache) - .await?; - let endpoint = - resolve_allowed_endpoint(&connection_mode, address_cache.get_address().await); - Some(ResolvedConnectionMode { - connection_mode, - endpoint, - setting: access_method.clone(), - }) - } + }); + Some(bypass_tx) +} - /// Resolve an access method into a set of connection details - fall back to - /// [`ApiConnectionMode::Direct`] in case `access_method` does not yield anything. - async fn resolve_with_default( - &mut self, - access_method: AccessMethodSetting, - ) -> ResolvedConnectionMode { - Self::resolve_inner_with_default( - &access_method, - &self.relay_selector, - &mut self.encrypted_dns_proxy_cache, - &self.address_cache, - ) - .await - } +#[derive(Clone, Copy)] +pub struct AllowedClientsSelector {} - async fn resolve_inner_with_default( - access_method: &AccessMethodSetting, - relay_selector: &RelaySelector, - encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, - address_cache: &AddressCache, - ) -> ResolvedConnectionMode { - match Self::resolve_inner( - access_method, - relay_selector, - encrypted_dns_proxy_cache, - address_cache, - ) - .await - { - Some(resolved) => resolved, - None => { - log::trace!("Defaulting to direct API connection"); - ResolvedConnectionMode { - connection_mode: ApiConnectionMode::Direct, - endpoint: resolve_allowed_endpoint( - &ApiConnectionMode::Direct, - address_cache.get_address().await, - ), - setting: access_method.clone(), - } - } +impl AllowedClientsProvider for AllowedClientsSelector { + #[cfg(unix)] + fn allowed_clients(&self, connection_mode: &ApiConnectionMode) -> AllowedClients { + match connection_mode { + ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::All, + ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => AllowedClients::Root, } } - async fn resolve_connection_mode( - access_method: &AccessMethodSetting, - relay_selector: &RelaySelector, - encrypted_dns_proxy_cache: &mut EncryptedDnsProxyState, - ) -> Option<ApiConnectionMode> { - let connection_mode = { - match &access_method.access_method { - AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct, - AccessMethod::BuiltIn(BuiltInAccessMethod::Bridge) => { - let Some(bridge) = relay_selector.get_bridge_forced() else { - log::warn!("Could not select a Mullvad bridge"); - log::debug!("The relay list might be empty"); - return None; - }; - let proxy = CustomProxy::Shadowsocks(bridge); - ApiConnectionMode::Proxied(ProxyConfig::from(proxy)) - } - AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => { - if let Err(error) = encrypted_dns_proxy_cache.fetch_configs("frakta.eu").await { - log::warn!("Failed to fetch new Encrypted DNS Proxy configurations"); - log::debug!("{error:#?}"); - } - let Some(edp) = encrypted_dns_proxy_cache.next_configuration() else { - log::warn!("Could not select next Encrypted DNS proxy config"); - return None; - }; - ApiConnectionMode::Proxied(ProxyConfig::from(edp)) - } - AccessMethod::Custom(config) => { - ApiConnectionMode::Proxied(ProxyConfig::from(config.clone())) - } + #[cfg(windows)] + fn allowed_clients(&self, connection_mode: &ApiConnectionMode) -> AllowedClients { + match connection_mode { + ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::all(), + ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => { + let daemon_exe = std::env::current_exe().expect("failed to obtain executable path"); + vec![ + daemon_exe + .parent() + .expect("missing executable parent directory") + .join("mullvad-problem-report.exe"), + daemon_exe, + ] + .into() } - }; - Some(connection_mode) - } -} - -pub fn resolve_allowed_endpoint( - connection_mode: &ApiConnectionMode, - fallback: SocketAddr, -) -> AllowedEndpoint { - let endpoint = match connection_mode.get_endpoint() { - Some(endpoint) => endpoint, - None => Endpoint::from_socket_address(fallback, TransportProtocol::Tcp), - }; - let clients = allowed_clients(connection_mode); - AllowedEndpoint { endpoint, clients } -} - -#[cfg(unix)] -pub fn allowed_clients(connection_mode: &ApiConnectionMode) -> AllowedClients { - match connection_mode { - ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::All, - ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => AllowedClients::Root, - } -} - -#[cfg(windows)] -pub fn allowed_clients(connection_mode: &ApiConnectionMode) -> AllowedClients { - match connection_mode { - ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::all(), - ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => { - let daemon_exe = std::env::current_exe().expect("failed to obtain executable path"); - vec![ - daemon_exe - .parent() - .expect("missing executable parent directory") - .join("mullvad-problem-report.exe"), - daemon_exe, - ] - .into() } } } @@ -705,23 +83,3 @@ pub(crate) fn forward_offline_state( } }); } - -#[cfg(target_os = "android")] -pub(crate) fn create_bypass_tx( - event_sender: &DaemonEventSender, -) -> Option<mpsc::Sender<mullvad_api::SocketBypassRequest>> { - let (bypass_tx, mut bypass_rx) = mpsc::channel(1); - let daemon_tx = event_sender.to_specialized_sender(); - tokio::spawn(async move { - while let Some((raw_fd, done_tx)) = bypass_rx.next().await { - if daemon_tx - .send(DaemonCommand::BypassSocket(raw_fd, done_tx)) - .is_err() - { - log::error!("Can't send socket bypass request to daemon"); - break; - } - } - }); - Some(bypass_tx) -} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 217823f6cd..45819476fa 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -31,7 +31,7 @@ pub mod version; mod version_check; use crate::target_state::PersistentTargetState; -use api::AccessMethodEvent; +use api::AllowedClientsSelector; use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent}; use futures::{ channel::{mpsc, oneshot}, @@ -42,6 +42,7 @@ use geoip::GeoIpHandler; use leak_checker::{LeakChecker, LeakInfo}; use management_interface::ManagementInterfaceServer; use mullvad_api::ApiEndpoint; +use mullvad_api::{api::AccessMethodEvent, proxy::AllowedClientsProvider}; use mullvad_relay_selector::{RelaySelector, SelectorConfig}; #[cfg(target_os = "android")] use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; @@ -196,7 +197,7 @@ pub enum Error { AccessMethodError(#[source] access_method::Error), #[error("API connection mode error")] - ApiConnectionModeError(#[source] api::Error), + ApiConnectionModeError(#[source] mullvad_api::api::Error), #[error("No custom bridge has been specified")] NoCustomProxySaved, @@ -543,7 +544,6 @@ impl DaemonEventSender { _event: PhantomData, } } - pub fn to_specialized_sender<E>(&self) -> DaemonEventSender<E> where InternalDaemonEvent: From<E>, @@ -569,6 +569,34 @@ where } } +impl<E> DaemonEventSender<E> +where + InternalDaemonEvent: From<E>, +{ + pub fn to_unbounded_sender<T>(&self) -> mpsc::UnboundedSender<T> + where + InternalDaemonEvent: From<E>, + T: Send + 'static, + E: From<T>, + { + let (tx, mut rx) = mpsc::unbounded::<T>(); + let sender = self.sender.clone(); + tokio::runtime::Handle::current().spawn(async move { + while let Some(msg) = rx.next().await { + if let Some(tx) = sender.upgrade() { + let e: E = E::from(msg); + if tx.send(e.into()).is_err() { + return; + } + } else { + return; + }; + } + }); + tx + } +} + pub struct Daemon { tunnel_state: TunnelState, target_state: PersistentTargetState, @@ -583,7 +611,7 @@ pub struct Daemon { account_history: account_history::AccountHistory, device_checker: device::TunnelStateChangeHandler, account_manager: device::AccountManagerHandle, - access_mode_handler: api::AccessModeSelectorHandle, + access_mode_handler: mullvad_api::api::AccessModeSelectorHandle, api_runtime: mullvad_api::Runtime, api_handle: mullvad_api::rest::MullvadRestHandle, version_updater_handle: version_check::VersionUpdaterHandle, @@ -679,17 +707,22 @@ impl Daemon { .set_config(SelectorConfig::from_settings(settings)); }); - let (access_mode_handler, access_mode_provider) = api::AccessModeSelector::spawn( - config.cache_dir.clone(), - relay_selector.clone(), - settings.api_access_methods.clone(), - #[cfg(feature = "api-override")] - config.endpoint.clone(), - internal_event_tx.to_specialized_sender(), - api_runtime.address_cache().clone(), - ) - .await - .map_err(Error::ApiConnectionModeError)?; + let allowed_clients_selector = AllowedClientsSelector {}; + let selector_box: Box<dyn AllowedClientsProvider> = Box::new(allowed_clients_selector); + + let (access_mode_handler, access_mode_provider) = + mullvad_api::api::AccessModeSelector::spawn( + config.cache_dir.clone(), + relay_selector.clone(), + settings.api_access_methods.clone(), + #[cfg(feature = "api-override")] + config.endpoint.clone(), + internal_event_tx.to_unbounded_sender(), + api_runtime.address_cache().clone(), + selector_box, + ) + .await + .map_err(Error::ApiConnectionModeError)?; let api_handle = api_runtime.mullvad_rest_handle(access_mode_provider); @@ -2815,6 +2848,7 @@ impl Daemon { tx: ResponseTx<bool, Error>, proxy: talpid_types::net::proxy::CustomProxy, ) { + use crate::AllowedClientsSelector; use mullvad_api::proxy::{ApiConnectionMode, ProxyConfig}; use talpid_types::net::AllowedEndpoint; @@ -2822,7 +2856,7 @@ impl Daemon { let api_proxy = self.create_limited_api_proxy(connection_mode.clone()); let proxy_endpoint = AllowedEndpoint { endpoint: proxy.get_remote_endpoint().endpoint, - clients: api::allowed_clients(&connection_mode), + clients: AllowedClientsSelector {}.allowed_clients(&connection_mode), }; let daemon_event_sender = self.tx.to_specialized_sender(); @@ -2864,7 +2898,7 @@ impl Daemon { { Ok(Some(test_subject)) => test_subject, Ok(None) => { - let error = Error::ApiConnectionModeError(self::api::Error::Resolve { + let error = Error::ApiConnectionModeError(mullvad_api::api::Error::Resolve { access_method: access_method.access_method, }); reply(Err(error)); |
