diff options
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/api.rs | 441 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 26 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 12 | ||||
| -rw-r--r-- | mullvad-jni/src/jni_event_listener.rs | 6 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 1 | ||||
| -rw-r--r-- | mullvad-management-interface/src/client.rs | 6 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/access_method.rs | 10 |
8 files changed, 344 insertions, 163 deletions
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index 5bed82b4c0..15b0d10dfe 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -75,6 +75,11 @@ impl Status { println!("Remove device event: {device:#?}"); } } + DaemonEvent::NewAccessMethod(access_method) => { + if args.debug { + println!("New access method: {access_method:#?}"); + } + } } } Ok(()) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index efb8f3088d..6efec8112b 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -4,33 +4,123 @@ //! [`ApiConnectionMode`], which in turn is used by `mullvad-api` for //! establishing connections when performing API requests. #[cfg(target_os = "android")] -use crate::{DaemonCommand, DaemonEventSender}; +use crate::DaemonCommand; +use crate::DaemonEventSender; use futures::{ - channel::{mpsc, oneshot}, - stream::unfold, + channel::{ + mpsc, + oneshot::{self, Canceled}, + }, Stream, StreamExt, }; use mullvad_api::{ availability::ApiAvailabilityHandle, proxy::{ApiConnectionMode, ProxyConfig}, - ApiEndpointUpdateCallback, + AddressCache, }; use mullvad_relay_selector::RelaySelector; use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, BuiltInAccessMethod}; -use std::{ - path::PathBuf, - sync::{Arc, Mutex, Weak}, -}; -#[cfg(target_os = "android")] +use std::{net::SocketAddr, path::PathBuf}; use talpid_core::mpsc::Sender; -use talpid_core::tunnel_state_machine::TunnelCommand; -use talpid_types::net::{AllowedEndpoint, Endpoint}; +use talpid_types::net::{ + proxy::{self, CustomProxy}, + AllowedClients, AllowedEndpoint, Endpoint, TransportProtocol, +}; pub enum Message { - Get(ResponseTx<AccessMethodSetting>), + Get(ResponseTx<ResolvedConnectionMode>), Set(ResponseTx<()>, AccessMethodSetting), Next(ResponseTx<ApiConnectionMode>), Update(ResponseTx<()>, Vec<AccessMethodSetting>), + Resolve(ResponseTx<ResolvedConnectionMode>, AccessMethodSetting), +} + +/// A [`NewAccessMethodEvent`] is emitted when the active access method changes, +/// which happens in any of the following two scenarios: +/// +/// * When a [`mullvad_api::rest::RequestService`] requests a new +/// [`ApiConnectionMode`] from the running [`AccessModeSelector`]. This will +/// lead to a [`crate::InternalDaemonEvent::AccessMethodEvent`] being sent to +/// the daemon, which in turn will notify all clients about the new access +/// method. +/// +/// * 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 [`NewAccessMethodEvent`]. +pub struct NewAccessMethodEvent { + /// The new active [`AccessMethodSetting`]. + pub setting: AccessMethodSetting, + /// The endpoint which represents how to connect to the Mullvad API and + /// which clients are allowed to initiate such a connection. + pub endpoint: AllowedEndpoint, + /// If the daemon should notify clients about the new access method. + /// + /// Defaults to `true`. + pub announce: bool, +} + +impl NewAccessMethodEvent { + /// Create a new [`NewAccessMethodEvent`] for the daemon to process. A + /// [`oneshot::Receiver`] can be used to await the daemon while it finishes + /// handling the new event. + pub fn new(setting: AccessMethodSetting, endpoint: AllowedEndpoint) -> NewAccessMethodEvent { + NewAccessMethodEvent { + setting, + endpoint, + announce: true, + } + } + + /// Whether the daemon should notify clients about the new access method or + /// not. + /// + /// * If `announce` is set to `true` the daemon will broadcast this event to + /// clients. + /// * If `announce` is set to `false` the daemon will *not* broadcast this + /// event. + pub fn announce(mut self, announce: bool) -> Self { + self.announce = announce; + self + } + + /// Send an internal daemon event which will punch a hole in the firewall + /// for the connection mode we are testing. + /// + /// Returns the channel on which the daemon will send a message over when it + /// is done applying the firewall changes. + pub(crate) async fn send( + self, + daemon_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + ) -> std::result::Result<(), Canceled> { + // 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 + } +} + +/// 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, } #[derive(err_derive::Error, Debug)] @@ -52,6 +142,7 @@ impl std::fmt::Display for Message { Message::Set(..) => f.write_str("Set"), Message::Next(_) => f.write_str("Next"), Message::Update(..) => f.write_str("Update"), + Message::Resolve(..) => f.write_str("Resolve"), } } } @@ -83,13 +174,11 @@ pub struct AccessModeSelectorHandle { 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)) - .map_err(Error::SendFailed)?; + self.cmd_tx.unbounded_send(make_cmd(tx))?; rx.await.map_err(Error::NotRunning)? } - pub async fn get_access_method(&self) -> Result<AccessMethodSetting> { + pub async fn get_current(&self) -> Result<ResolvedConnectionMode> { self.send_command(Message::Get).await.map_err(|err| { log::debug!("Failed to get current access method!"); err @@ -114,6 +203,15 @@ impl AccessModeSelectorHandle { }) } + pub async fn resolve(&self, setting: AccessMethodSetting) -> Result<ResolvedConnectionMode> { + self.send_command(|tx| Message::Resolve(tx, setting)) + .await + .map_err(|err| { + log::error!("Failed to update new access methods!"); + err + }) + } + pub async fn next(&self) -> Result<ApiConnectionMode> { self.send_command(Message::Next).await.map_err(|err| { log::debug!("Failed while getting the next access method"); @@ -124,10 +222,10 @@ impl AccessModeSelectorHandle { /// Convert this handle to a [`Stream`] of [`ApiConnectionMode`] from the /// associated [`AccessModeSelector`]. /// - /// Practically converts the handle to a listener for when the - /// currently valid connection modes changes. + /// Calling `next` on this stream will poll for the next access method, + /// which will be lazily produced (on-demand rather than speculatively). pub fn into_stream(self) -> impl Stream<Item = ApiConnectionMode> { - unfold(self, |handle| async move { + futures::stream::unfold(self, |handle| async move { match handle.next().await { Ok(connection_mode) => Some((connection_mode, handle)), // End this stream in case of failure in `next`. `next` should @@ -155,17 +253,22 @@ pub struct AccessModeSelector { /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, connection_modes: ConnectionModesIterator, + address_cache: AddressCache, + access_method_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + current: ResolvedConnectionMode, } impl AccessModeSelector { - pub fn spawn( + pub(crate) async fn spawn( cache_dir: PathBuf, relay_selector: RelaySelector, connection_modes: Vec<AccessMethodSetting>, - ) -> AccessModeSelectorHandle { + access_method_event_sender: DaemonEventSender<(NewAccessMethodEvent, oneshot::Sender<()>)>, + address_cache: AddressCache, + ) -> Result<AccessModeSelectorHandle> { let (cmd_tx, cmd_rx) = mpsc::unbounded(); - let connection_modes = match ConnectionModesIterator::new(connection_modes) { + let mut connection_modes = match ConnectionModesIterator::new(connection_modes) { Ok(provider) => provider, Err(Error::NoAccessMethods) | Err(_) => { // No settings seem to have been found. Default to using the the @@ -176,14 +279,25 @@ impl AccessModeSelector { ) } }; + + let initial_connection_mode = { + let next = connection_modes.next().ok_or(Error::NoAccessMethods)?; + Self::resolve_inner(next, &relay_selector, &address_cache).await + }; + let selector = AccessModeSelector { cmd_rx, cache_dir, relay_selector, connection_modes, + address_cache, + access_method_event_sender, + current: initial_connection_mode, }; + tokio::spawn(selector.into_future()); - AccessModeSelectorHandle { cmd_tx } + + Ok(AccessModeSelectorHandle { cmd_tx }) } async fn into_future(mut self) { @@ -192,8 +306,9 @@ impl AccessModeSelector { let execution = match cmd { Message::Get(tx) => self.on_get_access_method(tx), Message::Set(tx, value) => self.on_set_access_method(tx, value), - Message::Next(tx) => self.on_next_connection_mode(tx), + Message::Next(tx) => self.on_next_connection_mode(tx).await, Message::Update(tx, values) => self.on_update_access_methods(tx, values), + Message::Resolve(tx, setting) => self.on_resolve_access_method(tx, setting).await, }; match execution { Ok(_) => (), @@ -213,13 +328,8 @@ impl AccessModeSelector { Ok(()) } - fn on_get_access_method(&mut self, tx: ResponseTx<AccessMethodSetting>) -> Result<()> { - let value = self.get_access_method(); - self.reply(tx, value) - } - - fn get_access_method(&mut self) -> AccessMethodSetting { - self.connection_modes.peek() + fn on_get_access_method(&mut self, tx: ResponseTx<ResolvedConnectionMode>) -> Result<()> { + self.reply(tx, self.current.clone()) } fn on_set_access_method( @@ -231,40 +341,55 @@ impl AccessModeSelector { self.reply(tx, ()) } + /// Set the next access method to be returned by the [`Stream`] produced by + /// calling `into_stream`. fn set_access_method(&mut self, value: AccessMethodSetting) { self.connection_modes.set_access_method(value); } - fn on_next_connection_mode(&mut self, tx: ResponseTx<ApiConnectionMode>) -> Result<()> { - let next = self.next_connection_mode(); - // Save the new connection mode to cache! - { - let cache_dir = self.cache_dir.clone(); - let next = next.clone(); - tokio::spawn(async move { - if next.save(&cache_dir).await.is_err() { - log::warn!( - "Failed to save {connection_mode} to cache", - connection_mode = next - ) - } - }); - } + async fn on_next_connection_mode(&mut self, tx: ResponseTx<ApiConnectionMode>) -> Result<()> { + let next = self.next_connection_mode().await?; self.reply(tx, next) } - fn next_connection_mode(&mut self) -> ApiConnectionMode { - let access_method = self - .connection_modes - .next() - .map(|access_method_setting| access_method_setting.access_method) - .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)); + async fn next_connection_mode(&mut self) -> Result<ApiConnectionMode> { + let access_method = self.connection_modes.next().ok_or(Error::NoAccessMethods)?; + log::info!( + "A new API access method has been selected: {name}", + name = access_method.name + ); + let resolved = self.resolve(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(); + let endpoint = resolved.endpoint.clone(); + let daemon_sender = self.access_method_event_sender.clone(); + tokio::spawn(async move { + let _ = NewAccessMethodEvent::new(setting, endpoint) + .send(daemon_sender) + .await; + }); - let connection_mode = self.from(access_method); - log::info!("New API connection mode selected: {connection_mode}"); - connection_mode - } + // Save the new connection mode to cache! + let cache_dir = self.cache_dir.clone(); + let new_connection_mode = resolved.connection_mode.clone(); + tokio::spawn(async move { + if new_connection_mode.save(&cache_dir).await.is_err() { + log::warn!( + "Failed to save {connection_mode} to cache", + connection_mode = new_connection_mode + ) + } + }); + self.current = resolved; + Ok(self.current.connection_mode.clone()) + } fn on_update_access_methods( &mut self, tx: ResponseTx<()>, @@ -278,51 +403,78 @@ impl AccessModeSelector { self.connection_modes.update_access_methods(values) } - /// Ad-hoc version of [`std::convert::From::from`], but since some - /// [`ApiConnectionMode`]s require extra logic/data from - /// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait - /// can not be implemented. - fn from(&mut self, access_method: AccessMethod) -> ApiConnectionMode { - use talpid_types::net::proxy; - match access_method { - AccessMethod::BuiltIn(access_method) => match access_method { - BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, - BuiltInAccessMethod::Bridge => self - .relay_selector - .get_bridge_forced() - .and_then(|settings| match settings { - proxy::CustomProxy::Shadowsocks(ss_settings) => { - let ss_settings: proxy::Shadowsocks = proxy::Shadowsocks::new( - ss_settings.endpoint, - ss_settings.cipher, - ss_settings.password, - ); - Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( - ss_settings, - ))) - } - _ => { - log::error!("Received unexpected proxy settings type"); - None - } - }) - .unwrap_or(ApiConnectionMode::Direct), - }, - AccessMethod::Custom(access_method) => match access_method { - proxy::CustomProxy::Shadowsocks(shadowsocks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config)) - } - proxy::CustomProxy::Socks5Local(socks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(socks_config)) - } - proxy::CustomProxy::Socks5Remote(socks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Socks5Remote(socks_config)) - } - }, + pub async fn on_resolve_access_method( + &mut self, + tx: ResponseTx<ResolvedConnectionMode>, + setting: AccessMethodSetting, + ) -> Result<()> { + let reply = self.resolve(setting).await; + self.reply(tx, reply) + } + + async fn resolve(&mut self, access_method: AccessMethodSetting) -> ResolvedConnectionMode { + Self::resolve_inner(access_method, &self.relay_selector, &self.address_cache).await + } + + async fn resolve_inner( + access_method: AccessMethodSetting, + relay_selector: &RelaySelector, + address_cache: &AddressCache, + ) -> ResolvedConnectionMode { + let connection_mode = + resolve_connection_mode(access_method.access_method.clone(), relay_selector); + let endpoint = + resolve_allowed_endpoint(&connection_mode, address_cache.get_address().await); + ResolvedConnectionMode { + connection_mode, + endpoint, + setting: access_method, } } } +/// Ad-hoc version of [`std::convert::From::from`], but since some +/// [`ApiConnectionMode`]s require extra logic/data from +/// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait +/// can not be implemented. +fn resolve_connection_mode( + access_method: AccessMethod, + relay_selector: &RelaySelector, +) -> ApiConnectionMode { + match access_method { + AccessMethod::BuiltIn(access_method) => match access_method { + BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, + BuiltInAccessMethod::Bridge => relay_selector + .get_bridge_forced() + .and_then(|settings| match settings { + CustomProxy::Shadowsocks(settings) => Some(ApiConnectionMode::Proxied( + ProxyConfig::Shadowsocks(proxy::Shadowsocks::new( + settings.endpoint, + settings.cipher, + settings.password, + )), + )), + _ => { + log::error!("Received unexpected proxy settings type"); + None + } + }) + .unwrap_or(ApiConnectionMode::Direct), + }, + AccessMethod::Custom(access_method) => match access_method { + CustomProxy::Shadowsocks(shadowsocks) => { + ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks)) + } + CustomProxy::Socks5Local(socks) => { + ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(socks)) + } + CustomProxy::Socks5Remote(socks) => { + ApiConnectionMode::Proxied(ProxyConfig::Socks5Remote(socks)) + } + }, + } +} + /// An iterator which will always produce an [`AccessMethod`]. /// /// Safety: It is always safe to [`unwrap`] after calling [`next`] on a @@ -353,6 +505,7 @@ impl ConnectionModesIterator { pub fn set_access_method(&mut self, next: AccessMethodSetting) { self.next = Some(next); } + /// Update the collection of [`AccessMethod`] which this iterator will /// return. pub fn update_access_methods( @@ -376,11 +529,6 @@ impl ConnectionModesIterator { Ok(Box::new(access_methods.into_iter().cycle())) } } - - /// Look at the currently active [`AccessMethod`] - pub fn peek(&self) -> AccessMethodSetting { - self.current.clone() - } } impl Iterator for ConnectionModesIterator { @@ -397,73 +545,44 @@ impl Iterator for ConnectionModesIterator { } } -/// Notifies the tunnel state machine that the API (real or proxied) endpoint has -/// changed. [ApiEndpointUpdaterHandle::callback()] creates a callback that may -/// be passed to the `mullvad-api` runtime. -pub(super) struct ApiEndpointUpdaterHandle { - tunnel_cmd_tx: Arc<Mutex<Option<Weak<mpsc::UnboundedSender<TunnelCommand>>>>>, +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 } } -impl ApiEndpointUpdaterHandle { - pub fn new() -> Self { - Self { - tunnel_cmd_tx: Arc::new(Mutex::new(None)), - } - } - - pub fn set_tunnel_command_tx(&self, tunnel_cmd_tx: Weak<mpsc::UnboundedSender<TunnelCommand>>) { - *self.tunnel_cmd_tx.lock().unwrap() = Some(tunnel_cmd_tx); +#[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, } +} - pub fn callback(&self) -> impl ApiEndpointUpdateCallback { - let tunnel_tx = self.tunnel_cmd_tx.clone(); - move |allowed_endpoint: AllowedEndpoint| { - let inner_tx = tunnel_tx.clone(); - async move { - let tunnel_tx = if let Some(tunnel_tx) = { inner_tx.lock().unwrap().as_ref() } - .and_then(|tx: &Weak<mpsc::UnboundedSender<TunnelCommand>>| tx.upgrade()) - { - tunnel_tx - } else { - log::error!("Rejecting allowed endpoint: Tunnel state machine is not running"); - return false; - }; - let (result_tx, result_rx) = oneshot::channel(); - let _ = tunnel_tx.unbounded_send(TunnelCommand::AllowEndpoint( - allowed_endpoint.clone(), - result_tx, - )); - // Wait for the firewall policy to be updated. - let _ = result_rx.await; - log::debug!( - "API endpoint: {endpoint}", - endpoint = allowed_endpoint.endpoint - ); - true - } +#[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() } } } -pub(super) fn get_allowed_endpoint(endpoint: Endpoint) -> AllowedEndpoint { - #[cfg(unix)] - let clients = talpid_types::net::AllowedClients::Root; - #[cfg(windows)] - let clients = { - 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() - }; - - AllowedEndpoint { endpoint, clients } -} - pub(crate) fn forward_offline_state( api_availability: ApiAvailabilityHandle, mut offline_state_rx: mpsc::UnboundedReceiver<bool>, diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index f365496baf..f505ac94e4 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -27,6 +27,7 @@ pub mod version; mod version_check; use crate::target_state::PersistentTargetState; +use api::NewAccessMethodEvent; use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent}; use futures::{ channel::{mpsc, oneshot}, @@ -369,6 +370,11 @@ pub(crate) enum InternalDaemonEvent { NewAppVersionInfo(AppVersionInfo), /// Sent when a device is updated in any way (key rotation, login, logout, etc.). DeviceEvent(AccountEvent), + /// Sent when access methods are changed in any way (new active access method). + AccessMethodEvent { + event: NewAccessMethodEvent, + endpoint_active_tx: oneshot::Sender<()>, + }, /// Handles updates from versions without devices. DeviceMigrationEvent(Result<PrivateAccountAndDevice, device::Error>), /// A geographical location has has been received from am.i.mullvad.net @@ -408,6 +414,15 @@ impl From<AccountEvent> for InternalDaemonEvent { } } +impl From<(NewAccessMethodEvent, oneshot::Sender<()>)> for InternalDaemonEvent { + fn from(event: (NewAccessMethodEvent, oneshot::Sender<()>)) -> Self { + InternalDaemonEvent::AccessMethodEvent { + event: event.0, + endpoint_active_tx: event.1, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq)] enum DaemonExecutionState { Running, @@ -590,6 +605,9 @@ pub trait EventListener { /// Notify that a device was revoked using `RemoveDevice`. fn notify_remove_device_event(&self, event: RemoveDeviceEvent); + + /// Notify that the api access method changed. + fn notify_new_access_method_event(&self, new_access_method: AccessMethodSetting); } pub struct Daemon<L: EventListener> { @@ -692,6 +710,8 @@ where cache_dir.clone(), relay_selector.clone(), connection_modes, + internal_event_tx.to_specialized_sender(), + ) ); let api_handle = api_runtime @@ -962,6 +982,10 @@ where self.handle_new_app_version_info(app_version_info); } DeviceEvent(event) => self.handle_device_event(event).await, + AccessMethodEvent { + event, + endpoint_active_tx, + } => self.handle_access_method_event(event, endpoint_active_tx), DeviceMigrationEvent(event) => self.handle_device_migration_event(event), LocationEvent(location_data) => self.handle_location_event(location_data), #[cfg(windows)] @@ -1317,6 +1341,8 @@ where } } + fn handle_access_method_event(&mut self, event: NewAccessMethodEvent) {} + fn handle_device_migration_event( &mut self, result: Result<PrivateAccountAndDevice, device::Error>, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 6985af685c..8b8d840cfb 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -1009,6 +1009,18 @@ impl EventListener for ManagementInterfaceEventBroadcaster { )), }) } + + fn notify_new_access_method_event( + &self, + new_access_method: mullvad_types::access_method::AccessMethodSetting, + ) { + log::debug!("Broadcasting access method event"); + self.notify(types::DaemonEvent { + event: Some(daemon_event::Event::NewAccessMethod( + types::AccessMethodSetting::from(new_access_method), + )), + }) + } } impl ManagementInterfaceEventBroadcaster { diff --git a/mullvad-jni/src/jni_event_listener.rs b/mullvad-jni/src/jni_event_listener.rs index 2aeb8320e3..4567622975 100644 --- a/mullvad-jni/src/jni_event_listener.rs +++ b/mullvad-jni/src/jni_event_listener.rs @@ -7,6 +7,7 @@ use jnix::{ }; use mullvad_daemon::EventListener; use mullvad_types::{ + access_method::AccessMethodSetting, device::{DeviceEvent, RemoveDeviceEvent}, relay_list::RelayList, settings::Settings, @@ -71,6 +72,11 @@ impl EventListener for JniEventListener { fn notify_remove_device_event(&self, event: RemoveDeviceEvent) { let _ = self.0.send(Event::RemoveDevice(event)); } + + // TODO: Implement this function when API access methods is implemented in + // the Android app. + #[allow(dead_code, unused_variables)] + fn notify_new_access_method_event(&self, access_method: AccessMethodSetting) {} } struct JniEventHandler<'env> { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 268e70327d..6978e5a666 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -594,6 +594,7 @@ message DaemonEvent { AppVersionInfo version_info = 4; DeviceEvent device = 5; RemoveDeviceEvent remove_device = 6; + AccessMethodSetting new_access_method = 7; } } diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 85048e29e7..2c0ca2fecf 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -37,6 +37,7 @@ pub enum DaemonEvent { AppVersionInfo(AppVersionInfo), Device(DeviceEvent), RemoveDevice(RemoveDeviceEvent), + NewAccessMethod(AccessMethodSetting), } impl TryFrom<types::daemon_event::Event> for DaemonEvent { @@ -62,6 +63,11 @@ impl TryFrom<types::daemon_event::Event> for DaemonEvent { types::daemon_event::Event::RemoveDevice(event) => RemoveDeviceEvent::try_from(event) .map(DaemonEvent::RemoveDevice) .map_err(Error::InvalidResponse), + types::daemon_event::Event::NewAccessMethod(event) => { + AccessMethodSetting::try_from(event) + .map(DaemonEvent::NewAccessMethod) + .map_err(Error::InvalidResponse) + } } } } diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs index 368528f5bc..a5aa84d374 100644 --- a/mullvad-management-interface/src/types/conversions/access_method.rs +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -252,14 +252,20 @@ mod data { } } - impl From<Id> for proto::Uuid { - fn from(value: Id) -> Self { + impl From<&Id> for proto::Uuid { + fn from(value: &Id) -> Self { proto::Uuid { value: value.to_string(), } } } + impl From<Id> for proto::Uuid { + fn from(value: Id) -> Self { + proto::Uuid::from(&value) + } + } + impl TryFrom<proto::Uuid> for Id { type Error = FromProtobufTypeError; |
