diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2023-09-20 15:36:01 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2023-10-09 14:40:08 +0200 |
| commit | 43cb757d2cbb396a627fb6b970394a7b73d37dc5 (patch) | |
| tree | 084fcd0a9152d50242ed1934fc5623efe8a0052d | |
| parent | 7fcaad31643ca324c5dd4a17ddf0f445ac679384 (diff) | |
| download | mullvadvpn-43cb757d2cbb396a627fb6b970394a7b73d37dc5.tar.xz mullvadvpn-43cb757d2cbb396a627fb6b970394a7b73d37dc5.zip | |
Cleanup
- General code cleanup
- Fix some typos
- Add some doc comments
- Address several `TODO` comments
- Fix `clippy` warnings
- Removed unused dependency `mullvad-api` from `mullvad-cli`
- Removed unused dependency `rand` from `mullvad-daemon`
- Rename `mullvad proxy` to `mullvad api-access`
- Rename `mullvad_types::api_access_method` -> `mullvad_types::access_method`
- Remove unused `mullvad api-access edit` arguments
- Fix `Display` for `ProxyConfig` printing arguments in the wrong order
- Re-phrase `mullvad api-access test`
- If the API call failed, point out which tested access method that
did not work.
- Fix missing `socket_bypass_tx` value for Android
- Refactor `ApiAccessMethod` proto definition
- Simplify protobuf definitions of `SOCKS5` api access methods
- Remove the `Socks5` enum in favor of implementing `Socks5Local` and
`Socks5Remote` directly.
- Move `enabled` and `name` out of the individual messages and put them
next to the `oneof access_method` in `ApiAccessMethod` proto definition
- Use derived `PartialEq` and `Hash` from `AccessMethod`
- Instead of hand-rolling `Hash` and implementing an ad-hoc version of
half of `PartialEq`, these can now be derived and used as one would
imaging due to the refactoring wherer `name` and `enabled` was moved
out of `AccessMethod` into `ApiAccessMethod`.
20 files changed, 891 insertions, 910 deletions
diff --git a/Cargo.lock b/Cargo.lock index be316f8e51..b3b830e0ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1819,7 +1819,6 @@ dependencies = [ "env_logger 0.10.0", "futures", "itertools", - "mullvad-api", "mullvad-management-interface", "mullvad-types", "mullvad-version", diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 76130de185..cc7557a8d2 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -84,15 +84,38 @@ impl InnerConnectionMode { &self, hostname: &str, addr: &SocketAddr, + #[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>, ) -> Result<ApiConnection, std::io::Error> { use InnerConnectionMode::*; match self { - Direct => Self::handle_direct_connection(addr, hostname).await, + Direct => { + Self::handle_direct_connection( + addr, + hostname, + #[cfg(target_os = "android")] + socket_bypass_tx, + ) + .await + } Shadowsocks(config) => { - Self::handle_shadowsocks_connection(config.clone(), addr, hostname).await + Self::handle_shadowsocks_connection( + config.clone(), + addr, + hostname, + #[cfg(target_os = "android")] + socket_bypass_tx, + ) + .await } Socks5(proxy_config) => { - Self::handle_socks_connection(proxy_config.clone(), addr, hostname).await + Self::handle_socks_connection( + proxy_config.clone(), + addr, + hostname, + #[cfg(target_os = "android")] + socket_bypass_tx, + ) + .await } } } @@ -101,11 +124,12 @@ impl InnerConnectionMode { async fn handle_direct_connection( addr: &SocketAddr, hostname: &str, + #[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>, ) -> Result<ApiConnection, io::Error> { let socket = HttpsConnectorWithSni::open_socket( *addr, #[cfg(target_os = "android")] - socket_bypass_tx.clone(), + socket_bypass_tx, ) .await?; #[cfg(feature = "api-override")] @@ -122,11 +146,12 @@ impl InnerConnectionMode { shadowsocks: ShadowsocksConfig, addr: &SocketAddr, hostname: &str, + #[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>, ) -> Result<ApiConnection, io::Error> { let socket = HttpsConnectorWithSni::open_socket( shadowsocks.params.peer, #[cfg(target_os = "android")] - socket_bypass_tx.clone(), + socket_bypass_tx, ) .await?; let proxy = ProxyClientStream::from_stream( @@ -150,6 +175,7 @@ impl InnerConnectionMode { proxy_config: SocksConfig, addr: &SocketAddr, hostname: &str, + #[cfg(target_os = "android")] socket_bypass_tx: Option<mpsc::Sender<SocketBypassRequest>>, ) -> Result<ApiConnection, io::Error> { let socket = HttpsConnectorWithSni::open_socket( proxy_config.peer, @@ -222,12 +248,12 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode { }) } ProxyConfig::Socks(config) => match config { - mullvad_types::api_access_method::Socks5::Local(config) => { + mullvad_types::access_method::Socks5::Local(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: SocketAddr::new("127.0.0.1".parse().unwrap(), config.port), }) } - mullvad_types::api_access_method::Socks5::Remote(config) => { + mullvad_types::access_method::Socks5::Remote(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) } }, @@ -424,7 +450,12 @@ impl Service<Uri> for HttpsConnectorWithSni { let stream = loop { let notify = abort_notify.notified(); let proxy_config = { inner.lock().unwrap().proxy_config.clone() }; - let stream_fut = proxy_config.connect(&hostname, &addr); + let stream_fut = proxy_config.connect( + &hostname, + &addr, + #[cfg(target_os = "android")] + socket_bypass_tx.clone(), + ); pin_mut!(stream_fut); pin_mut!(notify); diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 186eea2919..44a2309587 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -1,6 +1,6 @@ use futures::Stream; use hyper::client::connect::Connected; -use mullvad_types::api_access_method; +use mullvad_types::access_method; use serde::{Deserialize, Serialize}; use std::{ fmt, io, @@ -36,8 +36,8 @@ impl fmt::Display for ApiConnectionMode { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ProxyConfig { - Shadowsocks(api_access_method::Shadowsocks), - Socks(api_access_method::Socks5), + Shadowsocks(access_method::Shadowsocks), + Socks(access_method::Socks5), } impl ProxyConfig { @@ -46,8 +46,8 @@ impl ProxyConfig { match self { ProxyConfig::Shadowsocks(ss) => ss.peer, ProxyConfig::Socks(socks) => match socks { - api_access_method::Socks5::Local(s) => s.peer, - api_access_method::Socks5::Remote(s) => s.peer, + access_method::Socks5::Local(s) => s.peer, + access_method::Socks5::Remote(s) => s.peer, }, } } @@ -59,10 +59,10 @@ impl fmt::Display for ProxyConfig { // TODO: Do not hardcode TCP ProxyConfig::Shadowsocks(ss) => write!(f, "Shadowsocks {}/TCP", ss.peer), ProxyConfig::Socks(socks) => match socks { - api_access_method::Socks5::Local(s) => { - write!(f, "Socks5 localhost:{} => {}/TCP", s.port, s.peer) + access_method::Socks5::Local(s) => { + write!(f, "Socks5 {}/TCP via localhost:{}", s.peer, s.port) } - api_access_method::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), + access_method::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), }, } } diff --git a/mullvad-cli/Cargo.toml b/mullvad-cli/Cargo.toml index fdfa16262d..5437b1980c 100644 --- a/mullvad-cli/Cargo.toml +++ b/mullvad-cli/Cargo.toml @@ -23,7 +23,6 @@ natord = "1.0.9" itertools = "0.10" mullvad-types = { path = "../mullvad-types", features = ["clap"] } -mullvad-api = { path = "../mullvad-api" } mullvad-version = { path = "../mullvad-version" } talpid-types = { path = "../talpid-types" } diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/api_access.rs index fa05081f89..921a5da811 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::api_access_method::{ +use mullvad_types::access_method::{ daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - AccessMethod, ObfuscationProtocol, + AccessMethod, ApiAccessMethod, ObfuscationProtocol, }; use std::net::IpAddr; @@ -10,7 +10,7 @@ use clap::{Args, Subcommand}; use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; #[derive(Subcommand, Debug, Clone)] -pub enum Proxy { +pub enum ApiAccess { /// List the configured API proxies List, /// Add a custom API proxy @@ -19,7 +19,7 @@ pub enum Proxy { /// Edit an API proxy Edit(EditCustomCommands), /// Remove an API proxy - Remove(RemoveCustomCommands), + Remove(SelectItem), /// Enable an API proxy Enable(SelectItem), /// Disable an API proxy @@ -27,47 +27,35 @@ pub enum Proxy { /// Test an API proxy Test(SelectItem), /// Force the use of a specific API proxy. + /// + /// Selecting "Mullvad Bridges" respects your current bridge settings. Use(SelectItem), } -impl Proxy { +impl ApiAccess { pub async fn handle(self) -> Result<()> { match self { - Proxy::List => { - //println!("Listing the API access methods: .."); + ApiAccess::List => { Self::list().await?; } - Proxy::Add(cmd) => { - //println!("Adding custom proxy"); + ApiAccess::Add(cmd) => { Self::add(cmd).await?; } - Proxy::Edit(cmd) => { - // Transform human-readable index to 0-based indexing. - let index = Self::zero_to_one_based_index(cmd.index)?; - Self::edit(EditCustomCommands { index, ..cmd }).await? - } - Proxy::Remove(cmd) => { - // Transform human-readable index to 0-based indexing. - let index = Self::zero_to_one_based_index(cmd.index)?; - Self::remove(RemoveCustomCommands { index }).await? - } - Proxy::Enable(cmd) => { - let index = Self::zero_to_one_based_index(cmd.index)?; + ApiAccess::Edit(cmd) => Self::edit(cmd).await?, + ApiAccess::Remove(cmd) => Self::remove(cmd).await?, + ApiAccess::Enable(cmd) => { let enabled = true; - Self::toggle(index, enabled).await?; + Self::toggle(cmd, enabled).await?; } - Proxy::Disable(cmd) => { - let index = Self::zero_to_one_based_index(cmd.index)?; + ApiAccess::Disable(cmd) => { let enabled = false; - Self::toggle(index, enabled).await?; + Self::toggle(cmd, enabled).await?; } - Proxy::Test(cmd) => { - let index = Self::zero_to_one_based_index(cmd.index)?; - Self::test(index).await?; + ApiAccess::Test(cmd) => { + Self::test(cmd).await?; } - Proxy::Use(cmd) => { - let index = Self::zero_to_one_based_index(cmd.index)?; - Self::set(index).await?; + ApiAccess::Use(cmd) => { + Self::set(cmd).await?; } }; Ok(()) @@ -75,13 +63,12 @@ impl Proxy { /// Show all API access methods. async fn list() -> Result<()> { - use crate::cmds::proxy::pp::AccessMethodFormatter; let mut rpc = MullvadProxyClient::new().await?; for (index, api_access_method) in rpc.get_api_access_methods().await?.iter().enumerate() { println!( "{}. {}", index + 1, - AccessMethodFormatter::new(&api_access_method) + pp::ApiAccessMethodFormatter::new(api_access_method) ); } Ok(()) @@ -90,23 +77,15 @@ impl Proxy { /// Add a custom API access method. async fn add(cmd: AddCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let proxy = AccessMethod::try_from(cmd.clone())?; + let proxy = ApiAccessMethod::try_from(cmd)?; rpc.add_access_method(proxy).await?; Ok(()) } /// Remove an API access method. - async fn remove(cmd: RemoveCustomCommands) -> Result<()> { + async fn remove(cmd: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = rpc - .get_api_access_methods() - .await? - .get(cmd.index) - .ok_or(anyhow!(format!( - "Access method {} does not exist", - cmd.index + 1 - )))? - .clone(); + let access_method = Self::get_access_method(&mut rpc, &cmd).await?; rpc.remove_access_method(access_method) .await .map_err(Into::<anyhow::Error>::into) @@ -115,67 +94,65 @@ impl Proxy { /// Edit the data of an API access method. async fn edit(cmd: EditCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - // Retrieve the access method to edit - let access_method = rpc - .get_api_access_methods() - .await? - .get(cmd.index) - .ok_or(anyhow!(format!( - "Access method {} does not exist", - cmd.index + 1 - )))? - .clone(); + let api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?; + let access_method = api_access_method + .as_custom() + .cloned() + .ok_or(anyhow!("Can not edit built-in access method"))?; // Create a new access method combining the new params with the previous values - let access_method = match access_method { - AccessMethod::BuiltIn(_) => Err(anyhow!("Can not edit built-in access method")), - AccessMethod::Custom(custom_access_method) => Ok(custom_access_method), - }?; - - let edited_access_method: AccessMethod = match access_method.access_method { + let edited_access_method: ApiAccessMethod = match access_method.access_method { ObfuscationProtocol::Shadowsocks(shadowsocks) => { let ip = cmd.params.ip.unwrap_or(shadowsocks.peer.ip()).to_string(); let port = cmd.params.port.unwrap_or(shadowsocks.peer.port()); let password = cmd.params.password.unwrap_or(shadowsocks.password); let cipher = cmd.params.cipher.unwrap_or(shadowsocks.cipher); - let name = cmd.params.name.unwrap_or(shadowsocks.name); - let enabled = shadowsocks.enabled; - mullvad_types::api_access_method::Shadowsocks::from_args( - ip, port, cipher, password, enabled, name, - ) - .map(|x| x.into()) + let name = cmd.params.name.unwrap_or(api_access_method.name); + let enabled = api_access_method.enabled; + mullvad_types::access_method::Shadowsocks::from_args(ip, port, cipher, password) + .map(|x| ApiAccessMethod { + name, + enabled, + access_method: AccessMethod::from(x), + }) } ObfuscationProtocol::Socks5(socks) => match socks { - mullvad_types::api_access_method::Socks5::Local(local) => { + mullvad_types::access_method::Socks5::Local(local) => { let ip = cmd.params.ip.unwrap_or(local.peer.ip()).to_string(); let port = cmd.params.port.unwrap_or(local.peer.port()); let local_port = cmd.params.local_port.unwrap_or(local.port); - let name = cmd.params.name.unwrap_or(local.name); - let enabled = local.enabled; - mullvad_types::api_access_method::Socks5Local::from_args( - ip, port, local_port, enabled, name, + let name = cmd.params.name.unwrap_or(api_access_method.get_name()); + let enabled = api_access_method.enabled(); + mullvad_types::access_method::Socks5Local::from_args(ip, port, local_port).map( + |x| ApiAccessMethod { + name, + enabled, + access_method: AccessMethod::from(x), + }, ) - .map(|x| x.into()) } - mullvad_types::api_access_method::Socks5::Remote(remote) => { + mullvad_types::access_method::Socks5::Remote(remote) => { let ip = cmd.params.ip.unwrap_or(remote.peer.ip()).to_string(); let port = cmd.params.port.unwrap_or(remote.peer.port()); - let name = cmd.params.name.unwrap_or(remote.name); - let enabled = remote.enabled; - mullvad_types::api_access_method::Socks5Remote::from_args( - ip, port, enabled, name, - ) - .map(|x| x.into()) + let name = cmd.params.name.unwrap_or(api_access_method.get_name()); + let enabled = api_access_method.enabled(); + mullvad_types::access_method::Socks5Remote::from_args(ip, port).map(|x| { + ApiAccessMethod { + name, + enabled, + access_method: AccessMethod::from(x), + } + }) } }, } .ok_or(anyhow!( "Could not edit access method {}, reverting changes.", - cmd.index + cmd.item ))?; rpc.replace_access_method(ApiAccessMethodReplace { - index: cmd.index, + index: cmd.item.as_array_index()?, access_method: edited_access_method, }) .await?; @@ -184,19 +161,9 @@ impl Proxy { } /// Toggle a custom API access method to be enabled or disabled. - async fn toggle(index: usize, enabled: bool) -> Result<()> { + async fn toggle(item: SelectItem, enabled: bool) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - // Retrieve the access method to edit - let access_method = rpc - .get_api_access_methods() - .await? - .get(index) - .ok_or(anyhow!(format!( - "Access method {} does not exist", - index + 1 - )))? - .clone(); - + let access_method = Self::get_access_method(&mut rpc, &item).await?; rpc.toggle_access_method(ApiAccessMethodToggle { access_method, enable: enabled, @@ -206,28 +173,18 @@ impl Proxy { } /// Test an access method to see if it successfully reaches the Mullvad API. - async fn test(index: usize) -> Result<()> { + async fn test(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - // TODO: Refactor this into some helper function. This code has been copy-pastead a lot already .. - // Step 1. - let access_method = rpc - .get_api_access_methods() - .await? - .get(index) - .ok_or(anyhow!(format!( - "Access method {} does not exist", - index + 1 - )))? - .clone(); - - // Step 2. + let access_method = Self::get_access_method(&mut rpc, &item).await?; + let name = access_method.get_name(); rpc.set_access_method(access_method).await?; - // Step 3. - let api_call = rpc.test_api().await; - // Step 4. - match api_call { - Ok(_) => println!("API call succeeded!"), - Err(_) => println!("API call failed :-("), + // Make the daemon perform an network request which involves talking to the Mullvad API. + match rpc.get_api_addressess().await { + Ok(_) => println!("Connected to the Mullvad API!"), + Err(_) => println!( + "Could *not* connect to the Mullvad API using access method \"{}\"", + name + ), } Ok(()) @@ -236,26 +193,22 @@ impl Proxy { /// Force the use of a specific access method when trying to reach the /// Mullvad API. If this method fails, the daemon will resume the automatic /// roll-over behavior (which is the default). - async fn set(index: usize) -> Result<()> { + async fn set(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = rpc - .get_api_access_methods() - .await? - .get(index) - .ok_or(anyhow!(format!( - "Access method {} does not exist", - index + 1 - )))? - .clone(); - + let access_method = Self::get_access_method(&mut rpc, &item).await?; rpc.set_access_method(access_method).await?; Ok(()) } - fn zero_to_one_based_index(index: usize) -> Result<usize> { - index - .checked_sub(1) - .ok_or(anyhow!("Access method 0 does not exist")) + async fn get_access_method( + rpc: &mut MullvadProxyClient, + item: &SelectItem, + ) -> Result<ApiAccessMethod> { + rpc.get_api_access_methods() + .await? + .get(item.as_array_index()?) + .cloned() + .ok_or(anyhow!(format!("Access method {} does not exist", item))) } } @@ -265,7 +218,7 @@ pub enum AddCustomCommands { #[clap(subcommand)] Socks5(Socks5AddCommands), - /// Configure bundled Shadowsocks proxy + /// Configure Shadowsocks proxy Shadowsocks { /// An easy to remember name for this custom proxy name: String, @@ -283,29 +236,6 @@ pub enum AddCustomCommands { }, } -/// A minimal wrapper type allowing the user to supply a list index to some -/// Access Method. -#[derive(Args, Debug, Clone)] -pub struct SelectItem { - /// Which access method to pick - index: usize, -} - -#[derive(Args, Debug, Clone)] -pub struct EditCustomCommands { - /// Which API proxy to edit - index: usize, - /// Editing parameters - #[clap(flatten)] - params: EditParams, -} - -#[derive(Args, Debug, Clone)] -pub struct RemoveCustomCommands { - /// Which API proxy to remove - index: usize, -} - #[derive(Subcommand, Debug, Clone)] pub enum Socks5AddCommands { /// Configure a local SOCKS5 proxy @@ -327,23 +257,47 @@ pub enum Socks5AddCommands { remote_ip: IpAddr, /// The port of the remote proxy server remote_port: u16, - /// Username for authentication - #[arg(requires = "password")] - username: Option<String>, - /// Password for authentication - #[arg(requires = "username")] - password: Option<String>, }, } +/// A minimal wrapper type allowing the user to supply a list index to some +/// Access Method. +#[derive(Args, Debug, Clone)] +pub struct SelectItem { + /// Which access method to pick + index: usize, +} + +impl SelectItem { + /// Transform human-readable (1-based) index to 0-based indexing. + pub fn as_array_index(&self) -> Result<usize> { + self.index + .checked_sub(1) + .ok_or(anyhow!("Access method 0 does not exist")) + } +} + +impl std::fmt::Display for SelectItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.index) + } +} + +#[derive(Args, Debug, Clone)] +pub struct EditCustomCommands { + /// Which API proxy to edit + #[clap(flatten)] + item: SelectItem, + /// Editing parameters + #[clap(flatten)] + params: EditParams, +} + #[derive(Args, Debug, Clone)] pub struct EditParams { /// Name of the API proxy in the Mullvad client [All] #[arg(long)] name: Option<String>, - /// Username for authentication [Shadowsocks] - #[arg(long)] - username: Option<String>, /// Password for authentication [Shadowsocks] #[arg(long)] password: Option<String>, @@ -367,11 +321,11 @@ pub struct EditParams { /// we define them in a hidden-away module. mod conversions { use anyhow::{anyhow, Error}; - use mullvad_types::api_access_method as daemon_types; + use mullvad_types::access_method as daemon_types; use super::{AddCustomCommands, Socks5AddCommands}; - impl TryFrom<AddCustomCommands> for daemon_types::AccessMethod { + impl TryFrom<AddCustomCommands> for daemon_types::ApiAccessMethod { type Error = Error; fn try_from(value: AddCustomCommands) -> Result<Self, Self::Error> { @@ -391,31 +345,33 @@ mod conversions { remote_ip.to_string(), remote_port, local_port, - enabled, - name, ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); - socks_proxy.into() + daemon_types::ApiAccessMethod { + name, + enabled, + access_method: daemon_types::AccessMethod::from(socks_proxy), + } } Socks5AddCommands::Remote { remote_ip, remote_port, - username, - password, name, } => { - println!("Adding REMOTE SOCKS5-proxy: {username:?}+{password:?} @ {remote_ip}:{remote_port}"); + println!("Adding REMOTE SOCKS5-proxy: {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Remote( daemon_types::Socks5Remote::from_args( remote_ip.to_string(), remote_port, - enabled, - name, ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); - socks_proxy.into() + daemon_types::ApiAccessMethod { + name, + enabled, + access_method: socks_proxy.into(), + } } }, AddCustomCommands::Shadowsocks { @@ -433,34 +389,37 @@ mod conversions { remote_port, cipher, password, - enabled, - name, ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - shadowsocks_proxy.into() + + daemon_types::ApiAccessMethod { + name, + enabled, + access_method: shadowsocks_proxy.into(), + } } }) } } } -/// Pretty printing of [`AccessMethod`]s +/// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::api_access_method::{ - AccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Socks5, + use mullvad_types::access_method::{ + AccessMethod, ApiAccessMethod, ObfuscationProtocol, Socks5, }; - pub struct AccessMethodFormatter<'a> { - access_method: &'a AccessMethod, + pub struct ApiAccessMethodFormatter<'a> { + api_access_method: &'a ApiAccessMethod, } - impl<'a> AccessMethodFormatter<'a> { - pub fn new(access_method: &'a AccessMethod) -> AccessMethodFormatter<'a> { - AccessMethodFormatter { access_method } + impl<'a> ApiAccessMethodFormatter<'a> { + pub fn new(api_access_method: &'a ApiAccessMethod) -> ApiAccessMethodFormatter<'a> { + ApiAccessMethodFormatter { api_access_method } } } - impl<'a> std::fmt::Display for AccessMethodFormatter<'a> { + impl<'a> std::fmt::Display for ApiAccessMethodFormatter<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use crate::print_option; @@ -472,22 +431,19 @@ mod pp { } }; - match self.access_method { - AccessMethod::BuiltIn(method) => match method { - BuiltInAccessMethod::Direct(enabled) => { - write!(f, "Direct")?; - write_status(f, *enabled) - } - BuiltInAccessMethod::Bridge(enabled) => { - write!(f, "Mullvad Bridges")?; - write_status(f, *enabled) - } - }, + // TODO: For debugging purposes only, remove later + writeln!(f, "{:?}", self.api_access_method)?; + + match &self.api_access_method.access_method { + AccessMethod::BuiltIn(method) => { + write!(f, "{}", method.canonical_name())?; + write_status(f, self.api_access_method.enabled()) + } AccessMethod::Custom(method) => match &method.access_method { ObfuscationProtocol::Shadowsocks(shadowsocks) => { - write!(f, "{}", shadowsocks.name)?; - write_status(f, shadowsocks.enabled)?; - writeln!(f, "")?; + write!(f, "{}", self.api_access_method.get_name())?; + write_status(f, self.api_access_method.enabled())?; + writeln!(f)?; print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher)); print_option!("Peer", shadowsocks.peer); print_option!("Password", shadowsocks.password); @@ -495,17 +451,17 @@ mod pp { } ObfuscationProtocol::Socks5(socks) => match socks { Socks5::Remote(remote) => { - write!(f, "{}", remote.name)?; - write_status(f, remote.enabled)?; - writeln!(f, "")?; + write!(f, "{}", self.api_access_method.get_name())?; + write_status(f, self.api_access_method.enabled())?; + writeln!(f)?; print_option!("Protocol", "Socks5"); print_option!("Peer", remote.peer); Ok(()) } Socks5::Local(local) => { - write!(f, "{}", local.name)?; - write_status(f, local.enabled)?; - writeln!(f, "")?; + write!(f, "{}", self.api_access_method.get_name())?; + write_status(f, self.api_access_method.enabled())?; + writeln!(f)?; print_option!("Protocol", "Socks5 (local)"); print_option!("Peer", local.peer); print_option!("Local port", local.port); diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index cf715c9e9f..88e4184f07 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -2,6 +2,7 @@ use clap::builder::{PossibleValuesParser, TypedValueParser, ValueParser}; use std::ops::Deref; pub mod account; +pub mod api_access; pub mod auto_connect; pub mod beta_program; pub mod bridge; @@ -10,7 +11,6 @@ pub mod dns; pub mod lan; pub mod lockdown; pub mod obfuscation; -pub mod proxy; pub mod relay; pub mod relay_constraints; pub mod reset; diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 057eb20dea..27855ded69 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -71,11 +71,11 @@ enum Cli { #[clap(subcommand)] Relay(relay::Relay), - /// Manage use of proxies (SOCKS proxies and Shadowsocks) for reaching the API. + /// Manage use of proxies for reaching the Mullvad API. /// Can make the daemon connect to the the Mullvad API via one of the - /// Mullvad bridge servers or a custom proxy. + /// Mullvad bridge servers or a custom proxy (SOCKS5 & Shadowsocks). #[clap(subcommand)] - Proxy(proxy::Proxy), + ApiAccess(api_access::ApiAccess), /// Manage use of obfuscation protocols for WireGuard. /// Can make WireGuard traffic look like something else on the network. @@ -140,7 +140,7 @@ async fn main() -> Result<()> { Cli::Dns(cmd) => cmd.handle().await, Cli::Lan(cmd) => cmd.handle().await, Cli::Obfuscation(cmd) => cmd.handle().await, - Cli::Proxy(cmd) => cmd.handle().await, + Cli::ApiAccess(cmd) => cmd.handle().await, Cli::Version => version::print().await, Cli::FactoryReset => reset::handle().await, Cli::Relay(cmd) => cmd.handle().await, diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_method.rs index a0c168c026..22a6146e8f 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_method.rs @@ -2,9 +2,8 @@ use crate::{ settings::{self, MadeChanges}, Daemon, EventListener, }; -use mullvad_types::api_access_method::{ - daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - AccessMethod, CustomAccessMethod, +use mullvad_types::access_method::{ + daemon::ApiAccessMethodReplace, ApiAccessMethod, CustomAccessMethod, }; #[derive(err_derive::Error, Debug)] @@ -21,14 +20,9 @@ impl<L> Daemon<L> where L: EventListener + Clone + Send + 'static, { - pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<(), Error> { + pub async fn add_access_method(&mut self, access_method: ApiAccessMethod) -> Result<(), Error> { self.settings - .update(|settings| { - settings - .api_access_methods - .api_access_methods - .push(access_method); - }) + .update(|settings| settings.api_access_methods.append(access_method)) .await .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) @@ -36,17 +30,15 @@ where pub async fn toggle_api_access_method( &mut self, - access_method_toggle: ApiAccessMethodToggle, + api_access_method: ApiAccessMethod, + enable: bool, ) -> Result<(), Error> { self.settings .update(|settings| { - if let Some(access_method) = settings - .api_access_methods - .api_access_methods - .iter_mut() - .find(|access_method| **access_method == access_method_toggle.access_method) + if let Some(api_access_method) = + settings.api_access_methods.find_mut(&api_access_method) { - access_method.toggle(access_method_toggle.enable); + api_access_method.toggle(enable); } }) .await @@ -59,13 +51,7 @@ where access_method: CustomAccessMethod, ) -> Result<(), Error> { self.settings - .update(|settings| { - settings.api_access_methods.api_access_methods.retain(|x| { - x.as_custom() - .map(|c| c.id != access_method.id) - .unwrap_or(true) - }); - }) + .update(|settings| settings.api_access_methods.remove(&access_method)) .await .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) @@ -77,8 +63,8 @@ where ) -> Result<(), Error> { self.settings .update(|settings| { - let access_methods = &mut settings.api_access_methods.api_access_methods; - access_methods.push(access_method_replace.access_method); + let access_methods = &mut settings.api_access_methods; + access_methods.append(access_method_replace.access_method); access_methods.swap_remove(access_method_replace.index); }) .await @@ -86,15 +72,12 @@ where .map_err(Error::Settings) } - pub async fn set_api_access_method( - &mut self, - access_method: AccessMethod, - ) -> Result<(), Error> { + pub fn set_api_access_method(&mut self, access_method: ApiAccessMethod) -> Result<(), Error> { { let mut connection_modes = self.connection_modes.lock().unwrap(); - connection_modes.set_access_method(access_method); + connection_modes.set_access_method(access_method.access_method); } - // Force a rotation of Acces Methods. + // Force a rotation of Access Methods. let _ = self.api_handle.service().next_api_endpoint(); Ok(()) } @@ -105,10 +88,15 @@ where self.event_listener .notify_settings(self.settings.to_settings()); - // TODO: Could this be replaced by message passing? Yes plz. let mut connection_modes = self.connection_modes.lock().unwrap(); - connection_modes - .update_access_methods(self.settings.api_access_methods.api_access_methods.clone()) + connection_modes.update_access_methods( + self.settings + .api_access_methods + .api_access_methods + .iter() + .map(|x| x.access_method.clone()) + .collect(), + ) }; } } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index f96dea9484..75b3ad9e47 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -10,7 +10,7 @@ use mullvad_api::{ ApiEndpointUpdateCallback, }; use mullvad_relay_selector::RelaySelector; -use mullvad_types::api_access_method; +use mullvad_types::access_method::{self, AccessMethod, BuiltInAccessMethod}; use std::{ net::SocketAddr, path::PathBuf, @@ -26,78 +26,24 @@ use talpid_types::{ ErrorExt, }; -/// TODO: Update this comment /// A stream that returns the next API connection mode to use for reaching the API. /// -/// When `mullvad-api` fails to contact the API, it requests a new connection mode. -/// The API can be connected to either directly (i.e., [`ApiConnectionMode::Direct`]) -/// via a bridge ([`ApiConnectionMode::Proxied`]) or via any supported obfuscation protocol ([`ObfuscationProtocol`]). +/// When `mullvad-api` fails to contact the API, it requests a new connection +/// mode. The API can be connected to either directly (i.e., +/// [`ApiConnectionMode::Direct`]) via a bridge ([`ApiConnectionMode::Proxied`]) +/// or via any supported custom proxy protocol ([`api_access_methods::ObfuscationProtocol`]). /// -/// * Every 3rd attempt returns [`ApiConnectionMode::Direct`]. -/// * Any other attempt returns a configuration for the bridge that is closest to the selected relay -/// location and matches all bridge constraints. -/// * When no matching bridge is found, e.g. if the selected hosting providers don't match any -/// bridge, [`ApiConnectionMode::Direct`] is returned. +/// The strategy for determining the next [`ApiConnectionMode`] is handled by +/// [`ConnectionModesIterator`]. pub struct ApiConnectionModeProvider { cache_dir: PathBuf, - /// Used for selecting a Relay when the `Bridges` access method is used. + /// Used for selecting a Relay when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, retry_attempt: u32, current_task: Option<Pin<Box<dyn Future<Output = ApiConnectionMode> + Send>>>, connection_modes: Arc<Mutex<ConnectionModesIterator>>, } -/// An iterator which will always produce an [`AccessMethod`]. -/// -/// Safety: It is always safe to [`unwrap`] after calling [`next`] on a -/// [`std::iter::Cycle`], so thereby it is safe to always call [`unwrap`] on a -/// [`ConnectionModesIterator`] -/// -/// [`unwrap`]: Option::unwrap -/// [`next`]: std::iter::Iterator::next -pub struct ConnectionModesIterator { - available_modes: Box<dyn Iterator<Item = AccessMethod> + Send>, - next: Option<AccessMethod>, -} - -impl ConnectionModesIterator { - pub fn new(modes: Vec<AccessMethod>) -> ConnectionModesIterator { - Self { - next: None, - available_modes: Self::get_filtered_access_methods(modes), - } - } - - /// Set the next [`AccessMethod`] to be returned from this iterator. - pub fn set_access_method(&mut self, next: AccessMethod) { - self.next = Some(next); - } - /// Update the collection of [`AccessMethod`] which this iterator will - /// return. - pub fn update_access_methods(&mut self, access_methods: Vec<AccessMethod>) { - self.available_modes = Self::get_filtered_access_methods(access_methods); - } - - fn get_filtered_access_methods( - modes: Vec<AccessMethod>, - ) -> Box<dyn Iterator<Item = AccessMethod> + Send> { - Box::new( - modes - .into_iter() - .filter(|access_method| access_method.enabled()) - .cycle(), - ) - } -} - -impl Iterator for ConnectionModesIterator { - type Item = AccessMethod; - - fn next(&mut self) -> Option<Self::Item> { - self.next.take().or_else(|| self.available_modes.next()) - } -} - impl Stream for ApiConnectionModeProvider { type Item = ApiConnectionMode; @@ -149,16 +95,15 @@ impl ApiConnectionModeProvider { } } + /// 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. - /// - /// TODO: Figure out an appropriate algorithm for selecting between the available AccessMethods. - /// We need to be able to poll the daemon's settings to find out which access methods are available & active. - /// For now, [`ApiConnectionModeProvider`] is only instanciated once during daemon startup, and does not change it's - /// available access methods based on any "Settings-changed" events. fn new_connection_mode(&mut self) -> ApiConnectionMode { log::debug!("Rotating Access mode!"); let access_method = { @@ -173,23 +118,22 @@ impl ApiConnectionModeProvider { /// 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 used. + /// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait + /// can not be implemented. fn from(&mut self, access_method: &AccessMethod) -> ApiConnectionMode { match access_method { AccessMethod::BuiltIn(access_method) => match access_method { - BuiltInAccessMethod::Direct(_enabled) => ApiConnectionMode::Direct, - BuiltInAccessMethod::Bridge(enabled) => self + BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, + BuiltInAccessMethod::Bridge => self .relay_selector .get_bridge_forced() .and_then(|settings| match settings { ProxySettings::Shadowsocks(ss_settings) => { - let ss_settings: api_access_method::Shadowsocks = - api_access_method::Shadowsocks::new( + let ss_settings: access_method::Shadowsocks = + access_method::Shadowsocks::new( ss_settings.peer, ss_settings.cipher, ss_settings.password, - enabled, - "Mullvad Bridges".to_string(), // TODO: Move this to some central location ); Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( ss_settings, @@ -203,10 +147,10 @@ impl ApiConnectionModeProvider { .unwrap_or(ApiConnectionMode::Direct), }, AccessMethod::Custom(access_method) => match &access_method.access_method { - api_access_method::ObfuscationProtocol::Shadowsocks(shadowsocks_config) => { + access_method::ObfuscationProtocol::Shadowsocks(shadowsocks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) } - api_access_method::ObfuscationProtocol::Socks5(socks_config) => { + access_method::ObfuscationProtocol::Socks5(socks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) } }, @@ -214,6 +158,59 @@ impl ApiConnectionModeProvider { } } +/// An iterator which will always produce an [`AccessMethod`]. +/// +/// Safety: It is always safe to [`unwrap`] after calling [`next`] on a +/// [`std::iter::Cycle`], so thereby it is safe to always call [`unwrap`] on a +/// [`ConnectionModesIterator`]. +/// +/// [`unwrap`]: Option::unwrap +/// [`next`]: std::iter::Iterator::next +pub struct ConnectionModesIterator { + available_modes: Box<dyn Iterator<Item = AccessMethod> + Send>, + next: Option<AccessMethod>, +} + +impl ConnectionModesIterator { + pub fn new(modes: Vec<AccessMethod>) -> ConnectionModesIterator { + Self { + next: None, + available_modes: Self::get_filtered_access_methods(modes), + } + } + + /// Set the next [`AccessMethod`] to be returned from this iterator. + pub fn set_access_method(&mut self, next: AccessMethod) { + self.next = Some(next); + } + /// Update the collection of [`AccessMethod`] which this iterator will + /// return. + pub fn update_access_methods(&mut self, api_access_methods: Vec<AccessMethod>) { + self.available_modes = Self::get_filtered_access_methods(api_access_methods); + } + + /// [`ConnectionModesIterator`] will only consider [`AccessMethod`]s which + /// are explicitly marked as enabled. As such, a pre-processing step before + /// assigning an iterator to `available_modes` is to filter out all disabled + /// [`AccessMethod`]s that may be present in the input. + fn get_filtered_access_methods( + modes: Vec<AccessMethod>, + ) -> Box<dyn Iterator<Item = AccessMethod> + Send> { + Box::new(modes + .into_iter() + // .filter(|access_method| access_method.enabled()) + .cycle()) + } +} + +impl Iterator for ConnectionModesIterator { + type Item = AccessMethod; + + fn next(&mut self) -> Option<Self::Item> { + self.next.take().or_else(|| self.available_modes.next()) + } +} + /// 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. @@ -245,15 +242,14 @@ impl ApiEndpointUpdaterHandle { 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( get_allowed_endpoint(address), result_tx, )); + // Wait for the firewall policy to be updated. let _ = result_rx.await; log::debug!("API endpoint: {}", address); - true } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index ed7bafd007..2cb35cfcf7 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1,7 +1,7 @@ #![deny(rust_2018_idioms)] #![recursion_limit = "512"] -mod access_methods; +mod access_method; pub mod account_history; mod api; #[cfg(not(target_os = "android"))] @@ -39,11 +39,11 @@ use mullvad_relay_selector::{ RelaySelector, SelectorConfig, }; use mullvad_types::{ - account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::{ + access_method::{ daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - AccessMethod, CustomAccessMethod, + ApiAccessMethod, CustomAccessMethod, }, + account::{AccountData, AccountToken, VoucherSubmission}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -176,7 +176,7 @@ pub enum Error { CustomListNotFound, #[error(display = "Access method error")] - AccessMethodError(#[error(source)] access_methods::Error), + AccessMethodError(#[error(source)] access_method::Error), #[cfg(target_os = "macos")] #[error(display = "Failed to set exclusion group")] @@ -264,9 +264,9 @@ pub enum DaemonCommand { /// Update a custom list with a given id UpdateCustomList(ResponseTx<(), Error>, CustomList), /// Get API access methods - GetApiAccessMethods(ResponseTx<Vec<AccessMethod>, Error>), + GetApiAccessMethods(ResponseTx<Vec<ApiAccessMethod>, Error>), /// Add API access methods - AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + AddApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethod), /// Remove an API access method RemoveApiAccessMethod(ResponseTx<(), Error>, CustomAccessMethod), /// Edit an API access method @@ -274,7 +274,7 @@ pub enum DaemonCommand { /// Toggle the status of an API access method ToggleApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodToggle), /// Set the API access method to use - SetApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + SetApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethod), /// Get the addresses of all known API endpoints GetApiAddresses(ResponseTx<Vec<std::net::SocketAddr>, Error>), /// Get information about the currently running and latest app versions @@ -639,11 +639,15 @@ where let initial_selector_config = new_selector_config(&settings); let relay_selector = RelaySelector::new(initial_selector_config, &resource_dir, &cache_dir); - // TODO: Should ApiConnectionModeProvider be an Actor instead of sharing a datastructure-behind-locks with the daemon with the daemon? let proxy_provider = api::ApiConnectionModeProvider::new( cache_dir.clone(), relay_selector.clone(), - settings.api_access_methods.api_access_methods.clone(), + settings + .api_access_methods + .api_access_methods + .iter() + .map(|x| x.access_method.clone()) + .collect(), ); let connection_modes = proxy_provider.handle(); @@ -1068,7 +1072,7 @@ where self.on_replace_api_access_method(tx, method).await } ToggleApiAccessMethod(tx, method) => self.on_toggle_api_access_method(tx, method).await, - SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method).await, + SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method), GetApiAddresses(tx) => self.on_get_api_addresses(tx).await, IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), @@ -2244,12 +2248,16 @@ where Self::oneshot_send(tx, result, "update_custom_list response"); } - fn on_get_api_access_methods(&mut self, tx: ResponseTx<Vec<AccessMethod>, Error>) { - let result = Ok(self.settings.api_access_methods.api_access_methods.clone()); + fn on_get_api_access_methods(&mut self, tx: ResponseTx<Vec<ApiAccessMethod>, Error>) { + let result = Ok(self.settings.api_access_methods.cloned()); Self::oneshot_send(tx, result, "get_api_access_methods response"); } - async fn on_add_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: AccessMethod) { + async fn on_add_api_access_method( + &mut self, + tx: ResponseTx<(), Error>, + method: ApiAccessMethod, + ) { let result = self .add_access_method(method) .await @@ -2284,19 +2292,21 @@ where async fn on_toggle_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: ApiAccessMethodToggle, + access_method_toggle: ApiAccessMethodToggle, ) { let result = self - .toggle_api_access_method(method) + .toggle_api_access_method( + access_method_toggle.access_method, + access_method_toggle.enable, + ) .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "toggle_api_access_method response"); } - async fn on_set_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: AccessMethod) { + fn on_set_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: ApiAccessMethod) { let result = self .set_api_access_method(method) - .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "set_api_access_method response"); } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 88ea87056c..19c4e1e412 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -619,13 +619,88 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + async fn get_api_access_methods( + &self, + _: Request<()>, + ) -> ServiceResult<mullvad_management_interface::types::ApiAccessMethods> { + log::debug!("get_api_access_methods"); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetApiAccessMethods(tx))?; + self.wait_for_result(rx) + .await? + .map(From::from) + .map(Response::new) + .map_err(map_daemon_error) + } + + async fn add_api_access_method( + &self, + request: Request<types::ApiAccessMethod>, + ) -> ServiceResult<()> { + log::debug!("add_api_access_method"); + let api_access_method = + mullvad_types::access_method::ApiAccessMethod::try_from(request.into_inner())?; + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::AddApiAccessMethod(tx, api_access_method))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + + async fn remove_api_access_method( + &self, + request: Request<types::ApiAccessMethod>, + ) -> ServiceResult<()> { + log::debug!("remove_api_access_method"); + let api_access_method = + mullvad_types::access_method::ApiAccessMethod::try_from(request.into_inner())?; + + match api_access_method.access_method.as_custom() { + None => Err(Status::not_found( + "Can not remove built-in API access method", + )), + Some(access_method) => { + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::RemoveApiAccessMethod( + tx, + access_method.clone(), + ))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + } + } + + async fn replace_api_access_method( + &self, + request: Request<types::ApiAccessMethodReplace>, + ) -> ServiceResult<()> { + log::debug!("edit_api_access_method"); + let access_method_replace = + mullvad_types::access_method::daemon::ApiAccessMethodReplace::try_from( + request.into_inner(), + )?; + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::ReplaceApiAccessMethod( + tx, + access_method_replace, + ))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + async fn toggle_api_access_method( &self, request: Request<types::ApiAccessMethodToggle>, ) -> ServiceResult<()> { log::debug!("toggle_api_access_method"); let access_method_toggle = - mullvad_types::api_access_method::daemon::ApiAccessMethodToggle::try_from( + mullvad_types::access_method::daemon::ApiAccessMethodToggle::try_from( request.into_inner(), )?; let (tx, rx) = oneshot::channel(); @@ -644,10 +719,10 @@ impl ManagementService for ManagementServiceImpl { request: Request<types::ApiAccessMethod>, ) -> ServiceResult<()> { log::debug!("set_api_access_method"); - let access_method = - mullvad_types::api_access_method::AccessMethod::try_from(request.into_inner())?; + let api_access_method = + mullvad_types::access_method::ApiAccessMethod::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, access_method))?; + self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(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 018906cdaf..e923ec1043 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -355,43 +355,34 @@ message CustomList { message CustomListSettings { repeated CustomList custom_lists = 1; } message ApiAccessMethod { - message Direct { bool enabled = 1; } - message Bridges { bool enabled = 1; } + message Direct {} + message Bridges {} message Socks5Local { - bool enabled = 1; - string name = 2; string id = 3; string ip = 4; uint32 port = 5; uint32 local_port = 6; } message Socks5Remote { - bool enabled = 1; - string name = 2; - string id = 3; - string ip = 4; - uint32 port = 5; - } - message Socks5 { - oneof Socks5type { - Socks5Local local = 1; - Socks5Remote remote = 2; - } + string id = 1; + string ip = 2; + uint32 port = 3; } message Shadowsocks { - bool enabled = 1; - string name = 2; - string id = 3; - string ip = 4; - uint32 port = 5; - string password = 6; - string cipher = 7; + string id = 1; + string ip = 2; + uint32 port = 3; + string password = 4; + string cipher = 5; } + string name = 1; + bool enabled = 2; oneof access_method { - Direct direct = 1; - Bridges bridges = 2; - Socks5 socks5 = 3; - Shadowsocks shadowsocks = 4; + Direct direct = 3; + Bridges bridges = 4; + Socks5Local socks5local = 5; + Socks5Remote socks5remote = 6; + Shadowsocks shadowsocks = 7; } } diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 049f8a2fe3..315e3276fc 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -3,11 +3,11 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ - account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::{ + access_method::{ daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - AccessMethod, + ApiAccessMethod, }, + account::{AccountData, AccountToken, VoucherSubmission}, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -167,7 +167,7 @@ impl MullvadProxyClient { mullvad_types::relay_list::RelayList::try_from(list).map_err(Error::InvalidResponse) } - pub async fn get_api_access_methods(&mut self) -> Result<Vec<AccessMethod>> { + pub async fn get_api_access_methods(&mut self) -> Result<Vec<ApiAccessMethod>> { self.0 .get_api_access_methods(()) .await @@ -175,13 +175,13 @@ impl MullvadProxyClient { .into_inner() .api_access_methods .into_iter() - .map(|access_method| { - AccessMethod::try_from(access_method).map_err(Error::InvalidResponse) + .map(|api_access_method| { + ApiAccessMethod::try_from(api_access_method).map_err(Error::InvalidResponse) }) .collect() } - pub async fn test_api(&mut self) -> Result<()> { + pub async fn get_api_addressess(&mut self) -> Result<()> { self.0.get_api_addressess(()).await.map_err(Error::Rpc)?; Ok(()) } @@ -480,9 +480,9 @@ impl MullvadProxyClient { Ok(()) } - pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + pub async fn add_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { self.0 - .add_api_access_method(types::ApiAccessMethod::from(access_method)) + .add_api_access_method(types::ApiAccessMethod::from(api_access_method)) .await .map_err(Error::Rpc) .map(drop) @@ -499,9 +499,9 @@ impl MullvadProxyClient { .map(drop) } - pub async fn remove_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + pub async fn remove_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { self.0 - .remove_api_access_method(types::ApiAccessMethod::from(access_method)) + .remove_api_access_method(types::ApiAccessMethod::from(api_access_method)) .await .map_err(Error::Rpc) .map(drop) @@ -527,9 +527,9 @@ impl MullvadProxyClient { /// method "randomly" /// /// [`ApiConnectionModeProvider`]: mullvad_daemon::api::ApiConnectionModeProvider - pub async fn set_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + pub async fn set_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { self.0 - .set_api_access_method(types::ApiAccessMethod::from(access_method)) + .set_api_access_method(types::ApiAccessMethod::from(api_access_method)) .await .map_err(Error::Rpc) .map(drop) diff --git a/mullvad-management-interface/src/types/conversions/api_access_method.rs b/mullvad-management-interface/src/types/conversions/api_access_method.rs index 958cbf0564..28859c5013 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -1,10 +1,12 @@ -/// Implements conversions for the auxilliary proto AccessMethod type to the internal AccessMethod data type. +/// Implements conversions for the auxilliary +/// [`crate::types::proto::ApiAccessMethodSettings`] type to the internal +/// [`mullvad_types::access_method::Settings`] data type. mod settings { use crate::types::{proto, FromProtobufTypeError}; - use mullvad_types::api_access_method; + use mullvad_types::access_method; - impl From<&api_access_method::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: &api_access_method::Settings) -> Self { + impl From<&access_method::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: &access_method::Settings) -> Self { Self { api_access_methods: settings .api_access_methods @@ -15,13 +17,13 @@ mod settings { } } - impl From<api_access_method::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: api_access_method::Settings) -> Self { + impl From<access_method::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: access_method::Settings) -> Self { proto::ApiAccessMethodSettings::from(&settings) } } - impl TryFrom<proto::ApiAccessMethodSettings> for api_access_method::Settings { + impl TryFrom<proto::ApiAccessMethodSettings> for access_method::Settings { type Error = FromProtobufTypeError; fn try_from(settings: proto::ApiAccessMethodSettings) -> Result<Self, Self::Error> { @@ -29,267 +31,219 @@ mod settings { api_access_methods: settings .api_access_methods .iter() - .map(api_access_method::AccessMethod::try_from) - .collect::<Result<Vec<api_access_method::AccessMethod>, _>>()?, + .map(access_method::ApiAccessMethod::try_from) + .collect::<Result<Vec<access_method::ApiAccessMethod>, _>>()?, }) } } - impl From<api_access_method::daemon::ApiAccessMethodReplace> for proto::ApiAccessMethodReplace { - fn from(value: api_access_method::daemon::ApiAccessMethodReplace) -> Self { + impl From<access_method::daemon::ApiAccessMethodReplace> for proto::ApiAccessMethodReplace { + fn from(value: access_method::daemon::ApiAccessMethodReplace) -> Self { + let api_access_method = value.access_method; proto::ApiAccessMethodReplace { index: value.index as u32, - access_method: Some(value.access_method.into()), + access_method: Some(proto::ApiAccessMethod::from(api_access_method)), } } } - impl TryFrom<proto::ApiAccessMethodReplace> for api_access_method::daemon::ApiAccessMethodReplace { + impl TryFrom<proto::ApiAccessMethodReplace> for access_method::daemon::ApiAccessMethodReplace { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethodReplace) -> Result<Self, Self::Error> { - Ok(api_access_method::daemon::ApiAccessMethodReplace { + Ok(access_method::daemon::ApiAccessMethodReplace { index: value.index as usize, access_method: value .access_method .ok_or(FromProtobufTypeError::InvalidArgument( "Could not convert Access Method from protobuf", )) - .and_then(TryInto::try_into)?, + .and_then(access_method::ApiAccessMethod::try_from)?, }) } } - impl From<api_access_method::daemon::ApiAccessMethodToggle> for proto::ApiAccessMethodToggle { - fn from(value: api_access_method::daemon::ApiAccessMethodToggle) -> Self { + impl From<access_method::daemon::ApiAccessMethodToggle> for proto::ApiAccessMethodToggle { + fn from(value: access_method::daemon::ApiAccessMethodToggle) -> Self { + let api_access_method = value.access_method; + let enabled = api_access_method.enabled(); proto::ApiAccessMethodToggle { - access_method: Some(value.access_method.into()), - enable: value.enable, + access_method: Some(proto::ApiAccessMethod::from(api_access_method)), + enable: enabled, } } } - impl TryFrom<proto::ApiAccessMethodToggle> for api_access_method::daemon::ApiAccessMethodToggle { + impl TryFrom<proto::ApiAccessMethodToggle> for access_method::daemon::ApiAccessMethodToggle { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethodToggle) -> Result<Self, Self::Error> { - Ok(api_access_method::daemon::ApiAccessMethodToggle { + Ok(access_method::daemon::ApiAccessMethodToggle { access_method: value .access_method .ok_or(FromProtobufTypeError::InvalidArgument( "Could not convert Access Method from protobuf", )) - .and_then(TryInto::try_into)?, + .and_then(access_method::ApiAccessMethod::try_from)?, enable: value.enable, }) } } } -/// Implements conversions for the 'main' AccessMethod data type. +/// Implements conversions for the auxilliary +/// [`crate::types::proto::ApiAccessMethod`] type to the internal +/// [`mullvad_types::access_method::AccessMethod`] data type. mod data { - use crate::types::{ - proto::{self, api_access_method::socks5::Socks5type}, - FromProtobufTypeError, - }; - use mullvad_types::api_access_method::{ - AccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Shadowsocks, Socks5, Socks5Local, - Socks5Remote, + use crate::types::{proto, FromProtobufTypeError}; + use mullvad_types::access_method::{ + AccessMethod, ApiAccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Shadowsocks, + Socks5, Socks5Local, Socks5Remote, }; - impl TryFrom<proto::ApiAccessMethods> for Vec<AccessMethod> { + impl TryFrom<proto::ApiAccessMethods> for Vec<ApiAccessMethod> { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethods) -> Result<Self, Self::Error> { value .api_access_methods .iter() - .map(AccessMethod::try_from) + .map(ApiAccessMethod::try_from) .collect() } } - impl TryFrom<proto::ApiAccessMethod> for AccessMethod { + impl TryFrom<proto::ApiAccessMethod> for ApiAccessMethod { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethod) -> Result<Self, Self::Error> { + let name = value.name; + let enabled = value.enabled; let access_method = value .access_method .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not convert Access Method from protobuf", + "Could not deserialize Access Method from protobuf", ))?; - Ok(match access_method { - proto::api_access_method::AccessMethod::Socks5(socks) => { - match socks.socks5type.unwrap() { - Socks5type::Local(local) => Socks5Local::from_args( - local.ip, - local.port as u16, - local.local_port as u16, - local.enabled, - local.name, - ) - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not parse Socks5 (local) message from protobuf", - ))? - .into(), - Socks5type::Remote(remote) => Socks5Remote::from_args( - remote.ip, - remote.port as u16, - remote.enabled, - remote.name, - ) - .ok_or({ - FromProtobufTypeError::InvalidArgument( - "Could not parse Socks5 (remote) message from protobuf", - ) - })? - .into(), - } + let x = match access_method { + proto::api_access_method::AccessMethod::Direct( + proto::api_access_method::Direct {}, + ) => AccessMethod::from(BuiltInAccessMethod::Direct), + + proto::api_access_method::AccessMethod::Bridges( + proto::api_access_method::Bridges {}, + ) => AccessMethod::from(BuiltInAccessMethod::Bridge), + proto::api_access_method::AccessMethod::Socks5local(local) => { + let socks = Socks5Local::from_args( + local.ip, + local.port as u16, + local.local_port as u16, + ) + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (local) message from protobuf", + ))?; + AccessMethod::from(socks) } - proto::api_access_method::AccessMethod::Shadowsocks(ss) => Shadowsocks::from_args( - ss.ip, - ss.port as u16, - ss.cipher, - ss.password, - ss.enabled, - ss.name, - ) - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not parse Shadowsocks message from protobuf", - ))? - .into(), - proto::api_access_method::AccessMethod::Direct(direct_settings) => { - BuiltInAccessMethod::Direct(direct_settings.enabled).into() + + proto::api_access_method::AccessMethod::Socks5remote(remote) => { + let socks = Socks5Remote::from_args(remote.ip, remote.port as u16).ok_or({ + FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (remote) message from protobuf", + ) + })?; + AccessMethod::from(socks) } - proto::api_access_method::AccessMethod::Bridges(bridge_settings) => { - BuiltInAccessMethod::Bridge(bridge_settings.enabled).into() + proto::api_access_method::AccessMethod::Shadowsocks(ss) => { + let socks = + Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password) + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse Shadowsocks message from protobuf", + ))?; + AccessMethod::from(socks) } + }; + + Ok(ApiAccessMethod { + name, + enabled, + access_method: x, }) } } - impl From<AccessMethod> for proto::ApiAccessMethod { - fn from(value: AccessMethod) -> Self { - match value { + impl From<ApiAccessMethod> for proto::ApiAccessMethod { + fn from(value: ApiAccessMethod) -> Self { + let name = value.get_name(); + let enabled = value.enabled(); + let access_method = match value.access_method { AccessMethod::Custom(value) => match value.access_method { - ObfuscationProtocol::Shadowsocks(ss) => proto::api_access_method::Shadowsocks { - id: value.id, - ip: ss.peer.ip().to_string(), - port: ss.peer.port() as u32, - password: ss.password, - cipher: ss.cipher, - enabled: ss.enabled, - name: ss.name, + ObfuscationProtocol::Shadowsocks(ss) => { + proto::api_access_method::AccessMethod::Shadowsocks( + proto::api_access_method::Shadowsocks { + id: value.id, + ip: ss.peer.ip().to_string(), + port: ss.peer.port() as u32, + password: ss.password, + cipher: ss.cipher, + }, + ) } - .into(), - - ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { - peer, - port, - enabled, - name, - })) => proto::api_access_method::Socks5Local { - id: value.id, - ip: peer.ip().to_string(), - port: peer.port() as u32, - local_port: port as u32, - enabled, - name, + ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { peer, port })) => { + proto::api_access_method::AccessMethod::Socks5local( + proto::api_access_method::Socks5Local { + id: value.id, + ip: peer.ip().to_string(), + port: peer.port() as u32, + local_port: port as u32, + }, + ) } - .into(), - ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { - peer, - enabled, - name, - })) => proto::api_access_method::Socks5Remote { - id: value.id, - ip: peer.ip().to_string(), - port: peer.port() as u32, - enabled, - name, + ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + proto::api_access_method::AccessMethod::Socks5remote( + proto::api_access_method::Socks5Remote { + id: value.id, + ip: peer.ip().to_string(), + port: peer.port() as u32, + }, + ) } - .into(), }, AccessMethod::BuiltIn(value) => match value { - mullvad_types::api_access_method::BuiltInAccessMethod::Direct(enabled) => { - proto::api_access_method::Direct { enabled }.into() + mullvad_types::access_method::BuiltInAccessMethod::Direct => { + proto::api_access_method::AccessMethod::Direct( + proto::api_access_method::Direct {}, + ) } - mullvad_types::api_access_method::BuiltInAccessMethod::Bridge(enabled) => { - proto::api_access_method::Bridges { enabled }.into() + mullvad_types::access_method::BuiltInAccessMethod::Bridge => { + proto::api_access_method::AccessMethod::Bridges( + proto::api_access_method::Bridges {}, + ) } }, + }; + + proto::ApiAccessMethod { + name, + enabled, + access_method: Some(access_method), } } } - impl TryFrom<&proto::ApiAccessMethod> for AccessMethod { + impl TryFrom<&proto::ApiAccessMethod> for ApiAccessMethod { type Error = FromProtobufTypeError; fn try_from(value: &proto::ApiAccessMethod) -> Result<Self, Self::Error> { - AccessMethod::try_from(value.clone()) + ApiAccessMethod::try_from(value.clone()) } } - impl From<Vec<AccessMethod>> for proto::ApiAccessMethods { - fn from(value: Vec<AccessMethod>) -> proto::ApiAccessMethods { + impl From<Vec<ApiAccessMethod>> for proto::ApiAccessMethods { + fn from(value: Vec<ApiAccessMethod>) -> proto::ApiAccessMethods { proto::ApiAccessMethods { api_access_methods: value.iter().map(|method| method.clone().into()).collect(), } } } - - impl From<proto::api_access_method::Shadowsocks> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Shadowsocks) -> Self { - proto::api_access_method::AccessMethod::Shadowsocks(value).into() - } - } - - impl From<proto::api_access_method::Socks5> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5) -> Self { - proto::api_access_method::AccessMethod::Socks5(value).into() - } - } - - impl From<proto::api_access_method::socks5::Socks5type> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::socks5::Socks5type) -> Self { - proto::api_access_method::AccessMethod::Socks5(proto::api_access_method::Socks5 { - socks5type: Some(value), - }) - .into() - } - } - - impl From<proto::api_access_method::Socks5Local> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5Local) -> Self { - proto::api_access_method::socks5::Socks5type::Local(value).into() - } - } - - impl From<proto::api_access_method::Socks5Remote> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5Remote) -> Self { - proto::api_access_method::socks5::Socks5type::Remote(value).into() - } - } - - impl From<proto::api_access_method::Direct> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Direct) -> Self { - proto::api_access_method::AccessMethod::Direct(value).into() - } - } - - impl From<proto::api_access_method::Bridges> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Bridges) -> Self { - proto::api_access_method::AccessMethod::Bridges(value).into() - } - } - - impl From<proto::api_access_method::AccessMethod> for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::AccessMethod) -> Self { - proto::ApiAccessMethod { - access_method: Some(value), - } - } - } } diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index 57694d31f0..98c195d935 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -180,7 +180,7 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings { custom_lists: mullvad_types::custom_list::CustomListsSettings::try_from( custom_lists_settings, )?, - api_access_methods: mullvad_types::api_access_method::Settings::try_from( + api_access_methods: mullvad_types::access_method::Settings::try_from( api_access_methods_settings, )?, }) diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs new file mode 100644 index 0000000000..73f51d25b8 --- /dev/null +++ b/mullvad-types/src/access_method.rs @@ -0,0 +1,311 @@ +use std::collections::hash_map::DefaultHasher; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; + +/// Daemon settings for API access methods. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Settings { + pub api_access_methods: Vec<ApiAccessMethod>, +} + +impl Settings { + /// Append an [`AccessMethod`] to the end of `api_access_methods`. + #[inline(always)] + pub fn append(&mut self, api_access_method: ApiAccessMethod) { + self.api_access_methods.push(api_access_method) + } + + /// Remove a [`CustomAccessMethod`] from `api_access_methods`. + #[inline(always)] + pub fn remove(&mut self, custom_access_method: &CustomAccessMethod) { + self.retain(|api_access_method| { + api_access_method + .access_method + .as_custom() + .map(|access_method| access_method.id != custom_access_method.id) + .unwrap_or(true) + }) + } + + /// Search for a particular [`AccessMethod`] in `api_access_methods`. + /// + /// If the [`AccessMethod`] is found to be part of `api_access_methods`, a + /// mutable reference to that inner element is returned. Otherwise, `None` + /// is returned. + #[inline(always)] + pub fn find_mut(&mut self, element: &ApiAccessMethod) -> Option<&mut ApiAccessMethod> { + self.api_access_methods + .iter_mut() + .find(|api_access_method| { + // TODO: Can probably replace with `element.id == api_access_method.id` + element.access_method == api_access_method.access_method + }) + } + + /// Equivalent to [`Vec::retain`]. + #[inline(always)] + pub fn retain<F>(&mut self, f: F) + where + F: FnMut(&ApiAccessMethod) -> bool, + { + self.api_access_methods.retain(f) + } + + /// Removes an element from `api_access_methods` and returns it. + /// The removed element is replaced by the last element of the vector. + /// + /// Equivalent to [`Vec::swap_remove`]. + #[inline(always)] + pub fn swap_remove(&mut self, index: usize) -> ApiAccessMethod { + self.api_access_methods.swap_remove(index) + } + + /// Clone the content of `api_access_methods`. + #[inline(always)] + pub fn cloned(&self) -> Vec<ApiAccessMethod> { + self.api_access_methods.clone() + } +} + +impl Default for Settings { + fn default() -> Self { + Self { + api_access_methods: vec![BuiltInAccessMethod::Direct, BuiltInAccessMethod::Bridge] + .into_iter() + .map(|built_in| ApiAccessMethod { + name: built_in.canonical_name(), + enabled: true, + access_method: AccessMethod::from(built_in), + }) + .collect(), + } + } +} + +/// API Access Method datastructure +/// +/// Mirrors the protobuf definition +/// TODO(Create a constructor functions for this struct (?)) +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ApiAccessMethod { + pub name: String, + pub enabled: bool, + pub access_method: AccessMethod, +} + +/// Access Method datastructure. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum AccessMethod { + BuiltIn(BuiltInAccessMethod), + Custom(CustomAccessMethod), +} + +impl ApiAccessMethod { + pub fn get_name(&self) -> String { + self.name.clone() + } + + pub fn enabled(&self) -> bool { + self.enabled + } + + pub fn as_custom(&self) -> Option<&CustomAccessMethod> { + self.access_method.as_custom() + } + + /// Set an API access method to be either enabled or disabled. + pub fn toggle(&mut self, enable: bool) { + self.enabled = enable; + } +} + +/// Built-In access method datastructure. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum BuiltInAccessMethod { + Direct, + Bridge, +} + +/// Custom access method datastructure. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct CustomAccessMethod { + pub id: String, + pub access_method: ObfuscationProtocol, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum ObfuscationProtocol { + Shadowsocks(Shadowsocks), + Socks5(Socks5), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum Socks5 { + Local(Socks5Local), + Remote(Socks5Remote), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct Shadowsocks { + pub peer: SocketAddr, + pub password: String, + /// One of [`shadowsocks_ciphers`]. + /// Gets validated at a later stage. Is assumed to be valid. + /// + /// shadowsocks_ciphers: talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS + pub cipher: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct Socks5Local { + pub peer: SocketAddr, + /// Port on localhost where the SOCKS5-proxy listens to. + pub port: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct Socks5Remote { + pub peer: SocketAddr, +} + +impl AccessMethod { + pub fn as_custom(&self) -> Option<&CustomAccessMethod> { + match self { + AccessMethod::BuiltIn(_) => None, + AccessMethod::Custom(access_method) => Some(access_method), + } + } +} + +impl BuiltInAccessMethod { + pub fn canonical_name(&self) -> String { + match self { + BuiltInAccessMethod::Direct => "Direct".to_string(), + BuiltInAccessMethod::Bridge => "Mullvad Bridges".to_string(), + } + } +} + +impl Shadowsocks { + pub fn new(peer: SocketAddr, cipher: String, password: String) -> Self { + Shadowsocks { + peer, + password, + cipher, + } + } + + /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. + /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`]. + pub fn from_args(ip: String, port: u16, cipher: String, password: String) -> Option<Self> { + let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); + Some(Self::new(peer, cipher, password)) + } +} + +impl Socks5Local { + pub fn new(peer: SocketAddr, port: u16) -> Self { + Self { peer, port } + } + + /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. + /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`]. + pub fn from_args(ip: String, port: u16, localport: u16) -> Option<Self> { + let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); + let peer = SocketAddr::new(peer_ip, port); + Some(Self::new(peer, localport)) + } +} + +impl Socks5Remote { + pub fn new(peer: SocketAddr) -> Self { + Self { peer } + } + + /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. + /// If `ip` or `port` are valid [`Some(Socks5Remote)`] is returned, otherwise [`None`]. + pub fn from_args(ip: String, port: u16) -> Option<Self> { + let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); + let peer = SocketAddr::new(peer_ip, port); + Some(Self::new(peer)) + } +} + +impl From<BuiltInAccessMethod> for AccessMethod { + fn from(value: BuiltInAccessMethod) -> Self { + AccessMethod::BuiltIn(value) + } +} + +impl From<CustomAccessMethod> for AccessMethod { + fn from(value: CustomAccessMethod) -> Self { + AccessMethod::Custom(value) + } +} + +impl From<ObfuscationProtocol> for AccessMethod { + fn from(value: ObfuscationProtocol) -> Self { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + AccessMethod::from(CustomAccessMethod { + id: hasher.finish().to_string(), + access_method: value, + }) + } +} + +impl From<Shadowsocks> for AccessMethod { + fn from(value: Shadowsocks) -> Self { + ObfuscationProtocol::Shadowsocks(value).into() + } +} + +impl From<Socks5> for AccessMethod { + fn from(value: Socks5) -> Self { + AccessMethod::from(ObfuscationProtocol::Socks5(value)) + } +} + +impl From<Socks5Remote> for AccessMethod { + fn from(value: Socks5Remote) -> Self { + Socks5::Remote(value).into() + } +} + +impl From<Socks5Local> for AccessMethod { + fn from(value: Socks5Local) -> Self { + Socks5::Local(value).into() + } +} + +impl From<Socks5Remote> for Socks5 { + fn from(value: Socks5Remote) -> Self { + Socks5::Remote(value) + } +} + +impl From<Socks5Local> for Socks5 { + fn from(value: Socks5Local) -> Self { + Socks5::Local(value) + } +} + +/// Some short-lived datastructure used in some RPC calls to the mullvad daemon. +pub mod daemon { + use super::*; + /// Argument to protobuf rpc `ApiAccessMethodReplace`. + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] + pub struct ApiAccessMethodReplace { + pub access_method: ApiAccessMethod, + pub index: usize, + } + /// Argument to protobuf rpc `ApiAccessMethodToggle`. + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] + pub struct ApiAccessMethodToggle { + pub access_method: ApiAccessMethod, + pub enable: bool, + } +} diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs deleted file mode 100644 index 6aaec7171d..0000000000 --- a/mullvad-types/src/api_access_method.rs +++ /dev/null @@ -1,314 +0,0 @@ -use std::collections::hash_map::DefaultHasher; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; - -/// Daemon settings for API access methods. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Settings { - pub api_access_methods: Vec<AccessMethod>, -} - -impl Default for Settings { - fn default() -> Self { - Self { - api_access_methods: vec![ - BuiltInAccessMethod::Direct(true).into(), - BuiltInAccessMethod::Bridge(true).into(), - ], - } - } -} - -/// Access method datastructure. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum AccessMethod { - BuiltIn(BuiltInAccessMethod), - Custom(CustomAccessMethod), -} - -/// Built-In access method datastructure. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum BuiltInAccessMethod { - Direct(bool), - Bridge(bool), -} - -/// Custom access method datastructure. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct CustomAccessMethod { - pub id: String, - pub access_method: ObfuscationProtocol, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum ObfuscationProtocol { - Shadowsocks(Shadowsocks), - Socks5(Socks5), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum Socks5 { - Local(Socks5Local), - Remote(Socks5Remote), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Shadowsocks { - pub peer: SocketAddr, - pub password: String, // TODO: Mask the password (using special type)? - pub cipher: String, // Gets validated at a later stage. Is assumed to be valid. - pub enabled: bool, - pub name: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Socks5Local { - pub peer: SocketAddr, - /// Port on localhost where the SOCKS5-proxy listens to. - pub port: u16, - pub enabled: bool, - pub name: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Socks5Remote { - pub peer: SocketAddr, - pub enabled: bool, - pub name: String, -} - -impl Hash for Shadowsocks { - fn hash<H: Hasher>(&self, state: &mut H) { - self.peer.hash(state); - self.password.hash(state); - self.cipher.hash(state); - } -} - -impl Hash for Socks5Local { - fn hash<H: Hasher>(&self, state: &mut H) { - self.peer.hash(state); - self.port.hash(state); - } -} - -impl Hash for Socks5Remote { - fn hash<H: Hasher>(&self, state: &mut H) { - self.peer.hash(state); - } -} - -impl Settings { - // TODO: Do I have to clone? - pub fn get_access_methods(&self) -> Vec<AccessMethod> { - self.api_access_methods.clone() - } -} - -impl AccessMethod { - pub fn is_custom(&self) -> bool { - matches!(self, AccessMethod::Custom(..)) - } - - pub fn is_builtin(&self) -> bool { - matches!(self, AccessMethod::BuiltIn(..)) - } - - pub fn as_custom(&self) -> Option<&CustomAccessMethod> { - match self { - AccessMethod::BuiltIn(_) => None, - AccessMethod::Custom(access_method) => Some(access_method), - } - } - - pub fn enabled(&self) -> bool { - match self { - AccessMethod::BuiltIn(method) => match method { - BuiltInAccessMethod::Direct(enabled) => *enabled, - BuiltInAccessMethod::Bridge(enabled) => *enabled, - }, - AccessMethod::Custom(method) => match &method.access_method { - ObfuscationProtocol::Shadowsocks(ss) => ss.enabled, - ObfuscationProtocol::Socks5(socks) => match socks { - Socks5::Local(local) => local.enabled, - Socks5::Remote(remote) => remote.enabled, - }, - }, - } - } - - /// Set an access method to be either enabled or disabled. - /// - /// This action mutates [`self`]. - pub fn toggle(&mut self, enable: bool) -> () { - match self { - AccessMethod::BuiltIn(method) => match method { - BuiltInAccessMethod::Direct(enabled) => *enabled = enable, - BuiltInAccessMethod::Bridge(enabled) => *enabled = enable, - }, - AccessMethod::Custom(method) => match method.access_method { - ObfuscationProtocol::Shadowsocks(ref mut ss) => ss.enabled = enable, - ObfuscationProtocol::Socks5(ref mut socks) => match socks { - Socks5::Local(local) => local.enabled = enable, - Socks5::Remote(remote) => remote.enabled = enable, - }, - }, - } - } -} - -impl Shadowsocks { - pub fn new( - peer: SocketAddr, - cipher: String, - password: String, - enabled: bool, - name: String, - ) -> Self { - Shadowsocks { - peer, - password, - cipher, - enabled, - name, - } - } - - /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. - /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`]. - pub fn from_args( - ip: String, - port: u16, - cipher: String, - password: String, - enabled: bool, - name: String, - ) -> Option<Self> { - let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); - Some(Self::new(peer, cipher, password, enabled, name)) - } -} - -impl Socks5Local { - pub fn new(peer: SocketAddr, port: u16, enabled: bool, name: String) -> Self { - Self { - peer, - port, - enabled, - name, - } - } - - /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. - /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`]. - pub fn from_args( - ip: String, - port: u16, - localport: u16, - enabled: bool, - name: String, - ) -> Option<Self> { - let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); - let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer, localport, enabled, name)) - } -} - -impl Socks5Remote { - pub fn new(peer: SocketAddr, enabled: bool, name: String) -> Self { - Self { - peer, - enabled, - name, - } - } - - /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. - /// If `ip` or `port` are valid [`Some(Socks5Remote)`] is returned, otherwise [`None`]. - pub fn from_args(ip: String, port: u16, enabled: bool, name: String) -> Option<Self> { - let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); - let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer, enabled, name)) - } -} - -impl From<BuiltInAccessMethod> for AccessMethod { - fn from(value: BuiltInAccessMethod) -> Self { - AccessMethod::BuiltIn(value) - } -} - -impl From<CustomAccessMethod> for AccessMethod { - fn from(value: CustomAccessMethod) -> Self { - AccessMethod::Custom(value) - } -} - -impl From<ObfuscationProtocol> for AccessMethod { - fn from(value: ObfuscationProtocol) -> Self { - let mut hasher = DefaultHasher::new(); - value.hash(&mut hasher); - CustomAccessMethod { - id: hasher.finish().to_string(), - access_method: value, - } - .into() - } -} - -impl From<Shadowsocks> for AccessMethod { - fn from(value: Shadowsocks) -> Self { - ObfuscationProtocol::Shadowsocks(value).into() - } -} - -impl From<Socks5> for AccessMethod { - fn from(value: Socks5) -> Self { - ObfuscationProtocol::Socks5(value).into() - } -} - -impl From<Socks5Remote> for AccessMethod { - fn from(value: Socks5Remote) -> Self { - Socks5::Remote(value).into() - } -} - -impl From<Socks5Local> for AccessMethod { - fn from(value: Socks5Local) -> Self { - Socks5::Local(value).into() - } -} - -impl From<Socks5Remote> for Socks5 { - fn from(value: Socks5Remote) -> Self { - Socks5::Remote(value) - } -} - -impl From<Socks5Local> for Socks5 { - fn from(value: Socks5Local) -> Self { - Socks5::Local(value) - } -} - -/// These are just extensions to the core [`AccessMethod`] datastructure which the mullvad daemon needs. -pub mod daemon { - use super::*; - /// TODO: Document why this is needed. - /// Hint: Argument to protobuf rpc `ApiAccessMethodReplace`. - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct ApiAccessMethodReplace { - pub index: usize, - pub access_method: AccessMethod, - } - /// TODO: Document why this is needed. - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct ApiAccessMethodToggle { - pub access_method: AccessMethod, - pub enable: bool, - } -} diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index 8b5a501663..8aefaeb400 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -1,7 +1,7 @@ #![deny(rust_2018_idioms)] +pub mod access_method; pub mod account; -pub mod api_access_method; pub mod auth_failed; pub mod custom_list; pub mod device; diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index 311a154c28..6ade7dea32 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -1,5 +1,5 @@ use crate::{ - api_access_method, + access_method, custom_list::CustomListsSettings, relay_constraints::{ BridgeConstraints, BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint, @@ -79,7 +79,7 @@ pub struct Settings { pub custom_lists: CustomListsSettings, /// API access methods. #[cfg_attr(target_os = "android", jnix(skip))] - pub api_access_methods: api_access_method::Settings, + pub api_access_methods: access_method::Settings, /// If the daemon should allow communication with private (LAN) networks. pub allow_lan: bool, /// Extra level of kill switch. When this setting is on, the disconnected state will block @@ -140,7 +140,7 @@ impl Default for Settings { split_tunnel: SplitTunnelSettings::default(), settings_version: CURRENT_SETTINGS_VERSION, custom_lists: CustomListsSettings::default(), - api_access_methods: api_access_method::Settings::default(), + api_access_methods: access_method::Settings::default(), } } } diff --git a/talpid-types/src/net/openvpn.rs b/talpid-types/src/net/openvpn.rs index 7dc64ca80c..5968331b52 100644 --- a/talpid-types/src/net/openvpn.rs +++ b/talpid-types/src/net/openvpn.rs @@ -132,21 +132,6 @@ impl ShadowsocksProxySettings { } } -/// Options for a bundled SOCKS5 proxy. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] -pub struct SocksProxySettings { - pub peer: SocketAddr, -} - -impl SocksProxySettings { - pub fn get_endpoint(&self) -> Endpoint { - Endpoint { - address: self.peer, - protocol: TransportProtocol::Tcp, - } - } -} - /// List of ciphers usable by a Shadowsocks proxy. /// Cf. [`ShadowsocksProxySettings::cipher`]. pub const SHADOWSOCKS_CIPHERS: [&str; 19] = [ |
