summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2023-11-14 12:04:48 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2023-12-04 09:06:42 +0100
commit4b7ed46ae1945a03bd080e12e8c9ab376dd1cf07 (patch)
tree9a720ab2a81c794c8f461d68b7abed9e4ddb7267
parent0d638dbbde30141a2da5ac64556e192fc98b633e (diff)
downloadmullvadvpn-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.rs24
-rw-r--r--mullvad-api/src/rest.rs4
-rw-r--r--mullvad-cli/src/cmds/api_access.rs4
-rw-r--r--mullvad-daemon/src/access_method.rs67
-rw-r--r--mullvad-daemon/src/api.rs264
-rw-r--r--mullvad-daemon/src/lib.rs146
-rw-r--r--mullvad-daemon/src/management_interface.rs2
-rw-r--r--mullvad-management-interface/proto/management_interface.proto2
-rw-r--r--mullvad-management-interface/src/client.rs7
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<()> {