summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-17 11:53:09 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-01-22 14:12:26 +0100
commit95f01209edcae0ef37fa1cae5b9408c29faf0a29 (patch)
tree53d3634106f233e269205719f43e83f3f19a5245
parent7b48518df4ff020a1abac1b7c21745eea4468514 (diff)
downloadmullvadvpn-95f01209edcae0ef37fa1cae5b9408c29faf0a29.tar.xz
mullvadvpn-95f01209edcae0ef37fa1cae5b9408c29faf0a29.zip
Add `TestCustomApiAccessMethod` RPC call
Add a new RPC call `TestCustomApiAccessMethod` for testing access methods on the fly, without having to save them to the daemon settings first. This only works for custom access methods.
-rw-r--r--mullvad-daemon/src/access_method.rs67
-rw-r--r--mullvad-daemon/src/api.rs11
-rw-r--r--mullvad-daemon/src/lib.rs93
-rw-r--r--mullvad-daemon/src/management_interface.rs26
-rw-r--r--mullvad-management-interface/proto/management_interface.proto7
-rw-r--r--mullvad-management-interface/src/client.rs14
-rw-r--r--mullvad-management-interface/src/types/conversions/access_method.rs28
7 files changed, 162 insertions, 84 deletions
diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs
index bad1fd6670..835061c77b 100644
--- a/mullvad-daemon/src/access_method.rs
+++ b/mullvad-daemon/src/access_method.rs
@@ -3,7 +3,7 @@ use crate::{
settings::{self, MadeChanges},
Daemon, EventListener,
};
-use mullvad_api::rest;
+use mullvad_api::{proxy::ApiConnectionMode, rest, ApiProxy};
use mullvad_types::{
access_method::{self, AccessMethod, AccessMethodSetting},
settings::Settings,
@@ -24,10 +24,11 @@ pub enum Error {
#[error(display = "Access method could not be rotated")]
RotationFailed,
/// Some error occured in the daemon's state of handling
- /// [`AccessMethodSetting`]s & [`ApiConnectionMode`]s.
+ /// [`AccessMethodSetting`]s & [`ApiConnectionMode`]s
#[error(display = "Error occured when handling connection settings & details")]
- ConnectionMode(#[error(source)] api::Error),
- #[error(display = "API endpoint rotation failed")]
+ ApiService(#[error(source)] api::Error),
+ /// A REST request failed
+ #[error(display = "Reset request failed")]
Rest(#[error(source)] rest::Error),
/// Access methods settings error
#[error(display = "Settings error")]
@@ -211,7 +212,7 @@ where
.get_current()
.await
.map(|current| current.setting)
- .map_err(Error::ConnectionMode)
+ .map_err(Error::ApiService)
}
/// Change which [`AccessMethodSetting`] which will be used as the Mullvad
@@ -227,6 +228,62 @@ where
})
}
+ /// Test if the API is reachable via `proxy`.
+ ///
+ /// This function tests if [`AccessMethod`] can be used to reach the API.
+ /// Its parameters are as low-level as possible to promot re-use between
+ /// different kinds of testing contexts, such as testing
+ /// [`AccessMethodSetting`]s or on the fly testing of
+ /// [`talpid_types::net::proxy::CustomProxy`]s.
+ pub(crate) async fn test_access_method(
+ proxy: talpid_types::net::AllowedEndpoint,
+ access_method_selector: api::AccessModeSelectorHandle,
+ daemon_event_sender: crate::DaemonEventSender<(
+ api::AccessMethodEvent,
+ futures::channel::oneshot::Sender<()>,
+ )>,
+ api_proxy: ApiProxy,
+ ) -> Result<bool, Error> {
+ let reset = access_method_selector
+ .get_current()
+ .await
+ .map(|connection_mode| connection_mode.endpoint)?;
+
+ api::AccessMethodEvent::Allow { endpoint: proxy }
+ .send(daemon_event_sender.clone())
+ .await?;
+
+ let result = Self::perform_api_request(api_proxy).await;
+
+ api::AccessMethodEvent::Allow { endpoint: reset }
+ .send(daemon_event_sender)
+ .await?;
+
+ result
+ }
+
+ /// Create an [`ApiProxy`] which will perform all REST requests against one
+ /// specific endpoint `proxy_provider`.
+ pub async fn create_limited_api_proxy(
+ &mut self,
+ proxy_provider: ApiConnectionMode,
+ ) -> ApiProxy {
+ let rest_handle = self
+ .api_runtime
+ .mullvad_rest_handle(proxy_provider.into_repeat())
+ .await;
+ ApiProxy::new(rest_handle)
+ }
+
+ /// Perform some REST request against the Mullvad API.
+ ///
+ /// * Returns `Ok(true)` if the API returned the expected result
+ /// * Returns `Ok(false)` if the API returned an unexpected result
+ /// * Returns `Err(..)` if the API could not be reached
+ async fn perform_api_request(api_proxy: ApiProxy) -> Result<bool, Error> {
+ api_proxy.api_addrs_available().await.map_err(Error::Rest)
+ }
+
/// If settings were changed due to an update, notify all listeners.
fn notify_on_change(&mut self, settings_changed: MadeChanges) -> &mut Self {
if settings_changed {
diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs
index caf39ec874..9a4614dca5 100644
--- a/mullvad-daemon/src/api.rs
+++ b/mullvad-daemon/src/api.rs
@@ -7,10 +7,7 @@
use crate::DaemonCommand;
use crate::DaemonEventSender;
use futures::{
- channel::{
- mpsc,
- oneshot::{self, Canceled},
- },
+ channel::{mpsc, oneshot},
Stream, StreamExt,
};
use mullvad_api::{
@@ -65,14 +62,14 @@ impl AccessMethodEvent {
pub(crate) async fn send(
self,
daemon_event_sender: DaemonEventSender<(AccessMethodEvent, oneshot::Sender<()>)>,
- ) -> std::result::Result<(), Canceled> {
+ ) -> 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
+ update_finished_rx.await.map_err(Error::NotRunning)
}
}
@@ -94,6 +91,8 @@ pub struct ResolvedConnectionMode {
pub setting: AccessMethodSetting,
}
+/// Describes all the ways the daemon service which handles access methods can
+/// fail.
#[derive(err_derive::Error, Debug)]
pub enum Error {
#[error(display = "No access methods were provided.")]
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index fbf3f86d55..d54598b284 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -298,7 +298,12 @@ pub enum DaemonCommand {
/// Get the currently used API access method
GetCurrentAccessMethod(ResponseTx<AccessMethodSetting, Error>),
/// Test an API access method
- TestApiAccessMethod(ResponseTx<bool, Error>, mullvad_types::access_method::Id),
+ TestApiAccessMethodById(ResponseTx<bool, Error>, mullvad_types::access_method::Id),
+ /// Test a custom API access method
+ TestCustomApiAccessMethod(
+ ResponseTx<bool, Error>,
+ talpid_types::net::proxy::CustomProxy,
+ ),
/// Get information about the currently running and latest app versions
GetVersionInfo(oneshot::Sender<Option<AppVersionInfo>>),
/// Return whether the daemon is performing post-upgrade tasks
@@ -1248,7 +1253,10 @@ where
UpdateApiAccessMethod(tx, method) => self.on_update_api_access_method(tx, method).await,
GetCurrentAccessMethod(tx) => self.on_get_current_api_access_method(tx),
SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method).await,
- TestApiAccessMethod(tx, method) => self.on_test_api_access_method(tx, method).await,
+ TestApiAccessMethodById(tx, method) => self.on_test_api_access_method(tx, method).await,
+ TestCustomApiAccessMethod(tx, proxy) => {
+ self.on_test_proxy_as_access_method(tx, proxy).await
+ }
IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx),
GetCurrentVersion(tx) => self.on_get_current_version(tx),
#[cfg(not(target_os = "android"))]
@@ -2481,6 +2489,37 @@ where
});
}
+ async fn on_test_proxy_as_access_method(
+ &mut self,
+ tx: ResponseTx<bool, Error>,
+ proxy: talpid_types::net::proxy::CustomProxy,
+ ) {
+ use mullvad_api::proxy::{ApiConnectionMode, ProxyConfig};
+ use talpid_types::net::AllowedEndpoint;
+
+ let connection_mode = ApiConnectionMode::Proxied(ProxyConfig::from(proxy.clone()));
+ let api_proxy = self.create_limited_api_proxy(connection_mode.clone()).await;
+ let proxy_endpoint = AllowedEndpoint {
+ endpoint: proxy.get_remote_endpoint().endpoint,
+ clients: api::allowed_clients(&connection_mode),
+ };
+
+ let daemon_event_sender = self.tx.to_specialized_sender();
+ let access_method_selector = self.connection_modes_handler.clone();
+ tokio::spawn(async move {
+ let result = Self::test_access_method(
+ proxy_endpoint,
+ access_method_selector,
+ daemon_event_sender,
+ api_proxy,
+ )
+ .await
+ .map_err(Error::AccessMethodError);
+
+ Self::oneshot_send(tx, result, "on_test_proxy_as_access_method response");
+ });
+ }
+
async fn on_test_api_access_method(
&mut self,
tx: ResponseTx<bool, Error>,
@@ -2505,51 +2544,25 @@ where
}
};
- let test_subject_name = test_subject.setting.name.clone();
- let proxy_provider = test_subject.connection_mode.clone().into_repeat();
- let rest_handle = self.api_runtime.mullvad_rest_handle(proxy_provider).await;
- let api_proxy = mullvad_api::ApiProxy::new(rest_handle);
+ let api_proxy = self
+ .create_limited_api_proxy(test_subject.connection_mode)
+ .await;
let daemon_event_sender = self.tx.to_specialized_sender();
let access_method_selector = self.connection_modes_handler.clone();
tokio::spawn(async move {
- let result = async move {
- // Send an internal daemon event which will punch a hole in the firewall
- // for the connection mode we are testing.
- let _ = api::AccessMethodEvent::Allow {
- endpoint: test_subject.endpoint,
- }
- .send(daemon_event_sender.clone())
- .await;
-
- // 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 = api_proxy
- .api_addrs_available()
- .await
- .map_err(Error::RestError);
-
- // Tell the daemon to reset the hole we just punched to whatever was in
- // place before.
- let active = access_method_selector
- .get_current()
- .await
- .map_err(Error::ApiConnectionModeError)?;
-
- let _ = api::AccessMethodEvent::Allow {
- endpoint: active.endpoint,
- }
- .send(daemon_event_sender.clone())
- .await;
-
- result
- }
- .await;
+ let result = Self::test_access_method(
+ test_subject.endpoint,
+ access_method_selector,
+ daemon_event_sender,
+ api_proxy,
+ )
+ .await
+ .map_err(Error::AccessMethodError);
log::debug!(
"API access method {method} {verdict}",
- method = test_subject_name,
+ method = test_subject.setting.name,
verdict = match result {
Ok(true) => "could successfully connect to the Mullvad API",
_ => "could not connect to the Mullvad API",
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 261aa52853..1c003018a5 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -682,11 +682,31 @@ impl ManagementService for ManagementServiceImpl {
.map_err(map_daemon_error)
}
- async fn test_api_access_method(&self, request: Request<types::Uuid>) -> ServiceResult<bool> {
- log::debug!("test_api_access_method");
+ async fn test_custom_api_access_method(
+ &self,
+ config: Request<types::CustomProxy>,
+ ) -> ServiceResult<bool> {
+ log::debug!("test_custom_api_access_method");
+ let (tx, rx) = oneshot::channel();
+ let proxy = talpid_types::net::proxy::CustomProxy::try_from(config.into_inner())?;
+ self.send_command_to_daemon(DaemonCommand::TestCustomApiAccessMethod(tx, proxy))?;
+ self.wait_for_result(rx)
+ .await?
+ .map(Response::new)
+ .map_err(map_daemon_error)
+ }
+
+ async fn test_api_access_method_by_id(
+ &self,
+ request: Request<types::Uuid>,
+ ) -> ServiceResult<bool> {
+ log::debug!("test_api_access_method_by_id");
let (tx, rx) = oneshot::channel();
let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?;
- self.send_command_to_daemon(DaemonCommand::TestApiAccessMethod(tx, api_access_method))?;
+ self.send_command_to_daemon(DaemonCommand::TestApiAccessMethodById(
+ tx,
+ api_access_method,
+ ))?;
self.wait_for_result(rx)
.await?
.map(Response::new)
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 7087184a47..7f2ad07b43 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -80,7 +80,8 @@ 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.BoolValue) {}
+ rpc TestCustomApiAccessMethod(CustomProxy) returns (google.protobuf.BoolValue) {}
+ rpc TestApiAccessMethodById(UUID) returns (google.protobuf.BoolValue) {}
// Split tunneling (Linux)
rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {}
@@ -359,9 +360,7 @@ message AccessMethod {
oneof access_method {
Direct direct = 1;
Bridges bridges = 2;
- Socks5Local socks5local = 3;
- Socks5Remote socks5remote = 4;
- Shadowsocks shadowsocks = 5;
+ CustomProxy custom = 3;
}
}
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 7ff26e0371..52dbd72b47 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -216,7 +216,19 @@ impl MullvadProxyClient {
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))
+ .test_api_access_method_by_id(types::Uuid::from(id))
+ .await
+ .map_err(Error::Rpc)?;
+ Ok(result.into_inner())
+ }
+
+ pub async fn test_custom_api_access_method(
+ &mut self,
+ config: talpid_types::net::proxy::CustomProxy,
+ ) -> Result<bool> {
+ let result = self
+ .0
+ .test_custom_api_access_method(types::CustomProxy::from(config))
.await
.map_err(Error::Rpc)?;
Ok(result.into_inner())
diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs
index 0b2406af47..ec5681d74d 100644
--- a/mullvad-management-interface/src/types/conversions/access_method.rs
+++ b/mullvad-management-interface/src/types/conversions/access_method.rs
@@ -104,14 +104,8 @@ mod data {
Ok(match access_method {
proto::access_method::AccessMethod::Direct(direct) => AccessMethod::from(direct),
proto::access_method::AccessMethod::Bridges(bridge) => AccessMethod::from(bridge),
- proto::access_method::AccessMethod::Socks5local(sockslocal) => {
- AccessMethod::try_from(sockslocal)?
- }
- proto::access_method::AccessMethod::Socks5remote(socksremote) => {
- AccessMethod::try_from(socksremote)?
- }
- proto::access_method::AccessMethod::Shadowsocks(shadowsocks) => {
- AccessMethod::try_from(shadowsocks)?
+ proto::access_method::AccessMethod::Custom(custom) => {
+ CustomProxy::try_from(custom).map(AccessMethod::from)?
}
})
}
@@ -193,23 +187,7 @@ mod data {
impl From<CustomProxy> for proto::access_method::AccessMethod {
fn from(value: CustomProxy) -> Self {
- match value {
- CustomProxy::Shadowsocks(config) => {
- proto::access_method::AccessMethod::Shadowsocks(proto::Shadowsocks::from(
- config,
- ))
- }
- CustomProxy::Socks5Local(config) => {
- proto::access_method::AccessMethod::Socks5local(proto::Socks5Local::from(
- config,
- ))
- }
- CustomProxy::Socks5Remote(config) => {
- proto::access_method::AccessMethod::Socks5remote(proto::Socks5Remote::from(
- config,
- ))
- }
- }
+ proto::access_method::AccessMethod::Custom(proto::CustomProxy::from(value))
}
}