diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2023-11-14 12:04:48 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2023-12-04 09:06:42 +0100 |
| commit | 4b7ed46ae1945a03bd080e12e8c9ab376dd1cf07 (patch) | |
| tree | 9a720ab2a81c794c8f461d68b7abed9e4ddb7267 | |
| parent | 0d638dbbde30141a2da5ac64556e192fc98b633e (diff) | |
| download | mullvadvpn-4b7ed46ae1945a03bd080e12e8c9ab376dd1cf07.tar.xz mullvadvpn-4b7ed46ae1945a03bd080e12e8c9ab376dd1cf07.zip | |
Perform testing of access methods asynchronously
Perform testing of access methods asynchronously in a separate `tokio`
task as to not block the daemon from handling other daemon events during
the testing window
| -rw-r--r-- | mullvad-api/src/lib.rs | 24 | ||||
| -rw-r--r-- | mullvad-api/src/rest.rs | 4 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/api_access.rs | 4 | ||||
| -rw-r--r-- | mullvad-daemon/src/access_method.rs | 67 | ||||
| -rw-r--r-- | mullvad-daemon/src/api.rs | 264 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 146 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 2 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 2 | ||||
| -rw-r--r-- | mullvad-management-interface/src/client.rs | 7 |
9 files changed, 367 insertions, 153 deletions
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 5427f51aad..c8765ec2b2 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -622,22 +622,14 @@ impl ApiProxy { } /// Check the availablility of `{APP_URL_PREFIX}/api-addrs`. - pub async fn api_addrs_available(&self) -> Result<(), rest::Error> { - let service = self.handle.service.clone(); - - rest::send_request( - &self.handle.factory, - service, - &format!("{APP_URL_PREFIX}/api-addrs"), - Method::HEAD, - None, - &[StatusCode::OK], - ) - .await?; + pub async fn api_addrs_available(&self) -> Result<bool, rest::Error> { + let request = self + .handle + .factory + .head(&format!("{APP_URL_PREFIX}/api-addrs"))? + .expected_status(&[StatusCode::OK]); - // NOTE: A HEAD request should *not* have a body: - // https://developer.mozilla.org/en-US/docs/web/http/methods/head - // I.e., no need to deserialize the result. - Ok(()) + let response = self.handle.service.request(request).await?; + Ok(response.status().is_success()) } } diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index 2484bec64b..559ddd4b4e 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -524,6 +524,10 @@ impl RequestFactory { self.request(path, Method::DELETE) } + pub fn head(&self, path: &str) -> Result<Request> { + self.request(path, Method::HEAD) + } + pub fn post_json<S: serde::Serialize>(&self, path: &str, body: &S) -> Result<Request> { self.json_request(Method::POST, path, body) } diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index a826c32b45..c6e01c52d6 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -186,11 +186,11 @@ impl ApiAccess { println!("Testing access method \"{}\"", access_method.name); match rpc.test_api_access_method(access_method.get_id()).await { - Ok(_) => { + Ok(true) => { println!("Success!"); Ok(()) } - Err(_) => Err(anyhow!("Could not reach the Mullvad API.")), + Ok(false) | Err(_) => Err(anyhow!("Could not reach the Mullvad API.")), } } diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index 4584aa374a..51a0f0ae4a 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -1,4 +1,5 @@ use crate::{ + api, settings::{self, MadeChanges}, Daemon, EventListener, }; @@ -25,6 +26,9 @@ pub enum Error { /// Access method could not be rotate #[error(display = "Access method could not be rotated")] RotationError, + /// Daemon API error + #[error(display = "Daemon API handling error")] + Api(#[error(source)] api::Error), /// Access methods settings error #[error(display = "Settings error")] Settings(#[error(source)] settings::Error), @@ -81,7 +85,9 @@ where Some(api_access_method) => { if api_access_method.is_builtin() { Err(Error::RemoveBuiltIn) - } else if api_access_method.get_id() == self.get_current_access_method()?.get_id() { + } else if api_access_method.get_id() + == self.get_current_access_method().await?.get_id() + { Ok(Command::Rotate) } else { Ok(Command::Nothing) @@ -108,15 +114,10 @@ where &mut self, access_method: access_method::Id, ) -> Result<(), Error> { - let access_method = self - .settings - .api_access_methods - .find(&access_method) - .ok_or(Error::NoSuchMethod(access_method))?; - { - let mut connection_modes = self.connection_modes.lock().unwrap(); - connection_modes.set_access_method(access_method.clone()); - } + let access_method = self.get_api_access_method(access_method)?; + self.connection_modes_handler + .set_access_method(access_method) + .await?; // Force a rotation of Access Methods. // // This is not a call to `process_command` due to the restrictions on @@ -124,6 +125,17 @@ where self.force_api_endpoint_rotation().await } + pub fn get_api_access_method( + &mut self, + access_method: access_method::Id, + ) -> Result<AccessMethodSetting, Error> { + self.settings + .api_access_methods + .find(&access_method) + .ok_or(Error::NoSuchMethod(access_method)) + .cloned() + } + /// "Updates" an [`AccessMethodSetting`] by replacing the existing entry /// with the argument `access_method_update` if an existing entry with /// matching [`access_method::Id`] is found. @@ -140,7 +152,7 @@ where // in the daemon's settings. Therefore, we have to safeguard against // this by explicitly checking for & disallow any update which would // cause the last enabled access method to become disabled. - let current = self.get_current_access_method()?; + let current = self.get_current_access_method().await?; let mut command = Command::Nothing; let settings_update = |settings: &mut Settings| { if let Some(access_method) = settings @@ -165,9 +177,8 @@ where /// Return the [`AccessMethodSetting`] which is currently used to access the /// Mullvad API. - pub fn get_current_access_method(&self) -> Result<AccessMethodSetting, Error> { - let connections_modes = self.connection_modes.lock().unwrap(); - Ok(connections_modes.peek()) + pub async fn get_current_access_method(&self) -> Result<AccessMethodSetting, Error> { + Ok(self.connection_modes_handler.get_access_method().await?) } /// Change which [`AccessMethodSetting`] which will be used to figure out @@ -189,7 +200,8 @@ where self.event_listener .notify_settings(self.settings.to_settings()); - let access_methods: Vec<_> = self + let handle = self.connection_modes_handler.clone(); + let new_access_methods = self .settings .api_access_methods .access_method_settings @@ -198,20 +210,19 @@ where .cloned() .collect(); - let mut connection_modes = self.connection_modes.lock().unwrap(); - match connection_modes.update_access_methods(access_methods) { - Ok(_) => (), - Err(crate::api::Error::NoAccessMethods) => { - // `access_methods` was empty! This implies that the user - // disabled all access methods. If we ever get into this - // state, we should default to using the direct access - // method. - let default = access_method::Settings::direct(); - connection_modes - .update_access_methods(vec![default]) - .expect("Failed to create the data structure responsible for managing access methods"); + tokio::spawn(async move { + match handle.update_access_methods(new_access_methods).await { + Ok(_) => (), + Err(crate::api::Error::NoAccessMethods) => { + // `access_methods` was empty! This implies that the user + // disabled all access methods. If we ever get into this + // state, we should default to using the direct access + // method. + let default = access_method::Settings::direct(); + handle.update_access_methods(vec![default]).await.expect("Failed to create the data structure responsible for managing access methods"); + } } - } + }); }; self } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index d5099ae74a..d309854473 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -2,7 +2,8 @@ use crate::{DaemonCommand, DaemonEventSender}; use futures::{ channel::{mpsc, oneshot}, - Future, Stream, StreamExt, + stream::unfold, + Stream, StreamExt, }; use mullvad_api::{ availability::ApiAvailabilityHandle, @@ -13,17 +14,205 @@ use mullvad_relay_selector::RelaySelector; use mullvad_types::access_method::{self, AccessMethod, AccessMethodSetting, BuiltInAccessMethod}; use std::{ path::PathBuf, - pin::Pin, sync::{Arc, Mutex, Weak}, - task::Poll, }; #[cfg(target_os = "android")] use talpid_core::mpsc::Sender; use talpid_core::tunnel_state_machine::TunnelCommand; -use talpid_types::{ - net::{openvpn::ProxySettings, AllowedEndpoint, Endpoint}, - ErrorExt, -}; +use talpid_types::net::{openvpn::ProxySettings, AllowedEndpoint, Endpoint}; + +// TODO(markus): Remove text +/// Here, a new agent was born. + +pub enum Message { + Get(ResponseTx<AccessMethodSetting>), + Set(ResponseTx<()>, AccessMethodSetting), + Next(ResponseTx<ApiConnectionMode>), + Update(ResponseTx<()>, Vec<AccessMethodSetting>), +} + +#[derive(err_derive::Error, Debug)] +pub enum Error { + /// Oddly specific. + #[error(display = "Very Generic error.")] + Generic, +} + +#[derive(Clone)] +pub struct Ehandle { + cmd_tx: mpsc::UnboundedSender<Message>, +} + +impl Ehandle { + pub fn new( + cache_dir: PathBuf, + relay_selector: RelaySelector, + connection_modes: Vec<AccessMethodSetting>, + ) -> Self { + let (cmd_tx, cmd_rx) = mpsc::unbounded(); + + let mut actor = EActor { + cmd_rx, + state: ApiConnectionModeProvider::new(cache_dir, relay_selector, connection_modes), + }; + tokio::spawn(async move { actor.run().await }); + Self { cmd_tx } + } + + async fn send_command<T>(&self, make_cmd: impl FnOnce(ResponseTx<T>) -> Message) -> Result<T> { + let (tx, rx) = oneshot::channel(); + // TODO(markus): Error handling + self.cmd_tx.unbounded_send(make_cmd(tx)).unwrap(); + // TODO(markus): Error handling + rx.await.unwrap() + } + + pub async fn get_access_method(&self) -> Result<AccessMethodSetting> { + self.send_command(Message::Get).await.map_err(|err| { + log::error!("Failed to get current access method!"); + err + }) + } + + pub async fn set_access_method(&self, value: AccessMethodSetting) -> Result<()> { + self.send_command(|tx| Message::Set(tx, value)) + .await + .map_err(|err| { + log::error!("Failed to set new access method!"); + err + }) + } + + pub async fn update_access_methods(&self, values: Vec<AccessMethodSetting>) -> Result<()> { + self.send_command(|tx| Message::Update(tx, values)) + .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::error!("Failed to update new access methods!"); + err + }) + } + + /// Stream the connection modes of this actor. + pub fn as_stream(&self) -> impl Stream<Item = ApiConnectionMode> { + let handle = self.clone(); + unfold(handle, |handle| async move { + let connection_mode = handle + .next() + .await + .expect("It should always be safe to `unwrap` a new API connection mode"); + Some((connection_mode, handle)) + }) + } +} + +pub struct EActor { + cmd_rx: mpsc::UnboundedReceiver<Message>, + state: ApiConnectionModeProvider, +} + +impl EActor { + async fn run(&mut self) { + while let Some(cmd) = self.cmd_rx.next().await { + let _ = 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::Update(tx, values) => self.on_update_access_methods(tx, values), + } + .map_err(|err| { + log::error!("todo(markus): Some error occured {err}"); + err + }); + } + } + + fn reply<T>(&self, tx: ResponseTx<T>, value: T) -> Result<()> { + // TODO(markus): The error probably should come from the value/tx + tx.send(Ok(value)).map_err(|_| Error::Generic) + } + + 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) -> Result<AccessMethodSetting> { + let connections_modes = self.state.connection_modes.lock().unwrap(); + Ok(connections_modes.peek()) + } + + fn on_set_access_method( + &mut self, + tx: ResponseTx<()>, + value: AccessMethodSetting, + ) -> Result<()> { + self.set_access_method(value)?; + self.reply(tx, ()) + } + + fn set_access_method(&mut self, value: AccessMethodSetting) -> Result<()> { + let mut connections_modes = self.state.connection_modes.lock().unwrap(); + connections_modes.set_access_method(value); + Ok(()) + } + + 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.state.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 + ) + } + }); + } + self.reply(tx, next) + } + + fn next_connection_mode(&mut self) -> ApiConnectionMode { + let access_method = { + let mut connection_modes = self.state.connection_modes.lock().unwrap(); + connection_modes + .next() + .map(|access_method_setting| access_method_setting.access_method) + .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)) + }; + + let connection_mode = self.state.from(access_method); + log::info!("New API connection mode selected: {}", connection_mode); + connection_mode + } + + fn on_update_access_methods( + &mut self, + tx: ResponseTx<()>, + values: Vec<AccessMethodSetting>, + ) -> Result<()> { + self.update_access_methods(values)?; + self.reply(tx, ()) + } + + fn update_access_methods(&mut self, values: Vec<AccessMethodSetting>) -> Result<()> { + let mut connection_modes = self.state.connection_modes.lock().unwrap(); + connection_modes.update_access_methods(values); + Ok(()) + } +} + +type ResponseTx<T> = oneshot::Sender<Result<T>>; +type Result<T> = std::result::Result<T, Error>; /// A stream that returns the next API connection mode to use for reaching the API. /// @@ -38,45 +227,9 @@ pub struct ApiConnectionModeProvider { cache_dir: PathBuf, /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, - current_task: Option<Pin<Box<dyn Future<Output = ApiConnectionMode> + Send>>>, connection_modes: Arc<Mutex<ConnectionModesIterator>>, } -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, - }; - } - - let connection_mode = self.new_connection_mode(); - - let cache_dir = self.cache_dir.clone(); - self.current_task = Some(Box::pin(async move { - if let Err(error) = connection_mode.save(&cache_dir).await { - log::debug!( - "{}", - error.display_chain_with_msg("Failed to save API endpoint") - ); - } - connection_mode - })); - - self.poll_next(cx) - } -} - impl ApiConnectionModeProvider { pub(crate) fn new( cache_dir: PathBuf, @@ -87,35 +240,10 @@ impl ApiConnectionModeProvider { Ok(Self { cache_dir, relay_selector, - current_task: None, connection_modes: Arc::new(Mutex::new(connection_modes_iterator)), }) } - /// Return a pointer to the underlying iterator over [`AccessMethod`]. - /// Having access to this iterator allow you to influence , e.g. by calling - /// [`ConnectionModesIterator::set_access_method()`] or - /// [`ConnectionModesIterator::update_access_methods()`]. - pub(crate) fn handle(&self) -> Arc<Mutex<ConnectionModesIterator>> { - self.connection_modes.clone() - } - - /// Return a new connection mode to be used for the API connection. - fn new_connection_mode(&mut self) -> ApiConnectionMode { - log::debug!("Rotating Access mode!"); - let access_method = { - let mut access_methods_picker = self.connection_modes.lock().unwrap(); - access_methods_picker - .next() - .map(|access_method_setting| access_method_setting.access_method) - .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)) - }; - - let connection_mode = self.from(access_method); - log::info!("New API connection mode selected: {}", connection_mode); - connection_mode - } - /// 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 diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index c1891bda4d..6299597858 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -66,7 +66,7 @@ use std::{ mem, path::PathBuf, pin::Pin, - sync::{Arc, Mutex, Weak}, + sync::{Arc, Weak}, time::Duration, }; #[cfg(any(target_os = "linux", windows))] @@ -179,6 +179,9 @@ pub enum Error { #[error(display = "Access method error")] AccessMethodError(#[error(source)] access_method::Error), + #[error(display = "API connection mode error")] + ApiConnectionModeError(#[error(source)] api::Error), + #[cfg(target_os = "macos")] #[error(display = "Failed to set exclusion group")] GroupIdError(#[error(source)] io::Error), @@ -294,7 +297,7 @@ pub enum DaemonCommand { /// Get the currently used API access method GetCurrentAccessMethod(ResponseTx<AccessMethodSetting, Error>), /// Test an API access method - TestApiAccessMethod(ResponseTx<(), Error>, mullvad_types::access_method::Id), + TestApiAccessMethod(ResponseTx<bool, Error>, mullvad_types::access_method::Id), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender<Option<AppVersionInfo>>), /// Return whether the daemon is performing post-upgrade tasks @@ -602,7 +605,7 @@ pub struct Daemon<L: EventListener> { account_history: account_history::AccountHistory, device_checker: device::TunnelStateChangeHandler, account_manager: device::AccountManagerHandle, - connection_modes: Arc<Mutex<api::ConnectionModesIterator>>, + connection_modes_handler: api::Ehandle, api_runtime: mullvad_api::Runtime, api_handle: mullvad_api::rest::MullvadRestHandle, version_updater_handle: version_check::VersionUpdaterHandle, @@ -680,17 +683,18 @@ where .set_config(new_selector_config(settings)); }); - let proxy_provider = match api::ApiConnectionModeProvider::new( - cache_dir.clone(), - relay_selector.clone(), - settings + let connection_modes: Vec<_> = settings .api_access_methods .access_method_settings .iter() // We only care about the access methods which are set to 'enabled' by the user. .filter(|api_access_method| api_access_method.enabled()) .cloned() - .collect(), + .collect(); + let proxy_provider = match api::ApiConnectionModeProvider::new( + cache_dir.clone(), + relay_selector.clone(), + connection_modes, ) { Ok(provider) => provider, Err(api::Error::NoAccessMethods) => { @@ -708,10 +712,14 @@ where } }; - let connection_modes = proxy_provider.handle(); + let connection_modes_handler = + api::Ehandle::new(cache_dir.clone(), relay_selector.clone(), connection_modes); let api_handle = api_runtime - .mullvad_rest_handle(proxy_provider, endpoint_updater.callback()) + .mullvad_rest_handle( + Box::pin(connection_modes_handler.as_stream()), + endpoint_updater.callback(), + ) .await; let migration_complete = if let Some(migration_data) = migration_data { @@ -861,7 +869,7 @@ where account_history, device_checker: device::TunnelStateChangeHandler::new(account_manager.clone()), account_manager, - connection_modes, + connection_modes_handler, api_runtime, api_handle, version_updater_handle, @@ -2375,10 +2383,14 @@ where } fn on_get_current_api_access_method(&mut self, tx: ResponseTx<AccessMethodSetting, Error>) { - let result = self - .get_current_access_method() - .map_err(Error::AccessMethodError); - Self::oneshot_send(tx, result, "get_current_api_access_method response"); + let handle = self.connection_modes_handler.clone(); + tokio::spawn(async move { + let result = handle + .get_access_method() + .await + .map_err(Error::ApiConnectionModeError); + Self::oneshot_send(tx, result, "get_current_api_access_method response"); + }); } /// Try to reach the Mullvad API using a specific access method, returning @@ -2390,48 +2402,114 @@ where /// access method is *always* reset. async fn on_test_api_access_method( &mut self, - tx: ResponseTx<(), Error>, + tx: ResponseTx<bool, Error>, access_method: mullvad_types::access_method::Id, ) { // NOTE: Preferably we would block all new API calls until the test is // done and the previous access method is reset. Otherwise we run the // risk of errounously triggering a rotation of the currently in-use // access method. - let result = async { + let api_handle = self.api_handle.clone(); + let handle = self.connection_modes_handler.clone(); + let access_method = self.get_api_access_method(access_method); + // TODO(markus): Clean up this error handling + let new_access_method = if let Ok(access_method) = access_method { + access_method + } else { + Self::oneshot_send( + tx, + access_method + .map(|_| false) + .map_err(Error::AccessMethodError), + "on_test_api_access_method response", + ); + return; + }; + + let fut = async move { // Setup test - let previous_access_method = self - .get_current_access_method() - .map_err(Error::AccessMethodError)?; + let previous_access_method = handle + .get_access_method() + .await + .map_err(Error::ApiConnectionModeError) + // TODO(markus): Do not unwrap! + .unwrap(); - self.set_api_access_method(access_method) + let x = new_access_method.clone(); + handle.set_access_method(new_access_method) .await - .map_err(Error::AccessMethodError)?; + .map_err(Error::ApiConnectionModeError) + // TODO(markus): Do not unwrap! + .unwrap(); + + // We need to perform a rotation of API endpoint after a set action + let rotation_handle = api_handle.clone(); + rotation_handle + .service() + .next_api_endpoint() + .await + .map_err(|err| { + log::error!("Failed to rotate API endpoint: {err}"); + err + }) + // TODO(markus): Error handling + .unwrap(); + + // Set up the reset + // + // In case the API call fails, the next API endpoint will + // automatically be selected, which means that we need to set up + // with the previous API endpoint beforehand. + handle + .set_access_method(previous_access_method) + .await + .map_err(|err| { + log::error!( + "Could not reset to previous access + method after API reachability test was carried out. This should only + happen if the previous access method was removed in the meantime." + ); + err + }) + // TODO(markus): Do not unwrap! + .unwrap(); + // Perform test // // Send a HEAD request to some Mullvad API endpoint. We issue a HEAD // request because we are *only* concerned with if we get a reply from // the API, and not with the actual data that the endpoint returns. - let result = mullvad_api::ApiProxy::new(self.api_handle.clone()) + let result = mullvad_api::ApiProxy::new(api_handle) .api_addrs_available() .await .map_err(Error::RestError); - // Reset test - self.set_api_access_method(previous_access_method.get_id()) + // We need to perform a rotation of API endpoint after a set action + // Note that this will be done automatically if the API call fails, + // so it only has to be done if the call succeeded .. + if result.as_ref().is_ok_and(|&succeeded| succeeded) { + rotation_handle + .service() + .next_api_endpoint() .await .map_err(|err| { - log::error!( - "Could not reset to previous access - method after API reachability test was carried out. This should only - happen if the previous access method was removed in the meantime." - ); - Error::AccessMethodError(err) - })?; + log::error!("Failed to rotate API endpoint: {err}"); + err + }) + // TODO(markus): Error handling + .unwrap(); + } + + log::info!( + "The result of testing {method:?} is {result:?}", + method = x.access_method, + result = result + ); - result + Self::oneshot_send(tx, result, "on_test_api_access_method response"); }; - Self::oneshot_send(tx, result.await, "on_test_api_access_method response"); + tokio::spawn(fut); } fn on_get_settings(&self, tx: oneshot::Sender<Settings>) { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 633bcc0945..c194825a34 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -693,7 +693,7 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn test_api_access_method(&self, request: Request<types::Uuid>) -> ServiceResult<()> { + async fn test_api_access_method(&self, request: Request<types::Uuid>) -> ServiceResult<bool> { log::debug!("test_api_access_method"); let (tx, rx) = oneshot::channel(); let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?; diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index bb426afdea..d66707b79f 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -81,7 +81,7 @@ service ManagementService { rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) {} rpc UpdateApiAccessMethod(AccessMethodSetting) returns (google.protobuf.Empty) {} rpc GetCurrentApiAccessMethod(google.protobuf.Empty) returns (AccessMethodSetting) {} - rpc TestApiAccessMethod(UUID) returns (google.protobuf.Empty) {} + rpc TestApiAccessMethod(UUID) returns (google.protobuf.BoolValue) {} // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index c24f30260e..1c9d80b2e8 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -208,12 +208,13 @@ impl MullvadProxyClient { }) } - pub async fn test_api_access_method(&mut self, id: access_method::Id) -> Result<()> { - self.0 + pub async fn test_api_access_method(&mut self, id: access_method::Id) -> Result<bool> { + let result = self + .0 .test_api_access_method(types::Uuid::from(id)) .await .map_err(Error::Rpc)?; - Ok(()) + Ok(result.into_inner()) } pub async fn update_relay_locations(&mut self) -> Result<()> { |
