diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-01-17 11:53:09 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-01-22 14:12:26 +0100 |
| commit | 95f01209edcae0ef37fa1cae5b9408c29faf0a29 (patch) | |
| tree | 53d3634106f233e269205719f43e83f3f19a5245 | |
| parent | 7b48518df4ff020a1abac1b7c21745eea4468514 (diff) | |
| download | mullvadvpn-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.rs | 67 | ||||
| -rw-r--r-- | mullvad-daemon/src/api.rs | 11 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 93 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 26 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 7 | ||||
| -rw-r--r-- | mullvad-management-interface/src/client.rs | 14 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/access_method.rs | 28 |
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)) } } |
