From 2796dda1376e0d7b78fc274ada988a8a371101a2 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 8 Aug 2023 16:12:57 +0200 Subject: [PATCH 01/29] Add `Socks5` as a Proxy setting - Rename `InnerConnectionMode` variant `Proxied` to `Shadowsocks` - Move proxy/socket connection logic to `InnerConnectMode` impl block - Move `handle_x_connection` functions to `InnerConnectionMode` impl block - These functions does not need to be visible in the entire module, really. - Refactor some code into standalone functions - Mostly for visibilities' sake, but it also helps `rustc` with inferring the return type of each match arm inside of `stream_fut`. --- Cargo.lock | 13 ++ mullvad-api/Cargo.toml | 1 + mullvad-api/src/https_client_with_sni.rs | 189 ++++++++++++++++------- mullvad-api/src/proxy.rs | 11 +- talpid-types/src/net/openvpn.rs | 15 ++ 5 files changed, 170 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ba669fe5e8c..b3b830e0ce20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1805,6 +1805,7 @@ dependencies = [ "talpid-types", "tokio", "tokio-rustls", + "tokio-socks", ] [[package]] @@ -3798,6 +3799,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" diff --git a/mullvad-api/Cargo.toml b/mullvad-api/Cargo.toml index 32d725a2c6fa..83d8bf0723e9 100644 --- a/mullvad-api/Cargo.toml +++ b/mullvad-api/Cargo.toml @@ -24,6 +24,7 @@ serde = "1" serde_json = "1.0" tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread", "net", "io-std", "io-util", "fs"] } tokio-rustls = "0.24.1" +tokio-socks = "0.5.1" rustls-pemfile = "1.0.3" once_cell = "1.13" diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index e8f7fb889cab..940bb249d4af 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -73,8 +73,112 @@ enum HttpsConnectorRequest { enum InnerConnectionMode { /// Connect directly to the target. Direct, - /// Connect to the destination via a proxy. - Proxied(ParsedShadowsocksConfig), + /// Connect to the destination via a Shadowsocks proxy. + Shadowsocks(ShadowsocksConfig), + /// Connect to the destination via a Socks proxy. + Socks5(SocksConfig), +} + +impl InnerConnectionMode { + async fn connect( + &self, + hostname: &str, + addr: &SocketAddr, + ) -> Result { + use InnerConnectionMode::*; + match self { + Direct => Self::handle_direct_connection(addr, hostname).await, + Shadowsocks(config) => { + Self::handle_shadowsocks_connection(config.clone(), addr, hostname).await + } + Socks5(proxy_config) => { + Self::handle_socks_connection(proxy_config.clone(), addr, hostname).await + } + } + } + + /// Set up a TCP-socket connection. + async fn handle_direct_connection( + addr: &SocketAddr, + hostname: &str, + ) -> Result { + let socket = HttpsConnectorWithSni::open_socket( + *addr, + #[cfg(target_os = "android")] + socket_bypass_tx.clone(), + ) + .await?; + #[cfg(feature = "api-override")] + if API.disable_tls { + return Ok(ApiConnection::new(Box::new(socket))); + } + + let tls_stream = TlsStream::connect_https(socket, hostname).await?; + Ok(ApiConnection::new(Box::new(tls_stream))) + } + + /// Set up a shadowsocks-socket connection. + async fn handle_shadowsocks_connection( + shadowsocks: ShadowsocksConfig, + addr: &SocketAddr, + hostname: &str, + ) -> Result { + let socket = HttpsConnectorWithSni::open_socket( + shadowsocks.params.peer, + #[cfg(target_os = "android")] + socket_bypass_tx.clone(), + ) + .await?; + let proxy = ProxyClientStream::from_stream( + shadowsocks.proxy_context, + socket, + &ServerConfig::from(shadowsocks.params), + *addr, + ); + + #[cfg(feature = "api-override")] + if API.disable_tls { + return Ok(ApiConnection::new(Box::new(ConnectionDecorator(proxy)))); + } + + let tls_stream = TlsStream::connect_https(proxy, hostname).await?; + Ok(ApiConnection::new(Box::new(tls_stream))) + } + + /// Set up a SOCKS5-socket connection. + /// + /// TODO: Handle case where the proxy-address is `localhost`. + async fn handle_socks_connection( + proxy_config: SocksConfig, + addr: &SocketAddr, + hostname: &str, + ) -> Result { + let socket = HttpsConnectorWithSni::open_socket( + proxy_config.peer, + #[cfg(target_os = "android")] + socket_bypass_tx.clone(), + ) + .await?; + let proxy = tokio_socks::tcp::Socks5Stream::connect_with_socket(socket, addr) + .await + .map_err(|error| { + io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) + })?; + + #[cfg(feature = "api-override")] + if API.disable_tls { + return Ok(ApiConnection::new(Box::new(ConnectionDecorator(proxy)))); + } + + let tls_stream = TlsStream::connect_https(proxy, hostname).await?; + Ok(ApiConnection::new(Box::new(tls_stream))) + } +} + +#[derive(Clone)] +struct ShadowsocksConfig { + proxy_context: SharedContext, + params: ParsedShadowsocksConfig, } #[derive(Clone)] @@ -90,6 +194,11 @@ impl From for ServerConfig { } } +#[derive(Clone)] +struct SocksConfig { + peer: SocketAddr, +} + #[derive(err_derive::Error, Debug)] enum ProxyConfigError { #[error(display = "Unrecognized cipher selected: {}", _0)] @@ -102,14 +211,22 @@ impl TryFrom for InnerConnectionMode { fn try_from(config: ApiConnectionMode) -> Result { Ok(match config { ApiConnectionMode::Direct => InnerConnectionMode::Direct, - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(config)) => { - InnerConnectionMode::Proxied(ParsedShadowsocksConfig { - peer: config.peer, - password: config.password, - cipher: CipherKind::from_str(&config.cipher) - .map_err(|_| ProxyConfigError::InvalidCipher(config.cipher))?, - }) - } + ApiConnectionMode::Proxied(proxy_settings) => match proxy_settings { + ProxyConfig::Shadowsocks(config) => { + InnerConnectionMode::Shadowsocks(ShadowsocksConfig { + params: ParsedShadowsocksConfig { + peer: config.peer, + password: config.password, + cipher: CipherKind::from_str(&config.cipher) + .map_err(|_| ProxyConfigError::InvalidCipher(config.cipher))?, + }, + proxy_context: SsContext::new_shared(ServerType::Local), + }) + } + ProxyConfig::Socks(config) => { + InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + } + }, }) } } @@ -121,7 +238,6 @@ pub struct HttpsConnectorWithSni { sni_hostname: Option, address_cache: AddressCache, abort_notify: Arc, - proxy_context: SharedContext, #[cfg(target_os = "android")] socket_bypass_tx: Option>, } @@ -186,7 +302,6 @@ impl HttpsConnectorWithSni { sni_hostname, address_cache, abort_notify, - proxy_context: SsContext::new_shared(ServerType::Local), #[cfg(target_os = "android")] socket_bypass_tx, }, @@ -194,6 +309,9 @@ impl HttpsConnectorWithSni { ) } + /// Establishes a TCP connection with a peer at the specified socket address. + /// + /// Will timeout after [`CONNECT_TIMEOUT`] seconds. async fn open_socket( addr: SocketAddr, #[cfg(target_os = "android")] socket_bypass_tx: Option>, @@ -281,7 +399,6 @@ impl Service for HttpsConnectorWithSni { }); let inner = self.inner.clone(); let abort_notify = self.abort_notify.clone(); - let proxy_context = self.proxy_context.clone(); #[cfg(target_os = "android")] let socket_bypass_tx = self.socket_bypass_tx.clone(); let address_cache = self.address_cache.clone(); @@ -301,50 +418,8 @@ impl Service for HttpsConnectorWithSni { // is selected while connecting. let stream = loop { let notify = abort_notify.notified(); - let config = { inner.lock().unwrap().proxy_config.clone() }; - let stream_fut = async { - match config { - InnerConnectionMode::Direct => { - let socket = Self::open_socket( - addr, - #[cfg(target_os = "android")] - socket_bypass_tx.clone(), - ) - .await?; - #[cfg(feature = "api-override")] - if API.disable_tls { - return Ok::<_, io::Error>(ApiConnection::new(Box::new(socket))); - } - - let tls_stream = TlsStream::connect_https(socket, &hostname).await?; - Ok::<_, io::Error>(ApiConnection::new(Box::new(tls_stream))) - } - InnerConnectionMode::Proxied(proxy_config) => { - let socket = Self::open_socket( - proxy_config.peer, - #[cfg(target_os = "android")] - socket_bypass_tx.clone(), - ) - .await?; - let proxy = ProxyClientStream::from_stream( - proxy_context.clone(), - socket, - &ServerConfig::from(proxy_config), - addr, - ); - - #[cfg(feature = "api-override")] - if API.disable_tls { - return Ok(ApiConnection::new(Box::new(ConnectionDecorator( - proxy, - )))); - } - - let tls_stream = TlsStream::connect_https(proxy, &hostname).await?; - Ok(ApiConnection::new(Box::new(tls_stream))) - } - } - }; + let proxy_config = { inner.lock().unwrap().proxy_config.clone() }; + let stream_fut = proxy_config.connect(&hostname, &addr); pin_mut!(stream_fut); pin_mut!(notify); diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 1e6ab41f8097..c5c2426933f7 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -8,7 +8,9 @@ use std::{ pin::Pin, task::{self, Poll}, }; -use talpid_types::{net::openvpn::ShadowsocksProxySettings, ErrorExt}; +use talpid_types::{ + net::openvpn::ShadowsocksProxySettings, net::openvpn::SocksProxySettings, ErrorExt, +}; use tokio::{ fs, io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf}, @@ -36,6 +38,7 @@ impl fmt::Display for ApiConnectionMode { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum ProxyConfig { Shadowsocks(ShadowsocksProxySettings), + Socks(SocksProxySettings), } impl fmt::Display for ProxyConfig { @@ -43,6 +46,7 @@ impl fmt::Display for ProxyConfig { match self { // TODO: Do not hardcode TCP ProxyConfig::Shadowsocks(ss) => write!(f, "Shadowsocks {}/TCP", ss.peer), + ProxyConfig::Socks(s) => write!(f, "Socks5 {}/TCP", s.peer), } } } @@ -110,8 +114,11 @@ impl ApiConnectionMode { /// Returns the remote address, or `None` for `ApiConnectionMode::Direct`. pub fn get_endpoint(&self) -> Option { match self { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(ss)) => Some(ss.peer), ApiConnectionMode::Direct => None, + ApiConnectionMode::Proxied(proxy_config) => match proxy_config { + ProxyConfig::Shadowsocks(ss) => Some(ss.peer), + ProxyConfig::Socks(s) => Some(s.peer), + }, } } diff --git a/talpid-types/src/net/openvpn.rs b/talpid-types/src/net/openvpn.rs index 5968331b526c..7dc64ca80c47 100644 --- a/talpid-types/src/net/openvpn.rs +++ b/talpid-types/src/net/openvpn.rs @@ -132,6 +132,21 @@ 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] = [ From 44656b65922ee0b8c76db50a22f93e10158e8b59 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 28 Aug 2023 10:10:46 +0200 Subject: [PATCH 02/29] Add `mullvad proxy` command The `proxy` subcommand will allow for adding/deleting/editing/showing different API access methods using the mullvad CLI. --- Cargo.lock | 1 + mullvad-cli/Cargo.toml | 1 + mullvad-cli/src/cmds/mod.rs | 1 + mullvad-cli/src/cmds/proxy.rs | 95 +++++++++++++++++++++++++++++++++++ mullvad-cli/src/main.rs | 7 +++ 5 files changed, 105 insertions(+) create mode 100644 mullvad-cli/src/cmds/proxy.rs diff --git a/Cargo.lock b/Cargo.lock index b3b830e0ce20..be316f8e51b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1819,6 +1819,7 @@ dependencies = [ "env_logger 0.10.0", "futures", "itertools", + "mullvad-api", "mullvad-management-interface", "mullvad-types", "mullvad-version", diff --git a/mullvad-cli/Cargo.toml b/mullvad-cli/Cargo.toml index 5437b1980c55..fdfa16262d46 100644 --- a/mullvad-cli/Cargo.toml +++ b/mullvad-cli/Cargo.toml @@ -23,6 +23,7 @@ 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/mod.rs b/mullvad-cli/src/cmds/mod.rs index c63a98113310..cf715c9e9f1a 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -10,6 +10,7 @@ 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/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs new file mode 100644 index 000000000000..c12aee368368 --- /dev/null +++ b/mullvad-cli/src/cmds/proxy.rs @@ -0,0 +1,95 @@ +use anyhow::Result; +use mullvad_management_interface::MullvadProxyClient; +use std::net::IpAddr; + +use clap::Subcommand; +use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; + +#[derive(Subcommand, Debug)] +pub enum Proxy { + /// Get current api settings + #[clap(subcommand)] + Api(ApiCommands), +} + +impl Proxy { + pub async fn handle(self) -> Result<()> { + match self { + Proxy::Api(cmd) => match cmd { + ApiCommands::List => { + println!("Listing the API access methods: .."); + Self::list().await?; + } + ApiCommands::Add(cmd) => match cmd { + _ => println!("[NOT IMPEMENTLED YET] Adding custom proxy: {:?}", cmd), + }, + }, + }; + Ok(()) + } + + /// Show all API access methods. + async fn list() -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + println!("Calling [rpc::get_api_access_methods] .."); + for api_access_method in rpc.get_api_access_methods().await? { + println!("{:?}", api_access_method); + } + Ok(()) + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum ApiCommands { + /// List the configured API proxies + List, + + /// Add a custom API proxy + #[clap(subcommand)] + Add(AddCustomCommands), +} + +#[derive(Subcommand, Debug, Clone)] +pub enum AddCustomCommands { + /// Configure a local SOCKS5 proxy + Local { + /// The port that the server on localhost is listening on + local_port: u16, + /// The IP of the remote peer + remote_ip: IpAddr, + /// The port of the remote peer + remote_port: u16, + }, + + /// Configure a remote SOCKS5 proxy + Remote { + /// The IP of the remote proxy server + remote_ip: IpAddr, + /// The port of the remote proxy server + remote_port: u16, + + /// Username for authentication + #[arg(requires = "password")] + username: Option, + /// Password for authentication + #[arg(requires = "username")] + password: Option, + }, + + /// Configure bundled Shadowsocks proxy + Shadowsocks { + /// The IP of the remote Shadowsocks server + remote_ip: IpAddr, + /// The port of the remote Shadowsocks server + #[arg(default_value = "443")] + remote_port: u16, + + /// Password for authentication + #[arg(default_value = "mullvad")] + password: String, + + /// Cipher to use + #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] + cipher: String, + }, +} diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 41f164397093..057eb20dea96 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -71,6 +71,12 @@ enum Cli { #[clap(subcommand)] Relay(relay::Relay), + /// Manage use of proxies (SOCKS proxies and Shadowsocks) for reaching the API. + /// Can make the daemon connect to the the Mullvad API via one of the + /// Mullvad bridge servers or a custom proxy. + #[clap(subcommand)] + Proxy(proxy::Proxy), + /// Manage use of obfuscation protocols for WireGuard. /// Can make WireGuard traffic look like something else on the network. /// Helps circumvent censorship and to establish a tunnel when on restricted networks @@ -134,6 +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::Version => version::print().await, Cli::FactoryReset => reset::handle().await, Cli::Relay(cmd) => cmd.handle().await, From 585a820373abea33e3338e97fb727028667968bb Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Thu, 24 Aug 2023 16:18:06 +0200 Subject: [PATCH 03/29] Add `mullvad proxy add` command Add daemon logic for storing custom access methods & allow a user to add a custom socks5 or shadowsocks proxy. Add all the necessary information for establishing Socks5 connections (both using a local Socks-proxy as well as the normal, remote-proxy, use case) and Shadowsocks connections. Add `api_access_settings` to `mullvad-daemon` Naturally, the Protobuf types has to be mirrored on the Rust/daemon side and lots of boilerplate code had to be written to convert between the two. --- mullvad-cli/src/cmds/proxy.rs | 126 ++++++++++--- mullvad-daemon/src/access_methods.rs | 37 ++++ mullvad-daemon/src/lib.rs | 24 +++ .../proto/management_interface.proto | 39 ++++ mullvad-management-interface/src/client.rs | 22 +++ .../types/conversions/api_access_method.rs | 171 ++++++++++++++++++ .../src/types/conversions/mod.rs | 1 + .../src/types/conversions/settings.rs | 12 ++ mullvad-types/src/api_access_method.rs | 94 ++++++++++ mullvad-types/src/lib.rs | 1 + mullvad-types/src/settings/mod.rs | 13 ++ 11 files changed, 519 insertions(+), 21 deletions(-) create mode 100644 mullvad-daemon/src/access_methods.rs create mode 100644 mullvad-management-interface/src/types/conversions/api_access_method.rs create mode 100644 mullvad-types/src/api_access_method.rs diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index c12aee368368..fec4a6f9665d 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -1,5 +1,6 @@ use anyhow::Result; use mullvad_management_interface::MullvadProxyClient; +use mullvad_types::api_access_method::AccessMethod; use std::net::IpAddr; use clap::Subcommand; @@ -17,12 +18,13 @@ impl Proxy { match self { Proxy::Api(cmd) => match cmd { ApiCommands::List => { - println!("Listing the API access methods: .."); + //println!("Listing the API access methods: .."); Self::list().await?; } - ApiCommands::Add(cmd) => match cmd { - _ => println!("[NOT IMPEMENTLED YET] Adding custom proxy: {:?}", cmd), - }, + ApiCommands::Add(cmd) => { + //println!("Adding custom proxy"); + Self::add(cmd).await?; + } }, }; Ok(()) @@ -37,13 +39,20 @@ impl Proxy { } Ok(()) } + + /// 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())?; + rpc.add_access_method(proxy).await?; + Ok(()) + } } #[derive(Subcommand, Debug, Clone)] pub enum ApiCommands { /// List the configured API proxies List, - /// Add a custom API proxy #[clap(subcommand)] Add(AddCustomCommands), @@ -51,6 +60,28 @@ pub enum ApiCommands { #[derive(Subcommand, Debug, Clone)] pub enum AddCustomCommands { + /// Configure SOCKS5 proxy + #[clap(subcommand)] + Socks5(Socks5AddCommands), + + /// Configure bundled Shadowsocks proxy + Shadowsocks { + /// The IP of the remote Shadowsocks server + remote_ip: IpAddr, + /// The port of the remote Shadowsocks server + #[arg(default_value = "443")] + remote_port: u16, + /// Password for authentication + #[arg(default_value = "mullvad")] + password: String, + /// Cipher to use + #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] + cipher: String, + }, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum Socks5AddCommands { /// Configure a local SOCKS5 proxy Local { /// The port that the server on localhost is listening on @@ -60,14 +91,12 @@ pub enum AddCustomCommands { /// The port of the remote peer remote_port: u16, }, - /// Configure a remote SOCKS5 proxy Remote { /// The IP of the remote proxy server remote_ip: IpAddr, /// The port of the remote proxy server remote_port: u16, - /// Username for authentication #[arg(requires = "password")] username: Option, @@ -75,21 +104,76 @@ pub enum AddCustomCommands { #[arg(requires = "username")] password: Option, }, +} - /// Configure bundled Shadowsocks proxy - Shadowsocks { - /// The IP of the remote Shadowsocks server - remote_ip: IpAddr, - /// The port of the remote Shadowsocks server - #[arg(default_value = "443")] - remote_port: u16, +/// Implement conversions from CLI types to Daemon types. +/// +/// Since these are not supposed to be used outside of the CLI, +/// we define them in a hidden-away module. +mod conversions { + use anyhow::{anyhow, Error}; + use mullvad_types::api_access_method as daemon_types; - /// Password for authentication - #[arg(default_value = "mullvad")] - password: String, + use super::{AddCustomCommands, Socks5AddCommands}; - /// Cipher to use - #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] - cipher: String, - }, + impl TryFrom for daemon_types::AccessMethod { + type Error = Error; + + fn try_from(value: AddCustomCommands) -> Result { + Ok(match value { + AddCustomCommands::Socks5(variant) => match variant { + Socks5AddCommands::Local { + local_port, + remote_ip, + remote_port, + } => { + println!("Adding LOCAL SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Local( + daemon_types::Socks5Local::from_args( + remote_ip.to_string(), + remote_port, + local_port, + ) + .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, + ); + daemon_types::AccessMethod::Socks5(socks_proxy) + } + Socks5AddCommands::Remote { + remote_ip, + remote_port, + username, + password, + } => { + println!("Adding REMOTE SOCKS5-proxy: {username:?}+{password:?} @ {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Remote( + daemon_types::Socks5Remote::from_args( + remote_ip.to_string(), + remote_port, + ) + .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, + ); + daemon_types::AccessMethod::Socks5(socks_proxy) + } + }, + AddCustomCommands::Shadowsocks { + remote_ip, + remote_port, + password, + cipher, + } => { + println!( + "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" + ); + let shadowsocks_proxy = daemon_types::Shadowsocks::from_args( + remote_ip.to_string(), + remote_port, + cipher, + password, + ) + .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; + daemon_types::AccessMethod::Shadowsocks(shadowsocks_proxy) + } + }) + } + } } diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs new file mode 100644 index 000000000000..634914d00a98 --- /dev/null +++ b/mullvad-daemon/src/access_methods.rs @@ -0,0 +1,37 @@ +use crate::{new_selector_config, settings, Daemon, EventListener}; +use mullvad_types::api_access_method::AccessMethod; + +#[derive(err_derive::Error, Debug)] +pub enum Error { + /// Can not add access method + #[error(display = "Cannot add custom access method")] + Add, + /// Access methods settings error + #[error(display = "Settings error")] + Settings(#[error(source)] settings::Error), +} + +impl Daemon +where + L: EventListener + Clone + Send + 'static, +{ + pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<(), Error> { + self.settings + .update(|settings| { + settings + .api_access_methods + .api_access_methods + .push(access_method); + }) + .await + .map(|changed| { + if changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + }; + }) + .map_err(Error::Settings) + } +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 81ebe1050c54..676e122a645d 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1,6 +1,7 @@ #![deny(rust_2018_idioms)] #![recursion_limit = "512"] +mod access_methods; pub mod account_history; mod api; #[cfg(not(target_os = "android"))] @@ -39,6 +40,7 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, + api_access_method::AccessMethod, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -170,6 +172,9 @@ pub enum Error { #[error(display = "A list with that name does not exist")] CustomListNotFound, + #[error(display = "Access method error")] + AccessMethodError(#[error(source)] access_methods::Error), + #[cfg(target_os = "macos")] #[error(display = "Failed to set exclusion group")] GroupIdError(#[error(source)] io::Error), @@ -255,6 +260,10 @@ pub enum DaemonCommand { DeleteCustomList(ResponseTx<(), Error>, mullvad_types::custom_list::Id), /// Update a custom list with a given id UpdateCustomList(ResponseTx<(), Error>, CustomList), + /// Get API access methods + GetApiAccessMethods(ResponseTx, Error>), + /// Add API access methods + AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1030,6 +1039,8 @@ where DeleteCustomList(tx, id) => self.on_delete_custom_list(tx, id).await, UpdateCustomList(tx, update) => self.on_update_custom_list(tx, update).await, GetVersionInfo(tx) => self.on_get_version_info(tx), + GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx), + AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await, IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), #[cfg(not(target_os = "android"))] @@ -2204,6 +2215,19 @@ where Self::oneshot_send(tx, result, "update_custom_list response"); } + fn on_get_api_access_methods(&mut self, tx: ResponseTx, Error>) { + let result = Ok(self.settings.api_access_methods.api_access_methods.clone()); + Self::oneshot_send(tx, result, "get_api_access_methods response"); + } + + async fn on_add_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: AccessMethod) { + let result = self + .add_access_method(method) + .await + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "add_api_access_method response"); + } + fn on_get_settings(&self, tx: oneshot::Sender) { Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index f4b3eb961d11..03c9a2c02c99 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -73,6 +73,12 @@ service ManagementService { rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {} rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {} + // API Access methods + rpc GetApiAccessMethods(google.protobuf.Empty) returns (ApiAccessMethods) {} + rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { + // Can I return something useful here instead of Empty? + } + // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {} @@ -324,6 +330,38 @@ message CustomList { message CustomListSettings { repeated CustomList custom_lists = 1; } +message ApiAccessMethod { + message Socks5Local { + string ip = 1; + uint32 port = 2; + uint32 local_port = 3; + } + message Socks5Remote { + string ip = 1; + uint32 port = 2; + } + message Socks5 { + oneof Socks5type { + Socks5Local local = 1; + Socks5Remote remote = 2; + } + } + message Shadowsocks { + string ip = 1; + uint32 port = 2; + string password = 3; + string cipher = 4; + } + oneof access_method { + Socks5 socks5 = 1; + Shadowsocks shadowsocks = 2; + } +} + +message ApiAccessMethods { repeated ApiAccessMethod api_access_methods = 1; } + +message ApiAccessMethodSettings { repeated ApiAccessMethod api_access_methods = 1; } + message Settings { RelaySettings relay_settings = 1; BridgeSettings bridge_settings = 2; @@ -336,6 +374,7 @@ message Settings { SplitTunnelSettings split_tunnel = 9; ObfuscationSettings obfuscation_settings = 10; CustomListSettings custom_lists = 11; + ApiAccessMethodSettings api_access_methods = 12; } message SplitTunnelSettings { diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index a1ddc5e39afa..e7c14800a51b 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -4,6 +4,7 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, + api_access_method::AccessMethod, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -163,6 +164,19 @@ impl MullvadProxyClient { mullvad_types::relay_list::RelayList::try_from(list).map_err(Error::InvalidResponse) } + pub async fn get_api_access_methods(&mut self) -> Result> { + Ok(self + .0 + .get_api_access_methods(()) + .await + .map_err(Error::Rpc)? + .into_inner() + .api_access_methods + .iter() + .map(From::from) + .collect()) + } + pub async fn update_relay_locations(&mut self) -> Result<()> { self.0 .update_relay_locations(()) @@ -457,6 +471,14 @@ impl MullvadProxyClient { Ok(()) } + pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + self.0 + .add_api_access_method(types::ApiAccessMethod::from(access_method)) + .await + .map_err(Error::Rpc) + .map(drop) + } + #[cfg(target_os = "linux")] pub async fn get_split_tunnel_processes(&mut self) -> Result> { use futures::TryStreamExt; diff --git a/mullvad-management-interface/src/types/conversions/api_access_method.rs b/mullvad-management-interface/src/types/conversions/api_access_method.rs new file mode 100644 index 000000000000..a91923e9e94a --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -0,0 +1,171 @@ +/// Implements conversions for the auxilliary proto AccessMethod type to the internal AccessMethod data type. +mod settings { + use crate::types::proto; + use mullvad_types::api_access_method; + + impl From<&api_access_method::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: &api_access_method::Settings) -> Self { + Self { + api_access_methods: settings + .api_access_methods + .iter() + .map(|method| method.clone().into()) + .collect(), + } + } + } + + impl From for proto::ApiAccessMethodSettings { + fn from(settings: api_access_method::Settings) -> Self { + proto::ApiAccessMethodSettings::from(&settings) + } + } + + impl From for api_access_method::Settings { + fn from(settings: proto::ApiAccessMethodSettings) -> Self { + Self { + api_access_methods: settings + .api_access_methods + .iter() + .map(api_access_method::AccessMethod::from) + .collect(), + } + } + } +} + +/// Implements conversions for the 'main' AccessMethod data type. +mod data { + use crate::types::proto::{self, api_access_method::socks5::Socks5type}; + use mullvad_types::api_access_method::{ + AccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote, + }; + + impl From for Vec { + fn from(api_access_methods: proto::ApiAccessMethods) -> Self { + api_access_methods + .api_access_methods + .iter() + .map(AccessMethod::from) + .collect() + } + } + + impl From for AccessMethod { + fn from(value: proto::ApiAccessMethod) -> Self { + // TODO: How to not unwrap? + match value.access_method.unwrap() { + proto::api_access_method::AccessMethod::Socks5(socks) => { + match socks.socks5type.unwrap() { + Socks5type::Local(local) => { + let local_proxy = Socks5Local::from_args( + local.ip, + local.port as u16, + local.local_port as u16, + ) + .unwrap(); // This is dangerous territory .. + AccessMethod::Socks5(Socks5::Local(local_proxy)) + } + + Socks5type::Remote(remote) => { + let remote_proxy = + Socks5Remote::from_args(remote.ip, remote.port as u16).unwrap(); // This is dangerous territory .. + AccessMethod::Socks5(Socks5::Remote(remote_proxy)) + } + } + } + proto::api_access_method::AccessMethod::Shadowsocks(ss) => { + let shadow_sock = + Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password) + .unwrap(); + AccessMethod::Shadowsocks(shadow_sock) + } + } + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: AccessMethod) -> Self { + match value { + AccessMethod::Shadowsocks(ss) => proto::api_access_method::Shadowsocks { + ip: ss.peer.ip().to_string(), + port: ss.peer.port() as u32, + password: ss.password, + cipher: ss.cipher, + } + .into(), + + AccessMethod::Socks5(Socks5::Local(Socks5Local { peer, port })) => { + proto::api_access_method::Socks5Local { + ip: peer.ip().to_string(), + port: peer.port() as u32, + local_port: port as u32, + } + .into() + } + AccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + proto::api_access_method::Socks5Remote { + ip: peer.ip().to_string(), + port: peer.port() as u32, + } + .into() + } + } + } + } + + impl From<&proto::ApiAccessMethod> for AccessMethod { + fn from(value: &proto::ApiAccessMethod) -> Self { + AccessMethod::from(value.clone()) + } + } + + impl From> for proto::ApiAccessMethods { + fn from(value: Vec) -> proto::ApiAccessMethods { + proto::ApiAccessMethods { + api_access_methods: value.iter().map(|method| method.clone().into()).collect(), + } + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Shadowsocks) -> Self { + proto::api_access_method::AccessMethod::Shadowsocks(value).into() + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Socks5) -> Self { + proto::api_access_method::AccessMethod::Socks5(value).into() + } + } + + impl From 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 for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Socks5Local) -> Self { + proto::api_access_method::socks5::Socks5type::Local(value).into() + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Socks5Remote) -> Self { + proto::api_access_method::socks5::Socks5type::Remote(value).into() + } + } + + impl From 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/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs index d2e8b60265a3..ddfe3f4282ba 100644 --- a/mullvad-management-interface/src/types/conversions/mod.rs +++ b/mullvad-management-interface/src/types/conversions/mod.rs @@ -1,6 +1,7 @@ use std::str::FromStr; mod account; +mod api_access_method; mod custom_list; mod custom_tunnel; mod device; diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index f123b417553b..d454af4cb02e 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -42,6 +42,9 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings { custom_lists: Some(proto::CustomListSettings::from( settings.custom_lists.clone(), )), + api_access_methods: Some(proto::ApiAccessMethodSettings::from( + &settings.api_access_methods, + )), } } } @@ -140,6 +143,12 @@ impl TryFrom for mullvad_types::settings::Settings { .ok_or(FromProtobufTypeError::InvalidArgument( "missing custom lists settings", ))?; + let api_access_methods_settings = + settings + .api_access_methods + .ok_or(FromProtobufTypeError::InvalidArgument( + "missing api access methods settings", + ))?; #[cfg(windows)] let split_tunnel = settings .split_tunnel @@ -171,6 +180,9 @@ impl TryFrom 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::from( + api_access_methods_settings, + ), }) } } diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs new file mode 100644 index 000000000000..5a4d32551fb1 --- /dev/null +++ b/mullvad-types/src/api_access_method.rs @@ -0,0 +1,94 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; + +/// Daemon settings for API access methods. +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Settings { + pub api_access_methods: Vec, +} + +/// API access method datastructure. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum AccessMethod { + Shadowsocks(Shadowsocks), + Socks5(Socks5), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +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. +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Socks5 { + Local(Socks5Local), + Remote(Socks5Remote), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Socks5Local { + pub peer: SocketAddr, + /// Port on localhost where the SOCKS5-proxy listens to. + pub port: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Socks5Remote { + pub peer: SocketAddr, +} + +impl Settings { + // TODO: Do I have to clone? + pub fn get_access_methods(&self) -> Vec { + self.api_access_methods.clone() + } +} + +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 { + let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); + Some(Self::new(peer, password, cipher)) + } +} + +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 { + 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 { + let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); + let peer = SocketAddr::new(peer_ip, port); + Some(Self::new(peer)) + } +} diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index bfac631f82ab..8b5a5016636e 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -1,6 +1,7 @@ #![deny(rust_2018_idioms)] 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 3b3ca15014ef..fc66649b8ac3 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -1,4 +1,5 @@ use crate::{ + api_access_method, custom_list::CustomListsSettings, relay_constraints::{ BridgeConstraints, BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint, @@ -76,6 +77,9 @@ pub struct Settings { /// All of the custom relay lists #[cfg_attr(target_os = "android", jnix(skip))] pub custom_lists: CustomListsSettings, + /// API access methods. + #[cfg_attr(target_os = "android", jnix(skip))] + pub api_access_methods: api_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 @@ -136,6 +140,15 @@ 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(), + // TODO: Remove this v in favor of this ^ when w can add or own access methods. + api_access_methods: { + use api_access_method::{AccessMethod, Shadowsocks}; + let mut xs = api_access_method::Settings::default(); + let mut ys = vec![]; + xs.api_access_methods.append(&mut ys); + xs + }, } } } From 411f80d4cd227686a57e9dcc39dd30a01f728575 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 11 Sep 2023 11:55:02 +0200 Subject: [PATCH 04/29] Add `mullvad proxy api remove` command Allow the user to manually remove a custom api proxy. --- mullvad-cli/src/cmds/proxy.rs | 42 ++++++++++++++++--- mullvad-daemon/src/access_methods.rs | 20 +++++++++ mullvad-daemon/src/lib.rs | 15 +++++++ .../proto/management_interface.proto | 3 ++ mullvad-management-interface/src/client.rs | 8 ++++ 5 files changed, 83 insertions(+), 5 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index fec4a6f9665d..583accef1e52 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -1,9 +1,9 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::api_access_method::AccessMethod; use std::net::IpAddr; -use clap::Subcommand; +use clap::{Args, Subcommand}; use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; #[derive(Subcommand, Debug)] @@ -25,6 +25,14 @@ impl Proxy { //println!("Adding custom proxy"); Self::add(cmd).await?; } + ApiCommands::Edit(_) => todo!(), + ApiCommands::Remove(cmd) => { + // Transform human-readable index to 0-based indexing. + match cmd.index.checked_sub(1) { + Some(index) => Self::remove(RemoveCustomCommands { index, ..cmd }).await?, + None => println!("Access method 0 does not exist"), + } + } }, }; Ok(()) @@ -33,9 +41,8 @@ impl Proxy { /// Show all API access methods. async fn list() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - println!("Calling [rpc::get_api_access_methods] .."); - for api_access_method in rpc.get_api_access_methods().await? { - println!("{:?}", api_access_method); + for (index, api_access_method) in rpc.get_api_access_methods().await?.iter().enumerate() { + println!("{}. {:?}", index + 1, api_access_method); } Ok(()) } @@ -47,6 +54,23 @@ impl Proxy { rpc.add_access_method(proxy).await?; Ok(()) } + + /// Remove an API access method. + async fn remove(cmd: RemoveCustomCommands) -> 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(); + rpc.remove_access_method(access_method) + .await + .map_err(Into::::into) + } } #[derive(Subcommand, Debug, Clone)] @@ -56,6 +80,8 @@ pub enum ApiCommands { /// Add a custom API proxy #[clap(subcommand)] Add(AddCustomCommands), + /// Remove an API proxy + Remove(RemoveCustomCommands), } #[derive(Subcommand, Debug, Clone)] @@ -80,6 +106,12 @@ pub enum AddCustomCommands { }, } +#[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 diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index 634914d00a98..12d8f10f261a 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -34,4 +34,24 @@ where }) .map_err(Error::Settings) } + + pub async fn remove_access_method(&mut self, access_method: AccessMethod) -> Result<(), Error> { + self.settings + .update(|settings| { + settings + .api_access_methods + .api_access_methods + .retain(|x| *x != access_method); + }) + .await + .map(|changed| { + if changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + }; + }) + .map_err(Error::Settings) + } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 676e122a645d..6ebb7f828e20 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -264,6 +264,8 @@ pub enum DaemonCommand { GetApiAccessMethods(ResponseTx, Error>), /// Add API access methods AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + /// Remove an API access method + RemoveApiAccessMethod(ResponseTx<(), Error>, AccessMethod), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1041,6 +1043,7 @@ where GetVersionInfo(tx) => self.on_get_version_info(tx), GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx), AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await, + RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), #[cfg(not(target_os = "android"))] @@ -2228,6 +2231,18 @@ where Self::oneshot_send(tx, result, "add_api_access_method response"); } + async fn on_remove_api_access_method( + &mut self, + tx: ResponseTx<(), Error>, + method: AccessMethod, + ) { + let result = self + .remove_access_method(method) + .await + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "remove_api_access_method response"); + } + fn on_get_settings(&self, tx: oneshot::Sender) { Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 03c9a2c02c99..9491541dc436 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -78,6 +78,9 @@ service ManagementService { rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } + rpc RemoveApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { + // Can I return something useful here instead of Empty? + } // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index e7c14800a51b..d4edabc3911e 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -479,6 +479,14 @@ impl MullvadProxyClient { .map(drop) } + pub async fn remove_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + self.0 + .remove_api_access_method(types::ApiAccessMethod::from(access_method)) + .await + .map_err(Error::Rpc) + .map(drop) + } + #[cfg(target_os = "linux")] pub async fn get_split_tunnel_processes(&mut self) -> Result> { use futures::TryStreamExt; From c899c1b63858c12c9367318c19120fe899b31394 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 11 Sep 2023 15:20:20 +0200 Subject: [PATCH 05/29] Add `mullvad proxy api edit` command Allow a user to edit an existing custom api proxy method --- mullvad-cli/src/cmds/proxy.rs | 106 +++++++++++++++++- mullvad-daemon/src/access_methods.rs | 24 +++- mullvad-daemon/src/lib.rs | 19 +++- .../proto/management_interface.proto | 3 + mullvad-management-interface/src/client.rs | 11 ++ .../types/conversions/api_access_method.rs | 19 ++++ mullvad-types/src/api_access_method.rs | 40 ++++++- 7 files changed, 213 insertions(+), 9 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 583accef1e52..5a4bf2fdd84b 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::api_access_method::AccessMethod; +use mullvad_types::api_access_method::{AccessMethod, ApiAccessMethodReplace}; use std::net::IpAddr; use clap::{Args, Subcommand}; @@ -25,13 +25,15 @@ impl Proxy { //println!("Adding custom proxy"); Self::add(cmd).await?; } - ApiCommands::Edit(_) => todo!(), + ApiCommands::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? + } ApiCommands::Remove(cmd) => { // Transform human-readable index to 0-based indexing. - match cmd.index.checked_sub(1) { - Some(index) => Self::remove(RemoveCustomCommands { index, ..cmd }).await?, - None => println!("Access method 0 does not exist"), - } + let index = Self::zero_to_one_based_index(cmd.index)?; + Self::remove(RemoveCustomCommands { index }).await? } }, }; @@ -71,6 +73,65 @@ impl Proxy { .await .map_err(Into::::into) } + + 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(); + + // Create a new access method combining the new params with the previous values + let edited_access_method: AccessMethod = match access_method { + AccessMethod::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); + mullvad_types::api_access_method::Shadowsocks::from_args(ip, port, cipher, password) + .map(|x| x.into()) + } + AccessMethod::Socks5(socks) => match socks { + mullvad_types::api_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); + mullvad_types::api_access_method::Socks5Local::from_args(ip, port, local_port) + .map(|x| x.into()) + } + mullvad_types::api_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()); + mullvad_types::api_access_method::Socks5Remote::from_args(ip, port) + .map(|x| x.into()) + } + }, + } + .ok_or(anyhow!( + "Could not edit access method {}, reverting changes.", + cmd.index + ))?; + + rpc.replace_access_method(ApiAccessMethodReplace { + index: cmd.index, + access_method: edited_access_method, + }) + .await?; + + Ok(()) + } + + fn zero_to_one_based_index(index: usize) -> Result { + index + .checked_sub(1) + .ok_or(anyhow!("Access method 0 does not exist")) + } } #[derive(Subcommand, Debug, Clone)] @@ -80,6 +141,8 @@ pub enum ApiCommands { /// Add a custom API proxy #[clap(subcommand)] Add(AddCustomCommands), + /// Edit an API proxy + Edit(EditCustomCommands), /// Remove an API proxy Remove(RemoveCustomCommands), } @@ -106,6 +169,15 @@ pub enum AddCustomCommands { }, } +#[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 @@ -138,6 +210,28 @@ pub enum Socks5AddCommands { }, } +#[derive(Args, Debug, Clone)] +pub struct EditParams { + /// Username for authentication [Shadowsocks] + #[arg(long)] + username: Option, + /// Password for authentication [Shadowsocks] + #[arg(long)] + password: Option, + /// Cipher to use [Shadowsocks] + #[arg(value_parser = SHADOWSOCKS_CIPHERS, long)] + cipher: Option, + /// The IP of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] + #[arg(long)] + ip: Option, + /// The port of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] + #[arg(long)] + port: Option, + /// The port that the server on localhost is listening on [Socks5 (Local proxy)] + #[arg(long)] + local_port: Option, +} + /// Implement conversions from CLI types to Daemon types. /// /// Since these are not supposed to be used outside of the CLI, diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index 12d8f10f261a..e778cd2eebd8 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -1,5 +1,5 @@ use crate::{new_selector_config, settings, Daemon, EventListener}; -use mullvad_types::api_access_method::AccessMethod; +use mullvad_types::api_access_method::{AccessMethod, ApiAccessMethodReplace}; #[derive(err_derive::Error, Debug)] pub enum Error { @@ -54,4 +54,26 @@ where }) .map_err(Error::Settings) } + + pub async fn replace_access_method( + &mut self, + access_method_replace: ApiAccessMethodReplace, + ) -> 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); + access_methods.swap_remove(access_method_replace.index); + }) + .await + .map(|changed| { + if changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + self.relay_selector + .set_config(new_selector_config(&self.settings)); + }; + }) + .map_err(Error::Settings) + } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 6ebb7f828e20..9528aa1d5ea1 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -40,7 +40,7 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::AccessMethod, + api_access_method::{AccessMethod, ApiAccessMethodReplace}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -266,6 +266,8 @@ pub enum DaemonCommand { AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod), /// Remove an API access method RemoveApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + /// Edit an API access method + ReplaceApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodReplace), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1044,6 +1046,9 @@ where GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx), AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await, RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, + ReplaceApiAccessMethod(tx, method) => { + self.on_replace_api_access_method(tx, method).await + } IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), #[cfg(not(target_os = "android"))] @@ -2243,6 +2248,18 @@ where Self::oneshot_send(tx, result, "remove_api_access_method response"); } + async fn on_replace_api_access_method( + &mut self, + tx: ResponseTx<(), Error>, + method: ApiAccessMethodReplace, + ) { + let result = self + .replace_access_method(method) + .await + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "edit_api_access_method response"); + } + fn on_get_settings(&self, tx: oneshot::Sender) { Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 9491541dc436..2c1061a7411f 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -81,6 +81,9 @@ service ManagementService { rpc RemoveApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } + rpc ReplaceApiAccessMethod(ApiAccessMethodReplace) returns (google.protobuf.Empty) { + // Can I return something useful here instead of Empty? + } // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index d4edabc3911e..892f4ea5cdf8 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -487,6 +487,17 @@ impl MullvadProxyClient { .map(drop) } + pub async fn replace_access_method( + &mut self, + access_method_replace: ApiAccessMethodReplace, + ) -> Result<()> { + self.0 + .replace_api_access_method(types::ApiAccessMethodReplace::from(access_method_replace)) + .await + .map_err(Error::Rpc) + .map(drop) + } + #[cfg(target_os = "linux")] pub async fn get_split_tunnel_processes(&mut self) -> Result> { use futures::TryStreamExt; 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 a91923e9e94a..b9217b88c974 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -32,6 +32,25 @@ mod settings { } } } + + impl From for proto::ApiAccessMethodReplace { + fn from(value: api_access_method::ApiAccessMethodReplace) -> Self { + proto::ApiAccessMethodReplace { + index: value.index as u32, + access_method: Some(value.access_method.into()), + } + } + } + + impl From for api_access_method::ApiAccessMethodReplace { + // TODO: Implement `TryFrom` instead, and skip the `unwrap`. + fn from(value: proto::ApiAccessMethodReplace) -> Self { + api_access_method::ApiAccessMethodReplace { + index: value.index as usize, + access_method: value.access_method.unwrap().into(), + } + } + } } /// Implements conversions for the 'main' AccessMethod data type. diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index 5a4d32551fb1..0cb417dda122 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -61,7 +61,7 @@ impl Shadowsocks { /// 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 { let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); - Some(Self::new(peer, password, cipher)) + Some(Self::new(peer, cipher, password)) } } @@ -92,3 +92,41 @@ impl Socks5Remote { Some(Self::new(peer)) } } + +impl From for AccessMethod { + fn from(value: Shadowsocks) -> Self { + AccessMethod::Shadowsocks(value) + } +} + +impl From for AccessMethod { + fn from(value: Socks5Remote) -> Self { + AccessMethod::Socks5(value.into()) + } +} + +impl From for AccessMethod { + fn from(value: Socks5Local) -> Self { + AccessMethod::Socks5(value.into()) + } +} + +impl From for Socks5 { + fn from(value: Socks5Remote) -> Self { + Socks5::Remote(value) + } +} + +impl From for Socks5 { + fn from(value: Socks5Local) -> Self { + Socks5::Local(value) + } +} + +/// 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, +} From 2db6e2ecc64c0ddb9dba0b0947a31b0fbefe9f53 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 11 Sep 2023 15:52:15 +0200 Subject: [PATCH 06/29] Code cleanup - Add a new datastructures for distinguishing between built-in & custom api access methods - Implement `TryFrom` instead of `From` for fallible conversions - Do not panic if a protobuf-message is ill-formatted - Do not allow removal of built-in api access methods - Refactor notification logic in `access_methods.rs` - Rename `mullvad proxy api` to simply `mullvad proxy` - Since there are no other kinds of proxies at the moment, the subcommand `proxy api` does not make much sense. - Remove left-over comments --- mullvad-api/src/https_client_with_sni.rs | 13 +- mullvad-api/src/proxy.rs | 23 ++- mullvad-cli/src/cmds/proxy.rs | 84 ++++---- mullvad-daemon/src/access_methods.rs | 50 +++-- mullvad-daemon/src/api.rs | 69 ++++--- mullvad-daemon/src/lib.rs | 6 +- .../proto/management_interface.proto | 8 +- mullvad-management-interface/src/client.rs | 11 +- .../types/conversions/api_access_method.rs | 186 ++++++++++++------ .../src/types/conversions/settings.rs | 4 +- mullvad-types/src/api_access_method.rs | 102 ++++++++-- mullvad-types/src/settings/mod.rs | 10 +- 12 files changed, 362 insertions(+), 204 deletions(-) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 940bb249d4af..5bffa3d32b18 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -146,8 +146,6 @@ impl InnerConnectionMode { } /// Set up a SOCKS5-socket connection. - /// - /// TODO: Handle case where the proxy-address is `localhost`. async fn handle_socks_connection( proxy_config: SocksConfig, addr: &SocketAddr, @@ -223,9 +221,14 @@ impl TryFrom for InnerConnectionMode { proxy_context: SsContext::new_shared(ServerType::Local), }) } - ProxyConfig::Socks(config) => { - InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) - } + ProxyConfig::Socks(config) => match config { + mullvad_types::api_access_method::Socks5::Local(config) => { + InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + } + mullvad_types::api_access_method::Socks5::Remote(config) => { + InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + } + }, }, }) } diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index c5c2426933f7..112a747f04ad 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -1,5 +1,6 @@ use futures::Stream; use hyper::client::connect::Connected; +use mullvad_types::api_access_method; use serde::{Deserialize, Serialize}; use std::{ fmt, io, @@ -8,9 +9,7 @@ use std::{ pin::Pin, task::{self, Poll}, }; -use talpid_types::{ - net::openvpn::ShadowsocksProxySettings, net::openvpn::SocksProxySettings, ErrorExt, -}; +use talpid_types::ErrorExt; use tokio::{ fs, io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf}, @@ -18,7 +17,7 @@ use tokio::{ const CURRENT_CONFIG_FILENAME: &str = "api-endpoint.json"; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ApiConnectionMode { /// Connect directly to the target. Direct, @@ -35,10 +34,10 @@ impl fmt::Display for ApiConnectionMode { } } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum ProxyConfig { - Shadowsocks(ShadowsocksProxySettings), - Socks(SocksProxySettings), + Shadowsocks(api_access_method::Shadowsocks), + Socks(api_access_method::Socks5), } impl fmt::Display for ProxyConfig { @@ -46,7 +45,10 @@ impl fmt::Display for ProxyConfig { match self { // TODO: Do not hardcode TCP ProxyConfig::Shadowsocks(ss) => write!(f, "Shadowsocks {}/TCP", ss.peer), - ProxyConfig::Socks(s) => write!(f, "Socks5 {}/TCP", s.peer), + ProxyConfig::Socks(socks) => match socks { + api_access_method::Socks5::Local(s) => write!(f, "Socks5 {}/TCP", s.peer), + api_access_method::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), + }, } } } @@ -117,7 +119,10 @@ impl ApiConnectionMode { ApiConnectionMode::Direct => None, ApiConnectionMode::Proxied(proxy_config) => match proxy_config { ProxyConfig::Shadowsocks(ss) => Some(ss.peer), - ProxyConfig::Socks(s) => Some(s.peer), + ProxyConfig::Socks(socks) => match socks { + api_access_method::Socks5::Local(s) => Some(s.peer), + api_access_method::Socks5::Remote(s) => Some(s.peer), + }, }, } } diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 5a4bf2fdd84b..4826a571aff2 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -1,41 +1,47 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::api_access_method::{AccessMethod, ApiAccessMethodReplace}; +use mullvad_types::api_access_method::{ + daemon::ApiAccessMethodReplace, AccessMethod, ObfuscationProtocol, +}; use std::net::IpAddr; use clap::{Args, Subcommand}; use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; -#[derive(Subcommand, Debug)] +#[derive(Subcommand, Debug, Clone)] pub enum Proxy { - /// Get current api settings + /// List the configured API proxies + List, + /// Add a custom API proxy #[clap(subcommand)] - Api(ApiCommands), + Add(AddCustomCommands), + /// Edit an API proxy + Edit(EditCustomCommands), + /// Remove an API proxy + Remove(RemoveCustomCommands), } impl Proxy { pub async fn handle(self) -> Result<()> { match self { - Proxy::Api(cmd) => match cmd { - ApiCommands::List => { - //println!("Listing the API access methods: .."); - Self::list().await?; - } - ApiCommands::Add(cmd) => { - //println!("Adding custom proxy"); - Self::add(cmd).await?; - } - ApiCommands::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? - } - ApiCommands::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::List => { + //println!("Listing the API access methods: .."); + Self::list().await?; + } + Proxy::Add(cmd) => { + //println!("Adding custom proxy"); + 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? + } }; Ok(()) } @@ -88,8 +94,13 @@ impl Proxy { .clone(); // Create a new access method combining the new params with the previous values - let edited_access_method: AccessMethod = match access_method { - AccessMethod::Shadowsocks(shadowsocks) => { + 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 { + 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); @@ -97,7 +108,7 @@ impl Proxy { mullvad_types::api_access_method::Shadowsocks::from_args(ip, port, cipher, password) .map(|x| x.into()) } - AccessMethod::Socks5(socks) => match socks { + ObfuscationProtocol::Socks5(socks) => match socks { mullvad_types::api_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()); @@ -134,19 +145,6 @@ impl Proxy { } } -#[derive(Subcommand, Debug, Clone)] -pub enum ApiCommands { - /// List the configured API proxies - List, - /// Add a custom API proxy - #[clap(subcommand)] - Add(AddCustomCommands), - /// Edit an API proxy - Edit(EditCustomCommands), - /// Remove an API proxy - Remove(RemoveCustomCommands), -} - #[derive(Subcommand, Debug, Clone)] pub enum AddCustomCommands { /// Configure SOCKS5 proxy @@ -262,7 +260,7 @@ mod conversions { ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); - daemon_types::AccessMethod::Socks5(socks_proxy) + socks_proxy.into() } Socks5AddCommands::Remote { remote_ip, @@ -278,7 +276,7 @@ mod conversions { ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); - daemon_types::AccessMethod::Socks5(socks_proxy) + socks_proxy.into() } }, AddCustomCommands::Shadowsocks { @@ -297,7 +295,7 @@ mod conversions { password, ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - daemon_types::AccessMethod::Shadowsocks(shadowsocks_proxy) + shadowsocks_proxy.into() } }) } diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index e778cd2eebd8..dbbf628f608a 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -1,5 +1,10 @@ -use crate::{new_selector_config, settings, Daemon, EventListener}; -use mullvad_types::api_access_method::{AccessMethod, ApiAccessMethodReplace}; +use crate::{ + settings::{self, MadeChanges}, + Daemon, EventListener, +}; +use mullvad_types::api_access_method::{ + daemon::ApiAccessMethodReplace, AccessMethod, CustomAccessMethod, +}; #[derive(err_derive::Error, Debug)] pub enum Error { @@ -24,18 +29,15 @@ where .push(access_method); }) .await - .map(|changed| { - if changed { - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - }; - }) + .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) } - pub async fn remove_access_method(&mut self, access_method: AccessMethod) -> Result<(), Error> { + pub async fn remove_access_method( + &mut self, + access_method: CustomAccessMethod, + ) -> Result<(), Error> { + let access_method = AccessMethod::from(access_method); self.settings .update(|settings| { settings @@ -44,14 +46,7 @@ where .retain(|x| *x != access_method); }) .await - .map(|changed| { - if changed { - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - }; - }) + .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) } @@ -66,14 +61,15 @@ where access_methods.swap_remove(access_method_replace.index); }) .await - .map(|changed| { - if changed { - self.event_listener - .notify_settings(self.settings.to_settings()); - self.relay_selector - .set_config(new_selector_config(&self.settings)); - }; - }) + .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) } + + /// If settings were changed due to an update, notify all listeners. + fn notify_on_change(&mut self, settings_changed: MadeChanges) { + if settings_changed { + self.event_listener + .notify_settings(self.settings.to_settings()); + }; + } } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 67f80ca2356d..18e77d5a28c8 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -10,6 +10,7 @@ use mullvad_api::{ ApiEndpointUpdateCallback, }; use mullvad_relay_selector::RelaySelector; +use mullvad_types::api_access_method; use std::{ net::SocketAddr, path::PathBuf, @@ -25,11 +26,12 @@ 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`]) -/// or from a bridge ([`ApiConnectionMode::Proxied`]). +/// via a bridge ([`ApiConnectionMode::Proxied`]) or via any supported obfuscation protocol ([`ObfuscationProtocol`]). /// /// * Every 3rd attempt returns [`ApiConnectionMode::Direct`]. /// * Any other attempt returns a configuration for the bridge that is closest to the selected relay @@ -38,10 +40,9 @@ use talpid_types::{ /// bridge, [`ApiConnectionMode::Direct`] is returned. pub struct ApiConnectionModeProvider { cache_dir: PathBuf, - + /// Used for selecting a Relay when the `Bridges` access method is used. relay_selector: RelaySelector, retry_attempt: u32, - current_task: Option + Send>>>, } @@ -63,35 +64,18 @@ impl Stream for ApiConnectionModeProvider { }; } - // Create a new task. - let config = if Self::should_use_bridge(self.retry_attempt) { - self.relay_selector - .get_bridge_forced() - .map(|settings| match settings { - ProxySettings::Shadowsocks(ss_settings) => { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(ss_settings)) - } - _ => { - log::error!("Received unexpected proxy settings type"); - ApiConnectionMode::Direct - } - }) - .unwrap_or(ApiConnectionMode::Direct) - } else { - ApiConnectionMode::Direct - }; - + let connection_mode = self.new_task(self.retry_attempt); self.retry_attempt = self.retry_attempt.wrapping_add(1); let cache_dir = self.cache_dir.clone(); self.current_task = Some(Box::pin(async move { - if let Err(error) = config.save(&cache_dir).await { + if let Err(error) = connection_mode.save(&cache_dir).await { log::debug!( "{}", error.display_chain_with_msg("Failed to save API endpoint") ); } - config + connection_mode })); self.poll_next(cx) @@ -113,6 +97,45 @@ impl ApiConnectionModeProvider { fn should_use_bridge(retry_attempt: u32) -> bool { retry_attempt % 3 > 0 } + + fn should_use_direct(retry_attempt: u32) -> bool { + // TODO: Change back before comitting! + false + // !Self::should_use_bridge(retry_attempt) + } + + /// 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, we a + fn new_task(&self, retry_attempt: u32) -> ApiConnectionMode { + if Self::should_use_direct(retry_attempt) { + ApiConnectionMode::Direct + } else { + 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( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + ); + println!("Using bridge mode to access the API! {:?}", ss_settings); + Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( + ss_settings, + ))) + } + _ => { + log::error!("Received unexpected proxy settings type"); + None + } + }) + .unwrap_or(ApiConnectionMode::Direct) + } + } } /// Notifies the tunnel state machine that the API (real or proxied) endpoint has diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 9528aa1d5ea1..6e7b98d8eccc 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -40,7 +40,7 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::{AccessMethod, ApiAccessMethodReplace}, + api_access_method::{daemon::ApiAccessMethodReplace, AccessMethod, CustomAccessMethod}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -265,7 +265,7 @@ pub enum DaemonCommand { /// Add API access methods AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod), /// Remove an API access method - RemoveApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + RemoveApiAccessMethod(ResponseTx<(), Error>, CustomAccessMethod), /// Edit an API access method ReplaceApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodReplace), /// Get information about the currently running and latest app versions @@ -2239,7 +2239,7 @@ where async fn on_remove_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: AccessMethod, + method: CustomAccessMethod, ) { let result = self .remove_access_method(method) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 2c1061a7411f..5e1aef6cea5e 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -337,6 +337,8 @@ message CustomList { message CustomListSettings { repeated CustomList custom_lists = 1; } message ApiAccessMethod { + message Direct {} + message Bridges {} message Socks5Local { string ip = 1; uint32 port = 2; @@ -359,8 +361,10 @@ message ApiAccessMethod { string cipher = 4; } oneof access_method { - Socks5 socks5 = 1; - Shadowsocks shadowsocks = 2; + Direct direct = 1; + Bridges bridges = 2; + Socks5 socks5 = 3; + Shadowsocks shadowsocks = 4; } } diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 892f4ea5cdf8..15a5be189239 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -165,16 +165,17 @@ impl MullvadProxyClient { } pub async fn get_api_access_methods(&mut self) -> Result> { - Ok(self - .0 + self.0 .get_api_access_methods(()) .await .map_err(Error::Rpc)? .into_inner() .api_access_methods - .iter() - .map(From::from) - .collect()) + .into_iter() + .map(|access_method| { + AccessMethod::try_from(access_method).map_err(Error::InvalidResponse) + }) + .collect() } pub async fn update_relay_locations(&mut self) -> Result<()> { 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 b9217b88c974..d6d71e75a274 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -1,6 +1,6 @@ /// Implements conversions for the auxilliary proto AccessMethod type to the internal AccessMethod data type. mod settings { - use crate::types::proto; + use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::api_access_method; impl From<&api_access_method::Settings> for proto::ApiAccessMethodSettings { @@ -21,20 +21,22 @@ mod settings { } } - impl From for api_access_method::Settings { - fn from(settings: proto::ApiAccessMethodSettings) -> Self { - Self { + impl TryFrom for api_access_method::Settings { + type Error = FromProtobufTypeError; + + fn try_from(settings: proto::ApiAccessMethodSettings) -> Result { + Ok(Self { api_access_methods: settings .api_access_methods .iter() - .map(api_access_method::AccessMethod::from) - .collect(), - } + .map(api_access_method::AccessMethod::try_from) + .collect::, _>>()?, + }) } } - impl From for proto::ApiAccessMethodReplace { - fn from(value: api_access_method::ApiAccessMethodReplace) -> Self { + impl From for proto::ApiAccessMethodReplace { + fn from(value: api_access_method::daemon::ApiAccessMethodReplace) -> Self { proto::ApiAccessMethodReplace { index: value.index as u32, access_method: Some(value.access_method.into()), @@ -42,100 +44,142 @@ mod settings { } } - impl From for api_access_method::ApiAccessMethodReplace { - // TODO: Implement `TryFrom` instead, and skip the `unwrap`. - fn from(value: proto::ApiAccessMethodReplace) -> Self { - api_access_method::ApiAccessMethodReplace { + impl TryFrom for api_access_method::daemon::ApiAccessMethodReplace { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::ApiAccessMethodReplace) -> Result { + Ok(api_access_method::daemon::ApiAccessMethodReplace { index: value.index as usize, - access_method: value.access_method.unwrap().into(), - } + access_method: value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not convert Access Method from protobuf", + )) + .and_then(TryInto::try_into)?, + }) } } } /// Implements conversions for the 'main' AccessMethod data type. mod data { - use crate::types::proto::{self, api_access_method::socks5::Socks5type}; + use crate::types::{ + proto::{self, api_access_method::socks5::Socks5type}, + FromProtobufTypeError, + }; use mullvad_types::api_access_method::{ - AccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote, + AccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Shadowsocks, Socks5, Socks5Local, + Socks5Remote, }; - impl From for Vec { - fn from(api_access_methods: proto::ApiAccessMethods) -> Self { - api_access_methods + impl TryFrom for Vec { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::ApiAccessMethods) -> Result { + value .api_access_methods .iter() - .map(AccessMethod::from) + .map(AccessMethod::try_from) .collect() } } - impl From for AccessMethod { - fn from(value: proto::ApiAccessMethod) -> Self { - // TODO: How to not unwrap? - match value.access_method.unwrap() { + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::ApiAccessMethod) -> Result { + let access_method = + value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not convert Access Method from protobuf", + ))?; + Ok(match access_method { proto::api_access_method::AccessMethod::Socks5(socks) => { match socks.socks5type.unwrap() { - Socks5type::Local(local) => { - let local_proxy = Socks5Local::from_args( - local.ip, - local.port as u16, - local.local_port as u16, - ) - .unwrap(); // This is dangerous territory .. - AccessMethod::Socks5(Socks5::Local(local_proxy)) - } + Socks5type::Local(local) => 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", + ))? + .into(), Socks5type::Remote(remote) => { - let remote_proxy = - Socks5Remote::from_args(remote.ip, remote.port as u16).unwrap(); // This is dangerous territory .. - AccessMethod::Socks5(Socks5::Remote(remote_proxy)) + Socks5Remote::from_args(remote.ip, remote.port as u16) + .ok_or({ + FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (remote) message from protobuf", + ) + })? + .into() } } } proto::api_access_method::AccessMethod::Shadowsocks(ss) => { - let shadow_sock = - Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password) - .unwrap(); - AccessMethod::Shadowsocks(shadow_sock) + Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password) + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse Shadowsocks message from protobuf", + ))? + .into() } - } + proto::api_access_method::AccessMethod::Direct(_) => { + BuiltInAccessMethod::Direct.into() + } + proto::api_access_method::AccessMethod::Bridges(_) => { + BuiltInAccessMethod::Bridge.into() + } + }) } } impl From for proto::ApiAccessMethod { fn from(value: AccessMethod) -> Self { match value { - AccessMethod::Shadowsocks(ss) => proto::api_access_method::Shadowsocks { - ip: ss.peer.ip().to_string(), - port: ss.peer.port() as u32, - password: ss.password, - cipher: ss.cipher, - } - .into(), + AccessMethod::Custom(value) => match value.access_method { + ObfuscationProtocol::Shadowsocks(ss) => proto::api_access_method::Shadowsocks { + ip: ss.peer.ip().to_string(), + port: ss.peer.port() as u32, + password: ss.password, + cipher: ss.cipher, + } + .into(), - AccessMethod::Socks5(Socks5::Local(Socks5Local { peer, port })) => { - proto::api_access_method::Socks5Local { - ip: peer.ip().to_string(), - port: peer.port() as u32, - local_port: port as u32, + ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { peer, port })) => { + proto::api_access_method::Socks5Local { + ip: peer.ip().to_string(), + port: peer.port() as u32, + local_port: port as u32, + } + .into() } - .into() - } - AccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { - proto::api_access_method::Socks5Remote { - ip: peer.ip().to_string(), - port: peer.port() as u32, + ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + proto::api_access_method::Socks5Remote { + ip: peer.ip().to_string(), + port: peer.port() as u32, + } + .into() } - .into() - } + }, + AccessMethod::BuiltIn(value) => match value { + mullvad_types::api_access_method::BuiltInAccessMethod::Direct => { + proto::api_access_method::Direct {}.into() + } + mullvad_types::api_access_method::BuiltInAccessMethod::Bridge => { + proto::api_access_method::Bridges {}.into() + } + }, } } } - impl From<&proto::ApiAccessMethod> for AccessMethod { - fn from(value: &proto::ApiAccessMethod) -> Self { - AccessMethod::from(value.clone()) + impl TryFrom<&proto::ApiAccessMethod> for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: &proto::ApiAccessMethod) -> Result { + AccessMethod::try_from(value.clone()) } } @@ -180,6 +224,18 @@ mod data { } } + impl From for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Direct) -> Self { + proto::api_access_method::AccessMethod::Direct(value).into() + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: proto::api_access_method::Bridges) -> Self { + proto::api_access_method::AccessMethod::Bridges(value).into() + } + } + impl From for proto::ApiAccessMethod { fn from(value: proto::api_access_method::AccessMethod) -> Self { proto::ApiAccessMethod { diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index d454af4cb02e..57694d31f0ae 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -180,9 +180,9 @@ impl TryFrom 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::from( + api_access_methods: mullvad_types::api_access_method::Settings::try_from( api_access_methods_settings, - ), + )?, }) } } diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index 0cb417dda122..693d67d7dcf3 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -4,14 +4,45 @@ use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; /// Daemon settings for API access methods. -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Settings { pub api_access_methods: Vec, } -/// API access method datastructure. +impl Default for Settings { + fn default() -> Self { + Self { + api_access_methods: vec![ + BuiltInAccessMethod::Direct.into(), + BuiltInAccessMethod::Bridge.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, + 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)] +pub enum ObfuscationProtocol { Shadowsocks(Shadowsocks), Socks5(Socks5), } @@ -48,6 +79,23 @@ impl Settings { } } +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), + } + } +} + impl Shadowsocks { pub fn new(peer: SocketAddr, cipher: String, password: String) -> Self { Shadowsocks { @@ -93,21 +141,49 @@ impl Socks5Remote { } } +impl From for AccessMethod { + fn from(value: BuiltInAccessMethod) -> Self { + AccessMethod::BuiltIn(value) + } +} + +impl From for AccessMethod { + fn from(value: CustomAccessMethod) -> Self { + AccessMethod::Custom(value) + } +} + +impl From for AccessMethod { + fn from(value: ObfuscationProtocol) -> Self { + CustomAccessMethod { + id: uuid::Uuid::new_v4().to_string(), + access_method: value, + } + .into() + } +} + impl From for AccessMethod { fn from(value: Shadowsocks) -> Self { - AccessMethod::Shadowsocks(value) + ObfuscationProtocol::Shadowsocks(value).into() + } +} + +impl From for AccessMethod { + fn from(value: Socks5) -> Self { + ObfuscationProtocol::Socks5(value).into() } } impl From for AccessMethod { fn from(value: Socks5Remote) -> Self { - AccessMethod::Socks5(value.into()) + Socks5::Remote(value).into() } } impl From for AccessMethod { fn from(value: Socks5Local) -> Self { - AccessMethod::Socks5(value.into()) + Socks5::Local(value).into() } } @@ -123,10 +199,14 @@ impl From for Socks5 { } } -/// 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, +/// 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, + } } diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index fc66649b8ac3..311a154c28ca 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -140,15 +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(), - // TODO: Remove this v in favor of this ^ when w can add or own access methods. - api_access_methods: { - use api_access_method::{AccessMethod, Shadowsocks}; - let mut xs = api_access_method::Settings::default(); - let mut ys = vec![]; - xs.api_access_methods.append(&mut ys); - xs - }, + api_access_methods: api_access_method::Settings::default(), } } } From e724505125196584dab6e7f2f2671ce34db9d23a Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 13 Sep 2023 11:01:59 +0200 Subject: [PATCH 07/29] (De)Serialize uuid for custom API access methods --- .../proto/management_interface.proto | 21 +++++++++++-------- .../types/conversions/api_access_method.rs | 3 +++ mullvad-types/src/api_access_method.rs | 16 ++++++++------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 5e1aef6cea5e..bc56929fc46f 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -340,13 +340,15 @@ message ApiAccessMethod { message Direct {} message Bridges {} message Socks5Local { - string ip = 1; - uint32 port = 2; - uint32 local_port = 3; + string id = 1; + string ip = 2; + uint32 port = 3; + uint32 local_port = 4; } message Socks5Remote { - string ip = 1; - uint32 port = 2; + string id = 1; + string ip = 2; + uint32 port = 3; } message Socks5 { oneof Socks5type { @@ -355,10 +357,11 @@ message ApiAccessMethod { } } message Shadowsocks { - string ip = 1; - uint32 port = 2; - string password = 3; - string cipher = 4; + string id = 1; + string ip = 2; + uint32 port = 3; + string password = 4; + string cipher = 5; } oneof access_method { Direct direct = 1; 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 d6d71e75a274..5612c18b1d81 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -140,6 +140,7 @@ mod data { match value { 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, @@ -149,6 +150,7 @@ mod data { ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { peer, port })) => { proto::api_access_method::Socks5Local { + id: value.id, ip: peer.ip().to_string(), port: peer.port() as u32, local_port: port as u32, @@ -157,6 +159,7 @@ mod data { } ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer })) => { proto::api_access_method::Socks5Remote { + id: value.id, ip: peer.ip().to_string(), port: peer.port() as u32, } diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index 693d67d7dcf3..af456f764985 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -1,6 +1,8 @@ +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. @@ -41,33 +43,33 @@ pub struct CustomAccessMethod { pub access_method: ObfuscationProtocol, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum ObfuscationProtocol { Shadowsocks(Shadowsocks), Socks5(Socks5), } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] 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. } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum Socks5 { Local(Socks5Local), Remote(Socks5Remote), } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[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)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Socks5Remote { pub peer: SocketAddr, } @@ -155,8 +157,10 @@ impl From for AccessMethod { impl From for AccessMethod { fn from(value: ObfuscationProtocol) -> Self { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); CustomAccessMethod { - id: uuid::Uuid::new_v4().to_string(), + id: hasher.finish().to_string(), access_method: value, } .into() From 5a54835d4ead23be5dcda581dddb2719fbc4370f Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 13 Sep 2023 11:50:46 +0200 Subject: [PATCH 08/29] Update used access methods on a daemon settings change --- mullvad-daemon/src/access_methods.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index dbbf628f608a..ddda95e81ba4 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -71,5 +71,16 @@ 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 = self + .settings + .api_access_methods + .api_access_methods + .clone() + .into_iter() + .map(|x| (x, 1)) + .collect(); } } From 158d9496a7b1e96a77781646ce4f1a26fda27f92 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 13 Sep 2023 17:22:38 +0200 Subject: [PATCH 09/29] Allowing traffic to and from a SOCKS5-proxy running on localhost. The daemon has to add a rule to allow traffix to/from the remote server which the locally running SOCKS5-proxy communicates with. --- mullvad-api/src/https_client_with_sni.rs | 4 +- mullvad-api/src/proxy.rs | 27 +++++--- mullvad-daemon/src/api.rs | 86 ++++++++++++++++-------- 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 5bffa3d32b18..76130de1856b 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -223,7 +223,9 @@ impl TryFrom for InnerConnectionMode { } ProxyConfig::Socks(config) => match config { mullvad_types::api_access_method::Socks5::Local(config) => { - InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + InnerConnectionMode::Socks5(SocksConfig { + peer: SocketAddr::new("127.0.0.1".parse().unwrap(), config.port), + }) } mullvad_types::api_access_method::Socks5::Remote(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 112a747f04ad..186eea2919d2 100644 --- a/mullvad-api/src/proxy.rs +++ b/mullvad-api/src/proxy.rs @@ -40,13 +40,28 @@ pub enum ProxyConfig { Socks(api_access_method::Socks5), } +impl ProxyConfig { + /// Returns the remote address to reach the proxy. + fn get_endpoint(&self) -> SocketAddr { + 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, + }, + } + } +} + impl fmt::Display for ProxyConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { // 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 {}/TCP", s.peer), + api_access_method::Socks5::Local(s) => { + write!(f, "Socks5 localhost:{} => {}/TCP", s.port, s.peer) + } api_access_method::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), }, } @@ -113,17 +128,11 @@ impl ApiConnectionMode { } } - /// Returns the remote address, or `None` for `ApiConnectionMode::Direct`. + /// Returns the remote address required to reach the API, or `None` for `ApiConnectionMode::Direct`. pub fn get_endpoint(&self) -> Option { match self { ApiConnectionMode::Direct => None, - ApiConnectionMode::Proxied(proxy_config) => match proxy_config { - ProxyConfig::Shadowsocks(ss) => Some(ss.peer), - ProxyConfig::Socks(socks) => match socks { - api_access_method::Socks5::Local(s) => Some(s.peer), - api_access_method::Socks5::Remote(s) => Some(s.peer), - }, - }, + ApiConnectionMode::Proxied(proxy_config) => Some(proxy_config.get_endpoint()), } } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 18e77d5a28c8..1bcc77f44bc3 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -108,32 +108,59 @@ impl ApiConnectionModeProvider { /// /// 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, we a - fn new_task(&self, retry_attempt: u32) -> ApiConnectionMode { - if Self::should_use_direct(retry_attempt) { - ApiConnectionMode::Direct - } else { - 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( - ss_settings.peer, - ss_settings.cipher, - ss_settings.password, - ); - println!("Using bridge mode to access the API! {:?}", ss_settings); - Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( - ss_settings, - ))) - } - _ => { - log::error!("Received unexpected proxy settings type"); - None - } - }) - .unwrap_or(ApiConnectionMode::Direct) + /// 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 = { + let mut access_methods_picker = self.connection_modes.lock().unwrap(); + // Rotate through the cycle of access methods. + // Safety: It is always safe to unwrap after calling `next` on a [`std::iter::Cycle`] + access_methods_picker.next().unwrap() + }; + let connection_mode = self.from(&access_method); + log::info!("New API connection mode selected: {}", connection_mode); + connection_mode + } + + /// Ad-hoc version of [`std::convert::From::from`], but since some + /// [`ApiConnectionMode`]s require extra logic/data from + /// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait can not be used. + fn from(&mut self, access_method: &AccessMethod) -> ApiConnectionMode { + match access_method { + AccessMethod::BuiltIn(access_method) => match access_method { + 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( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + *enabled, + ); + Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( + ss_settings, + ))) + } + _ => { + log::error!("Received unexpected proxy settings type"); + None + } + }) + .unwrap_or(ApiConnectionMode::Direct), + }, + AccessMethod::Custom(access_method) => match &access_method.access_method { + api_access_method::ObfuscationProtocol::Shadowsocks(shadowsocks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) + } + api_access_method::ObfuscationProtocol::Socks5(socks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) + } + }, } } } @@ -161,22 +188,23 @@ impl ApiEndpointUpdaterHandle { move |address: SocketAddr| { let inner_tx = tunnel_tx.clone(); async move { - let tunnel_tx = if let Some(Some(tunnel_tx)) = { inner_tx.lock().unwrap().as_ref() } - .map(|tx: &Weak>| tx.upgrade()) + let tunnel_tx = if let Some(tunnel_tx) = { inner_tx.lock().unwrap().as_ref() } + .and_then(|tx: &Weak>| tx.upgrade()) { tunnel_tx } else { 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 } } From d767d881e69f7a986272303bda833df5769a9d9b Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 18 Sep 2023 09:40:49 +0200 Subject: [PATCH 10/29] Add `mullvad api-access enable/disable` Add `mullvad api-access enable/disable`, which allows for toggling API access methods On/Off. Make `ApiConnectionModeProvider` consider status of access method. `ApiConnectionModeProvider` will only be able to return access methods which are enabled, as it will disregard those which are disabled. Add logic to guarantee the invariant that at least one API access method is available for selection by the `ApiConnectionModeProvider` --- mullvad-cli/src/cmds/proxy.rs | 79 +++++++++++++++-- mullvad-daemon/src/access_methods.rs | 23 ++++- mullvad-daemon/src/api.rs | 4 +- mullvad-daemon/src/lib.rs | 20 ++++- mullvad-daemon/src/management_interface.rs | 20 +++++ .../proto/management_interface.proto | 44 +++++++--- mullvad-management-interface/src/client.rs | 16 +++- .../types/conversions/api_access_method.rs | 85 +++++++++++++------ mullvad-types/src/api_access_method.rs | 85 +++++++++++++++---- 9 files changed, 310 insertions(+), 66 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 4826a571aff2..74e0e22c224f 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::api_access_method::{ - daemon::ApiAccessMethodReplace, AccessMethod, ObfuscationProtocol, + daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, + AccessMethod, ObfuscationProtocol, }; use std::net::IpAddr; @@ -19,6 +20,10 @@ pub enum Proxy { Edit(EditCustomCommands), /// Remove an API proxy Remove(RemoveCustomCommands), + /// Enable an API proxy + Enable(ToggleCustomCommands), + /// Disable an API proxy + Disable(ToggleCustomCommands), } impl Proxy { @@ -42,6 +47,16 @@ impl Proxy { 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)?; + let enabled = true; + Self::toggle(index, enabled).await?; + } + Proxy::Disable(cmd) => { + let index = Self::zero_to_one_based_index(cmd.index)?; + let enabled = false; + Self::toggle(index, enabled).await?; + } }; Ok(()) } @@ -50,7 +65,16 @@ impl Proxy { async fn list() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; for (index, api_access_method) in rpc.get_api_access_methods().await?.iter().enumerate() { - println!("{}. {:?}", index + 1, api_access_method); + println!( + "{}. {:?} {}", + index + 1, + api_access_method, + if api_access_method.enabled() { + "[enabled]" + } else { + "[disabled]" + } + ); } Ok(()) } @@ -80,6 +104,7 @@ impl Proxy { .map_err(Into::::into) } + /// 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 @@ -105,21 +130,28 @@ impl Proxy { 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); - mullvad_types::api_access_method::Shadowsocks::from_args(ip, port, cipher, password) - .map(|x| x.into()) + let enabled = shadowsocks.enabled; + mullvad_types::api_access_method::Shadowsocks::from_args( + ip, port, cipher, password, enabled, + ) + .map(|x| x.into()) } ObfuscationProtocol::Socks5(socks) => match socks { mullvad_types::api_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); - mullvad_types::api_access_method::Socks5Local::from_args(ip, port, local_port) - .map(|x| x.into()) + let enabled = local.enabled; + mullvad_types::api_access_method::Socks5Local::from_args( + ip, port, local_port, enabled, + ) + .map(|x| x.into()) } mullvad_types::api_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()); - mullvad_types::api_access_method::Socks5Remote::from_args(ip, port) + let enabled = remote.enabled; + mullvad_types::api_access_method::Socks5Remote::from_args(ip, port, enabled) .map(|x| x.into()) } }, @@ -138,6 +170,28 @@ impl Proxy { Ok(()) } + /// Toggle a custom API access method to be enabled or disabled. + async fn toggle(index: usize, 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(); + + rpc.toggle_access_method(ApiAccessMethodToggle { + access_method, + enable: enabled, + }) + .await?; + Ok(()) + } + fn zero_to_one_based_index(index: usize) -> Result { index .checked_sub(1) @@ -167,6 +221,12 @@ pub enum AddCustomCommands { }, } +#[derive(Args, Debug, Clone)] +pub struct ToggleCustomCommands { + /// Which access method to enable/disable + index: usize, +} + #[derive(Args, Debug, Clone)] pub struct EditCustomCommands { /// Which API proxy to edit @@ -244,6 +304,8 @@ mod conversions { type Error = Error; fn try_from(value: AddCustomCommands) -> Result { + // All api proxies are automatically enabled from the start. + let enabled = true; Ok(match value { AddCustomCommands::Socks5(variant) => match variant { Socks5AddCommands::Local { @@ -257,6 +319,7 @@ mod conversions { remote_ip.to_string(), remote_port, local_port, + enabled, ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); @@ -273,6 +336,7 @@ mod conversions { daemon_types::Socks5Remote::from_args( remote_ip.to_string(), remote_port, + enabled, ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); @@ -293,6 +357,7 @@ mod conversions { remote_port, cipher, password, + enabled, ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; shadowsocks_proxy.into() diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index ddda95e81ba4..407c142b4729 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -3,7 +3,8 @@ use crate::{ Daemon, EventListener, }; use mullvad_types::api_access_method::{ - daemon::ApiAccessMethodReplace, AccessMethod, CustomAccessMethod, + daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, + AccessMethod, CustomAccessMethod, }; #[derive(err_derive::Error, Debug)] @@ -33,6 +34,26 @@ where .map_err(Error::Settings) } + pub async fn toggle_api_access_method( + &mut self, + access_method_toggle: ApiAccessMethodToggle, + ) -> Result<(), Error> { + self.settings + .update(|settings| { + if let Some(ref mut access_method) = settings + .api_access_methods + .api_access_methods + .iter_mut() + .find(|access_method| **access_method == access_method_toggle.access_method) + { + access_method.toggle(access_method_toggle.enable); + } + }) + .await + .map(|did_change| self.notify_on_change(did_change)) + .map_err(Error::Settings) + } + pub async fn remove_access_method( &mut self, access_method: CustomAccessMethod, diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 1bcc77f44bc3..4a3bd6fabe8b 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -129,8 +129,8 @@ impl ApiConnectionModeProvider { fn from(&mut self, access_method: &AccessMethod) -> ApiConnectionMode { match access_method { AccessMethod::BuiltIn(access_method) => match access_method { - BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, - BuiltInAccessMethod::Bridge => self + BuiltInAccessMethod::Direct(_enabled) => ApiConnectionMode::Direct, + BuiltInAccessMethod::Bridge(enabled) => self .relay_selector .get_bridge_forced() .and_then(|settings| match settings { diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 6e7b98d8eccc..8427b1ea6b8f 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -40,7 +40,10 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::{daemon::ApiAccessMethodReplace, AccessMethod, CustomAccessMethod}, + api_access_method::{ + daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, + AccessMethod, CustomAccessMethod, + }, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -268,6 +271,8 @@ pub enum DaemonCommand { RemoveApiAccessMethod(ResponseTx<(), Error>, CustomAccessMethod), /// Edit an API access method ReplaceApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodReplace), + /// Toggle the status of an API access method + ToggleApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodToggle), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1049,6 +1054,7 @@ where ReplaceApiAccessMethod(tx, method) => { self.on_replace_api_access_method(tx, method).await } + ToggleApiAccessMethod(tx, method) => self.on_toggle_api_access_method(tx, method).await, IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), #[cfg(not(target_os = "android"))] @@ -2260,6 +2266,18 @@ where Self::oneshot_send(tx, result, "edit_api_access_method response"); } + async fn on_toggle_api_access_method( + &mut self, + tx: ResponseTx<(), Error>, + method: ApiAccessMethodToggle, + ) { + let result = self + .toggle_api_access_method(method) + .await + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "edit_api_access_method response"); + } + fn on_get_settings(&self, tx: oneshot::Sender) { Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 53586286403c..91be9352eca6 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -619,6 +619,26 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + async fn toggle_api_access_method( + &self, + request: Request, + ) -> ServiceResult<()> { + log::debug!("toggle_api_access_method"); + let access_method_toggle = + mullvad_types::api_access_method::daemon::ApiAccessMethodToggle::try_from( + request.into_inner(), + )?; + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::ToggleApiAccessMethod( + tx, + access_method_toggle, + ))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + // Split tunneling // diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index bc56929fc46f..e008ca5e50d6 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -84,6 +84,9 @@ service ManagementService { rpc ReplaceApiAccessMethod(ApiAccessMethodReplace) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } + rpc ToggleApiAccessMethod(ApiAccessMethodToggle) returns (google.protobuf.Empty) { + // Can I return something useful here instead of Empty? + } // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} @@ -328,6 +331,16 @@ message ObfuscationSettings { Udp2TcpObfuscationSettings udp2tcp = 2; } +message ApiAccessMethodReplace { + uint32 index = 1; + ApiAccessMethod access_method = 2; +} + +message ApiAccessMethodToggle { + ApiAccessMethod access_method = 1; + bool enable = 2; +} + message CustomList { string id = 1; string name = 2; @@ -337,18 +350,20 @@ message CustomList { message CustomListSettings { repeated CustomList custom_lists = 1; } message ApiAccessMethod { - message Direct {} - message Bridges {} + message Direct { bool enabled = 1; } + message Bridges { bool enabled = 1; } message Socks5Local { - string id = 1; - string ip = 2; - uint32 port = 3; - uint32 local_port = 4; + bool enabled = 1; + string id = 2; + string ip = 3; + uint32 port = 4; + uint32 local_port = 5; } message Socks5Remote { - string id = 1; - string ip = 2; - uint32 port = 3; + bool enabled = 1; + string id = 2; + string ip = 3; + uint32 port = 4; } message Socks5 { oneof Socks5type { @@ -357,11 +372,12 @@ message ApiAccessMethod { } } message Shadowsocks { - string id = 1; - string ip = 2; - uint32 port = 3; - string password = 4; - string cipher = 5; + bool enabled = 1; + string id = 2; + string ip = 3; + uint32 port = 4; + string password = 5; + string cipher = 6; } oneof access_method { Direct direct = 1; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 15a5be189239..fb2eae28c3ee 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -4,7 +4,10 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, - api_access_method::AccessMethod, + api_access_method::{ + daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, + AccessMethod, + }, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -480,6 +483,17 @@ impl MullvadProxyClient { .map(drop) } + pub async fn toggle_access_method( + &mut self, + access_method_toggle: ApiAccessMethodToggle, + ) -> Result<()> { + self.0 + .toggle_api_access_method(types::ApiAccessMethodToggle::from(access_method_toggle)) + .await + .map_err(Error::Rpc) + .map(drop) + } + pub async fn remove_access_method(&mut self, access_method: AccessMethod) -> Result<()> { self.0 .remove_api_access_method(types::ApiAccessMethod::from(access_method)) 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 5612c18b1d81..0b944dd44d92 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -59,6 +59,31 @@ mod settings { }) } } + + impl From for proto::ApiAccessMethodToggle { + fn from(value: api_access_method::daemon::ApiAccessMethodToggle) -> Self { + proto::ApiAccessMethodToggle { + access_method: Some(value.access_method.into()), + enable: value.enable, + } + } + } + + impl TryFrom for api_access_method::daemon::ApiAccessMethodToggle { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::ApiAccessMethodToggle) -> Result { + Ok(api_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)?, + enable: value.enable, + }) + } + } } /// Implements conversions for the 'main' AccessMethod data type. @@ -101,6 +126,7 @@ mod data { local.ip, local.port as u16, local.local_port as u16, + local.enabled, ) .ok_or(FromProtobufTypeError::InvalidArgument( "Could not parse Socks5 (local) message from protobuf", @@ -108,7 +134,7 @@ mod data { .into(), Socks5type::Remote(remote) => { - Socks5Remote::from_args(remote.ip, remote.port as u16) + Socks5Remote::from_args(remote.ip, remote.port as u16, remote.enabled) .ok_or({ FromProtobufTypeError::InvalidArgument( "Could not parse Socks5 (remote) message from protobuf", @@ -118,18 +144,22 @@ mod data { } } } - proto::api_access_method::AccessMethod::Shadowsocks(ss) => { - Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password) - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not parse Shadowsocks message from protobuf", - ))? - .into() - } - proto::api_access_method::AccessMethod::Direct(_) => { - BuiltInAccessMethod::Direct.into() + proto::api_access_method::AccessMethod::Shadowsocks(ss) => Shadowsocks::from_args( + ss.ip, + ss.port as u16, + ss.cipher, + ss.password, + ss.enabled, + ) + .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::Bridges(_) => { - BuiltInAccessMethod::Bridge.into() + proto::api_access_method::AccessMethod::Bridges(bridge_settings) => { + BuiltInAccessMethod::Bridge(bridge_settings.enabled).into() } }) } @@ -145,33 +175,38 @@ mod data { port: ss.peer.port() as u32, password: ss.password, cipher: ss.cipher, + enabled: ss.enabled, } .into(), - ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { peer, port })) => { - 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::Local(Socks5Local { + peer, + port, + enabled, + })) => proto::api_access_method::Socks5Local { + id: value.id, + ip: peer.ip().to_string(), + port: peer.port() as u32, + local_port: port as u32, + enabled, } - ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + .into(), + ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer, enabled })) => { proto::api_access_method::Socks5Remote { id: value.id, ip: peer.ip().to_string(), port: peer.port() as u32, + enabled, } .into() } }, AccessMethod::BuiltIn(value) => match value { - mullvad_types::api_access_method::BuiltInAccessMethod::Direct => { - proto::api_access_method::Direct {}.into() + mullvad_types::api_access_method::BuiltInAccessMethod::Direct(enabled) => { + proto::api_access_method::Direct { enabled }.into() } - mullvad_types::api_access_method::BuiltInAccessMethod::Bridge => { - proto::api_access_method::Bridges {}.into() + mullvad_types::api_access_method::BuiltInAccessMethod::Bridge(enabled) => { + proto::api_access_method::Bridges { enabled }.into() } }, } diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index af456f764985..5a4cd6cdcf72 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -15,8 +15,8 @@ impl Default for Settings { fn default() -> Self { Self { api_access_methods: vec![ - BuiltInAccessMethod::Direct.into(), - BuiltInAccessMethod::Bridge.into(), + BuiltInAccessMethod::Direct(true).into(), + BuiltInAccessMethod::Bridge(true).into(), ], } } @@ -32,8 +32,8 @@ pub enum AccessMethod { /// Built-In access method datastructure. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum BuiltInAccessMethod { - Direct, - Bridge, + Direct(bool), + Bridge(bool), } /// Custom access method datastructure. @@ -54,6 +54,7 @@ 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, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] @@ -67,11 +68,13 @@ pub struct Socks5Local { pub peer: SocketAddr, /// Port on localhost where the SOCKS5-proxy listens to. pub port: u16, + pub enabled: bool, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Socks5Remote { pub peer: SocketAddr, + pub enabled: bool, } impl Settings { @@ -96,50 +99,96 @@ impl AccessMethod { 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) -> Self { + pub fn new(peer: SocketAddr, cipher: String, password: String, enabled: bool) -> Self { Shadowsocks { peer, password, cipher, + enabled, } } /// 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 { + pub fn from_args( + ip: String, + port: u16, + cipher: String, + password: String, + enabled: bool, + ) -> Option { let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); - Some(Self::new(peer, cipher, password)) + Some(Self::new(peer, cipher, password, enabled)) } } impl Socks5Local { - pub fn new(peer: SocketAddr, port: u16) -> Self { - Self { peer, port } + pub fn new(peer: SocketAddr, port: u16, enabled: bool) -> Self { + Self { + peer, + port, + enabled, + } } /// 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 { + pub fn from_args(ip: String, port: u16, localport: u16, enabled: bool) -> Option { let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer, localport)) + Some(Self::new(peer, localport, enabled)) } } impl Socks5Remote { - pub fn new(peer: SocketAddr) -> Self { - Self { peer } + pub fn new(peer: SocketAddr, enabled: bool) -> Self { + Self { peer, enabled } } /// 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 { + pub fn from_args(ip: String, port: u16, enabled: bool) -> Option { let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer)) + Some(Self::new(peer, enabled)) } } @@ -213,4 +262,10 @@ pub mod daemon { 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, + } } From b66d4bf941cba35375fb6c3ae1665546d0a16d12 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 18 Sep 2023 11:55:41 +0200 Subject: [PATCH 11/29] Minor code cleanup - Fix removal of API access methods from daemon settings When removing an API access method, we now compare the ID (hash) of the "access method-to-remove" with the access methods stored in the daemon settings. This is because using the `==`/`!=` operator on two similiar `AccessMethod`s only differing in the `enabled` field will be different from hashing the objects and comparing those instead. The hash does not consider the `enabled` field in its calculation. --- mullvad-daemon/src/access_methods.rs | 12 ++++----- mullvad-daemon/src/api.rs | 4 +-- mullvad-types/src/api_access_method.rs | 37 ++++++++++++++++++++------ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index 407c142b4729..79afdae05423 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -40,7 +40,7 @@ where ) -> Result<(), Error> { self.settings .update(|settings| { - if let Some(ref mut access_method) = settings + if let Some(access_method) = settings .api_access_methods .api_access_methods .iter_mut() @@ -58,13 +58,13 @@ where &mut self, access_method: CustomAccessMethod, ) -> Result<(), Error> { - let access_method = AccessMethod::from(access_method); self.settings .update(|settings| { - settings - .api_access_methods - .api_access_methods - .retain(|x| *x != access_method); + settings.api_access_methods.api_access_methods.retain(|x| { + x.as_custom() + .map(|c| c.id != access_method.id) + .unwrap_or(true) + }); }) .await .map(|did_change| self.notify_on_change(did_change)) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 4a3bd6fabe8b..26974ed4ee1b 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -64,7 +64,7 @@ impl Stream for ApiConnectionModeProvider { }; } - let connection_mode = self.new_task(self.retry_attempt); + let connection_mode = self.new_connection_mode(); self.retry_attempt = self.retry_attempt.wrapping_add(1); let cache_dir = self.cache_dir.clone(); @@ -86,10 +86,8 @@ impl ApiConnectionModeProvider { pub(crate) fn new(cache_dir: PathBuf, relay_selector: RelaySelector) -> Self { Self { cache_dir, - relay_selector, retry_attempt: 0, - current_task: None, } } diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index 5a4cd6cdcf72..e3a423db7f0e 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -50,6 +50,12 @@ pub enum ObfuscationProtocol { } #[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)? @@ -57,13 +63,7 @@ pub struct Shadowsocks { pub enabled: bool, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum Socks5 { - Local(Socks5Local), - Remote(Socks5Remote), -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Socks5Local { pub peer: SocketAddr, /// Port on localhost where the SOCKS5-proxy listens to. @@ -71,12 +71,33 @@ pub struct Socks5Local { pub enabled: bool, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Socks5Remote { pub peer: SocketAddr, pub enabled: bool, } +impl Hash for Shadowsocks { + fn hash(&self, state: &mut H) { + self.peer.hash(state); + self.password.hash(state); + self.cipher.hash(state); + } +} + +impl Hash for Socks5Local { + fn hash(&self, state: &mut H) { + self.peer.hash(state); + self.port.hash(state); + } +} + +impl Hash for Socks5Remote { + fn hash(&self, state: &mut H) { + self.peer.hash(state); + } +} + impl Settings { // TODO: Do I have to clone? pub fn get_access_methods(&self) -> Vec { From be3da7e67723c96bdb997fb6e7dcbfebc036919d Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 18 Sep 2023 15:30:50 +0200 Subject: [PATCH 12/29] Add naming custom access methods Just a bookkeeping feature for the end user --- mullvad-cli/src/cmds/proxy.rs | 28 ++++++++++-- mullvad-daemon/src/api.rs | 3 +- .../proto/management_interface.proto | 27 ++++++----- .../types/conversions/api_access_method.rs | 45 ++++++++++++------- mullvad-types/src/api_access_method.rs | 40 +++++++++++++---- 5 files changed, 100 insertions(+), 43 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 74e0e22c224f..507d5fa46daf 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -130,9 +130,10 @@ impl Proxy { 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, + ip, port, cipher, password, enabled, name, ) .map(|x| x.into()) } @@ -141,18 +142,22 @@ impl Proxy { 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, + ip, port, local_port, enabled, name, ) .map(|x| x.into()) } mullvad_types::api_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) - .map(|x| x.into()) + mullvad_types::api_access_method::Socks5Remote::from_args( + ip, port, enabled, name, + ) + .map(|x| x.into()) } }, } @@ -207,6 +212,8 @@ pub enum AddCustomCommands { /// Configure bundled Shadowsocks proxy Shadowsocks { + /// An easy to remember name for this custom proxy + name: String, /// The IP of the remote Shadowsocks server remote_ip: IpAddr, /// The port of the remote Shadowsocks server @@ -246,6 +253,8 @@ pub struct RemoveCustomCommands { pub enum Socks5AddCommands { /// Configure a local SOCKS5 proxy Local { + /// An easy to remember name for this custom proxy + name: String, /// The port that the server on localhost is listening on local_port: u16, /// The IP of the remote peer @@ -255,6 +264,8 @@ pub enum Socks5AddCommands { }, /// Configure a remote SOCKS5 proxy Remote { + /// An easy to remember name for this custom proxy + name: String, /// The IP of the remote proxy server remote_ip: IpAddr, /// The port of the remote proxy server @@ -270,6 +281,9 @@ pub enum Socks5AddCommands { #[derive(Args, Debug, Clone)] pub struct EditParams { + /// Name of the API proxy in the Mullvad client [All] + #[arg(long)] + name: Option, /// Username for authentication [Shadowsocks] #[arg(long)] username: Option, @@ -312,6 +326,7 @@ mod conversions { local_port, remote_ip, remote_port, + name, } => { println!("Adding LOCAL SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Local( @@ -320,6 +335,7 @@ mod conversions { remote_port, local_port, enabled, + name, ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); @@ -330,6 +346,7 @@ mod conversions { remote_port, username, password, + name, } => { println!("Adding REMOTE SOCKS5-proxy: {username:?}+{password:?} @ {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Remote( @@ -337,6 +354,7 @@ mod conversions { remote_ip.to_string(), remote_port, enabled, + name, ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); @@ -348,6 +366,7 @@ mod conversions { remote_port, password, cipher, + name, } => { println!( "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" @@ -358,6 +377,7 @@ mod conversions { cipher, password, enabled, + name, ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; shadowsocks_proxy.into() diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 26974ed4ee1b..e8568ac6847a 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -138,7 +138,8 @@ impl ApiConnectionModeProvider { ss_settings.peer, ss_settings.cipher, ss_settings.password, - *enabled, + enabled, + "Mullvad Bridges".to_string(), // TODO: Move this to some central location ); Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( ss_settings, diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index e008ca5e50d6..ffc3f12120b0 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -354,16 +354,18 @@ message ApiAccessMethod { message Bridges { bool enabled = 1; } message Socks5Local { bool enabled = 1; - string id = 2; - string ip = 3; - uint32 port = 4; - uint32 local_port = 5; + string name = 2; + string id = 3; + string ip = 4; + uint32 port = 5; + uint32 local_port = 6; } message Socks5Remote { bool enabled = 1; - string id = 2; - string ip = 3; - uint32 port = 4; + string name = 2; + string id = 3; + string ip = 4; + uint32 port = 5; } message Socks5 { oneof Socks5type { @@ -373,11 +375,12 @@ message ApiAccessMethod { } message Shadowsocks { bool enabled = 1; - string id = 2; - string ip = 3; - uint32 port = 4; - string password = 5; - string cipher = 6; + string name = 2; + string id = 3; + string ip = 4; + uint32 port = 5; + string password = 6; + string cipher = 7; } oneof access_method { Direct direct = 1; 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 0b944dd44d92..958cbf0564cf 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -127,21 +127,25 @@ mod data { 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) - .ok_or({ - FromProtobufTypeError::InvalidArgument( - "Could not parse Socks5 (remote) 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(), } } proto::api_access_method::AccessMethod::Shadowsocks(ss) => Shadowsocks::from_args( @@ -150,6 +154,7 @@ mod data { ss.cipher, ss.password, ss.enabled, + ss.name, ) .ok_or(FromProtobufTypeError::InvalidArgument( "Could not parse Shadowsocks message from protobuf", @@ -176,6 +181,7 @@ mod data { password: ss.password, cipher: ss.cipher, enabled: ss.enabled, + name: ss.name, } .into(), @@ -183,23 +189,28 @@ mod data { 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, } .into(), - ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer, enabled })) => { - proto::api_access_method::Socks5Remote { - id: value.id, - ip: peer.ip().to_string(), - port: peer.port() as u32, - enabled, - } - .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, } + .into(), }, AccessMethod::BuiltIn(value) => match value { mullvad_types::api_access_method::BuiltInAccessMethod::Direct(enabled) => { diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs index e3a423db7f0e..6aaec7171df1 100644 --- a/mullvad-types/src/api_access_method.rs +++ b/mullvad-types/src/api_access_method.rs @@ -61,6 +61,7 @@ pub struct Shadowsocks { 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)] @@ -69,12 +70,14 @@ pub struct Socks5Local { /// 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 { @@ -158,12 +161,19 @@ impl AccessMethod { } impl Shadowsocks { - pub fn new(peer: SocketAddr, cipher: String, password: String, enabled: bool) -> Self { + pub fn new( + peer: SocketAddr, + cipher: String, + password: String, + enabled: bool, + name: String, + ) -> Self { Shadowsocks { peer, password, cipher, enabled, + name, } } @@ -175,41 +185,53 @@ impl Shadowsocks { cipher: String, password: String, enabled: bool, + name: String, ) -> Option { let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into(); - Some(Self::new(peer, cipher, password, enabled)) + Some(Self::new(peer, cipher, password, enabled, name)) } } impl Socks5Local { - pub fn new(peer: SocketAddr, port: u16, enabled: bool) -> Self { + 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) -> Option { + pub fn from_args( + ip: String, + port: u16, + localport: u16, + enabled: bool, + name: String, + ) -> Option { let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer, localport, enabled)) + Some(Self::new(peer, localport, enabled, name)) } } impl Socks5Remote { - pub fn new(peer: SocketAddr, enabled: bool) -> Self { - Self { peer, enabled } + 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) -> Option { + pub fn from_args(ip: String, port: u16, enabled: bool, name: String) -> Option { let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); let peer = SocketAddr::new(peer_ip, port); - Some(Self::new(peer, enabled)) + Some(Self::new(peer, enabled, name)) } } From be5e93c32f3b3e9ec59da18215fad80457dd4d49 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 18 Sep 2023 16:27:06 +0200 Subject: [PATCH 13/29] Pretty print custom access methods `mullvad proxy list` will now pretty print all configured access methods in a human-readable way --- mullvad-cli/src/cmds/proxy.rs | 84 ++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 507d5fa46daf..48ef302c1960 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -63,17 +63,13 @@ 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, - api_access_method, - if api_access_method.enabled() { - "[enabled]" - } else { - "[disabled]" - } + AccessMethodFormatter::new(&api_access_method) ); } Ok(()) @@ -386,3 +382,77 @@ mod conversions { } } } + +/// Pretty printing of [`AccessMethod`]s +mod pp { + use mullvad_types::api_access_method::{ + AccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Socks5, + }; + + pub struct AccessMethodFormatter<'a> { + access_method: &'a AccessMethod, + } + + impl<'a> AccessMethodFormatter<'a> { + pub fn new(access_method: &'a AccessMethod) -> AccessMethodFormatter<'a> { + AccessMethodFormatter { access_method } + } + } + + impl<'a> std::fmt::Display for AccessMethodFormatter<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use crate::print_option; + + let write_status = |f: &mut std::fmt::Formatter<'_>, enabled: bool| { + if enabled { + write!(f, " *") + } else { + write!(f, "") + } + }; + + 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) + } + }, + AccessMethod::Custom(method) => match &method.access_method { + ObfuscationProtocol::Shadowsocks(shadowsocks) => { + write!(f, "{}", shadowsocks.name)?; + write_status(f, shadowsocks.enabled)?; + writeln!(f, "")?; + print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher)); + print_option!("Peer", shadowsocks.peer); + print_option!("Password", shadowsocks.password); + Ok(()) + } + ObfuscationProtocol::Socks5(socks) => match socks { + Socks5::Remote(remote) => { + write!(f, "{}", remote.name)?; + write_status(f, remote.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, "")?; + print_option!("Protocol", "Socks5 (local)"); + print_option!("Peer", local.peer); + print_option!("Local port", local.port); + Ok(()) + } + }, + }, + } + } + } +} From fc477f4e4df6db2973ff88fc4b6819d38d64d8cc Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 19 Sep 2023 13:43:53 +0200 Subject: [PATCH 14/29] Add `mullvad proxy use` Allow for settings a specific Access Method to use --- mullvad-cli/src/cmds/proxy.rs | 35 +++++++-- mullvad-daemon/src/access_methods.rs | 29 +++++--- mullvad-daemon/src/api.rs | 72 ++++++++++++++++--- mullvad-daemon/src/lib.rs | 28 ++++++-- mullvad-daemon/src/management_interface.rs | 15 ++++ .../proto/management_interface.proto | 3 + mullvad-management-interface/src/client.rs | 17 +++++ 7 files changed, 169 insertions(+), 30 deletions(-) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index 48ef302c1960..c876a25d2ede 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -21,9 +21,11 @@ pub enum Proxy { /// Remove an API proxy Remove(RemoveCustomCommands), /// Enable an API proxy - Enable(ToggleCustomCommands), + Enable(SelectItem), /// Disable an API proxy - Disable(ToggleCustomCommands), + Disable(SelectItem), + /// Force the use of a specific API proxy. + Use(SelectItem), } impl Proxy { @@ -57,6 +59,10 @@ impl Proxy { let enabled = false; Self::toggle(index, enabled).await?; } + Proxy::Use(cmd) => { + let index = Self::zero_to_one_based_index(cmd.index)?; + Self::set(index).await?; + } }; Ok(()) } @@ -193,6 +199,25 @@ impl Proxy { Ok(()) } + /// 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<()> { + 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(); + + rpc.set_access_method(access_method).await?; + Ok(()) + } + fn zero_to_one_based_index(index: usize) -> Result { index .checked_sub(1) @@ -224,9 +249,11 @@ 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 ToggleCustomCommands { - /// Which access method to enable/disable +pub struct SelectItem { + /// Which access method to pick index: usize, } diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs index 79afdae05423..a0c168c026fe 100644 --- a/mullvad-daemon/src/access_methods.rs +++ b/mullvad-daemon/src/access_methods.rs @@ -86,22 +86,29 @@ where .map_err(Error::Settings) } + pub async fn set_api_access_method( + &mut self, + access_method: AccessMethod, + ) -> Result<(), Error> { + { + let mut connection_modes = self.connection_modes.lock().unwrap(); + connection_modes.set_access_method(access_method); + } + // Force a rotation of Acces Methods. + let _ = self.api_handle.service().next_api_endpoint(); + Ok(()) + } + /// If settings were changed due to an update, notify all listeners. fn notify_on_change(&mut self, settings_changed: MadeChanges) { if settings_changed { 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 = self - .settings - .api_access_methods - .api_access_methods - .clone() - .into_iter() - .map(|x| (x, 1)) - .collect(); + // 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()) + }; } } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index e8568ac6847a..f96dea9484e5 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -44,6 +44,58 @@ pub struct ApiConnectionModeProvider { relay_selector: RelaySelector, retry_attempt: u32, current_task: Option + Send>>>, + connection_modes: Arc>, +} + +/// 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 + Send>, + next: Option, +} + +impl ConnectionModesIterator { + pub fn new(modes: Vec) -> 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) { + self.available_modes = Self::get_filtered_access_methods(access_methods); + } + + fn get_filtered_access_methods( + modes: Vec, + ) -> Box + 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.next.take().or_else(|| self.available_modes.next()) + } } impl Stream for ApiConnectionModeProvider { @@ -83,23 +135,22 @@ impl Stream for ApiConnectionModeProvider { } impl ApiConnectionModeProvider { - pub(crate) fn new(cache_dir: PathBuf, relay_selector: RelaySelector) -> Self { + pub(crate) fn new( + cache_dir: PathBuf, + relay_selector: RelaySelector, + connection_modes: Vec, + ) -> Self { Self { cache_dir, relay_selector, retry_attempt: 0, current_task: None, + connection_modes: Arc::new(Mutex::new(ConnectionModesIterator::new(connection_modes))), } } - fn should_use_bridge(retry_attempt: u32) -> bool { - retry_attempt % 3 > 0 - } - - fn should_use_direct(retry_attempt: u32) -> bool { - // TODO: Change back before comitting! - false - // !Self::should_use_bridge(retry_attempt) + pub(crate) fn handle(&self) -> Arc> { + self.connection_modes.clone() } /// Return a new connection mode to be used for the API connection. @@ -112,10 +163,9 @@ impl ApiConnectionModeProvider { log::debug!("Rotating Access mode!"); let access_method = { let mut access_methods_picker = self.connection_modes.lock().unwrap(); - // Rotate through the cycle of access methods. - // Safety: It is always safe to unwrap after calling `next` on a [`std::iter::Cycle`] access_methods_picker.next().unwrap() }; + let connection_mode = self.from(&access_method); log::info!("New API connection mode selected: {}", connection_mode); connection_mode diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 8427b1ea6b8f..3c822fdf4e84 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -65,7 +65,7 @@ use std::{ mem, path::PathBuf, pin::Pin, - sync::{Arc, Weak}, + sync::{Arc, Mutex, Weak}, time::Duration, }; #[cfg(any(target_os = "linux", windows))] @@ -273,6 +273,8 @@ pub enum DaemonCommand { ReplaceApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodReplace), /// Toggle the status of an API access method ToggleApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodToggle), + /// Set the API access method to use + SetApiAccessMethod(ResponseTx<(), Error>, AccessMethod), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -572,6 +574,7 @@ pub struct Daemon { account_history: account_history::AccountHistory, device_checker: device::TunnelStateChangeHandler, account_manager: device::AccountManagerHandle, + connection_modes: Arc>, api_runtime: mullvad_api::Runtime, api_handle: mullvad_api::rest::MullvadRestHandle, version_updater_handle: version_check::VersionUpdaterHandle, @@ -634,8 +637,15 @@ where let initial_selector_config = new_selector_config(&settings); let relay_selector = RelaySelector::new(initial_selector_config, &resource_dir, &cache_dir); - let proxy_provider = - api::ApiConnectionModeProvider::new(cache_dir.clone(), relay_selector.clone()); + // 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(), + ); + + let connection_modes = proxy_provider.handle(); + let api_handle = api_runtime .mullvad_rest_handle(proxy_provider, endpoint_updater.callback()) .await; @@ -772,6 +782,7 @@ where account_history, device_checker: device::TunnelStateChangeHandler::new(account_manager.clone()), account_manager, + connection_modes, api_runtime, api_handle, version_updater_handle, @@ -1055,6 +1066,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, IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx), GetCurrentVersion(tx) => self.on_get_current_version(tx), #[cfg(not(target_os = "android"))] @@ -2275,7 +2287,15 @@ where .toggle_api_access_method(method) .await .map_err(Error::AccessMethodError); - Self::oneshot_send(tx, result, "edit_api_access_method response"); + Self::oneshot_send(tx, result, "toggle_api_access_method response"); + } + + async fn on_set_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: AccessMethod) { + let result = self + .set_api_access_method(method) + .await + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "set_api_access_method response"); } fn on_get_settings(&self, tx: oneshot::Sender) { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 91be9352eca6..2b6481207a12 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -639,6 +639,21 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + async fn set_api_access_method( + &self, + request: Request, + ) -> ServiceResult<()> { + log::debug!("set_api_access_method"); + let access_method = + mullvad_types::api_access_method::AccessMethod::try_from(request.into_inner())?; + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, access_method))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + // Split tunneling // diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index ffc3f12120b0..a062e0b64217 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -87,6 +87,9 @@ service ManagementService { rpc ToggleApiAccessMethod(ApiAccessMethodToggle) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } + rpc SetApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { + // Can I return something useful here instead of Empty? + } // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index fb2eae28c3ee..900775b10a6a 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -513,6 +513,23 @@ impl MullvadProxyClient { .map(drop) } + /// Set the [`AccessMethod`] which [`ApiConnectionModeProvider`] should + /// pick. + /// + /// - `access_method`: If `Some(access_method)`, [`ApiConnectionModeProvider`] will skip + /// ahead and return `access_method` when asked for a new access method. + /// If `None`, [`ApiConnectionModeProvider`] will pick the next access + /// method "randomly" + /// + /// [`ApiConnectionModeProvider`]: mullvad_daemon::api::ApiConnectionModeProvider + pub async fn set_access_method(&mut self, access_method: AccessMethod) -> Result<()> { + self.0 + .set_api_access_method(types::ApiAccessMethod::from(access_method)) + .await + .map_err(Error::Rpc) + .map(drop) + } + #[cfg(target_os = "linux")] pub async fn get_split_tunnel_processes(&mut self) -> Result> { use futures::TryStreamExt; From 7fcaad31643ca324c5dd4a17ddf0f445ac679384 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 20 Sep 2023 13:41:43 +0200 Subject: [PATCH 15/29] Add `mullvad proxy test` For quickly assessing whether an api access method can reach the API or not. --- mullvad-cli/src/cmds/proxy.rs | 34 +++++++++++++++++++ mullvad-daemon/src/lib.rs | 10 ++++++ mullvad-daemon/src/management_interface.rs | 11 ++++++ .../proto/management_interface.proto | 2 ++ mullvad-management-interface/src/client.rs | 5 +++ 5 files changed, 62 insertions(+) diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs index c876a25d2ede..fa05081f8907 100644 --- a/mullvad-cli/src/cmds/proxy.rs +++ b/mullvad-cli/src/cmds/proxy.rs @@ -24,6 +24,8 @@ pub enum Proxy { Enable(SelectItem), /// Disable an API proxy Disable(SelectItem), + /// Test an API proxy + Test(SelectItem), /// Force the use of a specific API proxy. Use(SelectItem), } @@ -59,6 +61,10 @@ impl Proxy { let enabled = false; Self::toggle(index, enabled).await?; } + Proxy::Test(cmd) => { + let index = Self::zero_to_one_based_index(cmd.index)?; + Self::test(index).await?; + } Proxy::Use(cmd) => { let index = Self::zero_to_one_based_index(cmd.index)?; Self::set(index).await?; @@ -199,6 +205,34 @@ impl Proxy { Ok(()) } + /// Test an access method to see if it successfully reaches the Mullvad API. + async fn test(index: usize) -> 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. + 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 :-("), + } + + Ok(()) + } + /// 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). diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 3c822fdf4e84..ed7bafd00796 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -275,6 +275,8 @@ pub enum DaemonCommand { ToggleApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodToggle), /// Set the API access method to use SetApiAccessMethod(ResponseTx<(), Error>, AccessMethod), + /// Get the addresses of all known API endpoints + GetApiAddresses(ResponseTx, Error>), /// Get information about the currently running and latest app versions GetVersionInfo(oneshot::Sender>), /// Return whether the daemon is performing post-upgrade tasks @@ -1067,6 +1069,7 @@ where } ToggleApiAccessMethod(tx, method) => self.on_toggle_api_access_method(tx, method).await, SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method).await, + 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), #[cfg(not(target_os = "android"))] @@ -2298,6 +2301,13 @@ where Self::oneshot_send(tx, result, "set_api_access_method response"); } + async fn on_get_api_addresses(&mut self, tx: ResponseTx, Error>) { + let api_proxy = mullvad_api::ApiProxy::new(self.api_handle.clone()); + let result = api_proxy.get_api_addrs().await.map_err(Error::RestError); + + Self::oneshot_send(tx, result, "on_get_api_adressess response"); + } + fn on_get_settings(&self, tx: oneshot::Sender) { Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response"); } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 2b6481207a12..88ea87056c09 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -654,6 +654,17 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + async fn get_api_addressess(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("test_api"); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::GetApiAddresses(tx))?; + self.wait_for_result(rx) + .await? + .map(drop) + .map(Response::new) + .map_err(map_daemon_error) + } + // Split tunneling // diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index a062e0b64217..018906cdafcb 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -91,6 +91,8 @@ service ManagementService { // Can I return something useful here instead of Empty? } + rpc GetApiAddressess(google.protobuf.Empty) returns (google.protobuf.Empty) {} + // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 900775b10a6a..049f8a2fe3f5 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -181,6 +181,11 @@ impl MullvadProxyClient { .collect() } + pub async fn test_api(&mut self) -> Result<()> { + self.0.get_api_addressess(()).await.map_err(Error::Rpc)?; + Ok(()) + } + pub async fn update_relay_locations(&mut self) -> Result<()> { self.0 .update_relay_locations(()) From 43cb757d2cbb396a627fb6b970394a7b73d37dc5 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 20 Sep 2023 15:36:01 +0200 Subject: [PATCH 16/29] 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`. --- Cargo.lock | 1 - mullvad-api/src/https_client_with_sni.rs | 47 ++- mullvad-api/src/proxy.rs | 16 +- mullvad-cli/Cargo.toml | 1 - .../src/cmds/{proxy.rs => api_access.rs} | 364 ++++++++---------- mullvad-cli/src/cmds/mod.rs | 2 +- mullvad-cli/src/main.rs | 8 +- .../{access_methods.rs => access_method.rs} | 58 ++- mullvad-daemon/src/api.rs | 152 ++++---- mullvad-daemon/src/lib.rs | 46 ++- mullvad-daemon/src/management_interface.rs | 83 +++- .../proto/management_interface.proto | 43 +-- mullvad-management-interface/src/client.rs | 26 +- .../types/conversions/api_access_method.rs | 304 +++++++-------- .../src/types/conversions/settings.rs | 2 +- mullvad-types/src/access_method.rs | 311 +++++++++++++++ mullvad-types/src/api_access_method.rs | 314 --------------- mullvad-types/src/lib.rs | 2 +- mullvad-types/src/settings/mod.rs | 6 +- talpid-types/src/net/openvpn.rs | 15 - 20 files changed, 891 insertions(+), 910 deletions(-) rename mullvad-cli/src/cmds/{proxy.rs => api_access.rs} (59%) rename mullvad-daemon/src/{access_methods.rs => access_method.rs} (59%) create mode 100644 mullvad-types/src/access_method.rs delete mode 100644 mullvad-types/src/api_access_method.rs diff --git a/Cargo.lock b/Cargo.lock index be316f8e51b3..b3b830e0ce20 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 76130de1856b..cc7557a8d2ad 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>, ) -> Result { 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>, ) -> Result { 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>, ) -> Result { 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>, ) -> Result { let socket = HttpsConnectorWithSni::open_socket( proxy_config.peer, @@ -222,12 +248,12 @@ impl TryFrom 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 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 186eea2919d2..44a2309587e5 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 fdfa16262d46..5437b1980c55 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 similarity index 59% rename from mullvad-cli/src/cmds/proxy.rs rename to mullvad-cli/src/cmds/api_access.rs index fa05081f8907..921a5da81113 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::::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 { - index - .checked_sub(1) - .ok_or(anyhow!("Access method 0 does not exist")) + async fn get_access_method( + rpc: &mut MullvadProxyClient, + item: &SelectItem, + ) -> Result { + 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, - /// Password for authentication - #[arg(requires = "username")] - password: Option, }, } +/// 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 { + 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, - /// Username for authentication [Shadowsocks] - #[arg(long)] - username: Option, /// Password for authentication [Shadowsocks] #[arg(long)] password: Option, @@ -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 for daemon_types::AccessMethod { + impl TryFrom for daemon_types::ApiAccessMethod { type Error = Error; fn try_from(value: AddCustomCommands) -> Result { @@ -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 cf715c9e9f1a..88e4184f0729 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 057eb20dea96..27855ded699d 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 similarity index 59% rename from mullvad-daemon/src/access_methods.rs rename to mullvad-daemon/src/access_method.rs index a0c168c026fe..22a6146e8f22 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 Daemon 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 f96dea9484e5..75b3ad9e47c8 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 + Send>>>, connection_modes: Arc>, } -/// 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 + Send>, - next: Option, -} - -impl ConnectionModesIterator { - pub fn new(modes: Vec) -> 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) { - self.available_modes = Self::get_filtered_access_methods(access_methods); - } - - fn get_filtered_access_methods( - modes: Vec, - ) -> Box + 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.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> { 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 + Send>, + next: Option, +} + +impl ConnectionModesIterator { + pub fn new(modes: Vec) -> 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) { + 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, + ) -> Box + 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.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 ed7bafd00796..2cb35cfcf714 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, Error>), + GetApiAccessMethods(ResponseTx, 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, 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, Error>) { - let result = Ok(self.settings.api_access_methods.api_access_methods.clone()); + fn on_get_api_access_methods(&mut self, tx: ResponseTx, 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 88ea87056c09..19c4e1e412e1 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 { + 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, + ) -> 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, + ) -> 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, + ) -> 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, ) -> 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, ) -> 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 018906cdafcb..e923ec1043fa 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 049f8a2fe3f5..315e3276fcd0 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> { + pub async fn get_api_access_methods(&mut self) -> Result> { 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 958cbf0564cf..28859c5013ce 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 for proto::ApiAccessMethodSettings { - fn from(settings: api_access_method::Settings) -> Self { + impl From for proto::ApiAccessMethodSettings { + fn from(settings: access_method::Settings) -> Self { proto::ApiAccessMethodSettings::from(&settings) } } - impl TryFrom for api_access_method::Settings { + impl TryFrom for access_method::Settings { type Error = FromProtobufTypeError; fn try_from(settings: proto::ApiAccessMethodSettings) -> Result { @@ -29,267 +31,219 @@ mod settings { api_access_methods: settings .api_access_methods .iter() - .map(api_access_method::AccessMethod::try_from) - .collect::, _>>()?, + .map(access_method::ApiAccessMethod::try_from) + .collect::, _>>()?, }) } } - impl From for proto::ApiAccessMethodReplace { - fn from(value: api_access_method::daemon::ApiAccessMethodReplace) -> Self { + impl From 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 for api_access_method::daemon::ApiAccessMethodReplace { + impl TryFrom for access_method::daemon::ApiAccessMethodReplace { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethodReplace) -> Result { - 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 for proto::ApiAccessMethodToggle { - fn from(value: api_access_method::daemon::ApiAccessMethodToggle) -> Self { + impl From 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 for api_access_method::daemon::ApiAccessMethodToggle { + impl TryFrom for access_method::daemon::ApiAccessMethodToggle { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethodToggle) -> Result { - 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 for Vec { + impl TryFrom for Vec { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethods) -> Result { value .api_access_methods .iter() - .map(AccessMethod::try_from) + .map(ApiAccessMethod::try_from) .collect() } } - impl TryFrom for AccessMethod { + impl TryFrom for ApiAccessMethod { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethod) -> Result { + 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 for proto::ApiAccessMethod { - fn from(value: AccessMethod) -> Self { - match value { + impl From 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 { - AccessMethod::try_from(value.clone()) + ApiAccessMethod::try_from(value.clone()) } } - impl From> for proto::ApiAccessMethods { - fn from(value: Vec) -> proto::ApiAccessMethods { + impl From> for proto::ApiAccessMethods { + fn from(value: Vec) -> proto::ApiAccessMethods { proto::ApiAccessMethods { api_access_methods: value.iter().map(|method| method.clone().into()).collect(), } } } - - impl From for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Shadowsocks) -> Self { - proto::api_access_method::AccessMethod::Shadowsocks(value).into() - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5) -> Self { - proto::api_access_method::AccessMethod::Socks5(value).into() - } - } - - impl From 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 for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5Local) -> Self { - proto::api_access_method::socks5::Socks5type::Local(value).into() - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Socks5Remote) -> Self { - proto::api_access_method::socks5::Socks5type::Remote(value).into() - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Direct) -> Self { - proto::api_access_method::AccessMethod::Direct(value).into() - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: proto::api_access_method::Bridges) -> Self { - proto::api_access_method::AccessMethod::Bridges(value).into() - } - } - - impl From 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 57694d31f0ae..98c195d93562 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 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 000000000000..73f51d25b828 --- /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, +} + +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(&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 { + 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 { + 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 { + 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 { + let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?); + let peer = SocketAddr::new(peer_ip, port); + Some(Self::new(peer)) + } +} + +impl From for AccessMethod { + fn from(value: BuiltInAccessMethod) -> Self { + AccessMethod::BuiltIn(value) + } +} + +impl From for AccessMethod { + fn from(value: CustomAccessMethod) -> Self { + AccessMethod::Custom(value) + } +} + +impl From 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 for AccessMethod { + fn from(value: Shadowsocks) -> Self { + ObfuscationProtocol::Shadowsocks(value).into() + } +} + +impl From for AccessMethod { + fn from(value: Socks5) -> Self { + AccessMethod::from(ObfuscationProtocol::Socks5(value)) + } +} + +impl From for AccessMethod { + fn from(value: Socks5Remote) -> Self { + Socks5::Remote(value).into() + } +} + +impl From for AccessMethod { + fn from(value: Socks5Local) -> Self { + Socks5::Local(value).into() + } +} + +impl From for Socks5 { + fn from(value: Socks5Remote) -> Self { + Socks5::Remote(value) + } +} + +impl From 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 6aaec7171df1..000000000000 --- 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, -} - -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(&self, state: &mut H) { - self.peer.hash(state); - self.password.hash(state); - self.cipher.hash(state); - } -} - -impl Hash for Socks5Local { - fn hash(&self, state: &mut H) { - self.peer.hash(state); - self.port.hash(state); - } -} - -impl Hash for Socks5Remote { - fn hash(&self, state: &mut H) { - self.peer.hash(state); - } -} - -impl Settings { - // TODO: Do I have to clone? - pub fn get_access_methods(&self) -> Vec { - 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 { - 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 { - 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 { - 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 for AccessMethod { - fn from(value: BuiltInAccessMethod) -> Self { - AccessMethod::BuiltIn(value) - } -} - -impl From for AccessMethod { - fn from(value: CustomAccessMethod) -> Self { - AccessMethod::Custom(value) - } -} - -impl From 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 for AccessMethod { - fn from(value: Shadowsocks) -> Self { - ObfuscationProtocol::Shadowsocks(value).into() - } -} - -impl From for AccessMethod { - fn from(value: Socks5) -> Self { - ObfuscationProtocol::Socks5(value).into() - } -} - -impl From for AccessMethod { - fn from(value: Socks5Remote) -> Self { - Socks5::Remote(value).into() - } -} - -impl From for AccessMethod { - fn from(value: Socks5Local) -> Self { - Socks5::Local(value).into() - } -} - -impl From for Socks5 { - fn from(value: Socks5Remote) -> Self { - Socks5::Remote(value) - } -} - -impl From 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 8b5a5016636e..8aefaeb4000b 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 311a154c28ca..6ade7dea3271 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 7dc64ca80c47..5968331b526c 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] = [ From 6a6843cbe2775aa6ad24a1f61d6b863ff982b817 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 22 Sep 2023 16:08:33 +0200 Subject: [PATCH 17/29] Refactor protobuf `ApiAccessMethod` definitions - Replace rpcs `ReplaceApiAccessMethod` and `ToggleApiAccessMethod` in favor of a commmon `UpdateApiAccessMethod` (which resembles `ReplaceApiAccessMethod` in a lot of ways). - `UpdateApiAccessMethod` works with unique identifiers instead of array indices to pinpoint which API access method to update. - Toggling an API access method to be enabled/disabled now happens via `UpdateApiAccessMethod` - Add the useful `UUID` protobuf type definition, which conveys more information that a raw string. - Refactor `SetApiAccessMethod` to use API access method ID - Update `ApiAcessMethod` messages to use `UUID` type for uuid values - Use unique id for removing custom `ApiAccessMethods` --- mullvad-cli/src/cmds/api_access.rs | 97 +++++------ mullvad-daemon/src/access_method.rs | 55 +++--- mullvad-daemon/src/api.rs | 10 +- mullvad-daemon/src/lib.rs | 49 ++---- mullvad-daemon/src/management_interface.rs | 56 ++----- .../proto/management_interface.proto | 43 ++--- mullvad-management-interface/src/client.rs | 66 ++++++-- mullvad-management-interface/src/lib.rs | 3 + .../types/conversions/api_access_method.rs | 146 +++++++++------- mullvad-types/src/access_method.rs | 157 +++++++++++------- 10 files changed, 357 insertions(+), 325 deletions(-) diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 921a5da81113..e9bf6f9b32d4 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -1,8 +1,7 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::access_method::{ - daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - AccessMethod, ApiAccessMethod, ObfuscationProtocol, + daemon::ApiAccessMethodUpdate, AccessMethod, ApiAccessMethod, CustomAccessMethod, }; use std::net::IpAddr; @@ -44,12 +43,10 @@ impl ApiAccess { ApiAccess::Edit(cmd) => Self::edit(cmd).await?, ApiAccess::Remove(cmd) => Self::remove(cmd).await?, ApiAccess::Enable(cmd) => { - let enabled = true; - Self::toggle(cmd, enabled).await?; + Self::enable(cmd).await?; } ApiAccess::Disable(cmd) => { - let enabled = false; - Self::toggle(cmd, enabled).await?; + Self::disable(cmd).await?; } ApiAccess::Test(cmd) => { Self::test(cmd).await?; @@ -95,14 +92,15 @@ impl ApiAccess { async fn edit(cmd: EditCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?; + let id = api_access_method.get_id(); 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 edited_access_method: ApiAccessMethod = match access_method.access_method { - ObfuscationProtocol::Shadowsocks(shadowsocks) => { + let edited_access_method: ApiAccessMethod = match access_method { + CustomAccessMethod::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); @@ -110,39 +108,27 @@ impl ApiAccess { 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), + .map(|shadowsocks| { + ApiAccessMethod::new(name, enabled, AccessMethod::from(shadowsocks)) }) } - ObfuscationProtocol::Socks5(socks) => match socks { + CustomAccessMethod::Socks5(socks) => match socks { 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(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), - }, - ) + mullvad_types::access_method::Socks5Local::from_args(ip, port, local_port) + .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) } 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(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), - } - }) + mullvad_types::access_method::Socks5Remote::from_args(ip, port) + .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) } }, } @@ -151,8 +137,8 @@ impl ApiAccess { cmd.item ))?; - rpc.replace_access_method(ApiAccessMethodReplace { - index: cmd.item.as_array_index()?, + rpc.update_access_method(ApiAccessMethodUpdate { + id, access_method: edited_access_method, }) .await?; @@ -160,15 +146,19 @@ impl ApiAccess { Ok(()) } - /// Toggle a custom API access method to be enabled or disabled. - async fn toggle(item: SelectItem, enabled: bool) -> Result<()> { + /// Enable a custom API access method. + async fn enable(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let access_method = Self::get_access_method(&mut rpc, &item).await?; - rpc.toggle_access_method(ApiAccessMethodToggle { - access_method, - enable: enabled, - }) - .await?; + rpc.enable_access_method(access_method.get_id()).await?; + Ok(()) + } + + /// Disable a custom API access method. + async fn disable(item: SelectItem) -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + let access_method = Self::get_access_method(&mut rpc, &item).await?; + rpc.disable_access_method(access_method.get_id()).await?; Ok(()) } @@ -176,14 +166,13 @@ impl ApiAccess { async fn test(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let access_method = Self::get_access_method(&mut rpc, &item).await?; - let name = access_method.get_name(); - rpc.set_access_method(access_method).await?; + rpc.set_access_method(access_method.get_id()).await?; // 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 + access_method.name ), } @@ -196,7 +185,7 @@ impl ApiAccess { async fn set(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let access_method = Self::get_access_method(&mut rpc, &item).await?; - rpc.set_access_method(access_method).await?; + rpc.set_access_method(access_method.get_id()).await?; Ok(()) } @@ -348,11 +337,11 @@ mod conversions { ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); - daemon_types::ApiAccessMethod { + daemon_types::ApiAccessMethod::new( name, enabled, - access_method: daemon_types::AccessMethod::from(socks_proxy), - } + daemon_types::AccessMethod::from(socks_proxy), + ) } Socks5AddCommands::Remote { remote_ip, @@ -367,11 +356,11 @@ mod conversions { ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); - daemon_types::ApiAccessMethod { + daemon_types::ApiAccessMethod::new( name, enabled, - access_method: socks_proxy.into(), - } + daemon_types::AccessMethod::from(socks_proxy), + ) } }, AddCustomCommands::Shadowsocks { @@ -392,11 +381,11 @@ mod conversions { ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - daemon_types::ApiAccessMethod { + daemon_types::ApiAccessMethod::new( name, enabled, - access_method: shadowsocks_proxy.into(), - } + daemon_types::AccessMethod::from(shadowsocks_proxy), + ) } }) } @@ -405,9 +394,7 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::access_method::{ - AccessMethod, ApiAccessMethod, ObfuscationProtocol, Socks5, - }; + use mullvad_types::access_method::{AccessMethod, ApiAccessMethod, CustomAccessMethod, Socks5}; pub struct ApiAccessMethodFormatter<'a> { api_access_method: &'a ApiAccessMethod, @@ -439,8 +426,8 @@ mod pp { write!(f, "{}", method.canonical_name())?; write_status(f, self.api_access_method.enabled()) } - AccessMethod::Custom(method) => match &method.access_method { - ObfuscationProtocol::Shadowsocks(shadowsocks) => { + AccessMethod::Custom(method) => match &method { + CustomAccessMethod::Shadowsocks(shadowsocks) => { write!(f, "{}", self.api_access_method.get_name())?; write_status(f, self.api_access_method.enabled())?; writeln!(f)?; @@ -449,7 +436,7 @@ mod pp { print_option!("Password", shadowsocks.password); Ok(()) } - ObfuscationProtocol::Socks5(socks) => match socks { + CustomAccessMethod::Socks5(socks) => match socks { Socks5::Remote(remote) => { write!(f, "{}", self.api_access_method.get_name())?; write_status(f, self.api_access_method.enabled())?; diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index 22a6146e8f22..a44008becd9c 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -3,7 +3,7 @@ use crate::{ Daemon, EventListener, }; use mullvad_types::access_method::{ - daemon::ApiAccessMethodReplace, ApiAccessMethod, CustomAccessMethod, + daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId, }; #[derive(err_derive::Error, Debug)] @@ -11,6 +11,9 @@ pub enum Error { /// Can not add access method #[error(display = "Cannot add custom access method")] Add, + /// Can not find access method + #[error(display = "Cannot find custom access method {}", _0)] + NoSuchMethod(ApiAccessMethodId), /// Access methods settings error #[error(display = "Settings error")] Settings(#[error(source)] settings::Error), @@ -28,27 +31,9 @@ where .map_err(Error::Settings) } - pub async fn toggle_api_access_method( - &mut self, - api_access_method: ApiAccessMethod, - enable: bool, - ) -> Result<(), Error> { - self.settings - .update(|settings| { - if let Some(api_access_method) = - settings.api_access_methods.find_mut(&api_access_method) - { - api_access_method.toggle(enable); - } - }) - .await - .map(|did_change| self.notify_on_change(did_change)) - .map_err(Error::Settings) - } - pub async fn remove_access_method( &mut self, - access_method: CustomAccessMethod, + access_method: ApiAccessMethodId, ) -> Result<(), Error> { self.settings .update(|settings| settings.api_access_methods.remove(&access_method)) @@ -57,29 +42,37 @@ where .map_err(Error::Settings) } - pub async fn replace_access_method( + pub async fn update_access_method( &mut self, - access_method_replace: ApiAccessMethodReplace, + access_method_update: ApiAccessMethodUpdate, ) -> Result<(), Error> { self.settings .update(|settings| { let access_methods = &mut settings.api_access_methods; - access_methods.append(access_method_replace.access_method); - access_methods.swap_remove(access_method_replace.index); + if let Some(access_method) = + // TODO: This will not work, has to be based on ID! + access_methods.find_mut(&access_method_update.id) + { + *access_method = access_method_update.access_method + } }) .await .map(|did_change| self.notify_on_change(did_change)) .map_err(Error::Settings) } - 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.access_method); + pub fn set_api_access_method(&mut self, access_method: ApiAccessMethodId) -> Result<(), Error> { + if let Some(access_method) = self.settings.api_access_methods.find(&access_method) { + { + let mut connection_modes = self.connection_modes.lock().unwrap(); + connection_modes.set_access_method(access_method.access_method.clone()); + } + // Force a rotation of Access Methods. + let _ = self.api_handle.service().next_api_endpoint(); + Ok(()) + } else { + Err(Error::NoSuchMethod(access_method)) } - // Force a rotation of Access Methods. - let _ = self.api_handle.service().next_api_endpoint(); - Ok(()) } /// If settings were changed due to an update, notify all listeners. diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 75b3ad9e47c8..e07037edada3 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -108,7 +108,9 @@ impl ApiConnectionModeProvider { log::debug!("Rotating Access mode!"); let access_method = { let mut access_methods_picker = self.connection_modes.lock().unwrap(); - access_methods_picker.next().unwrap() + access_methods_picker + .next() + .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)) }; let connection_mode = self.from(&access_method); @@ -146,11 +148,11 @@ impl ApiConnectionModeProvider { }) .unwrap_or(ApiConnectionMode::Direct), }, - AccessMethod::Custom(access_method) => match &access_method.access_method { - access_method::ObfuscationProtocol::Shadowsocks(shadowsocks_config) => { + AccessMethod::Custom(access_method) => match &access_method { + access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) } - access_method::ObfuscationProtocol::Socks5(socks_config) => { + access_method::CustomAccessMethod::Socks5(socks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) } }, diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 2cb35cfcf714..f8cf6cda287c 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -39,10 +39,7 @@ use mullvad_relay_selector::{ RelaySelector, SelectorConfig, }; use mullvad_types::{ - access_method::{ - daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - ApiAccessMethod, CustomAccessMethod, - }, + access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, auth_failed::AuthFailed, custom_list::CustomList, @@ -268,13 +265,11 @@ pub enum DaemonCommand { /// Add API access methods AddApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethod), /// Remove an API access method - RemoveApiAccessMethod(ResponseTx<(), Error>, CustomAccessMethod), - /// Edit an API access method - ReplaceApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodReplace), - /// Toggle the status of an API access method - ToggleApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodToggle), + RemoveApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodId), /// Set the API access method to use - SetApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethod), + SetApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodId), + /// Edit an API access method + UpdateApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodUpdate), /// Get the addresses of all known API endpoints GetApiAddresses(ResponseTx, Error>), /// Get information about the currently running and latest app versions @@ -1068,10 +1063,7 @@ where GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx), AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await, RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, - ReplaceApiAccessMethod(tx, method) => { - self.on_replace_api_access_method(tx, method).await - } - ToggleApiAccessMethod(tx, method) => self.on_toggle_api_access_method(tx, method).await, + UpdateApiAccessMethod(tx, method) => self.on_update_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), @@ -2268,45 +2260,34 @@ where async fn on_remove_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: CustomAccessMethod, + api_access_method: ApiAccessMethodId, ) { let result = self - .remove_access_method(method) + .remove_access_method(api_access_method) .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "remove_api_access_method response"); } - async fn on_replace_api_access_method( + async fn on_update_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: ApiAccessMethodReplace, + method: ApiAccessMethodUpdate, ) { let result = self - .replace_access_method(method) + .update_access_method(method) .await .map_err(Error::AccessMethodError); - Self::oneshot_send(tx, result, "edit_api_access_method response"); + Self::oneshot_send(tx, result, "update_api_access_method response"); } - async fn on_toggle_api_access_method( + fn on_set_api_access_method( &mut self, tx: ResponseTx<(), Error>, - access_method_toggle: ApiAccessMethodToggle, + access_method: ApiAccessMethodId, ) { let result = self - .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"); - } - - fn on_set_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: ApiAccessMethod) { - let result = self - .set_api_access_method(method) + .set_api_access_method(access_method) .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 19c4e1e412e1..b3a8831ef19c 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -654,59 +654,28 @@ impl ManagementService for ManagementServiceImpl { ) -> 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, - ) -> ServiceResult<()> { - log::debug!("edit_api_access_method"); - let access_method_replace = - mullvad_types::access_method::daemon::ApiAccessMethodReplace::try_from( - request.into_inner(), - )?; + mullvad_types::api_access::ApiAccessMethodId::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::ReplaceApiAccessMethod( - tx, - access_method_replace, - ))?; + self.send_command_to_daemon(DaemonCommand::RemoveApiAccessMethod(tx, api_access_method))?; self.wait_for_result(rx) .await? .map(Response::new) .map_err(map_daemon_error) } - async fn toggle_api_access_method( + async fn update_api_access_method( &self, - request: Request, + request: Request, ) -> ServiceResult<()> { - log::debug!("toggle_api_access_method"); - let access_method_toggle = - mullvad_types::access_method::daemon::ApiAccessMethodToggle::try_from( + log::debug!("update_api_access_method"); + let access_method_update = + mullvad_types::access_method::daemon::ApiAccessMethodUpdate::try_from( request.into_inner(), )?; let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::ToggleApiAccessMethod( + self.send_command_to_daemon(DaemonCommand::UpdateApiAccessMethod( tx, - access_method_toggle, + access_method_update, ))?; self.wait_for_result(rx) .await? @@ -714,13 +683,10 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn set_api_access_method( - &self, - request: Request, - ) -> ServiceResult<()> { + async fn set_api_access_method(&self, request: Request) -> ServiceResult<()> { log::debug!("set_api_access_method"); let api_access_method = - mullvad_types::access_method::ApiAccessMethod::try_from(request.into_inner())?; + mullvad_types::api_access::ApiAccessMethodId::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, api_access_method))?; self.wait_for_result(rx) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index e923ec1043fa..ad982ea556ad 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -81,13 +81,10 @@ service ManagementService { rpc RemoveApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } - rpc ReplaceApiAccessMethod(ApiAccessMethodReplace) returns (google.protobuf.Empty) { + rpc UpdateApiAccessMethod(ApiAccessMethodUpdate) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } - rpc ToggleApiAccessMethod(ApiAccessMethodToggle) returns (google.protobuf.Empty) { - // Can I return something useful here instead of Empty? - } - rpc SetApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { + rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) { // Can I return something useful here instead of Empty? } @@ -111,6 +108,8 @@ service ManagementService { rpc CheckVolumes(google.protobuf.Empty) returns (google.protobuf.Empty) {} } +message UUID { string value = 1; } + message RelaySettingsUpdate { oneof type { CustomRelaySettings custom = 1; @@ -336,16 +335,6 @@ message ObfuscationSettings { Udp2TcpObfuscationSettings udp2tcp = 2; } -message ApiAccessMethodReplace { - uint32 index = 1; - ApiAccessMethod access_method = 2; -} - -message ApiAccessMethodToggle { - ApiAccessMethod access_method = 1; - bool enable = 2; -} - message CustomList { string id = 1; string name = 2; @@ -358,31 +347,30 @@ message ApiAccessMethod { message Direct {} message Bridges {} message Socks5Local { - string id = 3; string ip = 4; uint32 port = 5; uint32 local_port = 6; } message Socks5Remote { - string id = 1; string ip = 2; uint32 port = 3; } message Shadowsocks { - string id = 1; string ip = 2; uint32 port = 3; string password = 4; string cipher = 5; } - string name = 1; - bool enabled = 2; + + UUID id = 1; + string name = 2; + bool enabled = 3; oneof access_method { - Direct direct = 3; - Bridges bridges = 4; - Socks5Local socks5local = 5; - Socks5Remote socks5remote = 6; - Shadowsocks shadowsocks = 7; + Direct direct = 4; + Bridges bridges = 5; + Socks5Local socks5local = 6; + Socks5Remote socks5remote = 7; + Shadowsocks shadowsocks = 8; } } @@ -390,6 +378,11 @@ message ApiAccessMethods { repeated ApiAccessMethod api_access_methods = 1; } message ApiAccessMethodSettings { repeated ApiAccessMethod api_access_methods = 1; } +message ApiAccessMethodUpdate { + UUID id = 1; + ApiAccessMethod access_method = 2; +} + message Settings { RelaySettings relay_settings = 1; BridgeSettings bridge_settings = 2; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 315e3276fcd0..c79b4d2d8045 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -3,10 +3,7 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ - access_method::{ - daemon::{ApiAccessMethodReplace, ApiAccessMethodToggle}, - ApiAccessMethod, - }, + access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -181,6 +178,26 @@ impl MullvadProxyClient { .collect() } + pub async fn get_api_access_method( + &mut self, + id: &ApiAccessMethodId, + ) -> Result { + self.0 + .get_api_access_methods(()) + .await + .map_err(Error::Rpc)? + .into_inner() + .api_access_methods + .into_iter() + .map(|api_access_method| { + ApiAccessMethod::try_from(api_access_method) + .map_err(Error::InvalidResponse) + .expect("Failed to convert proto Api Access Method to daemon representation") + }) + .find(|api_access_method| api_access_method.get_id() == *id) + .ok_or(Error::ApiAccessMethodNotFound) + } + pub async fn get_api_addressess(&mut self) -> Result<()> { self.0.get_api_addressess(()).await.map_err(Error::Rpc)?; Ok(()) @@ -488,12 +505,37 @@ impl MullvadProxyClient { .map(drop) } - pub async fn toggle_access_method( + pub async fn enable_access_method( &mut self, - access_method_toggle: ApiAccessMethodToggle, + api_access_method_id: ApiAccessMethodId, ) -> Result<()> { + let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; + new_api_access_method.enable(); + let update = ApiAccessMethodUpdate { + id: api_access_method_id, + access_method: new_api_access_method, + }; + + self.0 + .update_api_access_method(types::ApiAccessMethodUpdate::from(update)) + .await + .map_err(Error::Rpc) + .map(drop) + } + + pub async fn disable_access_method( + &mut self, + api_access_method_id: ApiAccessMethodId, + ) -> Result<()> { + let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; + new_api_access_method.disable(); + let update = ApiAccessMethodUpdate { + id: api_access_method_id, + access_method: new_api_access_method, + }; + self.0 - .toggle_api_access_method(types::ApiAccessMethodToggle::from(access_method_toggle)) + .update_api_access_method(types::ApiAccessMethodUpdate::from(update)) .await .map_err(Error::Rpc) .map(drop) @@ -507,12 +549,12 @@ impl MullvadProxyClient { .map(drop) } - pub async fn replace_access_method( + pub async fn update_access_method( &mut self, - access_method_replace: ApiAccessMethodReplace, + access_method_update: ApiAccessMethodUpdate, ) -> Result<()> { self.0 - .replace_api_access_method(types::ApiAccessMethodReplace::from(access_method_replace)) + .update_api_access_method(types::ApiAccessMethodUpdate::from(access_method_update)) .await .map_err(Error::Rpc) .map(drop) @@ -527,9 +569,9 @@ impl MullvadProxyClient { /// method "randomly" /// /// [`ApiConnectionModeProvider`]: mullvad_daemon::api::ApiConnectionModeProvider - pub async fn set_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { + pub async fn set_access_method(&mut self, api_access_method: ApiAccessMethodId) -> Result<()> { self.0 - .set_api_access_method(types::ApiAccessMethod::from(api_access_method)) + .set_api_access_method(types::Uuid::from(api_access_method)) .await .map_err(Error::Rpc) .map(drop) diff --git a/mullvad-management-interface/src/lib.rs b/mullvad-management-interface/src/lib.rs index cf1a7988786e..39d5f5df9d0e 100644 --- a/mullvad-management-interface/src/lib.rs +++ b/mullvad-management-interface/src/lib.rs @@ -103,6 +103,9 @@ pub enum Error { #[error(display = "Location was not found in the custom list")] LocationNotFoundInCustomlist, + + #[error(display = "An access method with that id does not exist")] + ApiAccessMethodNotFound, } #[deprecated(note = "Prefer MullvadProxyClient")] 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 28859c5013ce..e72a4a1f9745 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -37,55 +37,36 @@ mod settings { } } - impl From 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(proto::ApiAccessMethod::from(api_access_method)), + impl From for proto::ApiAccessMethodUpdate { + fn from(value: access_method::daemon::ApiAccessMethodUpdate) -> Self { + proto::ApiAccessMethodUpdate { + id: Some(proto::Uuid::from(value.id)), + access_method: Some(proto::ApiAccessMethod::from(value.access_method)), } } } - impl TryFrom for access_method::daemon::ApiAccessMethodReplace { + impl TryFrom for access_method::daemon::ApiAccessMethodUpdate { type Error = FromProtobufTypeError; - fn try_from(value: proto::ApiAccessMethodReplace) -> Result { - 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(access_method::ApiAccessMethod::try_from)?, - }) - } - } + fn try_from(value: proto::ApiAccessMethodUpdate) -> Result { + let api_access_method = value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not convert Access Method from protobuf", + )) + .and_then(access_method::ApiAccessMethod::try_from)?; - impl From 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(proto::ApiAccessMethod::from(api_access_method)), - enable: enabled, - } - } - } + let id = value + .id + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not convert Access Method from protobuf", + )) + .map(access_method::ApiAccessMethodId::from)?; - impl TryFrom for access_method::daemon::ApiAccessMethodToggle { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::ApiAccessMethodToggle) -> Result { - Ok(access_method::daemon::ApiAccessMethodToggle { - access_method: value - .access_method - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not convert Access Method from protobuf", - )) - .and_then(access_method::ApiAccessMethod::try_from)?, - enable: value.enable, + Ok(access_method::daemon::ApiAccessMethodUpdate { + id, + access_method: api_access_method, }) } } @@ -97,8 +78,8 @@ mod settings { mod data { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::access_method::{ - AccessMethod, ApiAccessMethod, BuiltInAccessMethod, ObfuscationProtocol, Shadowsocks, - Socks5, Socks5Local, Socks5Remote, + AccessMethod, ApiAccessMethod, ApiAccessMethodId, BuiltInAccessMethod, CustomAccessMethod, + Shadowsocks, Socks5, Socks5Local, Socks5Remote, }; impl TryFrom for Vec { @@ -117,8 +98,48 @@ mod data { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethod) -> Result { + let id = value + .id + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Access Method from protobuf", + )) + .and_then(ApiAccessMethodId::try_from)?; let name = value.name; let enabled = value.enabled; + let access_method = value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Access Method from protobuf", + )) + .and_then(AccessMethod::try_from)?; + + Ok(AccessMethodSetting::with_id( + id, + name, + enabled, + access_method, + )) + } + } + + impl From for proto::ApiAccessMethod { + fn from(value: AccessMethodSetting) -> Self { + let id = proto::Uuid::from(value.get_id()); + let name = value.get_name(); + let enabled = value.enabled(); + proto::ApiAccessMethod { + id: Some(id), + name, + enabled, + access_method: Some(proto::AccessMethod::from(value.access_method)), + } + } + } + + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::AccessMethod) -> Result { let access_method = value .access_method @@ -126,7 +147,7 @@ mod data { "Could not deserialize Access Method from protobuf", ))?; - let x = match access_method { + let access_method = match access_method { proto::api_access_method::AccessMethod::Direct( proto::api_access_method::Direct {}, ) => AccessMethod::from(BuiltInAccessMethod::Direct), @@ -164,24 +185,38 @@ mod data { } }; - Ok(ApiAccessMethod { - name, - enabled, - access_method: x, - }) + Ok(ApiAccessMethod::with_id(id, name, enabled, access_method)) + } + } + + impl From for proto::Uuid { + fn from(value: ApiAccessMethodId) -> Self { + proto::Uuid { + value: value.to_string(), + } + } + } + + impl TryFrom for ApiAccessMethodId { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::Uuid) -> Result { + Self::from_string(value.value).ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse UUID message from protobuf", + )) } } impl From for proto::ApiAccessMethod { fn from(value: ApiAccessMethod) -> Self { + let id = proto::Uuid::from(value.get_id()); 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) => { + AccessMethod::Custom(value) => match value { + CustomAccessMethod::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, @@ -189,20 +224,18 @@ mod data { }, ) } - ObfuscationProtocol::Socks5(Socks5::Local(Socks5Local { peer, port })) => { + CustomAccessMethod::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, }, ) } - ObfuscationProtocol::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + CustomAccessMethod::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, }, @@ -224,6 +257,7 @@ mod data { }; proto::ApiAccessMethod { + id: Some(id), name, enabled, access_method: Some(access_method), diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index 73f51d25b828..db591efd3b5d 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -1,8 +1,6 @@ -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. @@ -18,16 +16,18 @@ impl Settings { self.api_access_methods.push(api_access_method) } - /// Remove a [`CustomAccessMethod`] from `api_access_methods`. + /// Remove an [`ApiAccessMethod`] 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) - }) + pub fn remove(&mut self, api_access_method: &ApiAccessMethodId) { + self.retain(|method| method.get_id() != *api_access_method) + } + + /// Search for a particular [`AccessMethod`] in `api_access_methods`. + #[inline(always)] + pub fn find(&self, element: &ApiAccessMethodId) -> Option<&ApiAccessMethod> { + self.api_access_methods + .iter() + .find(|api_access_method| *element == api_access_method.get_id()) } /// Search for a particular [`AccessMethod`] in `api_access_methods`. @@ -36,13 +36,10 @@ impl Settings { /// 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> { + pub fn find_mut(&mut self, element: &ApiAccessMethodId) -> 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 - }) + .find(|api_access_method| *element == api_access_method.get_id()) } /// Equivalent to [`Vec::retain`]. @@ -54,15 +51,6 @@ impl Settings { 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 { @@ -75,10 +63,12 @@ impl Default for Settings { 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), + .map(|built_in| { + ApiAccessMethod::new( + built_in.canonical_name(), + true, + AccessMethod::from(built_in), + ) }) .collect(), } @@ -91,19 +81,78 @@ impl Default for Settings { /// TODO(Create a constructor functions for this struct (?)) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ApiAccessMethod { + /// Some unique id (distinct for each `AccessMethod`). + id: ApiAccessMethodId, pub name: String, pub enabled: bool, pub access_method: AccessMethod, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct ApiAccessMethodId(uuid::Uuid); + +impl ApiAccessMethodId { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self(uuid::Uuid::new_v4()) + } + /// Tries to parse a UUID from a raw String. If it is successful, an + /// [`ApiAccessMethodId`] is instantiated. + pub fn from_string(id: String) -> Option { + uuid::Uuid::from_str(&id).ok().map(Self) + } +} + +impl std::fmt::Display for ApiAccessMethodId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// Access Method datastructure. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash)] pub enum AccessMethod { BuiltIn(BuiltInAccessMethod), Custom(CustomAccessMethod), } impl ApiAccessMethod { + pub fn new(name: String, enabled: bool, access_method: AccessMethod) -> Self { + Self { + id: ApiAccessMethodId::new(), + name, + enabled, + access_method, + } + } + + /// Just like [`new`], [`with_id`] will create a new [`ApiAccessMethod`]. + /// But instead of automatically generating a new UUID, the id is instead + /// passed as an argument. + /// + /// This is useful when converting to [`ApiAccessMethod`] from other data + /// representations, such as protobuf. + /// + /// [`new`]: ApiAccessMethod::new + /// [`with_id`]: ApiAccessMethod::with_id + pub fn with_id( + id: ApiAccessMethodId, + name: String, + enabled: bool, + access_method: AccessMethod, + ) -> Self { + Self { + id, + name, + enabled, + access_method, + } + } + + pub fn get_id(&self) -> ApiAccessMethodId { + self.id.clone() + } + pub fn get_name(&self) -> String { self.name.clone() } @@ -116,28 +165,27 @@ impl ApiAccessMethod { 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; + /// Set an API access method to be enabled. + pub fn enable(&mut self) { + self.enabled = true; + } + + /// Set an API access method to be disabled. + pub fn disable(&mut self) { + self.enabled = false; } } /// Built-In access method datastructure. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash)] 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 { +pub enum CustomAccessMethod { Shadowsocks(Shadowsocks), Socks5(Socks5), } @@ -246,26 +294,15 @@ impl From for AccessMethod { } } -impl From 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 for AccessMethod { fn from(value: Shadowsocks) -> Self { - ObfuscationProtocol::Shadowsocks(value).into() + CustomAccessMethod::Shadowsocks(value).into() } } impl From for AccessMethod { fn from(value: Socks5) -> Self { - AccessMethod::from(ObfuscationProtocol::Socks5(value)) + AccessMethod::from(CustomAccessMethod::Socks5(value)) } } @@ -296,16 +333,10 @@ impl From for Socks5 { /// 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`. + /// Argument to protobuf rpc `UpdateApiAccessMethod`. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct ApiAccessMethodToggle { + pub struct ApiAccessMethodUpdate { + pub id: ApiAccessMethodId, pub access_method: ApiAccessMethod, - pub enable: bool, } } From 72d4f157ab3c4955ac3636b6500d24cf36d1a0ed Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 25 Sep 2023 13:42:33 +0200 Subject: [PATCH 18/29] Refrain from using the word `proxy` where it does not make sense Do not use the word "proxy" in the context of API access methods, but only in the context where we are actually refering to a proxy (such as `SOCKS5` or `Shadowsocks` proxies). --- mullvad-cli/src/cmds/api_access.rs | 24 ++++++++++++------------ mullvad-cli/src/main.rs | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index e9bf6f9b32d4..7b7656ca6af2 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -10,22 +10,22 @@ use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; #[derive(Subcommand, Debug, Clone)] pub enum ApiAccess { - /// List the configured API proxies + /// List the configured API access methods List, - /// Add a custom API proxy + /// Add a custom API access method #[clap(subcommand)] Add(AddCustomCommands), - /// Edit an API proxy + /// Edit an API access method Edit(EditCustomCommands), - /// Remove an API proxy + /// Remove an API access method Remove(SelectItem), - /// Enable an API proxy + /// Enable an API access method Enable(SelectItem), - /// Disable an API proxy + /// Disable an API access method Disable(SelectItem), - /// Test an API proxy + /// Test an API access method Test(SelectItem), - /// Force the use of a specific API proxy. + /// Force the use of a specific API access method. /// /// Selecting "Mullvad Bridges" respects your current bridge settings. Use(SelectItem), @@ -74,8 +74,8 @@ impl ApiAccess { /// Add a custom API access method. async fn add(cmd: AddCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let proxy = ApiAccessMethod::try_from(cmd)?; - rpc.add_access_method(proxy).await?; + let access_method = ApiAccessMethod::try_from(cmd)?; + rpc.add_access_method(access_method).await?; Ok(()) } @@ -274,7 +274,7 @@ impl std::fmt::Display for SelectItem { #[derive(Args, Debug, Clone)] pub struct EditCustomCommands { - /// Which API proxy to edit + /// Which API access method to edit #[clap(flatten)] item: SelectItem, /// Editing parameters @@ -284,7 +284,7 @@ pub struct EditCustomCommands { #[derive(Args, Debug, Clone)] pub struct EditParams { - /// Name of the API proxy in the Mullvad client [All] + /// Name of the API access method in the Mullvad client [All] #[arg(long)] name: Option, /// Password for authentication [Shadowsocks] diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 27855ded699d..a79465f9658d 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -71,8 +71,8 @@ enum Cli { #[clap(subcommand)] Relay(relay::Relay), - /// Manage use of proxies for reaching the Mullvad API. - /// Can make the daemon connect to the the Mullvad API via one of the + /// Manage use of access methods for reaching the Mullvad API. + /// Can be used to connect to the the Mullvad API via one of the /// Mullvad bridge servers or a custom proxy (SOCKS5 & Shadowsocks). #[clap(subcommand)] ApiAccess(api_access::ApiAccess), From cb7e844df06ac8127a9f66a61ee102f3c80ecf51 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 25 Sep 2023 15:42:47 +0200 Subject: [PATCH 19/29] Code cleanup - Refactor `RemoveApiAccessMethod` to be based on UUID - Remove debug-prints in `mullvad api-access list` et al - Get rid of `GetApiAccessMethods` RPC - Use the more generic RPC `GetSettings` to get hold of all API access methods instead - Rename `mullvad_types::access_method` to `mullvad_types::api_access` - Remove (unjustified) `#[inline(always)]` attributes --- mullvad-api/src/https_client_with_sni.rs | 4 +-- mullvad-api/src/proxy.rs | 14 +++++----- mullvad-cli/src/cmds/api_access.rs | 28 +++++++++---------- mullvad-daemon/src/access_method.rs | 19 ++++++++++--- mullvad-daemon/src/api.rs | 17 ++++++----- mullvad-daemon/src/lib.rs | 1 + mullvad-daemon/src/management_interface.rs | 21 ++------------ .../proto/management_interface.proto | 20 ++++--------- mullvad-management-interface/src/client.rs | 25 ++++++++--------- mullvad-management-interface/src/lib.rs | 3 ++ .../types/conversions/api_access_method.rs | 24 ++++++++-------- .../src/types/conversions/settings.rs | 2 +- .../src/types/rpc/api_access_method_update.rs | 8 ++++++ .../src/{access_method.rs => api_access.rs} | 10 +++---- mullvad-types/src/lib.rs | 2 +- mullvad-types/src/settings/mod.rs | 6 ++-- 16 files changed, 96 insertions(+), 108 deletions(-) create mode 100644 mullvad-management-interface/src/types/rpc/api_access_method_update.rs rename mullvad-types/src/{access_method.rs => api_access.rs} (98%) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index cc7557a8d2ad..e95fbf98fcdc 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -248,12 +248,12 @@ impl TryFrom for InnerConnectionMode { }) } ProxyConfig::Socks(config) => match config { - mullvad_types::access_method::Socks5::Local(config) => { + mullvad_types::api_access::Socks5::Local(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: SocketAddr::new("127.0.0.1".parse().unwrap(), config.port), }) } - mullvad_types::access_method::Socks5::Remote(config) => { + mullvad_types::api_access::Socks5::Remote(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) } }, diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 44a2309587e5..7d33840d6947 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::access_method; +use mullvad_types::api_access; 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(access_method::Shadowsocks), - Socks(access_method::Socks5), + Shadowsocks(api_access::Shadowsocks), + Socks(api_access::Socks5), } impl ProxyConfig { @@ -46,8 +46,8 @@ impl ProxyConfig { match self { ProxyConfig::Shadowsocks(ss) => ss.peer, ProxyConfig::Socks(socks) => match socks { - access_method::Socks5::Local(s) => s.peer, - access_method::Socks5::Remote(s) => s.peer, + api_access::Socks5::Local(s) => s.peer, + api_access::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 { - access_method::Socks5::Local(s) => { + api_access::Socks5::Local(s) => { write!(f, "Socks5 {}/TCP via localhost:{}", s.peer, s.port) } - access_method::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), + api_access::Socks5::Remote(s) => write!(f, "Socks5 {}/TCP", s.peer), }, } } diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 7b7656ca6af2..ae202055b6d5 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -83,7 +83,7 @@ impl ApiAccess { async fn remove(cmd: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let access_method = Self::get_access_method(&mut rpc, &cmd).await?; - rpc.remove_access_method(access_method) + rpc.remove_access_method(access_method.get_id()) .await .map_err(Into::::into) } @@ -107,27 +107,28 @@ impl ApiAccess { let cipher = cmd.params.cipher.unwrap_or(shadowsocks.cipher); 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(|shadowsocks| { + mullvad_types::api_access::Shadowsocks::from_args(ip, port, cipher, password).map( + |shadowsocks| { ApiAccessMethod::new(name, enabled, AccessMethod::from(shadowsocks)) - }) + }, + ) } CustomAccessMethod::Socks5(socks) => match socks { - mullvad_types::access_method::Socks5::Local(local) => { + mullvad_types::api_access::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(api_access_method.get_name()); let enabled = api_access_method.enabled(); - mullvad_types::access_method::Socks5Local::from_args(ip, port, local_port) + mullvad_types::api_access::Socks5Local::from_args(ip, port, local_port) .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) } - mullvad_types::access_method::Socks5::Remote(remote) => { + mullvad_types::api_access::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(api_access_method.get_name()); let enabled = api_access_method.enabled(); - mullvad_types::access_method::Socks5Remote::from_args(ip, port) + mullvad_types::api_access::Socks5Remote::from_args(ip, port) .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) } }, @@ -310,7 +311,7 @@ pub struct EditParams { /// we define them in a hidden-away module. mod conversions { use anyhow::{anyhow, Error}; - use mullvad_types::access_method as daemon_types; + use mullvad_types::api_access as daemon_types; use super::{AddCustomCommands, Socks5AddCommands}; @@ -328,7 +329,7 @@ mod conversions { remote_port, name, } => { - println!("Adding LOCAL SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); + println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Local( daemon_types::Socks5Local::from_args( remote_ip.to_string(), @@ -348,7 +349,7 @@ mod conversions { remote_port, name, } => { - println!("Adding REMOTE SOCKS5-proxy: {remote_ip}:{remote_port}"); + println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Remote( daemon_types::Socks5Remote::from_args( remote_ip.to_string(), @@ -394,7 +395,7 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::access_method::{AccessMethod, ApiAccessMethod, CustomAccessMethod, Socks5}; + use mullvad_types::api_access::{AccessMethod, ApiAccessMethod, CustomAccessMethod, Socks5}; pub struct ApiAccessMethodFormatter<'a> { api_access_method: &'a ApiAccessMethod, @@ -418,9 +419,6 @@ mod pp { } }; - // 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())?; diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index a44008becd9c..fa4da174d3c8 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -11,6 +11,9 @@ pub enum Error { /// Can not add access method #[error(display = "Cannot add custom access method")] Add, + /// Can not remove built-in access method + #[error(display = "Cannot remove built-in access method")] + RemoveBuiltIn, /// Can not find access method #[error(display = "Cannot find custom access method {}", _0)] NoSuchMethod(ApiAccessMethodId), @@ -35,6 +38,17 @@ where &mut self, access_method: ApiAccessMethodId, ) -> Result<(), Error> { + // Make sure that we are not trying to remove a built-in API access + // method + match self.settings.api_access_methods.find(&access_method) { + None => return Ok(()), + Some(api_access_method) => { + if api_access_method.is_builtin() { + return Err(Error::RemoveBuiltIn); + } + } + }; + self.settings .update(|settings| settings.api_access_methods.remove(&access_method)) .await @@ -49,10 +63,7 @@ where self.settings .update(|settings| { let access_methods = &mut settings.api_access_methods; - if let Some(access_method) = - // TODO: This will not work, has to be based on ID! - access_methods.find_mut(&access_method_update.id) - { + if let Some(access_method) = access_methods.find_mut(&access_method_update.id) { *access_method = access_method_update.access_method } }) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index e07037edada3..5605f567caf1 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::access_method::{self, AccessMethod, BuiltInAccessMethod}; +use mullvad_types::api_access::{self, AccessMethod, BuiltInAccessMethod}; use std::{ net::SocketAddr, path::PathBuf, @@ -131,12 +131,11 @@ impl ApiConnectionModeProvider { .get_bridge_forced() .and_then(|settings| match settings { ProxySettings::Shadowsocks(ss_settings) => { - let ss_settings: access_method::Shadowsocks = - access_method::Shadowsocks::new( - ss_settings.peer, - ss_settings.cipher, - ss_settings.password, - ); + let ss_settings: api_access::Shadowsocks = api_access::Shadowsocks::new( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + ); Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( ss_settings, ))) @@ -149,10 +148,10 @@ impl ApiConnectionModeProvider { .unwrap_or(ApiConnectionMode::Direct), }, AccessMethod::Custom(access_method) => match &access_method { - access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { + api_access::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) } - access_method::CustomAccessMethod::Socks5(socks_config) => { + api_access::CustomAccessMethod::Socks5(socks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) } }, diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index f8cf6cda287c..1fedbfbde2ae 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -41,6 +41,7 @@ use mullvad_relay_selector::{ use mullvad_types::{ access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, + api_access::{ApiAccessMethod, ApiAccessMethodId}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index b3a8831ef19c..12671a41c73c 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -619,27 +619,13 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn get_api_access_methods( - &self, - _: Request<()>, - ) -> ServiceResult { - 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, ) -> ServiceResult<()> { log::debug!("add_api_access_method"); let api_access_method = - mullvad_types::access_method::ApiAccessMethod::try_from(request.into_inner())?; + mullvad_types::api_access::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) @@ -648,10 +634,7 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn remove_api_access_method( - &self, - request: Request, - ) -> ServiceResult<()> { + async fn remove_api_access_method(&self, request: Request) -> ServiceResult<()> { log::debug!("remove_api_access_method"); let api_access_method = mullvad_types::api_access::ApiAccessMethodId::try_from(request.into_inner())?; diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index ad982ea556ad..667778475a0d 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -22,6 +22,7 @@ service ManagementService { rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {} rpc GetVersionInfo(google.protobuf.Empty) returns (AppVersionInfo) {} + rpc GetApiAddressess(google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc IsPerformingPostUpgrade(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} @@ -74,21 +75,10 @@ service ManagementService { rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {} // API Access methods - rpc GetApiAccessMethods(google.protobuf.Empty) returns (ApiAccessMethods) {} - rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { - // Can I return something useful here instead of Empty? - } - rpc RemoveApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) { - // Can I return something useful here instead of Empty? - } - rpc UpdateApiAccessMethod(ApiAccessMethodUpdate) returns (google.protobuf.Empty) { - // Can I return something useful here instead of Empty? - } - rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) { - // Can I return something useful here instead of Empty? - } - - rpc GetApiAddressess(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) {} + rpc RemoveApiAccessMethod(UUID) returns (google.protobuf.Empty) {} + rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) {} + rpc UpdateApiAccessMethod(ApiAccessMethodUpdate) returns (google.protobuf.Empty) {} // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index c79b4d2d8045..2503d6d55c08 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -5,6 +5,7 @@ use futures::{Stream, StreamExt}; use mullvad_types::{ access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, + api_access::{ApiAccessMethod, ApiAccessMethodId}, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -166,11 +167,13 @@ impl MullvadProxyClient { pub async fn get_api_access_methods(&mut self) -> Result> { self.0 - .get_api_access_methods(()) + .get_settings(()) .await .map_err(Error::Rpc)? .into_inner() .api_access_methods + .ok_or(Error::ApiAccessMethodSettingsNotFound)? + .api_access_methods .into_iter() .map(|api_access_method| { ApiAccessMethod::try_from(api_access_method).map_err(Error::InvalidResponse) @@ -182,18 +185,9 @@ impl MullvadProxyClient { &mut self, id: &ApiAccessMethodId, ) -> Result { - self.0 - .get_api_access_methods(()) - .await - .map_err(Error::Rpc)? - .into_inner() - .api_access_methods + self.get_api_access_methods() + .await? .into_iter() - .map(|api_access_method| { - ApiAccessMethod::try_from(api_access_method) - .map_err(Error::InvalidResponse) - .expect("Failed to convert proto Api Access Method to daemon representation") - }) .find(|api_access_method| api_access_method.get_id() == *id) .ok_or(Error::ApiAccessMethodNotFound) } @@ -541,9 +535,12 @@ impl MullvadProxyClient { .map(drop) } - pub async fn remove_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { + pub async fn remove_access_method( + &mut self, + api_access_method: ApiAccessMethodId, + ) -> Result<()> { self.0 - .remove_api_access_method(types::ApiAccessMethod::from(api_access_method)) + .remove_api_access_method(types::Uuid::from(api_access_method)) .await .map_err(Error::Rpc) .map(drop) diff --git a/mullvad-management-interface/src/lib.rs b/mullvad-management-interface/src/lib.rs index 39d5f5df9d0e..c9414d03bf67 100644 --- a/mullvad-management-interface/src/lib.rs +++ b/mullvad-management-interface/src/lib.rs @@ -104,6 +104,9 @@ pub enum Error { #[error(display = "Location was not found in the custom list")] LocationNotFoundInCustomlist, + #[error(display = "Could not retrieve API access methods from settings")] + ApiAccessMethodSettingsNotFound, + #[error(display = "An access method with that id does not exist")] ApiAccessMethodNotFound, } 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 e72a4a1f9745..fa0d34678801 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -5,8 +5,8 @@ mod settings { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::access_method; - impl From<&access_method::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: &access_method::Settings) -> Self { + impl From<&api_access::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: &api_access::Settings) -> Self { Self { api_access_methods: settings .api_access_methods @@ -17,13 +17,13 @@ mod settings { } } - impl From for proto::ApiAccessMethodSettings { - fn from(settings: access_method::Settings) -> Self { + impl From for proto::ApiAccessMethodSettings { + fn from(settings: api_access::Settings) -> Self { proto::ApiAccessMethodSettings::from(&settings) } } - impl TryFrom for access_method::Settings { + impl TryFrom for api_access::Settings { type Error = FromProtobufTypeError; fn try_from(settings: proto::ApiAccessMethodSettings) -> Result { @@ -31,8 +31,8 @@ mod settings { api_access_methods: settings .api_access_methods .iter() - .map(access_method::ApiAccessMethod::try_from) - .collect::, _>>()?, + .map(api_access::ApiAccessMethod::try_from) + .collect::, _>>()?, }) } } @@ -55,14 +55,14 @@ mod settings { .ok_or(FromProtobufTypeError::InvalidArgument( "Could not convert Access Method from protobuf", )) - .and_then(access_method::ApiAccessMethod::try_from)?; + .and_then(api_access::ApiAccessMethod::try_from)?; let id = value .id .ok_or(FromProtobufTypeError::InvalidArgument( "Could not convert Access Method from protobuf", )) - .map(access_method::ApiAccessMethodId::from)?; + .map(api_access::ApiAccessMethodId::from)?; Ok(access_method::daemon::ApiAccessMethodUpdate { id, @@ -77,7 +77,7 @@ mod settings { /// [`mullvad_types::access_method::AccessMethod`] data type. mod data { use crate::types::{proto, FromProtobufTypeError}; - use mullvad_types::access_method::{ + use mullvad_types::api_access::{ AccessMethod, ApiAccessMethod, ApiAccessMethodId, BuiltInAccessMethod, CustomAccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote, }; @@ -243,12 +243,12 @@ mod data { } }, AccessMethod::BuiltIn(value) => match value { - mullvad_types::access_method::BuiltInAccessMethod::Direct => { + mullvad_types::api_access::BuiltInAccessMethod::Direct => { proto::api_access_method::AccessMethod::Direct( proto::api_access_method::Direct {}, ) } - mullvad_types::access_method::BuiltInAccessMethod::Bridge => { + mullvad_types::api_access::BuiltInAccessMethod::Bridge => { proto::api_access_method::AccessMethod::Bridges( proto::api_access_method::Bridges {}, ) diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index 98c195d93562..532e5e8f2985 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 for mullvad_types::settings::Settings { custom_lists: mullvad_types::custom_list::CustomListsSettings::try_from( custom_lists_settings, )?, - api_access_methods: mullvad_types::access_method::Settings::try_from( + api_access_methods: mullvad_types::api_access::Settings::try_from( api_access_methods_settings, )?, }) diff --git a/mullvad-management-interface/src/types/rpc/api_access_method_update.rs b/mullvad-management-interface/src/types/rpc/api_access_method_update.rs new file mode 100644 index 000000000000..b2708931026f --- /dev/null +++ b/mullvad-management-interface/src/types/rpc/api_access_method_update.rs @@ -0,0 +1,8 @@ +/// A short-lived datastructure used in the `ApiAccessMethodUpdate` RPC call. +use mullvad_types::api_access::{ApiAccessMethod, ApiAccessMethodId}; +/// Argument to gRPC call `UpdateApiAccessMethod`. +#[derive(Debug, Clone, PartialEq)] +pub struct ApiAccessMethodUpdate { + pub id: ApiAccessMethodId, + pub access_method: ApiAccessMethod, +} diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/api_access.rs similarity index 98% rename from mullvad-types/src/access_method.rs rename to mullvad-types/src/api_access.rs index db591efd3b5d..c20a1fe7fd70 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/api_access.rs @@ -11,19 +11,16 @@ pub struct Settings { 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 an [`ApiAccessMethod`] from `api_access_methods`. - #[inline(always)] pub fn remove(&mut self, api_access_method: &ApiAccessMethodId) { self.retain(|method| method.get_id() != *api_access_method) } /// Search for a particular [`AccessMethod`] in `api_access_methods`. - #[inline(always)] pub fn find(&self, element: &ApiAccessMethodId) -> Option<&ApiAccessMethod> { self.api_access_methods .iter() @@ -35,7 +32,6 @@ impl Settings { /// 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: &ApiAccessMethodId) -> Option<&mut ApiAccessMethod> { self.api_access_methods .iter_mut() @@ -43,7 +39,6 @@ impl Settings { } /// Equivalent to [`Vec::retain`]. - #[inline(always)] pub fn retain(&mut self, f: F) where F: FnMut(&ApiAccessMethod) -> bool, @@ -52,7 +47,6 @@ impl Settings { } /// Clone the content of `api_access_methods`. - #[inline(always)] pub fn cloned(&self) -> Vec { self.api_access_methods.clone() } @@ -165,6 +159,10 @@ impl ApiAccessMethod { self.access_method.as_custom() } + pub fn is_builtin(&self) -> bool { + self.as_custom().is_none() + } + /// Set an API access method to be enabled. pub fn enable(&mut self) { self.enabled = true; diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index 8aefaeb4000b..e1af195220b5 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; 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 6ade7dea3271..2c40172a2cc6 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -1,5 +1,5 @@ use crate::{ - access_method, + api_access, 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: access_method::Settings, + pub api_access_methods: api_access::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: access_method::Settings::default(), + api_access_methods: api_access::Settings::default(), } } } From 2aae80bf5bad388e78daa95cc5d8e79cc712713d Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Mon, 25 Sep 2023 16:59:57 +0200 Subject: [PATCH 20/29] `AccessMethod`s are now filtered by the daemon rather than `ConnectionModesIterator` Move the duty of filtering active `AccessMethod`s from `ConnectionModesIterator` to the daemon. This provides more flexibility in the iterator as it does not need to know about `AccessMethod` at all. --- mullvad-daemon/src/access_method.rs | 3 ++- mullvad-daemon/src/api.rs | 21 ++++++--------------- mullvad-daemon/src/lib.rs | 4 +++- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index fa4da174d3c8..dc6c06de06c5 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -98,7 +98,8 @@ where .api_access_methods .api_access_methods .iter() - .map(|x| x.access_method.clone()) + .filter(|api_access_method| api_access_method.enabled()) + .map(|api_access_method| api_access_method.access_method.clone()) .collect(), ) }; diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 5605f567caf1..c5a5bda73e2b 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -173,10 +173,10 @@ pub struct ConnectionModesIterator { } impl ConnectionModesIterator { - pub fn new(modes: Vec) -> ConnectionModesIterator { + pub fn new(access_methods: Vec) -> ConnectionModesIterator { Self { next: None, - available_modes: Self::get_filtered_access_methods(modes), + available_modes: Self::cycle(access_methods), } } @@ -186,21 +186,12 @@ impl ConnectionModesIterator { } /// Update the collection of [`AccessMethod`] which this iterator will /// return. - pub fn update_access_methods(&mut self, api_access_methods: Vec) { - self.available_modes = Self::get_filtered_access_methods(api_access_methods); + pub fn update_access_methods(&mut self, access_methods: Vec) { + self.available_modes = Self::cycle(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, - ) -> Box + Send> { - Box::new(modes - .into_iter() - // .filter(|access_method| access_method.enabled()) - .cycle()) + fn cycle(access_methods: Vec) -> Box + Send> { + Box::new(access_methods.into_iter().cycle()) } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 1fedbfbde2ae..8d9980ba4231 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -642,7 +642,9 @@ where .api_access_methods .api_access_methods .iter() - .map(|x| x.access_method.clone()) + // We only care about the access methods which are set to 'enabled' by the user. + .filter(|api_access_method| api_access_method.enabled()) + .map(|api_access_method| api_access_method.access_method.clone()) .collect(), ); From 7c9de7a434abde196da06c2e2157af71e83caa4e Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 26 Sep 2023 10:24:10 +0200 Subject: [PATCH 21/29] Rename `ApiAccessMethod` to `AccessMethodSetting` `ApiAccessMethod` was just an app-centric wrapper around `AccessMethod`. --- mullvad-cli/src/cmds/api_access.rs | 40 ++++++++++--------- mullvad-daemon/src/access_method.rs | 5 ++- mullvad-daemon/src/lib.rs | 10 ++--- mullvad-daemon/src/management_interface.rs | 2 +- mullvad-management-interface/src/client.rs | 13 +++--- .../types/conversions/api_access_method.rs | 35 +++++++++------- .../src/types/rpc/api_access_method_update.rs | 8 ---- mullvad-types/src/api_access.rs | 19 +++++---- 8 files changed, 69 insertions(+), 63 deletions(-) delete mode 100644 mullvad-management-interface/src/types/rpc/api_access_method_update.rs diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index ae202055b6d5..efb1e7bfd8f3 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -1,8 +1,6 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::access_method::{ - daemon::ApiAccessMethodUpdate, AccessMethod, ApiAccessMethod, CustomAccessMethod, -}; +use mullvad_types::api_access::{AccessMethod, AccessMethodSetting, CustomAccessMethod}; use std::net::IpAddr; use clap::{Args, Subcommand}; @@ -74,7 +72,7 @@ impl ApiAccess { /// Add a custom API access method. async fn add(cmd: AddCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = ApiAccessMethod::try_from(cmd)?; + let access_method = AccessMethodSetting::try_from(cmd)?; rpc.add_access_method(access_method).await?; Ok(()) } @@ -99,7 +97,7 @@ impl ApiAccess { .ok_or(anyhow!("Can not edit built-in access method"))?; // Create a new access method combining the new params with the previous values - let edited_access_method: ApiAccessMethod = match access_method { + let edited_access_method: AccessMethodSetting = match access_method { CustomAccessMethod::Shadowsocks(shadowsocks) => { let ip = cmd.params.ip.unwrap_or(shadowsocks.peer.ip()).to_string(); let port = cmd.params.port.unwrap_or(shadowsocks.peer.port()); @@ -109,7 +107,7 @@ impl ApiAccess { let enabled = api_access_method.enabled; mullvad_types::api_access::Shadowsocks::from_args(ip, port, cipher, password).map( |shadowsocks| { - ApiAccessMethod::new(name, enabled, AccessMethod::from(shadowsocks)) + AccessMethodSetting::new(name, enabled, AccessMethod::from(shadowsocks)) }, ) } @@ -120,16 +118,18 @@ impl ApiAccess { let local_port = cmd.params.local_port.unwrap_or(local.port); let name = cmd.params.name.unwrap_or(api_access_method.get_name()); let enabled = api_access_method.enabled(); - mullvad_types::api_access::Socks5Local::from_args(ip, port, local_port) - .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) + mullvad_types::api_access::Socks5Local::from_args(ip, port, local_port).map( + |socks| AccessMethodSetting::new(name, enabled, AccessMethod::from(socks)), + ) } mullvad_types::api_access::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(api_access_method.get_name()); let enabled = api_access_method.enabled(); - mullvad_types::api_access::Socks5Remote::from_args(ip, port) - .map(|socks| ApiAccessMethod::new(name, enabled, AccessMethod::from(socks))) + mullvad_types::api_access::Socks5Remote::from_args(ip, port).map(|socks| { + AccessMethodSetting::new(name, enabled, AccessMethod::from(socks)) + }) } }, } @@ -193,7 +193,7 @@ impl ApiAccess { async fn get_access_method( rpc: &mut MullvadProxyClient, item: &SelectItem, - ) -> Result { + ) -> Result { rpc.get_api_access_methods() .await? .get(item.as_array_index()?) @@ -315,7 +315,7 @@ mod conversions { use super::{AddCustomCommands, Socks5AddCommands}; - impl TryFrom for daemon_types::ApiAccessMethod { + impl TryFrom for daemon_types::AccessMethodSetting { type Error = Error; fn try_from(value: AddCustomCommands) -> Result { @@ -338,7 +338,7 @@ mod conversions { ) .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, ); - daemon_types::ApiAccessMethod::new( + daemon_types::AccessMethodSetting::new( name, enabled, daemon_types::AccessMethod::from(socks_proxy), @@ -357,7 +357,7 @@ mod conversions { ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, ); - daemon_types::ApiAccessMethod::new( + daemon_types::AccessMethodSetting::new( name, enabled, daemon_types::AccessMethod::from(socks_proxy), @@ -382,7 +382,7 @@ mod conversions { ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - daemon_types::ApiAccessMethod::new( + daemon_types::AccessMethodSetting::new( name, enabled, daemon_types::AccessMethod::from(shadowsocks_proxy), @@ -395,14 +395,16 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::api_access::{AccessMethod, ApiAccessMethod, CustomAccessMethod, Socks5}; + use mullvad_types::api_access::{ + AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, + }; pub struct ApiAccessMethodFormatter<'a> { - api_access_method: &'a ApiAccessMethod, + api_access_method: &'a AccessMethodSetting, } impl<'a> ApiAccessMethodFormatter<'a> { - pub fn new(api_access_method: &'a ApiAccessMethod) -> ApiAccessMethodFormatter<'a> { + pub fn new(api_access_method: &'a AccessMethodSetting) -> ApiAccessMethodFormatter<'a> { ApiAccessMethodFormatter { api_access_method } } } @@ -419,6 +421,8 @@ mod pp { } }; + writeln!(f, "{:?}", self.api_access_method)?; + match &self.api_access_method.access_method { AccessMethod::BuiltIn(method) => { write!(f, "{}", method.canonical_name())?; diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index dc6c06de06c5..a3ece35ecbd1 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -26,7 +26,10 @@ impl Daemon where L: EventListener + Clone + Send + 'static, { - pub async fn add_access_method(&mut self, access_method: ApiAccessMethod) -> Result<(), Error> { + pub async fn add_access_method( + &mut self, + access_method: AccessMethodSetting, + ) -> Result<(), Error> { self.settings .update(|settings| settings.api_access_methods.append(access_method)) .await diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 8d9980ba4231..cac152c8436a 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -41,7 +41,7 @@ use mullvad_relay_selector::{ use mullvad_types::{ access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, - api_access::{ApiAccessMethod, ApiAccessMethodId}, + api_access::{AccessMethodSetting, ApiAccessMethodId}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -262,9 +262,9 @@ pub enum DaemonCommand { /// Update a custom list with a given id UpdateCustomList(ResponseTx<(), Error>, CustomList), /// Get API access methods - GetApiAccessMethods(ResponseTx, Error>), + GetApiAccessMethods(ResponseTx, Error>), /// Add API access methods - AddApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethod), + AddApiAccessMethod(ResponseTx<(), Error>, AccessMethodSetting), /// Remove an API access method RemoveApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodId), /// Set the API access method to use @@ -2243,7 +2243,7 @@ where Self::oneshot_send(tx, result, "update_custom_list response"); } - fn on_get_api_access_methods(&mut self, tx: ResponseTx, Error>) { + fn on_get_api_access_methods(&mut self, tx: ResponseTx, Error>) { let result = Ok(self.settings.api_access_methods.cloned()); Self::oneshot_send(tx, result, "get_api_access_methods response"); } @@ -2251,7 +2251,7 @@ where async fn on_add_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: ApiAccessMethod, + method: AccessMethodSetting, ) { let result = self .add_access_method(method) diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 12671a41c73c..eabdb1182616 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -625,7 +625,7 @@ impl ManagementService for ManagementServiceImpl { ) -> ServiceResult<()> { log::debug!("add_api_access_method"); let api_access_method = - mullvad_types::api_access::ApiAccessMethod::try_from(request.into_inner())?; + mullvad_types::api_access::AccessMethodSetting::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) diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 2503d6d55c08..a7de6ae55f97 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -5,7 +5,7 @@ use futures::{Stream, StreamExt}; use mullvad_types::{ access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, account::{AccountData, AccountToken, VoucherSubmission}, - api_access::{ApiAccessMethod, ApiAccessMethodId}, + api_access::{AccessMethodSetting, ApiAccessMethodId}, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -165,7 +165,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> { + pub async fn get_api_access_methods(&mut self) -> Result> { self.0 .get_settings(()) .await @@ -176,7 +176,7 @@ impl MullvadProxyClient { .api_access_methods .into_iter() .map(|api_access_method| { - ApiAccessMethod::try_from(api_access_method).map_err(Error::InvalidResponse) + AccessMethodSetting::try_from(api_access_method).map_err(Error::InvalidResponse) }) .collect() } @@ -184,7 +184,7 @@ impl MullvadProxyClient { pub async fn get_api_access_method( &mut self, id: &ApiAccessMethodId, - ) -> Result { + ) -> Result { self.get_api_access_methods() .await? .into_iter() @@ -491,7 +491,10 @@ impl MullvadProxyClient { Ok(()) } - pub async fn add_access_method(&mut self, api_access_method: ApiAccessMethod) -> Result<()> { + pub async fn add_access_method( + &mut self, + api_access_method: AccessMethodSetting, + ) -> Result<()> { self.0 .add_api_access_method(types::ApiAccessMethod::from(api_access_method)) .await 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 fa0d34678801..0fd29f4b93e2 100644 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs @@ -31,8 +31,8 @@ mod settings { api_access_methods: settings .api_access_methods .iter() - .map(api_access::ApiAccessMethod::try_from) - .collect::, _>>()?, + .map(api_access::AccessMethodSetting::try_from) + .collect::, _>>()?, }) } } @@ -55,7 +55,7 @@ mod settings { .ok_or(FromProtobufTypeError::InvalidArgument( "Could not convert Access Method from protobuf", )) - .and_then(api_access::ApiAccessMethod::try_from)?; + .and_then(api_access::AccessMethodSetting::try_from)?; let id = value .id @@ -78,23 +78,23 @@ mod settings { mod data { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::api_access::{ - AccessMethod, ApiAccessMethod, ApiAccessMethodId, BuiltInAccessMethod, CustomAccessMethod, - Shadowsocks, Socks5, Socks5Local, Socks5Remote, + AccessMethod, AccessMethodSetting, ApiAccessMethodId, BuiltInAccessMethod, + CustomAccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote, }; - impl TryFrom for Vec { + impl TryFrom for Vec { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethods) -> Result { value .api_access_methods .iter() - .map(ApiAccessMethod::try_from) + .map(AccessMethodSetting::try_from) .collect() } } - impl TryFrom for ApiAccessMethod { + impl TryFrom for AccessMethodSetting { type Error = FromProtobufTypeError; fn try_from(value: proto::ApiAccessMethod) -> Result { @@ -185,7 +185,12 @@ mod data { } }; - Ok(ApiAccessMethod::with_id(id, name, enabled, access_method)) + Ok(AccessMethodSetting::with_id( + id, + name, + enabled, + access_method, + )) } } @@ -207,8 +212,8 @@ mod data { } } - impl From for proto::ApiAccessMethod { - fn from(value: ApiAccessMethod) -> Self { + impl From for proto::ApiAccessMethod { + fn from(value: AccessMethodSetting) -> Self { let id = proto::Uuid::from(value.get_id()); let name = value.get_name(); let enabled = value.enabled(); @@ -265,16 +270,16 @@ mod data { } } - impl TryFrom<&proto::ApiAccessMethod> for ApiAccessMethod { + impl TryFrom<&proto::ApiAccessMethod> for AccessMethodSetting { type Error = FromProtobufTypeError; fn try_from(value: &proto::ApiAccessMethod) -> Result { - ApiAccessMethod::try_from(value.clone()) + AccessMethodSetting::try_from(value.clone()) } } - impl From> for proto::ApiAccessMethods { - fn from(value: Vec) -> proto::ApiAccessMethods { + impl From> for proto::ApiAccessMethods { + fn from(value: Vec) -> proto::ApiAccessMethods { proto::ApiAccessMethods { api_access_methods: value.iter().map(|method| method.clone().into()).collect(), } diff --git a/mullvad-management-interface/src/types/rpc/api_access_method_update.rs b/mullvad-management-interface/src/types/rpc/api_access_method_update.rs deleted file mode 100644 index b2708931026f..000000000000 --- a/mullvad-management-interface/src/types/rpc/api_access_method_update.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// A short-lived datastructure used in the `ApiAccessMethodUpdate` RPC call. -use mullvad_types::api_access::{ApiAccessMethod, ApiAccessMethodId}; -/// Argument to gRPC call `UpdateApiAccessMethod`. -#[derive(Debug, Clone, PartialEq)] -pub struct ApiAccessMethodUpdate { - pub id: ApiAccessMethodId, - pub access_method: ApiAccessMethod, -} diff --git a/mullvad-types/src/api_access.rs b/mullvad-types/src/api_access.rs index c20a1fe7fd70..b916a7f323a5 100644 --- a/mullvad-types/src/api_access.rs +++ b/mullvad-types/src/api_access.rs @@ -6,12 +6,12 @@ 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, + pub api_access_methods: Vec, } impl Settings { /// Append an [`AccessMethod`] to the end of `api_access_methods`. - pub fn append(&mut self, api_access_method: ApiAccessMethod) { + pub fn append(&mut self, api_access_method: AccessMethodSetting) { self.api_access_methods.push(api_access_method) } @@ -21,7 +21,7 @@ impl Settings { } /// Search for a particular [`AccessMethod`] in `api_access_methods`. - pub fn find(&self, element: &ApiAccessMethodId) -> Option<&ApiAccessMethod> { + pub fn find(&self, element: &ApiAccessMethodId) -> Option<&AccessMethodSetting> { self.api_access_methods .iter() .find(|api_access_method| *element == api_access_method.get_id()) @@ -32,7 +32,7 @@ impl Settings { /// 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. - pub fn find_mut(&mut self, element: &ApiAccessMethodId) -> Option<&mut ApiAccessMethod> { + pub fn find_mut(&mut self, element: &ApiAccessMethodId) -> Option<&mut AccessMethodSetting> { self.api_access_methods .iter_mut() .find(|api_access_method| *element == api_access_method.get_id()) @@ -41,13 +41,13 @@ impl Settings { /// Equivalent to [`Vec::retain`]. pub fn retain(&mut self, f: F) where - F: FnMut(&ApiAccessMethod) -> bool, + F: FnMut(&AccessMethodSetting) -> bool, { self.api_access_methods.retain(f) } /// Clone the content of `api_access_methods`. - pub fn cloned(&self) -> Vec { + pub fn cloned(&self) -> Vec { self.api_access_methods.clone() } } @@ -58,7 +58,7 @@ impl Default for Settings { api_access_methods: vec![BuiltInAccessMethod::Direct, BuiltInAccessMethod::Bridge] .into_iter() .map(|built_in| { - ApiAccessMethod::new( + AccessMethodSetting::new( built_in.canonical_name(), true, AccessMethod::from(built_in), @@ -72,9 +72,8 @@ impl Default for Settings { /// 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 struct AccessMethodSetting { /// Some unique id (distinct for each `AccessMethod`). id: ApiAccessMethodId, pub name: String, @@ -110,7 +109,7 @@ pub enum AccessMethod { Custom(CustomAccessMethod), } -impl ApiAccessMethod { +impl AccessMethodSetting { pub fn new(name: String, enabled: bool, access_method: AccessMethod) -> Self { Self { id: ApiAccessMethodId::new(), From 28c09203858b4d36b46a7d2c1d7af1f1fedaeb9c Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 26 Sep 2023 11:35:03 +0200 Subject: [PATCH 22/29] Code cleanup - Rename `mullvad_types::api_access.rs` -> `mullvad_types::access_method.rs` - Rename `ApiAccessMethodId` to simply `Id` Prefer to prefix with module name `access_method` to disambiguate use of `Id` instead, like `access_method::Id` - Remove dead code - Remove `AccessMethodSettingsUpdate` - Remove the `retry_attempt` struct field from `ApiConnectionModeProvider`, as it is no longer used for anything. - Fix typos - `GetApiAddressess` is now correctly spelled `GetApiAddresses` (a single trailing "s") - Deprecate the name `ApiAccessMethod` in favor of `AccessMethodSetting` - To decrease the confusion between `AccessMethod` & `ApiAccessMethod`. `AccessMethodSetting` adds some app-specific settings details on top of an `AccessMethod`, which is not too far fetched with the new naming convention. - Refactor proto file - Rename protobuf message `AccessMethodSettingAdd` to `NewAccessMethodSetting` - `AccessMethod` is now its own message - `AccessMethods` is removed - Add `ApiAccessMethodAdd` protobuf message - The `ApiAccessMethodAdd` returns a `UUID` for the . One important change is that new `AccessMethodSetting`s are created in the daemon, rather than in the CLI/other clients. This means that the daemon now has full control over generating new `AccessMethodSetting`s from `AccessMethod`s. - Clean up conversion code to/from `AccessMethod` protobuf types - Simplify `UpdateApiAccessMethod` RPC - Remove the extranous `ApiAccessMethodUpdate` data type - get rid of `ApiAccessMethodUpdate` protobuf message. Use `UUID` of `ApiAccessMethod` to identify which struct to edit. - get rid of `ApiAccessMethodUpdate` struct --- mullvad-api/src/https_client_with_sni.rs | 4 +- mullvad-api/src/proxy.rs | 14 +- mullvad-cli/src/cmds/api_access.rs | 237 +++++++------- mullvad-daemon/src/access_method.rs | 33 +- mullvad-daemon/src/api.rs | 20 +- mullvad-daemon/src/lib.rs | 39 ++- mullvad-daemon/src/management_interface.rs | 34 ++- .../proto/management_interface.proto | 38 +-- mullvad-management-interface/src/client.rs | 56 ++-- .../src/types/conversions/access_method.rs | 258 ++++++++++++++++ .../types/conversions/api_access_method.rs | 288 ------------------ .../src/types/conversions/mod.rs | 2 +- .../src/types/conversions/settings.rs | 2 +- .../src/{api_access.rs => access_method.rs} | 52 ++-- mullvad-types/src/lib.rs | 2 +- mullvad-types/src/settings/mod.rs | 6 +- 16 files changed, 511 insertions(+), 574 deletions(-) create mode 100644 mullvad-management-interface/src/types/conversions/access_method.rs delete mode 100644 mullvad-management-interface/src/types/conversions/api_access_method.rs rename mullvad-types/src/{api_access.rs => access_method.rs} (85%) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index e95fbf98fcdc..cc7557a8d2ad 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -248,12 +248,12 @@ impl TryFrom for InnerConnectionMode { }) } ProxyConfig::Socks(config) => match config { - mullvad_types::api_access::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::Socks5::Remote(config) => { + mullvad_types::access_method::Socks5::Remote(config) => { InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) } }, diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs index 7d33840d6947..44a2309587e5 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; +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::Shadowsocks), - Socks(api_access::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::Socks5::Local(s) => s.peer, - api_access::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::Socks5::Local(s) => { + access_method::Socks5::Local(s) => { write!(f, "Socks5 {}/TCP via localhost:{}", s.peer, s.port) } - api_access::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/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index efb1e7bfd8f3..260f0c449668 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::api_access::{AccessMethod, AccessMethodSetting, CustomAccessMethod}; +use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, CustomAccessMethod}; use std::net::IpAddr; use clap::{Args, Subcommand}; @@ -72,8 +72,10 @@ impl ApiAccess { /// Add a custom API access method. async fn add(cmd: AddCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = AccessMethodSetting::try_from(cmd)?; - rpc.add_access_method(access_method).await?; + let name = cmd.name(); + let enabled = cmd.enabled(); + let access_method = AccessMethod::try_from(cmd)?; + rpc.add_access_method(name, enabled, access_method).await?; Ok(()) } @@ -89,60 +91,46 @@ impl ApiAccess { /// Edit the data of an API access method. async fn edit(cmd: EditCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?; - let id = api_access_method.get_id(); - let access_method = api_access_method - .as_custom() - .cloned() - .ok_or(anyhow!("Can not edit built-in access method"))?; + let mut api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?; // Create a new access method combining the new params with the previous values - let edited_access_method: AccessMethodSetting = match access_method { - CustomAccessMethod::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(api_access_method.name); - let enabled = api_access_method.enabled; - mullvad_types::api_access::Shadowsocks::from_args(ip, port, cipher, password).map( - |shadowsocks| { - AccessMethodSetting::new(name, enabled, AccessMethod::from(shadowsocks)) - }, - ) - } - CustomAccessMethod::Socks5(socks) => match socks { - mullvad_types::api_access::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(api_access_method.get_name()); - let enabled = api_access_method.enabled(); - mullvad_types::api_access::Socks5Local::from_args(ip, port, local_port).map( - |socks| AccessMethodSetting::new(name, enabled, AccessMethod::from(socks)), - ) - } - mullvad_types::api_access::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(api_access_method.get_name()); - let enabled = api_access_method.enabled(); - mullvad_types::api_access::Socks5Remote::from_args(ip, port).map(|socks| { - AccessMethodSetting::new(name, enabled, AccessMethod::from(socks)) - }) + let access_method = match api_access_method.as_custom() { + None => return Err(anyhow!("Can not edit built-in access method")), + Some(x) => match x.clone() { + CustomAccessMethod::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); + mullvad_types::access_method::Shadowsocks::from_args(ip, port, cipher, password) + .map(AccessMethod::from) } + CustomAccessMethod::Socks5(socks) => match socks { + 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); + mullvad_types::access_method::Socks5Local::from_args(ip, port, local_port) + .map(AccessMethod::from) + } + 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()); + mullvad_types::access_method::Socks5Remote::from_args(ip, port) + .map(AccessMethod::from) + } + }, }, + }; + + if let Some(name) = cmd.params.name { + api_access_method.name = name; + }; + if let Some(access_method) = access_method { + api_access_method.access_method = access_method; } - .ok_or(anyhow!( - "Could not edit access method {}, reverting changes.", - cmd.item - ))?; - rpc.update_access_method(ApiAccessMethodUpdate { - id, - access_method: edited_access_method, - }) - .await?; + rpc.update_access_method(api_access_method).await?; Ok(()) } @@ -169,7 +157,7 @@ impl ApiAccess { let access_method = Self::get_access_method(&mut rpc, &item).await?; rpc.set_access_method(access_method.get_id()).await?; // Make the daemon perform an network request which involves talking to the Mullvad API. - match rpc.get_api_addressess().await { + match rpc.get_api_addresses().await { Ok(_) => println!("Connected to the Mullvad API!"), Err(_) => println!( "Could *not* connect to the Mullvad API using access method \"{}\"", @@ -204,32 +192,8 @@ impl ApiAccess { #[derive(Subcommand, Debug, Clone)] pub enum AddCustomCommands { - /// Configure SOCKS5 proxy - #[clap(subcommand)] - Socks5(Socks5AddCommands), - - /// Configure Shadowsocks proxy - Shadowsocks { - /// An easy to remember name for this custom proxy - name: String, - /// The IP of the remote Shadowsocks server - remote_ip: IpAddr, - /// The port of the remote Shadowsocks server - #[arg(default_value = "443")] - remote_port: u16, - /// Password for authentication - #[arg(default_value = "mullvad")] - password: String, - /// Cipher to use - #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] - cipher: String, - }, -} - -#[derive(Subcommand, Debug, Clone)] -pub enum Socks5AddCommands { /// Configure a local SOCKS5 proxy - Local { + Socks5Local { /// An easy to remember name for this custom proxy name: String, /// The port that the server on localhost is listening on @@ -240,7 +204,7 @@ pub enum Socks5AddCommands { remote_port: u16, }, /// Configure a remote SOCKS5 proxy - Remote { + Socks5Remote { /// An easy to remember name for this custom proxy name: String, /// The IP of the remote proxy server @@ -248,6 +212,37 @@ pub enum Socks5AddCommands { /// The port of the remote proxy server remote_port: u16, }, + /// Configure Shadowsocks proxy + Shadowsocks { + /// An easy to remember name for this custom proxy + name: String, + /// The IP of the remote Shadowsocks server + remote_ip: IpAddr, + /// The port of the remote Shadowsocks server + #[arg(default_value = "443")] + remote_port: u16, + /// Password for authentication + #[arg(default_value = "mullvad")] + password: String, + /// Cipher to use + #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] + cipher: String, + }, +} + +impl AddCustomCommands { + fn name(&self) -> String { + match self { + AddCustomCommands::Socks5Local { name, .. } => name, + AddCustomCommands::Socks5Remote { name, .. } => name, + AddCustomCommands::Shadowsocks { name, .. } => name, + } + .clone() + } + /// TODO: Actually add an `enabled` flag to the variants of `AddCustomCommands` + fn enabled(&self) -> bool { + true + } } /// A minimal wrapper type allowing the user to supply a list index to some @@ -311,65 +306,50 @@ pub struct EditParams { /// we define them in a hidden-away module. mod conversions { use anyhow::{anyhow, Error}; - use mullvad_types::api_access as daemon_types; + use mullvad_types::access_method as daemon_types; - use super::{AddCustomCommands, Socks5AddCommands}; + use super::AddCustomCommands; - impl TryFrom for daemon_types::AccessMethodSetting { + impl TryFrom for daemon_types::AccessMethod { type Error = Error; fn try_from(value: AddCustomCommands) -> Result { - // All api proxies are automatically enabled from the start. - let enabled = true; Ok(match value { - AddCustomCommands::Socks5(variant) => match variant { - Socks5AddCommands::Local { - local_port, - remote_ip, - remote_port, - name, - } => { - println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Local( - daemon_types::Socks5Local::from_args( - remote_ip.to_string(), - remote_port, - local_port, - ) - .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, - ); - daemon_types::AccessMethodSetting::new( - name, - enabled, - daemon_types::AccessMethod::from(socks_proxy), + AddCustomCommands::Socks5Local { + local_port, + remote_ip, + remote_port, + name: _, + } => { + println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Local( + daemon_types::Socks5Local::from_args( + remote_ip.to_string(), + remote_port, + local_port, ) - } - Socks5AddCommands::Remote { - remote_ip, - remote_port, - name, - } => { - println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Remote( - daemon_types::Socks5Remote::from_args( - remote_ip.to_string(), - remote_port, - ) + .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, + ); + daemon_types::AccessMethod::from(socks_proxy) + } + AddCustomCommands::Socks5Remote { + remote_ip, + remote_port, + name: _, + } => { + println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Remote( + daemon_types::Socks5Remote::from_args(remote_ip.to_string(), remote_port) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, - ); - daemon_types::AccessMethodSetting::new( - name, - enabled, - daemon_types::AccessMethod::from(socks_proxy), - ) - } - }, + ); + daemon_types::AccessMethod::from(socks_proxy) + } AddCustomCommands::Shadowsocks { remote_ip, remote_port, password, cipher, - name, + name: _, } => { println!( "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" @@ -381,12 +361,7 @@ mod conversions { password, ) .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - - daemon_types::AccessMethodSetting::new( - name, - enabled, - daemon_types::AccessMethod::from(shadowsocks_proxy), - ) + daemon_types::AccessMethod::from(shadowsocks_proxy) } }) } @@ -395,7 +370,7 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::api_access::{ + use mullvad_types::access_method::{ AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, }; diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index a3ece35ecbd1..0501a4336c28 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -2,9 +2,7 @@ use crate::{ settings::{self, MadeChanges}, Daemon, EventListener, }; -use mullvad_types::access_method::{ - daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId, -}; +use mullvad_types::access_method::{self, AccessMethod, AccessMethodSetting}; #[derive(err_derive::Error, Debug)] pub enum Error { @@ -16,7 +14,7 @@ pub enum Error { RemoveBuiltIn, /// Can not find access method #[error(display = "Cannot find custom access method {}", _0)] - NoSuchMethod(ApiAccessMethodId), + NoSuchMethod(access_method::Id), /// Access methods settings error #[error(display = "Settings error")] Settings(#[error(source)] settings::Error), @@ -28,18 +26,23 @@ where { pub async fn add_access_method( &mut self, - access_method: AccessMethodSetting, - ) -> Result<(), Error> { + name: String, + enabled: bool, + access_method: AccessMethod, + ) -> Result { + let access_method_setting = AccessMethodSetting::new(name, enabled, access_method); + let id = access_method_setting.get_id(); self.settings - .update(|settings| settings.api_access_methods.append(access_method)) + .update(|settings| settings.api_access_methods.append(access_method_setting)) .await .map(|did_change| self.notify_on_change(did_change)) + .map(|_| id) .map_err(Error::Settings) } pub async fn remove_access_method( &mut self, - access_method: ApiAccessMethodId, + access_method: access_method::Id, ) -> Result<(), Error> { // Make sure that we are not trying to remove a built-in API access // method @@ -59,15 +62,19 @@ where .map_err(Error::Settings) } + /// "Updates" an [`AccessMethodSetting`] by replacing the existing entry + /// with the argument `access_method_update` if an existing entry with + /// matching UUID is found. pub async fn update_access_method( &mut self, - access_method_update: ApiAccessMethodUpdate, + access_method_update: AccessMethodSetting, ) -> Result<(), Error> { self.settings .update(|settings| { let access_methods = &mut settings.api_access_methods; - if let Some(access_method) = access_methods.find_mut(&access_method_update.id) { - *access_method = access_method_update.access_method + if let Some(access_method) = access_methods.find_mut(&access_method_update.get_id()) + { + *access_method = access_method_update } }) .await @@ -75,7 +82,7 @@ where .map_err(Error::Settings) } - pub fn set_api_access_method(&mut self, access_method: ApiAccessMethodId) -> Result<(), Error> { + pub fn set_api_access_method(&mut self, access_method: access_method::Id) -> Result<(), Error> { if let Some(access_method) = self.settings.api_access_methods.find(&access_method) { { let mut connection_modes = self.connection_modes.lock().unwrap(); @@ -99,7 +106,7 @@ where connection_modes.update_access_methods( self.settings .api_access_methods - .api_access_methods + .access_method_settings .iter() .filter(|api_access_method| api_access_method.enabled()) .map(|api_access_method| api_access_method.access_method.clone()) diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index c5a5bda73e2b..8c8af4d4dc55 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::{self, AccessMethod, BuiltInAccessMethod}; +use mullvad_types::access_method::{self, AccessMethod, BuiltInAccessMethod}; use std::{ net::SocketAddr, path::PathBuf, @@ -39,7 +39,6 @@ pub struct ApiConnectionModeProvider { cache_dir: PathBuf, /// Used for selecting a Relay when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, - retry_attempt: u32, current_task: Option + Send>>>, connection_modes: Arc>, } @@ -63,7 +62,6 @@ impl Stream for ApiConnectionModeProvider { } let connection_mode = self.new_connection_mode(); - self.retry_attempt = self.retry_attempt.wrapping_add(1); let cache_dir = self.cache_dir.clone(); self.current_task = Some(Box::pin(async move { @@ -89,7 +87,6 @@ impl ApiConnectionModeProvider { Self { cache_dir, relay_selector, - retry_attempt: 0, current_task: None, connection_modes: Arc::new(Mutex::new(ConnectionModesIterator::new(connection_modes))), } @@ -131,11 +128,12 @@ impl ApiConnectionModeProvider { .get_bridge_forced() .and_then(|settings| match settings { ProxySettings::Shadowsocks(ss_settings) => { - let ss_settings: api_access::Shadowsocks = api_access::Shadowsocks::new( - ss_settings.peer, - ss_settings.cipher, - ss_settings.password, - ); + let ss_settings: access_method::Shadowsocks = + access_method::Shadowsocks::new( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + ); Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( ss_settings, ))) @@ -148,10 +146,10 @@ impl ApiConnectionModeProvider { .unwrap_or(ApiConnectionMode::Direct), }, AccessMethod::Custom(access_method) => match &access_method { - api_access::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { + access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) } - api_access::CustomAccessMethod::Socks5(socks_config) => { + access_method::CustomAccessMethod::Socks5(socks_config) => { ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) } }, diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index cac152c8436a..26f7956dfb06 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -39,9 +39,8 @@ use mullvad_relay_selector::{ RelaySelector, SelectorConfig, }; use mullvad_types::{ - access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, + access_method::{AccessMethod, AccessMethodSetting}, account::{AccountData, AccountToken, VoucherSubmission}, - api_access::{AccessMethodSetting, ApiAccessMethodId}, auth_failed::AuthFailed, custom_list::CustomList, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, @@ -264,13 +263,18 @@ pub enum DaemonCommand { /// Get API access methods GetApiAccessMethods(ResponseTx, Error>), /// Add API access methods - AddApiAccessMethod(ResponseTx<(), Error>, AccessMethodSetting), + AddApiAccessMethod( + ResponseTx, + String, + bool, + AccessMethod, + ), /// Remove an API access method - RemoveApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodId), + RemoveApiAccessMethod(ResponseTx<(), Error>, mullvad_types::access_method::Id), /// Set the API access method to use - SetApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodId), + SetApiAccessMethod(ResponseTx<(), Error>, mullvad_types::access_method::Id), /// Edit an API access method - UpdateApiAccessMethod(ResponseTx<(), Error>, ApiAccessMethodUpdate), + UpdateApiAccessMethod(ResponseTx<(), Error>, AccessMethodSetting), /// Get the addresses of all known API endpoints GetApiAddresses(ResponseTx, Error>), /// Get information about the currently running and latest app versions @@ -640,7 +644,7 @@ where relay_selector.clone(), settings .api_access_methods - .api_access_methods + .access_method_settings .iter() // We only care about the access methods which are set to 'enabled' by the user. .filter(|api_access_method| api_access_method.enabled()) @@ -1064,7 +1068,10 @@ where UpdateCustomList(tx, update) => self.on_update_custom_list(tx, update).await, GetVersionInfo(tx) => self.on_get_version_info(tx), GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx), - AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await, + AddApiAccessMethod(tx, name, enabled, access_method) => { + self.on_add_access_method(tx, name, enabled, access_method) + .await + } RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, UpdateApiAccessMethod(tx, method) => self.on_update_api_access_method(tx, method).await, SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method), @@ -2248,13 +2255,15 @@ where Self::oneshot_send(tx, result, "get_api_access_methods response"); } - async fn on_add_api_access_method( + async fn on_add_access_method( &mut self, - tx: ResponseTx<(), Error>, - method: AccessMethodSetting, + tx: ResponseTx, + name: String, + enabled: bool, + access_method: AccessMethod, ) { let result = self - .add_access_method(method) + .add_access_method(name, enabled, access_method) .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "add_api_access_method response"); @@ -2263,7 +2272,7 @@ where async fn on_remove_api_access_method( &mut self, tx: ResponseTx<(), Error>, - api_access_method: ApiAccessMethodId, + api_access_method: mullvad_types::access_method::Id, ) { let result = self .remove_access_method(api_access_method) @@ -2275,7 +2284,7 @@ where async fn on_update_api_access_method( &mut self, tx: ResponseTx<(), Error>, - method: ApiAccessMethodUpdate, + method: AccessMethodSetting, ) { let result = self .update_access_method(method) @@ -2287,7 +2296,7 @@ where fn on_set_api_access_method( &mut self, tx: ResponseTx<(), Error>, - access_method: ApiAccessMethodId, + access_method: mullvad_types::access_method::Id, ) { let result = self .set_api_access_method(access_method) diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index eabdb1182616..ce12332b3b58 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -621,23 +621,30 @@ impl ManagementService for ManagementServiceImpl { async fn add_api_access_method( &self, - request: Request, - ) -> ServiceResult<()> { + request: Request, + ) -> ServiceResult { log::debug!("add_api_access_method"); - let api_access_method = - mullvad_types::api_access::AccessMethodSetting::try_from(request.into_inner())?; + let request = request.into_inner(); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::AddApiAccessMethod(tx, api_access_method))?; + self.send_command_to_daemon(DaemonCommand::AddApiAccessMethod( + tx, + request.name, + request.enabled, + request + .access_method + .ok_or(Status::invalid_argument("Could not find access method")) + .map(mullvad_types::access_method::AccessMethod::try_from)??, + ))?; self.wait_for_result(rx) .await? + .map(types::Uuid::from) .map(Response::new) .map_err(map_daemon_error) } async fn remove_api_access_method(&self, request: Request) -> ServiceResult<()> { log::debug!("remove_api_access_method"); - let api_access_method = - mullvad_types::api_access::ApiAccessMethodId::try_from(request.into_inner())?; + let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::RemoveApiAccessMethod(tx, api_access_method))?; self.wait_for_result(rx) @@ -648,13 +655,11 @@ impl ManagementService for ManagementServiceImpl { async fn update_api_access_method( &self, - request: Request, + request: Request, ) -> ServiceResult<()> { log::debug!("update_api_access_method"); let access_method_update = - mullvad_types::access_method::daemon::ApiAccessMethodUpdate::try_from( - request.into_inner(), - )?; + mullvad_types::access_method::AccessMethodSetting::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::UpdateApiAccessMethod( tx, @@ -668,8 +673,7 @@ impl ManagementService for ManagementServiceImpl { async fn set_api_access_method(&self, request: Request) -> ServiceResult<()> { log::debug!("set_api_access_method"); - let api_access_method = - mullvad_types::api_access::ApiAccessMethodId::try_from(request.into_inner())?; + let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?; let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, api_access_method))?; self.wait_for_result(rx) @@ -678,8 +682,8 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn get_api_addressess(&self, _: Request<()>) -> ServiceResult<()> { - log::debug!("test_api"); + async fn get_api_addresses(&self, _: Request<()>) -> ServiceResult<()> { + log::debug!("get_api_addresses"); let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::GetApiAddresses(tx))?; self.wait_for_result(rx) diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 667778475a0d..a7b3cb11a16e 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -22,7 +22,7 @@ service ManagementService { rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {} rpc GetVersionInfo(google.protobuf.Empty) returns (AppVersionInfo) {} - rpc GetApiAddressess(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GetApiAddresses(google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc IsPerformingPostUpgrade(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} @@ -75,10 +75,10 @@ service ManagementService { rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {} // API Access methods - rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) {} + rpc AddApiAccessMethod(NewAccessMethodSetting) returns (UUID) {} rpc RemoveApiAccessMethod(UUID) returns (google.protobuf.Empty) {} rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) {} - rpc UpdateApiAccessMethod(ApiAccessMethodUpdate) returns (google.protobuf.Empty) {} + rpc UpdateApiAccessMethod(AccessMethodSetting) returns (google.protobuf.Empty) {} // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} @@ -333,7 +333,7 @@ message CustomList { message CustomListSettings { repeated CustomList custom_lists = 1; } -message ApiAccessMethod { +message AccessMethod { message Direct {} message Bridges {} message Socks5Local { @@ -351,28 +351,30 @@ message ApiAccessMethod { string password = 4; string cipher = 5; } + oneof access_method { + Direct direct = 1; + Bridges bridges = 2; + Socks5Local socks5local = 3; + Socks5Remote socks5remote = 4; + Shadowsocks shadowsocks = 5; + } +} +message AccessMethodSetting { UUID id = 1; string name = 2; bool enabled = 3; - oneof access_method { - Direct direct = 4; - Bridges bridges = 5; - Socks5Local socks5local = 6; - Socks5Remote socks5remote = 7; - Shadowsocks shadowsocks = 8; - } + AccessMethod access_method = 4; } -message ApiAccessMethods { repeated ApiAccessMethod api_access_methods = 1; } - -message ApiAccessMethodSettings { repeated ApiAccessMethod api_access_methods = 1; } - -message ApiAccessMethodUpdate { - UUID id = 1; - ApiAccessMethod access_method = 2; +message NewAccessMethodSetting { + string name = 1; + bool enabled = 2; + AccessMethod access_method = 3; } +message ApiAccessMethodSettings { repeated AccessMethodSetting access_method_settings = 1; } + message Settings { RelaySettings relay_settings = 1; BridgeSettings bridge_settings = 2; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index a7de6ae55f97..aafee5b5c500 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -3,9 +3,8 @@ use crate::types; use futures::{Stream, StreamExt}; use mullvad_types::{ - access_method::{daemon::ApiAccessMethodUpdate, ApiAccessMethod, ApiAccessMethodId}, + access_method::{self, AccessMethod, AccessMethodSetting}, account::{AccountData, AccountToken, VoucherSubmission}, - api_access::{AccessMethodSetting, ApiAccessMethodId}, custom_list::{CustomList, Id}, device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, @@ -173,7 +172,7 @@ impl MullvadProxyClient { .into_inner() .api_access_methods .ok_or(Error::ApiAccessMethodSettingsNotFound)? - .api_access_methods + .access_method_settings .into_iter() .map(|api_access_method| { AccessMethodSetting::try_from(api_access_method).map_err(Error::InvalidResponse) @@ -183,7 +182,7 @@ impl MullvadProxyClient { pub async fn get_api_access_method( &mut self, - id: &ApiAccessMethodId, + id: &access_method::Id, ) -> Result { self.get_api_access_methods() .await? @@ -192,8 +191,8 @@ impl MullvadProxyClient { .ok_or(Error::ApiAccessMethodNotFound) } - pub async fn get_api_addressess(&mut self) -> Result<()> { - self.0.get_api_addressess(()).await.map_err(Error::Rpc)?; + pub async fn get_api_addresses(&mut self) -> Result<()> { + self.0.get_api_addresses(()).await.map_err(Error::Rpc)?; Ok(()) } @@ -493,10 +492,17 @@ impl MullvadProxyClient { pub async fn add_access_method( &mut self, - api_access_method: AccessMethodSetting, + name: String, + enabled: bool, + access_method: AccessMethod, ) -> Result<()> { + let request = types::NewAccessMethodSetting { + name, + enabled, + access_method: Some(types::AccessMethod::from(access_method)), + }; self.0 - .add_api_access_method(types::ApiAccessMethod::from(api_access_method)) + .add_api_access_method(request) .await .map_err(Error::Rpc) .map(drop) @@ -504,43 +510,25 @@ impl MullvadProxyClient { pub async fn enable_access_method( &mut self, - api_access_method_id: ApiAccessMethodId, + api_access_method_id: access_method::Id, ) -> Result<()> { let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; new_api_access_method.enable(); - let update = ApiAccessMethodUpdate { - id: api_access_method_id, - access_method: new_api_access_method, - }; - - self.0 - .update_api_access_method(types::ApiAccessMethodUpdate::from(update)) - .await - .map_err(Error::Rpc) - .map(drop) + self.update_access_method(new_api_access_method).await } pub async fn disable_access_method( &mut self, - api_access_method_id: ApiAccessMethodId, + api_access_method_id: access_method::Id, ) -> Result<()> { let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; new_api_access_method.disable(); - let update = ApiAccessMethodUpdate { - id: api_access_method_id, - access_method: new_api_access_method, - }; - - self.0 - .update_api_access_method(types::ApiAccessMethodUpdate::from(update)) - .await - .map_err(Error::Rpc) - .map(drop) + self.update_access_method(new_api_access_method).await } pub async fn remove_access_method( &mut self, - api_access_method: ApiAccessMethodId, + api_access_method: access_method::Id, ) -> Result<()> { self.0 .remove_api_access_method(types::Uuid::from(api_access_method)) @@ -551,10 +539,10 @@ impl MullvadProxyClient { pub async fn update_access_method( &mut self, - access_method_update: ApiAccessMethodUpdate, + access_method_update: AccessMethodSetting, ) -> Result<()> { self.0 - .update_api_access_method(types::ApiAccessMethodUpdate::from(access_method_update)) + .update_api_access_method(types::AccessMethodSetting::from(access_method_update)) .await .map_err(Error::Rpc) .map(drop) @@ -569,7 +557,7 @@ impl MullvadProxyClient { /// method "randomly" /// /// [`ApiConnectionModeProvider`]: mullvad_daemon::api::ApiConnectionModeProvider - pub async fn set_access_method(&mut self, api_access_method: ApiAccessMethodId) -> Result<()> { + pub async fn set_access_method(&mut self, api_access_method: access_method::Id) -> Result<()> { self.0 .set_api_access_method(types::Uuid::from(api_access_method)) .await diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs new file mode 100644 index 000000000000..0f39d34e9eef --- /dev/null +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -0,0 +1,258 @@ +/// 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::access_method; + + impl From<&access_method::Settings> for proto::ApiAccessMethodSettings { + fn from(settings: &access_method::Settings) -> Self { + Self { + access_method_settings: settings + .access_method_settings + .iter() + .map(|method| method.clone().into()) + .collect(), + } + } + } + + impl From for proto::ApiAccessMethodSettings { + fn from(settings: access_method::Settings) -> Self { + proto::ApiAccessMethodSettings::from(&settings) + } + } + + impl TryFrom for access_method::Settings { + type Error = FromProtobufTypeError; + + fn try_from(settings: proto::ApiAccessMethodSettings) -> Result { + Ok(Self { + access_method_settings: settings + .access_method_settings + .iter() + .map(access_method::AccessMethodSetting::try_from) + .collect::, _>>()?, + }) + } + } +} + +/// Implements conversions for the auxilliary +/// [`crate::types::proto::ApiAccessMethod`] type to the internal +/// [`mullvad_types::access_method::AccessMethodSetting`] data type. +mod data { + use crate::types::{proto, FromProtobufTypeError}; + use mullvad_types::access_method::{ + AccessMethod, AccessMethodSetting, BuiltInAccessMethod, CustomAccessMethod, Id, + Shadowsocks, Socks5, Socks5Local, Socks5Remote, + }; + + impl TryFrom for AccessMethodSetting { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::AccessMethodSetting) -> Result { + let id = value + .id + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Access Method from protobuf", + )) + .and_then(Id::try_from)?; + let name = value.name; + let enabled = value.enabled; + let access_method = value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Access Method from protobuf", + )) + .and_then(AccessMethod::try_from)?; + + Ok(AccessMethodSetting::with_id( + id, + name, + enabled, + access_method, + )) + } + } + + impl From for proto::AccessMethodSetting { + fn from(value: AccessMethodSetting) -> Self { + let id = proto::Uuid::from(value.get_id()); + let name = value.get_name(); + let enabled = value.enabled(); + proto::AccessMethodSetting { + id: Some(id), + name, + enabled, + access_method: Some(proto::AccessMethod::from(value.access_method)), + } + } + } + + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::AccessMethod) -> Result { + let access_method = + value + .access_method + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not deserialize Access Method from protobuf", + ))?; + + 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)? + } + }) + } + } + + impl From for proto::AccessMethod { + fn from(value: AccessMethod) -> Self { + match value { + AccessMethod::Custom(value) => proto::AccessMethod::from(value), + AccessMethod::BuiltIn(value) => proto::AccessMethod::from(value), + } + } + } + + impl From for AccessMethod { + fn from(_value: proto::access_method::Direct) -> Self { + AccessMethod::from(BuiltInAccessMethod::Direct) + } + } + + impl From for AccessMethod { + fn from(_value: proto::access_method::Bridges) -> Self { + AccessMethod::from(BuiltInAccessMethod::Bridge) + } + } + + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::access_method::Socks5Local) -> Result { + Socks5Local::from_args(value.ip, value.port as u16, value.local_port as u16) + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (local) message from protobuf", + )) + .map(AccessMethod::from) + } + } + + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::access_method::Socks5Remote) -> Result { + Socks5Remote::from_args(value.ip, value.port as u16) + .ok_or({ + FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (remote) message from protobuf", + ) + }) + .map(AccessMethod::from) + } + } + + impl TryFrom for AccessMethod { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::access_method::Shadowsocks) -> Result { + Shadowsocks::from_args(value.ip, value.port as u16, value.cipher, value.password) + .ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse Shadowsocks message from protobuf", + )) + .map(AccessMethod::from) + } + } + + impl From for proto::AccessMethod { + fn from(value: BuiltInAccessMethod) -> Self { + let access_method = match value { + mullvad_types::access_method::BuiltInAccessMethod::Direct => { + proto::access_method::AccessMethod::Direct(proto::access_method::Direct {}) + } + mullvad_types::access_method::BuiltInAccessMethod::Bridge => { + proto::access_method::AccessMethod::Bridges(proto::access_method::Bridges {}) + } + }; + proto::AccessMethod { + access_method: Some(access_method), + } + } + } + + impl From for proto::AccessMethod { + fn from(value: CustomAccessMethod) -> Self { + let access_method = match value { + CustomAccessMethod::Shadowsocks(ss) => { + proto::access_method::AccessMethod::Shadowsocks( + proto::access_method::Shadowsocks { + ip: ss.peer.ip().to_string(), + port: ss.peer.port() as u32, + password: ss.password, + cipher: ss.cipher, + }, + ) + } + CustomAccessMethod::Socks5(Socks5::Local(Socks5Local { peer, port })) => { + proto::access_method::AccessMethod::Socks5local( + proto::access_method::Socks5Local { + ip: peer.ip().to_string(), + port: peer.port() as u32, + local_port: port as u32, + }, + ) + } + CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { + proto::access_method::AccessMethod::Socks5remote( + proto::access_method::Socks5Remote { + ip: peer.ip().to_string(), + port: peer.port() as u32, + }, + ) + } + }; + + proto::AccessMethod { + access_method: Some(access_method), + } + } + } + + impl From for proto::Uuid { + fn from(value: Id) -> Self { + proto::Uuid { + value: value.to_string(), + } + } + } + + impl TryFrom for Id { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::Uuid) -> Result { + Self::from_string(value.value).ok_or(FromProtobufTypeError::InvalidArgument( + "Could not parse UUID message from protobuf", + )) + } + } + + impl TryFrom<&proto::AccessMethodSetting> for AccessMethodSetting { + type Error = FromProtobufTypeError; + + fn try_from(value: &proto::AccessMethodSetting) -> Result { + AccessMethodSetting::try_from(value.clone()) + } + } +} diff --git a/mullvad-management-interface/src/types/conversions/api_access_method.rs b/mullvad-management-interface/src/types/conversions/api_access_method.rs deleted file mode 100644 index 0fd29f4b93e2..000000000000 --- a/mullvad-management-interface/src/types/conversions/api_access_method.rs +++ /dev/null @@ -1,288 +0,0 @@ -/// 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::access_method; - - impl From<&api_access::Settings> for proto::ApiAccessMethodSettings { - fn from(settings: &api_access::Settings) -> Self { - Self { - api_access_methods: settings - .api_access_methods - .iter() - .map(|method| method.clone().into()) - .collect(), - } - } - } - - impl From for proto::ApiAccessMethodSettings { - fn from(settings: api_access::Settings) -> Self { - proto::ApiAccessMethodSettings::from(&settings) - } - } - - impl TryFrom for api_access::Settings { - type Error = FromProtobufTypeError; - - fn try_from(settings: proto::ApiAccessMethodSettings) -> Result { - Ok(Self { - api_access_methods: settings - .api_access_methods - .iter() - .map(api_access::AccessMethodSetting::try_from) - .collect::, _>>()?, - }) - } - } - - impl From for proto::ApiAccessMethodUpdate { - fn from(value: access_method::daemon::ApiAccessMethodUpdate) -> Self { - proto::ApiAccessMethodUpdate { - id: Some(proto::Uuid::from(value.id)), - access_method: Some(proto::ApiAccessMethod::from(value.access_method)), - } - } - } - - impl TryFrom for access_method::daemon::ApiAccessMethodUpdate { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::ApiAccessMethodUpdate) -> Result { - let api_access_method = value - .access_method - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not convert Access Method from protobuf", - )) - .and_then(api_access::AccessMethodSetting::try_from)?; - - let id = value - .id - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not convert Access Method from protobuf", - )) - .map(api_access::ApiAccessMethodId::from)?; - - Ok(access_method::daemon::ApiAccessMethodUpdate { - id, - access_method: api_access_method, - }) - } - } -} - -/// 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, FromProtobufTypeError}; - use mullvad_types::api_access::{ - AccessMethod, AccessMethodSetting, ApiAccessMethodId, BuiltInAccessMethod, - CustomAccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote, - }; - - impl TryFrom for Vec { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::ApiAccessMethods) -> Result { - value - .api_access_methods - .iter() - .map(AccessMethodSetting::try_from) - .collect() - } - } - - impl TryFrom for AccessMethodSetting { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::ApiAccessMethod) -> Result { - let id = value - .id - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not deserialize Access Method from protobuf", - )) - .and_then(ApiAccessMethodId::try_from)?; - let name = value.name; - let enabled = value.enabled; - let access_method = value - .access_method - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not deserialize Access Method from protobuf", - )) - .and_then(AccessMethod::try_from)?; - - Ok(AccessMethodSetting::with_id( - id, - name, - enabled, - access_method, - )) - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: AccessMethodSetting) -> Self { - let id = proto::Uuid::from(value.get_id()); - let name = value.get_name(); - let enabled = value.enabled(); - proto::ApiAccessMethod { - id: Some(id), - name, - enabled, - access_method: Some(proto::AccessMethod::from(value.access_method)), - } - } - } - - impl TryFrom for AccessMethod { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::AccessMethod) -> Result { - let access_method = - value - .access_method - .ok_or(FromProtobufTypeError::InvalidArgument( - "Could not deserialize Access Method from protobuf", - ))?; - - let access_method = 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::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::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(AccessMethodSetting::with_id( - id, - name, - enabled, - access_method, - )) - } - } - - impl From for proto::Uuid { - fn from(value: ApiAccessMethodId) -> Self { - proto::Uuid { - value: value.to_string(), - } - } - } - - impl TryFrom for ApiAccessMethodId { - type Error = FromProtobufTypeError; - - fn try_from(value: proto::Uuid) -> Result { - Self::from_string(value.value).ok_or(FromProtobufTypeError::InvalidArgument( - "Could not parse UUID message from protobuf", - )) - } - } - - impl From for proto::ApiAccessMethod { - fn from(value: AccessMethodSetting) -> Self { - let id = proto::Uuid::from(value.get_id()); - let name = value.get_name(); - let enabled = value.enabled(); - let access_method = match value.access_method { - AccessMethod::Custom(value) => match value { - CustomAccessMethod::Shadowsocks(ss) => { - proto::api_access_method::AccessMethod::Shadowsocks( - proto::api_access_method::Shadowsocks { - ip: ss.peer.ip().to_string(), - port: ss.peer.port() as u32, - password: ss.password, - cipher: ss.cipher, - }, - ) - } - CustomAccessMethod::Socks5(Socks5::Local(Socks5Local { peer, port })) => { - proto::api_access_method::AccessMethod::Socks5local( - proto::api_access_method::Socks5Local { - ip: peer.ip().to_string(), - port: peer.port() as u32, - local_port: port as u32, - }, - ) - } - CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { - proto::api_access_method::AccessMethod::Socks5remote( - proto::api_access_method::Socks5Remote { - ip: peer.ip().to_string(), - port: peer.port() as u32, - }, - ) - } - }, - AccessMethod::BuiltIn(value) => match value { - mullvad_types::api_access::BuiltInAccessMethod::Direct => { - proto::api_access_method::AccessMethod::Direct( - proto::api_access_method::Direct {}, - ) - } - mullvad_types::api_access::BuiltInAccessMethod::Bridge => { - proto::api_access_method::AccessMethod::Bridges( - proto::api_access_method::Bridges {}, - ) - } - }, - }; - - proto::ApiAccessMethod { - id: Some(id), - name, - enabled, - access_method: Some(access_method), - } - } - } - - impl TryFrom<&proto::ApiAccessMethod> for AccessMethodSetting { - type Error = FromProtobufTypeError; - - fn try_from(value: &proto::ApiAccessMethod) -> Result { - AccessMethodSetting::try_from(value.clone()) - } - } - - impl From> for proto::ApiAccessMethods { - fn from(value: Vec) -> proto::ApiAccessMethods { - proto::ApiAccessMethods { - api_access_methods: value.iter().map(|method| method.clone().into()).collect(), - } - } - } -} diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs index ddfe3f4282ba..dd6fcd450167 100644 --- a/mullvad-management-interface/src/types/conversions/mod.rs +++ b/mullvad-management-interface/src/types/conversions/mod.rs @@ -1,7 +1,7 @@ use std::str::FromStr; +mod access_method; mod account; -mod api_access_method; mod custom_list; mod custom_tunnel; mod device; diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index 532e5e8f2985..98c195d93562 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 for mullvad_types::settings::Settings { custom_lists: mullvad_types::custom_list::CustomListsSettings::try_from( custom_lists_settings, )?, - api_access_methods: mullvad_types::api_access::Settings::try_from( + api_access_methods: mullvad_types::access_method::Settings::try_from( api_access_methods_settings, )?, }) diff --git a/mullvad-types/src/api_access.rs b/mullvad-types/src/access_method.rs similarity index 85% rename from mullvad-types/src/api_access.rs rename to mullvad-types/src/access_method.rs index b916a7f323a5..a300072c82f8 100644 --- a/mullvad-types/src/api_access.rs +++ b/mullvad-types/src/access_method.rs @@ -6,23 +6,23 @@ 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, + pub access_method_settings: Vec, } impl Settings { /// Append an [`AccessMethod`] to the end of `api_access_methods`. pub fn append(&mut self, api_access_method: AccessMethodSetting) { - self.api_access_methods.push(api_access_method) + self.access_method_settings.push(api_access_method) } /// Remove an [`ApiAccessMethod`] from `api_access_methods`. - pub fn remove(&mut self, api_access_method: &ApiAccessMethodId) { + pub fn remove(&mut self, api_access_method: &Id) { self.retain(|method| method.get_id() != *api_access_method) } /// Search for a particular [`AccessMethod`] in `api_access_methods`. - pub fn find(&self, element: &ApiAccessMethodId) -> Option<&AccessMethodSetting> { - self.api_access_methods + pub fn find(&self, element: &Id) -> Option<&AccessMethodSetting> { + self.access_method_settings .iter() .find(|api_access_method| *element == api_access_method.get_id()) } @@ -32,8 +32,8 @@ impl Settings { /// 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. - pub fn find_mut(&mut self, element: &ApiAccessMethodId) -> Option<&mut AccessMethodSetting> { - self.api_access_methods + pub fn find_mut(&mut self, element: &Id) -> Option<&mut AccessMethodSetting> { + self.access_method_settings .iter_mut() .find(|api_access_method| *element == api_access_method.get_id()) } @@ -43,19 +43,19 @@ impl Settings { where F: FnMut(&AccessMethodSetting) -> bool, { - self.api_access_methods.retain(f) + self.access_method_settings.retain(f) } /// Clone the content of `api_access_methods`. pub fn cloned(&self) -> Vec { - self.api_access_methods.clone() + self.access_method_settings.clone() } } impl Default for Settings { fn default() -> Self { Self { - api_access_methods: vec![BuiltInAccessMethod::Direct, BuiltInAccessMethod::Bridge] + access_method_settings: vec![BuiltInAccessMethod::Direct, BuiltInAccessMethod::Bridge] .into_iter() .map(|built_in| { AccessMethodSetting::new( @@ -75,28 +75,28 @@ impl Default for Settings { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct AccessMethodSetting { /// Some unique id (distinct for each `AccessMethod`). - id: ApiAccessMethodId, + id: Id, pub name: String, pub enabled: bool, pub access_method: AccessMethod, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct ApiAccessMethodId(uuid::Uuid); +pub struct Id(uuid::Uuid); -impl ApiAccessMethodId { +impl Id { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } /// Tries to parse a UUID from a raw String. If it is successful, an - /// [`ApiAccessMethodId`] is instantiated. + /// [`Id`] is instantiated. pub fn from_string(id: String) -> Option { uuid::Uuid::from_str(&id).ok().map(Self) } } -impl std::fmt::Display for ApiAccessMethodId { +impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } @@ -112,7 +112,7 @@ pub enum AccessMethod { impl AccessMethodSetting { pub fn new(name: String, enabled: bool, access_method: AccessMethod) -> Self { Self { - id: ApiAccessMethodId::new(), + id: Id::new(), name, enabled, access_method, @@ -128,12 +128,7 @@ impl AccessMethodSetting { /// /// [`new`]: ApiAccessMethod::new /// [`with_id`]: ApiAccessMethod::with_id - pub fn with_id( - id: ApiAccessMethodId, - name: String, - enabled: bool, - access_method: AccessMethod, - ) -> Self { + pub fn with_id(id: Id, name: String, enabled: bool, access_method: AccessMethod) -> Self { Self { id, name, @@ -142,7 +137,7 @@ impl AccessMethodSetting { } } - pub fn get_id(&self) -> ApiAccessMethodId { + pub fn get_id(&self) -> Id { self.id.clone() } @@ -326,14 +321,3 @@ impl From for Socks5 { 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 `UpdateApiAccessMethod`. - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - pub struct ApiAccessMethodUpdate { - pub id: ApiAccessMethodId, - pub access_method: ApiAccessMethod, - } -} diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index e1af195220b5..8aefaeb4000b 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; 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 2c40172a2cc6..6ade7dea3271 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -1,5 +1,5 @@ use crate::{ - api_access, + 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::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::Settings::default(), + api_access_methods: access_method::Settings::default(), } } } From 532bd6d38c9cbb95b6d628dd4470f89f06025d94 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Tue, 26 Sep 2023 15:34:20 +0200 Subject: [PATCH 23/29] Split up `mullvad api-access add` command for SOCKS5-proxy Split up `mullvad api-access add` command for SOCKS5-proxy into "local" and "remote". --- mullvad-cli/src/cmds/api_access.rs | 112 ++++++++++++++++------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 260f0c449668..655f10a5de70 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -193,18 +193,30 @@ impl ApiAccess { #[derive(Subcommand, Debug, Clone)] pub enum AddCustomCommands { /// Configure a local SOCKS5 proxy - Socks5Local { + #[clap(subcommand)] + Socks5(AddSocks5Commands), + /// Configure Shadowsocks proxy + Shadowsocks { /// An easy to remember name for this custom proxy name: String, - /// The port that the server on localhost is listening on - local_port: u16, - /// The IP of the remote peer + /// The IP of the remote Shadowsocks server remote_ip: IpAddr, - /// The port of the remote peer + /// The port of the remote Shadowsocks server + #[arg(default_value = "443")] remote_port: u16, + /// Password for authentication + #[arg(default_value = "mullvad")] + password: String, + /// Cipher to use + #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] + cipher: String, }, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum AddSocks5Commands { /// Configure a remote SOCKS5 proxy - Socks5Remote { + Remote { /// An easy to remember name for this custom proxy name: String, /// The IP of the remote proxy server @@ -212,30 +224,27 @@ pub enum AddCustomCommands { /// The port of the remote proxy server remote_port: u16, }, - /// Configure Shadowsocks proxy - Shadowsocks { + /// Configure a local SOCKS5 proxy + Local { /// An easy to remember name for this custom proxy name: String, - /// The IP of the remote Shadowsocks server + /// The port that the server on localhost is listening on + local_port: u16, + /// The IP of the remote peer remote_ip: IpAddr, - /// The port of the remote Shadowsocks server - #[arg(default_value = "443")] + /// The port of the remote peer remote_port: u16, - /// Password for authentication - #[arg(default_value = "mullvad")] - password: String, - /// Cipher to use - #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] - cipher: String, }, } impl AddCustomCommands { fn name(&self) -> String { match self { - AddCustomCommands::Socks5Local { name, .. } => name, - AddCustomCommands::Socks5Remote { name, .. } => name, AddCustomCommands::Shadowsocks { name, .. } => name, + AddCustomCommands::Socks5(socks) => match socks { + AddSocks5Commands::Remote { name, .. } => name, + AddSocks5Commands::Local { name, .. } => name, + }, } .clone() } @@ -308,42 +317,47 @@ mod conversions { use anyhow::{anyhow, Error}; use mullvad_types::access_method as daemon_types; - use super::AddCustomCommands; + use super::{AddCustomCommands, AddSocks5Commands}; impl TryFrom for daemon_types::AccessMethod { type Error = Error; fn try_from(value: AddCustomCommands) -> Result { Ok(match value { - AddCustomCommands::Socks5Local { - local_port, - remote_ip, - remote_port, - name: _, - } => { - println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Local( - daemon_types::Socks5Local::from_args( - remote_ip.to_string(), - remote_port, - local_port, - ) - .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, - ); - daemon_types::AccessMethod::from(socks_proxy) - } - AddCustomCommands::Socks5Remote { - remote_ip, - remote_port, - name: _, - } => { - println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Remote( - daemon_types::Socks5Remote::from_args(remote_ip.to_string(), remote_port) + AddCustomCommands::Socks5(socks) => match socks { + AddSocks5Commands::Local { + local_port, + remote_ip, + remote_port, + name: _, + } => { + println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Local( + daemon_types::Socks5Local::from_args( + remote_ip.to_string(), + remote_port, + local_port, + ) + .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, + ); + daemon_types::AccessMethod::from(socks_proxy) + } + AddSocks5Commands::Remote { + remote_ip, + remote_port, + name: _, + } => { + println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); + let socks_proxy = daemon_types::Socks5::Remote( + daemon_types::Socks5Remote::from_args( + remote_ip.to_string(), + remote_port, + ) .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, - ); - daemon_types::AccessMethod::from(socks_proxy) - } + ); + daemon_types::AccessMethod::from(socks_proxy) + } + }, AddCustomCommands::Shadowsocks { remote_ip, remote_port, @@ -396,8 +410,6 @@ mod pp { } }; - writeln!(f, "{:?}", self.api_access_method)?; - match &self.api_access_method.access_method { AccessMethod::BuiltIn(method) => { write!(f, "{}", method.canonical_name())?; From dec790e8c876c913120c194f560ef94732d9ba62 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 27 Sep 2023 09:27:38 +0200 Subject: [PATCH 24/29] Allow the user to specify if a new access method should be disabled when added Adds the `-d | --disabled` flag to `mullvad api-access add ` command. If the `-d | --disable` is set, the access method is *not* enabled from the start. Note that it may still be tested using the `mullvad api-access test` command, even if it is principally marked as `disabled`. --- mullvad-cli/src/cmds/api_access.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 655f10a5de70..c760e8e22353 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -210,6 +210,10 @@ pub enum AddCustomCommands { /// Cipher to use #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")] cipher: String, + /// Disable the use of this custom access method. It has to be manually + /// enabled at a later stage to be used when accessing the Mullvad API. + #[arg(default_value_t = false, short, long)] + disabled: bool, }, } @@ -223,6 +227,10 @@ pub enum AddSocks5Commands { remote_ip: IpAddr, /// The port of the remote proxy server remote_port: u16, + /// Disable the use of this custom access method. It has to be manually + /// enabled at a later stage to be used when accessing the Mullvad API. + #[arg(default_value_t = false, short, long)] + disabled: bool, }, /// Configure a local SOCKS5 proxy Local { @@ -234,6 +242,10 @@ pub enum AddSocks5Commands { remote_ip: IpAddr, /// The port of the remote peer remote_port: u16, + /// Disable the use of this custom access method. It has to be manually + /// enabled at a later stage to be used when accessing the Mullvad API. + #[arg(default_value_t = false, short, long)] + disabled: bool, }, } @@ -248,9 +260,15 @@ impl AddCustomCommands { } .clone() } - /// TODO: Actually add an `enabled` flag to the variants of `AddCustomCommands` + fn enabled(&self) -> bool { - true + match self { + AddCustomCommands::Shadowsocks { disabled, .. } => !disabled, + AddCustomCommands::Socks5(socks) => match socks { + AddSocks5Commands::Remote { disabled, .. } => !disabled, + AddSocks5Commands::Local { disabled, .. } => !disabled, + }, + } } } @@ -330,6 +348,7 @@ mod conversions { remote_ip, remote_port, name: _, + disabled: _, } => { println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Local( @@ -346,6 +365,7 @@ mod conversions { remote_ip, remote_port, name: _, + disabled: _, } => { println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); let socks_proxy = daemon_types::Socks5::Remote( @@ -364,6 +384,7 @@ mod conversions { password, cipher, name: _, + disabled: _, } => { println!( "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" From 189c8d0273c036681142f73dfe3a5c619a0e0d28 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 27 Sep 2023 15:10:38 +0200 Subject: [PATCH 25/29] Add `GetCurrentApiAccessMethod` to show the current API access method - Add a new RPC message: `GetCurrentApiAccessMethod`. This message may be used to retrieve the access method which is currently in use by the daemon for connecting to the Mullvad API. - Add `mullvad api-access status` for showing the API access method in use --- mullvad-cli/src/cmds/api_access.rs | 12 ++ mullvad-daemon/src/access_method.rs | 37 ++++-- mullvad-daemon/src/api.rs | 119 ++++++++++-------- mullvad-daemon/src/lib.rs | 26 +++- mullvad-daemon/src/management_interface.rs | 26 +++- .../proto/management_interface.proto | 3 +- mullvad-management-interface/src/client.rs | 11 ++ 7 files changed, 161 insertions(+), 73 deletions(-) diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index c760e8e22353..4c1594b1fe90 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -27,6 +27,8 @@ pub enum ApiAccess { /// /// Selecting "Mullvad Bridges" respects your current bridge settings. Use(SelectItem), + /// Show which access method is currently used to access the Mullvad API. + Status, } impl ApiAccess { @@ -52,6 +54,9 @@ impl ApiAccess { ApiAccess::Use(cmd) => { Self::set(cmd).await?; } + ApiAccess::Status => { + Self::status().await?; + } }; Ok(()) } @@ -178,6 +183,13 @@ impl ApiAccess { Ok(()) } + async fn status() -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + let current = rpc.get_current_api_access_method().await?; + println!("{}", pp::ApiAccessMethodFormatter::new(¤t)); + Ok(()) + } + async fn get_access_method( rpc: &mut MullvadProxyClient, item: &SelectItem, diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index 0501a4336c28..73fd35fcbd4b 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -15,6 +15,10 @@ pub enum Error { /// Can not find access method #[error(display = "Cannot find custom access method {}", _0)] NoSuchMethod(access_method::Id), + /// Can not find *any* access method. This should never happen. If it does, + /// the user should do a factory reset. + #[error(display = "No access methods are configured")] + NoMethodsExist, /// Access methods settings error #[error(display = "Settings error")] Settings(#[error(source)] settings::Error), @@ -62,6 +66,20 @@ where .map_err(Error::Settings) } + pub fn set_api_access_method(&mut self, access_method: access_method::Id) -> Result<(), Error> { + if let Some(access_method) = self.settings.api_access_methods.find(&access_method) { + { + let mut connection_modes = self.connection_modes.lock().unwrap(); + connection_modes.set_access_method(access_method.clone()); + } + // Force a rotation of Access Methods. + let _ = self.api_handle.service().next_api_endpoint(); + Ok(()) + } else { + Err(Error::NoSuchMethod(access_method)) + } + } + /// "Updates" an [`AccessMethodSetting`] by replacing the existing entry /// with the argument `access_method_update` if an existing entry with /// matching UUID is found. @@ -82,18 +100,11 @@ where .map_err(Error::Settings) } - pub fn set_api_access_method(&mut self, access_method: access_method::Id) -> Result<(), Error> { - if let Some(access_method) = self.settings.api_access_methods.find(&access_method) { - { - let mut connection_modes = self.connection_modes.lock().unwrap(); - connection_modes.set_access_method(access_method.access_method.clone()); - } - // Force a rotation of Access Methods. - let _ = self.api_handle.service().next_api_endpoint(); - Ok(()) - } else { - Err(Error::NoSuchMethod(access_method)) - } + /// Return the [`AccessMethodSetting`] which is currently used to access the + /// Mullvad API. + pub async fn get_current_access_method(&mut self) -> Result { + let connections_modes = self.connection_modes.lock().unwrap(); + Ok(connections_modes.peek()) } /// If settings were changed due to an update, notify all listeners. @@ -109,7 +120,7 @@ where .access_method_settings .iter() .filter(|api_access_method| api_access_method.enabled()) - .map(|api_access_method| api_access_method.access_method.clone()) + .cloned() .collect(), ) }; diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 8c8af4d4dc55..d44bdd0849a7 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::access_method::{self, AccessMethod, BuiltInAccessMethod}; +use mullvad_types::access_method::AccessMethodSetting; use std::{ net::SocketAddr, path::PathBuf, @@ -37,7 +37,7 @@ use talpid_types::{ /// [`ConnectionModesIterator`]. pub struct ApiConnectionModeProvider { cache_dir: PathBuf, - /// Used for selecting a Relay when the `Mullvad Bridges` access method is used. + /// Used for selecting a Bridge when the `Mullvad Bridges` access method is used. relay_selector: RelaySelector, current_task: Option + Send>>>, connection_modes: Arc>, @@ -82,13 +82,14 @@ impl ApiConnectionModeProvider { pub(crate) fn new( cache_dir: PathBuf, relay_selector: RelaySelector, - connection_modes: Vec, + connection_modes: Vec, ) -> Self { + let connection_modes_iterator = ConnectionModesIterator::new(connection_modes); Self { cache_dir, relay_selector, current_task: None, - connection_modes: Arc::new(Mutex::new(ConnectionModesIterator::new(connection_modes))), + connection_modes: Arc::new(Mutex::new(connection_modes_iterator)), } } @@ -105,12 +106,10 @@ impl ApiConnectionModeProvider { log::debug!("Rotating Access mode!"); let access_method = { let mut access_methods_picker = self.connection_modes.lock().unwrap(); - access_methods_picker - .next() - .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)) + access_methods_picker.next() }; - let connection_mode = self.from(&access_method); + let connection_mode = self.from(access_method.as_ref()); log::info!("New API connection mode selected: {}", connection_mode); connection_mode } @@ -119,39 +118,45 @@ impl ApiConnectionModeProvider { /// [`ApiConnectionMode`]s require extra logic/data from /// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait /// can not be implemented. - fn from(&mut self, access_method: &AccessMethod) -> ApiConnectionMode { + fn from(&mut self, access_method: Option<&AccessMethodSetting>) -> ApiConnectionMode { + use mullvad_types::access_method::{self, AccessMethod, BuiltInAccessMethod}; match access_method { - AccessMethod::BuiltIn(access_method) => match access_method { - BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, - BuiltInAccessMethod::Bridge => self - .relay_selector - .get_bridge_forced() - .and_then(|settings| match settings { - ProxySettings::Shadowsocks(ss_settings) => { - let ss_settings: access_method::Shadowsocks = - access_method::Shadowsocks::new( - ss_settings.peer, - ss_settings.cipher, - ss_settings.password, - ); - Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( - ss_settings, - ))) - } - _ => { - log::error!("Received unexpected proxy settings type"); - None - } - }) - .unwrap_or(ApiConnectionMode::Direct), - }, - AccessMethod::Custom(access_method) => match &access_method { - access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config.clone())) - } - access_method::CustomAccessMethod::Socks5(socks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) - } + None => ApiConnectionMode::Direct, + Some(access_method) => match &access_method.access_method { + AccessMethod::BuiltIn(access_method) => match access_method { + BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, + BuiltInAccessMethod::Bridge => self + .relay_selector + .get_bridge_forced() + .and_then(|settings| match settings { + ProxySettings::Shadowsocks(ss_settings) => { + let ss_settings: access_method::Shadowsocks = + access_method::Shadowsocks::new( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + ); + Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( + ss_settings, + ))) + } + _ => { + log::error!("Received unexpected proxy settings type"); + None + } + }) + .unwrap_or(ApiConnectionMode::Direct), + }, + AccessMethod::Custom(access_method) => match &access_method { + access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( + shadowsocks_config.clone(), + )) + } + access_method::CustomAccessMethod::Socks5(socks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) + } + }, }, } } @@ -166,38 +171,54 @@ impl ApiConnectionModeProvider { /// [`unwrap`]: Option::unwrap /// [`next`]: std::iter::Iterator::next pub struct ConnectionModesIterator { - available_modes: Box + Send>, - next: Option, + available_modes: Box + Send>, + next: Option, + current: AccessMethodSetting, } impl ConnectionModesIterator { - pub fn new(access_methods: Vec) -> ConnectionModesIterator { + pub fn new(access_methods: Vec) -> ConnectionModesIterator { + let mut iterator = Self::cycle(access_methods); Self { next: None, - available_modes: Self::cycle(access_methods), + current: iterator.next().unwrap(), + available_modes: iterator, } } /// Set the next [`AccessMethod`] to be returned from this iterator. - pub fn set_access_method(&mut self, next: AccessMethod) { + pub fn set_access_method(&mut self, next: AccessMethodSetting) { self.next = Some(next); } /// Update the collection of [`AccessMethod`] which this iterator will /// return. - pub fn update_access_methods(&mut self, access_methods: Vec) { + pub fn update_access_methods(&mut self, access_methods: Vec) { self.available_modes = Self::cycle(access_methods) } - fn cycle(access_methods: Vec) -> Box + Send> { + fn cycle( + access_methods: Vec, + ) -> Box + Send> { Box::new(access_methods.into_iter().cycle()) } + + /// Look at the currently active [`AccessMethod`] + pub fn peek(&self) -> AccessMethodSetting { + self.current.clone() + } } impl Iterator for ConnectionModesIterator { - type Item = AccessMethod; + type Item = AccessMethodSetting; fn next(&mut self) -> Option { - self.next.take().or_else(|| self.available_modes.next()) + let next = self + .next + .take() + .or_else(|| self.available_modes.next()) + .unwrap(); + self.current = next.clone(); + Some(next) } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 26f7956dfb06..0a00639c09ed 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -275,6 +275,8 @@ pub enum DaemonCommand { SetApiAccessMethod(ResponseTx<(), Error>, mullvad_types::access_method::Id), /// Edit an API access method UpdateApiAccessMethod(ResponseTx<(), Error>, AccessMethodSetting), + /// Get the currently used API access method + GetCurrentAccessMethod(ResponseTx), /// Get the addresses of all known API endpoints GetApiAddresses(ResponseTx, Error>), /// Get information about the currently running and latest app versions @@ -648,7 +650,7 @@ where .iter() // We only care about the access methods which are set to 'enabled' by the user. .filter(|api_access_method| api_access_method.enabled()) - .map(|api_access_method| api_access_method.access_method.clone()) + .cloned() .collect(), ); @@ -1074,6 +1076,7 @@ where } RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, UpdateApiAccessMethod(tx, method) => self.on_update_api_access_method(tx, method).await, + GetCurrentAccessMethod(tx) => self.on_get_current_api_access_method(tx).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), @@ -2281,6 +2284,17 @@ where Self::oneshot_send(tx, result, "remove_api_access_method response"); } + fn on_set_api_access_method( + &mut self, + tx: ResponseTx<(), Error>, + access_method: mullvad_types::access_method::Id, + ) { + let result = self + .set_api_access_method(access_method) + .map_err(Error::AccessMethodError); + Self::oneshot_send(tx, result, "set_api_access_method response"); + } + async fn on_update_api_access_method( &mut self, tx: ResponseTx<(), Error>, @@ -2293,15 +2307,15 @@ where Self::oneshot_send(tx, result, "update_api_access_method response"); } - fn on_set_api_access_method( + async fn on_get_current_api_access_method( &mut self, - tx: ResponseTx<(), Error>, - access_method: mullvad_types::access_method::Id, + tx: ResponseTx, ) { let result = self - .set_api_access_method(access_method) + .get_current_access_method() + .await .map_err(Error::AccessMethodError); - Self::oneshot_send(tx, result, "set_api_access_method response"); + Self::oneshot_send(tx, result, "get_current_api_access_method response"); } async fn on_get_api_addresses(&mut self, tx: ResponseTx, Error>) { diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index ce12332b3b58..f92b895ba8fd 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -619,6 +619,8 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + // Access Methods + async fn add_api_access_method( &self, request: Request, @@ -653,6 +655,17 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } + async fn set_api_access_method(&self, request: Request) -> ServiceResult<()> { + log::debug!("set_api_access_method"); + let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?; + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, api_access_method))?; + self.wait_for_result(rx) + .await? + .map(Response::new) + .map_err(map_daemon_error) + } + async fn update_api_access_method( &self, request: Request, @@ -671,13 +684,18 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn set_api_access_method(&self, request: Request) -> ServiceResult<()> { - log::debug!("set_api_access_method"); - let api_access_method = mullvad_types::access_method::Id::try_from(request.into_inner())?; + /// Return the [`types::AccessMethodSetting`] which the daemon is using to + /// connect to the Mullvad API. + async fn get_current_api_access_method( + &self, + _: Request<()>, + ) -> ServiceResult { + log::debug!("get_current_api_access_method"); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetApiAccessMethod(tx, api_access_method))?; + self.send_command_to_daemon(DaemonCommand::GetCurrentAccessMethod(tx))?; self.wait_for_result(rx) .await? + .map(types::AccessMethodSetting::from) .map(Response::new) .map_err(map_daemon_error) } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index a7b3cb11a16e..412358c9c5d0 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -74,11 +74,12 @@ service ManagementService { rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {} rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {} - // API Access methods + // Access methods rpc AddApiAccessMethod(NewAccessMethodSetting) returns (UUID) {} rpc RemoveApiAccessMethod(UUID) returns (google.protobuf.Empty) {} rpc SetApiAccessMethod(UUID) returns (google.protobuf.Empty) {} rpc UpdateApiAccessMethod(AccessMethodSetting) returns (google.protobuf.Empty) {} + rpc GetCurrentApiAccessMethod(google.protobuf.Empty) returns (AccessMethodSetting) {} // Split tunneling (Linux) rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {} diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index aafee5b5c500..9e1b3772db3a 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -191,6 +191,17 @@ impl MullvadProxyClient { .ok_or(Error::ApiAccessMethodNotFound) } + pub async fn get_current_api_access_method(&mut self) -> Result { + self.0 + .get_current_api_access_method(()) + .await + .map_err(Error::Rpc) + .map(tonic::Response::into_inner) + .and_then(|access_method| { + AccessMethodSetting::try_from(access_method).map_err(Error::InvalidResponse) + }) + } + pub async fn get_api_addresses(&mut self) -> Result<()> { self.0.get_api_addresses(()).await.map_err(Error::Rpc)?; Ok(()) From 998fd39aaebc1435065f1f4886f4bd40f5889e5a Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Wed, 27 Sep 2023 09:39:44 +0200 Subject: [PATCH 26/29] Code cleanup - Get rid of extraneous calls to `clone` - Address nit: combine similar match arms into a single match arm - Fix `clippy` lint "unused `async` for function with no await statements" - Fix protobuf field numbers should start from 1 - This was not the case for `Socks5Local` & `Shadowsocks` - Refactor code for opening proxy connections - Cut down on duplicated code for setting up a proxied connection in `mullvad-api`. The difference between different connection modes is how they wrap the underlying `TCP` stream. - Remove `enable_access_method` & `disable_access_method` from RPC-client --- mullvad-api/src/https_client_with_sni.rs | 132 ++++++++---------- mullvad-cli/src/cmds/api_access.rs | 31 ++-- mullvad-daemon/src/access_method.rs | 2 +- mullvad-daemon/src/api.rs | 80 ++++++----- mullvad-daemon/src/lib.rs | 8 +- .../proto/management_interface.proto | 14 +- mullvad-management-interface/src/client.rs | 18 --- 7 files changed, 122 insertions(+), 163 deletions(-) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index cc7557a8d2ad..47b6923d37e3 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -36,6 +36,7 @@ use std::{ use talpid_types::ErrorExt; use tokio::{ + io::{AsyncRead, AsyncWrite}, net::{TcpSocket, TcpStream}, time::timeout, }; @@ -81,37 +82,59 @@ enum InnerConnectionMode { impl InnerConnectionMode { async fn connect( - &self, + self, hostname: &str, addr: &SocketAddr, #[cfg(target_os = "android")] socket_bypass_tx: Option>, ) -> Result { - use InnerConnectionMode::*; match self { - Direct => { - Self::handle_direct_connection( - addr, + // Set up a TCP-socket connection. + InnerConnectionMode::Direct => { + let first_hop = *addr; + let make_proxy_stream = |tcp_stream| async { Ok(tcp_stream) }; + Self::connect_proxied( + first_hop, hostname, + make_proxy_stream, #[cfg(target_os = "android")] socket_bypass_tx, ) .await } - Shadowsocks(config) => { - Self::handle_shadowsocks_connection( - config.clone(), - addr, + // Set up a Shadowsocks-connection. + InnerConnectionMode::Shadowsocks(shadowsocks) => { + let first_hop = shadowsocks.params.peer; + let make_proxy_stream = |tcp_stream| async { + Ok(ProxyClientStream::from_stream( + shadowsocks.proxy_context, + tcp_stream, + &ServerConfig::from(shadowsocks.params), + *addr, + )) + }; + Self::connect_proxied( + first_hop, hostname, + make_proxy_stream, #[cfg(target_os = "android")] socket_bypass_tx, ) .await } - Socks5(proxy_config) => { - Self::handle_socks_connection( - proxy_config.clone(), - addr, + // Set up a SOCKS5-connection. + InnerConnectionMode::Socks5(socks) => { + let first_hop = socks.peer; + let make_proxy_stream = |tcp_stream| async { + tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr) + .await + .map_err(|error| { + io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) + }) + }; + Self::connect_proxied( + first_hop, hostname, + make_proxy_stream, #[cfg(target_os = "android")] socket_bypass_tx, ) @@ -120,74 +143,37 @@ impl InnerConnectionMode { } } - /// Set up a TCP-socket connection. - async fn handle_direct_connection( - addr: &SocketAddr, - hostname: &str, - #[cfg(target_os = "android")] socket_bypass_tx: Option>, - ) -> Result { - let socket = HttpsConnectorWithSni::open_socket( - *addr, - #[cfg(target_os = "android")] - socket_bypass_tx, - ) - .await?; - #[cfg(feature = "api-override")] - if API.disable_tls { - return Ok(ApiConnection::new(Box::new(socket))); - } - - let tls_stream = TlsStream::connect_https(socket, hostname).await?; - Ok(ApiConnection::new(Box::new(tls_stream))) - } - - /// Set up a shadowsocks-socket connection. - async fn handle_shadowsocks_connection( - shadowsocks: ShadowsocksConfig, - addr: &SocketAddr, + /// Create an [`ApiConnection`] from a [`TcpStream`]. + /// + /// The `make_proxy_stream` closure receives a [`TcpStream`] and produces a + /// stream which can send to and receive data from some server using any + /// proxy protocol. The only restriction is that this stream must implement + /// [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`], as well as + /// [`Unpin`] and [`Send`]. + /// + /// If a direct connection is to be established (i.e. the stream will not be + /// using any proxy protocol) `make_proxy_stream` may return the + /// [`TcpStream`] itself. See for example how a connection is established + /// from connection mode [`InnerConnectionMode::Direct`]. + async fn connect_proxied( + first_hop: SocketAddr, hostname: &str, + make_proxy_stream: ProxyFactory, #[cfg(target_os = "android")] socket_bypass_tx: Option>, - ) -> Result { + ) -> Result + where + ProxyFactory: FnOnce(TcpStream) -> ProxyFuture, + ProxyFuture: Future>, + Proxy: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { let socket = HttpsConnectorWithSni::open_socket( - shadowsocks.params.peer, + first_hop, #[cfg(target_os = "android")] socket_bypass_tx, ) .await?; - let proxy = ProxyClientStream::from_stream( - shadowsocks.proxy_context, - socket, - &ServerConfig::from(shadowsocks.params), - *addr, - ); - - #[cfg(feature = "api-override")] - if API.disable_tls { - return Ok(ApiConnection::new(Box::new(ConnectionDecorator(proxy)))); - } - let tls_stream = TlsStream::connect_https(proxy, hostname).await?; - Ok(ApiConnection::new(Box::new(tls_stream))) - } - - /// Set up a SOCKS5-socket connection. - async fn handle_socks_connection( - proxy_config: SocksConfig, - addr: &SocketAddr, - hostname: &str, - #[cfg(target_os = "android")] socket_bypass_tx: Option>, - ) -> Result { - let socket = HttpsConnectorWithSni::open_socket( - proxy_config.peer, - #[cfg(target_os = "android")] - socket_bypass_tx.clone(), - ) - .await?; - let proxy = tokio_socks::tcp::Socks5Stream::connect_with_socket(socket, addr) - .await - .map_err(|error| { - io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) - })?; + let proxy = make_proxy_stream(socket).await?; #[cfg(feature = "api-override")] if API.disable_tls { diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 4c1594b1fe90..a3b17aca0bba 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -77,7 +77,7 @@ impl ApiAccess { /// Add a custom API access method. async fn add(cmd: AddCustomCommands) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let name = cmd.name(); + let name = cmd.name().to_string(); let enabled = cmd.enabled(); let access_method = AccessMethod::try_from(cmd)?; rpc.add_access_method(name, enabled, access_method).await?; @@ -143,16 +143,18 @@ impl ApiAccess { /// Enable a custom API access method. async fn enable(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = Self::get_access_method(&mut rpc, &item).await?; - rpc.enable_access_method(access_method.get_id()).await?; + let mut access_method = Self::get_access_method(&mut rpc, &item).await?; + access_method.enable(); + rpc.update_access_method(access_method).await?; Ok(()) } /// Disable a custom API access method. async fn disable(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = Self::get_access_method(&mut rpc, &item).await?; - rpc.disable_access_method(access_method.get_id()).await?; + let mut access_method = Self::get_access_method(&mut rpc, &item).await?; + access_method.disable(); + rpc.update_access_method(access_method).await?; Ok(()) } @@ -262,24 +264,19 @@ pub enum AddSocks5Commands { } impl AddCustomCommands { - fn name(&self) -> String { + fn name(&self) -> &str { match self { - AddCustomCommands::Shadowsocks { name, .. } => name, - AddCustomCommands::Socks5(socks) => match socks { - AddSocks5Commands::Remote { name, .. } => name, - AddSocks5Commands::Local { name, .. } => name, - }, + AddCustomCommands::Shadowsocks { name, .. } + | AddCustomCommands::Socks5(AddSocks5Commands::Remote { name, .. }) + | AddCustomCommands::Socks5(AddSocks5Commands::Local { name, .. }) => name, } - .clone() } fn enabled(&self) -> bool { match self { - AddCustomCommands::Shadowsocks { disabled, .. } => !disabled, - AddCustomCommands::Socks5(socks) => match socks { - AddSocks5Commands::Remote { disabled, .. } => !disabled, - AddSocks5Commands::Local { disabled, .. } => !disabled, - }, + AddCustomCommands::Shadowsocks { disabled, .. } + | AddCustomCommands::Socks5(AddSocks5Commands::Remote { disabled, .. }) + | AddCustomCommands::Socks5(AddSocks5Commands::Local { disabled, .. }) => !disabled, } } } diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index 73fd35fcbd4b..ea096cf5ba64 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -102,7 +102,7 @@ where /// Return the [`AccessMethodSetting`] which is currently used to access the /// Mullvad API. - pub async fn get_current_access_method(&mut self) -> Result { + pub fn get_current_access_method(&mut self) -> Result { let connections_modes = self.connection_modes.lock().unwrap(); Ok(connections_modes.peek()) } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index d44bdd0849a7..c548f0a293c4 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::access_method::AccessMethodSetting; +use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, BuiltInAccessMethod}; use std::{ net::SocketAddr, path::PathBuf, @@ -106,10 +106,13 @@ impl ApiConnectionModeProvider { log::debug!("Rotating Access mode!"); let access_method = { let mut access_methods_picker = self.connection_modes.lock().unwrap(); - access_methods_picker.next() + access_methods_picker + .next() + .map(|access_method_setting| access_method_setting.access_method) + .unwrap_or(AccessMethod::from(BuiltInAccessMethod::Direct)) }; - let connection_mode = self.from(access_method.as_ref()); + let connection_mode = self.from(access_method); log::info!("New API connection mode selected: {}", connection_mode); connection_mode } @@ -118,45 +121,40 @@ impl ApiConnectionModeProvider { /// [`ApiConnectionMode`]s require extra logic/data from /// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait /// can not be implemented. - fn from(&mut self, access_method: Option<&AccessMethodSetting>) -> ApiConnectionMode { - use mullvad_types::access_method::{self, AccessMethod, BuiltInAccessMethod}; + fn from(&mut self, access_method: AccessMethod) -> ApiConnectionMode { + use mullvad_types::access_method; match access_method { - None => ApiConnectionMode::Direct, - Some(access_method) => match &access_method.access_method { - AccessMethod::BuiltIn(access_method) => match access_method { - BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, - BuiltInAccessMethod::Bridge => self - .relay_selector - .get_bridge_forced() - .and_then(|settings| match settings { - ProxySettings::Shadowsocks(ss_settings) => { - let ss_settings: access_method::Shadowsocks = - access_method::Shadowsocks::new( - ss_settings.peer, - ss_settings.cipher, - ss_settings.password, - ); - Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( - ss_settings, - ))) - } - _ => { - log::error!("Received unexpected proxy settings type"); - None - } - }) - .unwrap_or(ApiConnectionMode::Direct), - }, - AccessMethod::Custom(access_method) => match &access_method { - access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( - shadowsocks_config.clone(), - )) - } - access_method::CustomAccessMethod::Socks5(socks_config) => { - ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config.clone())) - } - }, + AccessMethod::BuiltIn(access_method) => match access_method { + BuiltInAccessMethod::Direct => ApiConnectionMode::Direct, + BuiltInAccessMethod::Bridge => self + .relay_selector + .get_bridge_forced() + .and_then(|settings| match settings { + ProxySettings::Shadowsocks(ss_settings) => { + let ss_settings: access_method::Shadowsocks = + access_method::Shadowsocks::new( + ss_settings.peer, + ss_settings.cipher, + ss_settings.password, + ); + Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks( + ss_settings, + ))) + } + _ => { + log::error!("Received unexpected proxy settings type"); + None + } + }) + .unwrap_or(ApiConnectionMode::Direct), + }, + AccessMethod::Custom(access_method) => match access_method { + access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config)) + } + access_method::CustomAccessMethod::Socks5(socks_config) => { + ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config)) + } }, } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 0a00639c09ed..3def0855c207 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1076,7 +1076,7 @@ where } RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, UpdateApiAccessMethod(tx, method) => self.on_update_api_access_method(tx, method).await, - GetCurrentAccessMethod(tx) => self.on_get_current_api_access_method(tx).await, + GetCurrentAccessMethod(tx) => self.on_get_current_api_access_method(tx), 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), @@ -2307,13 +2307,9 @@ where Self::oneshot_send(tx, result, "update_api_access_method response"); } - async fn on_get_current_api_access_method( - &mut self, - tx: ResponseTx, - ) { + fn on_get_current_api_access_method(&mut self, tx: ResponseTx) { let result = self .get_current_access_method() - .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "get_current_api_access_method response"); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 412358c9c5d0..c9b9bc5138cf 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -338,19 +338,19 @@ message AccessMethod { message Direct {} message Bridges {} message Socks5Local { - string ip = 4; - uint32 port = 5; - uint32 local_port = 6; + string ip = 1; + uint32 port = 2; + uint32 local_port = 3; } message Socks5Remote { string ip = 2; uint32 port = 3; } message Shadowsocks { - string ip = 2; - uint32 port = 3; - string password = 4; - string cipher = 5; + string ip = 1; + uint32 port = 2; + string password = 3; + string cipher = 4; } oneof access_method { Direct direct = 1; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 9e1b3772db3a..a4d56dd4450b 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -519,24 +519,6 @@ impl MullvadProxyClient { .map(drop) } - pub async fn enable_access_method( - &mut self, - api_access_method_id: access_method::Id, - ) -> Result<()> { - let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; - new_api_access_method.enable(); - self.update_access_method(new_api_access_method).await - } - - pub async fn disable_access_method( - &mut self, - api_access_method_id: access_method::Id, - ) -> Result<()> { - let mut new_api_access_method = self.get_api_access_method(&api_access_method_id).await?; - new_api_access_method.disable(); - self.update_access_method(new_api_access_method).await - } - pub async fn remove_access_method( &mut self, api_access_method: access_method::Id, From ccc5aaaff23291366b4c0074f57a5096b94cdfce Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Thu, 28 Sep 2023 10:41:31 +0200 Subject: [PATCH 27/29] Add authentication with username+password for SOCKS5 access method Add the option to authenticate against remote SOCKS5 proxies with a username+password combination. It was an oversight that this was not added from the start. --- mullvad-api/src/https_client_with_sni.rs | 47 +++++++++--- mullvad-cli/src/cmds/api_access.rs | 75 +++++++++++++++---- .../proto/management_interface.proto | 9 ++- .../src/types/conversions/access_method.rs | 63 ++++++++++++---- mullvad-types/src/access_method.rs | 24 +++++- 5 files changed, 176 insertions(+), 42 deletions(-) diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs index 47b6923d37e3..17d9f7f0d850 100644 --- a/mullvad-api/src/https_client_with_sni.rs +++ b/mullvad-api/src/https_client_with_sni.rs @@ -125,11 +125,21 @@ impl InnerConnectionMode { InnerConnectionMode::Socks5(socks) => { let first_hop = socks.peer; let make_proxy_stream = |tcp_stream| async { - tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr) - .await - .map_err(|error| { - io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) - }) + match socks.authentication { + SocksAuth::None => { + tokio_socks::tcp::Socks5Stream::connect_with_socket(tcp_stream, addr) + .await + } + SocksAuth::Password { username, password } => { + tokio_socks::tcp::Socks5Stream::connect_with_password_and_socket( + tcp_stream, addr, &username, &password, + ) + .await + } + } + .map_err(|error| { + io::Error::new(io::ErrorKind::Other, format!("SOCKS error: {error}")) + }) }; Self::connect_proxied( first_hop, @@ -207,6 +217,13 @@ impl From for ServerConfig { #[derive(Clone)] struct SocksConfig { peer: SocketAddr, + authentication: SocksAuth, +} + +#[derive(Clone)] +pub enum SocksAuth { + None, + Password { username: String, password: String }, } #[derive(err_derive::Error, Debug)] @@ -219,6 +236,8 @@ impl TryFrom for InnerConnectionMode { type Error = ProxyConfigError; fn try_from(config: ApiConnectionMode) -> Result { + use mullvad_types::access_method; + use std::net::Ipv4Addr; Ok(match config { ApiConnectionMode::Direct => InnerConnectionMode::Direct, ApiConnectionMode::Proxied(proxy_settings) => match proxy_settings { @@ -234,13 +253,23 @@ impl TryFrom for InnerConnectionMode { }) } ProxyConfig::Socks(config) => match config { - mullvad_types::access_method::Socks5::Local(config) => { + access_method::Socks5::Local(config) => { InnerConnectionMode::Socks5(SocksConfig { - peer: SocketAddr::new("127.0.0.1".parse().unwrap(), config.port), + peer: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), config.port), + authentication: SocksAuth::None, }) } - mullvad_types::access_method::Socks5::Remote(config) => { - InnerConnectionMode::Socks5(SocksConfig { peer: config.peer }) + access_method::Socks5::Remote(config) => { + let authentication = match config.authentication { + Some(access_method::SocksAuth { username, password }) => { + SocksAuth::Password { username, password } + } + None => SocksAuth::None, + }; + InnerConnectionMode::Socks5(SocksConfig { + peer: config.peer, + authentication, + }) } }, }, diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index a3b17aca0bba..75fe7c44bf21 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -121,8 +121,20 @@ impl ApiAccess { 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()); - mullvad_types::access_method::Socks5Remote::from_args(ip, port) - .map(AccessMethod::from) + match remote.authentication { + None => mullvad_types::access_method::Socks5Remote::from_args(ip, port), + Some(mullvad_types::access_method::SocksAuth { + username, + password, + }) => { + let username = cmd.params.username.unwrap_or(username); + let password = cmd.params.password.unwrap_or(password); + mullvad_types::access_method::Socks5Remote::from_args_with_password( + ip, port, username, password, + ) + } + } + .map(AccessMethod::from) } }, }, @@ -241,6 +253,8 @@ pub enum AddSocks5Commands { remote_ip: IpAddr, /// The port of the remote proxy server remote_port: u16, + #[clap(flatten)] + authentication: Option, /// Disable the use of this custom access method. It has to be manually /// enabled at a later stage to be used when accessing the Mullvad API. #[arg(default_value_t = false, short, long)] @@ -263,6 +277,16 @@ pub enum AddSocks5Commands { }, } +#[derive(Args, Debug, Clone)] +pub struct SocksAuthentication { + /// Username for authentication against a remote SOCKS5 proxy + #[arg(short, long)] + username: String, + /// Password for authentication against a remote SOCKS5 proxy + #[arg(short, long)] + password: String, +} + impl AddCustomCommands { fn name(&self) -> &str { match self { @@ -319,7 +343,10 @@ pub struct EditParams { /// Name of the API access method in the Mullvad client [All] #[arg(long)] name: Option, - /// Password for authentication [Shadowsocks] + /// Username for authentication [Socks5 (Remote proxy)] + #[arg(long)] + username: Option, + /// Password for authentication [Socks5 (Remote proxy), Shadowsocks] #[arg(long)] password: Option, /// Cipher to use [Shadowsocks] @@ -344,7 +371,7 @@ mod conversions { use anyhow::{anyhow, Error}; use mullvad_types::access_method as daemon_types; - use super::{AddCustomCommands, AddSocks5Commands}; + use super::{AddCustomCommands, AddSocks5Commands, SocksAuthentication}; impl TryFrom for daemon_types::AccessMethod { type Error = Error; @@ -373,18 +400,31 @@ mod conversions { AddSocks5Commands::Remote { remote_ip, remote_port, + authentication, name: _, disabled: _, } => { - println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Remote( - daemon_types::Socks5Remote::from_args( - remote_ip.to_string(), - remote_port, - ) - .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?, - ); - daemon_types::AccessMethod::from(socks_proxy) + match authentication { + Some(SocksAuthentication { username, password }) => { + println!("Adding SOCKS5-proxy: {username}:{password}@{remote_ip}:{remote_port}"); + daemon_types::Socks5Remote::from_args_with_password( + remote_ip.to_string(), + remote_port, + username, + password + ) + } + None => { + println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); + daemon_types::Socks5Remote::from_args( + remote_ip.to_string(), + remote_port, + ) + } + } + .map(daemon_types::Socks5::Remote) + .map(daemon_types::AccessMethod::from) + .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))? } }, AddCustomCommands::Shadowsocks { @@ -415,7 +455,7 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { use mullvad_types::access_method::{ - AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, + AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, SocksAuth, }; pub struct ApiAccessMethodFormatter<'a> { @@ -462,6 +502,13 @@ mod pp { writeln!(f)?; print_option!("Protocol", "Socks5"); print_option!("Peer", remote.peer); + match &remote.authentication { + Some(SocksAuth { username, password }) => { + print_option!("Username", username); + print_option!("Password", password); + } + None => (), + } Ok(()) } Socks5::Local(local) => { diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index c9b9bc5138cf..1bbb7f501281 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -342,9 +342,14 @@ message AccessMethod { uint32 port = 2; uint32 local_port = 3; } + message SocksAuth { + string username = 1; + string password = 2; + } message Socks5Remote { - string ip = 2; - uint32 port = 3; + string ip = 1; + uint32 port = 2; + SocksAuth authentication = 3; } message Shadowsocks { string ip = 1; diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs index 0f39d34e9eef..8907c4da2947 100644 --- a/mullvad-management-interface/src/types/conversions/access_method.rs +++ b/mullvad-management-interface/src/types/conversions/access_method.rs @@ -45,7 +45,7 @@ mod data { use crate::types::{proto, FromProtobufTypeError}; use mullvad_types::access_method::{ AccessMethod, AccessMethodSetting, BuiltInAccessMethod, CustomAccessMethod, Id, - Shadowsocks, Socks5, Socks5Local, Socks5Remote, + Shadowsocks, Socks5, Socks5Local, Socks5Remote, SocksAuth, }; impl TryFrom for AccessMethodSetting { @@ -154,13 +154,24 @@ mod data { type Error = FromProtobufTypeError; fn try_from(value: proto::access_method::Socks5Remote) -> Result { - Socks5Remote::from_args(value.ip, value.port as u16) - .ok_or({ - FromProtobufTypeError::InvalidArgument( - "Could not parse Socks5 (remote) message from protobuf", - ) - }) - .map(AccessMethod::from) + let proto::access_method::Socks5Remote { + ip, + port, + authentication, + } = value; + let port = port as u16; + match authentication.map(SocksAuth::from) { + Some(SocksAuth { username, password }) => { + Socks5Remote::from_args_with_password(ip, port, username, password) + } + None => Socks5Remote::from_args(ip, port), + } + .ok_or({ + FromProtobufTypeError::InvalidArgument( + "Could not parse Socks5 (remote) message from protobuf", + ) + }) + .map(AccessMethod::from) } } @@ -214,14 +225,16 @@ mod data { }, ) } - CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => { - proto::access_method::AccessMethod::Socks5remote( - proto::access_method::Socks5Remote { - ip: peer.ip().to_string(), - port: peer.port() as u32, - }, - ) - } + CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote { + peer, + authentication, + })) => proto::access_method::AccessMethod::Socks5remote( + proto::access_method::Socks5Remote { + ip: peer.ip().to_string(), + port: peer.port() as u32, + authentication: authentication.map(proto::access_method::SocksAuth::from), + }, + ), }; proto::AccessMethod { @@ -248,6 +261,24 @@ mod data { } } + impl From for proto::access_method::SocksAuth { + fn from(value: SocksAuth) -> Self { + proto::access_method::SocksAuth { + username: value.username, + password: value.password, + } + } + } + + impl From for SocksAuth { + fn from(value: proto::access_method::SocksAuth) -> Self { + Self { + username: value.username, + password: value.password, + } + } + } + impl TryFrom<&proto::AccessMethodSetting> for AccessMethodSetting { type Error = FromProtobufTypeError; diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs index a300072c82f8..30c1f25192ae 100644 --- a/mullvad-types/src/access_method.rs +++ b/mullvad-types/src/access_method.rs @@ -209,6 +209,13 @@ pub struct Socks5Local { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Socks5Remote { pub peer: SocketAddr, + pub authentication: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct SocksAuth { + pub username: String, + pub password: String, } impl AccessMethod { @@ -262,7 +269,10 @@ impl Socks5Local { impl Socks5Remote { pub fn new(peer: SocketAddr) -> Self { - Self { peer } + Self { + peer, + authentication: None, + } } /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you. @@ -272,6 +282,18 @@ impl Socks5Remote { let peer = SocketAddr::new(peer_ip, port); Some(Self::new(peer)) } + + /// Like [from_args()], but with authentication. + pub fn from_args_with_password( + ip: String, + port: u16, + username: String, + password: String, + ) -> Option { + let mut socks = Self::from_args(ip, port)?; + socks.authentication = Some(SocksAuth { username, password }); + Some(socks) + } } impl From for AccessMethod { From 432a35281a836741918990bb8aeb7af3925adb27 Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Thu, 28 Sep 2023 12:51:26 +0200 Subject: [PATCH 28/29] UX improvements for `mullvad api-access` - Re-phrase help texts for a lot of `mullvad api-access` commands - Add to help texts for some `mullvad api-access` commands - Compact the output of `mullvad api-access test` - `mullvad api-access status` is changed to `mullvad api-access get` to align with other `mullvad` commands. - `mullvad api-access get` does not print the enabled/disabled status of the shown access method - Rotate access method if the currently active one is updated or removed - Fix reset access method after `mullvad api-access test` After running `mullvad api-access test`, the previously used access method should be used, which was not the case previously. - Fix `mullvad api-access use` API connectivity check - `mullvad api-access use` now runs a test-routine to check that the new access method will function before comitting to it. If this check fails, the previously used access method will be used instead - guarantee that `set_api_access_method` has finished upon returning. Make `mullvad_api::rest::next_api_endpoint` `async` and send a message upon completion. This is propagated to the caller of `next_api_endpoint` which can `await` the result --- mullvad-api/src/rest.rs | 24 +++- mullvad-cli/src/cmds/api_access.rs | 177 +++++++++++++++++++--------- mullvad-cli/src/main.rs | 17 ++- mullvad-daemon/src/access_method.rs | 137 ++++++++++++++++----- mullvad-daemon/src/lib.rs | 7 +- 5 files changed, 269 insertions(+), 93 deletions(-) diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs index 0fc31353a7fa..674bcf8c4eb0 100644 --- a/mullvad-api/src/rest.rs +++ b/mullvad-api/src/rest.rs @@ -77,6 +77,10 @@ pub enum Error { /// The string given was not a valid URI. #[error(display = "Not a valid URI")] UriError(#[error(source)] http::uri::InvalidUri), + + /// A new API config was requested, but the request could not be completed. + #[error(display = "Failed to rotate API config")] + NextApiConfigError, } impl Error { @@ -207,7 +211,9 @@ impl< if err.is_network_error() && !api_availability.get_state().is_offline() { log::error!("{}", err.display_chain_with_msg("HTTP request failed")); if let Some(tx) = tx { - let _ = tx.unbounded_send(RequestCommand::NextApiConfig); + let (completion_tx, _completion_rx) = oneshot::channel(); + let _ = + tx.unbounded_send(RequestCommand::NextApiConfig(completion_tx)); } } } @@ -223,10 +229,11 @@ impl< RequestCommand::Reset => { self.connector_handle.reset(); } - RequestCommand::NextApiConfig => { + RequestCommand::NextApiConfig(completion_tx) => { #[cfg(feature = "api-override")] if API.force_direct_connection { log::debug!("Ignoring API connection mode"); + let _ = completion_tx.send(Ok(())); return; } @@ -240,6 +247,8 @@ impl< self.connector_handle.set_connection_mode(new_config); } } + + let _ = completion_tx.send(Ok(())); } } } @@ -274,10 +283,13 @@ impl RequestServiceHandle { } /// Forcibly update the connection mode. - pub fn next_api_endpoint(&self) -> Result<()> { + pub async fn next_api_endpoint(&self) -> Result<()> { + let (completion_tx, completion_rx) = oneshot::channel(); self.tx - .unbounded_send(RequestCommand::NextApiConfig) - .map_err(|_| Error::SendError) + .unbounded_send(RequestCommand::NextApiConfig(completion_tx)) + .map_err(|_| Error::SendError)?; + + completion_rx.await.map_err(|_| Error::NextApiConfigError)? } } @@ -288,7 +300,7 @@ pub(crate) enum RequestCommand { oneshot::Sender>, ), Reset, - NextApiConfig, + NextApiConfig(oneshot::Sender>), } /// A REST request that is sent to the RequestService to be executed. diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index 75fe7c44bf21..a03d3ba11fbb 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -8,27 +8,31 @@ use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; #[derive(Subcommand, Debug, Clone)] pub enum ApiAccess { - /// List the configured API access methods - List, + /// Display the current API access method. + Get, /// Add a custom API access method #[clap(subcommand)] Add(AddCustomCommands), - /// Edit an API access method + /// Lists all API access methods + /// + /// * = Enabled + List, + /// Edit a custom API access method Edit(EditCustomCommands), - /// Remove an API access method + /// Remove a custom API access method Remove(SelectItem), /// Enable an API access method Enable(SelectItem), /// Disable an API access method Disable(SelectItem), - /// Test an API access method - Test(SelectItem), - /// Force the use of a specific API access method. + /// Try to use a specific API access method (If the API is unreachable, reverts back to the previous access method) + /// + /// Selecting "Direct" will connect to the Mullvad API without going through any proxy. This connection use https and is therefore encrypted. /// - /// Selecting "Mullvad Bridges" respects your current bridge settings. + /// Selecting "Mullvad Bridges" respects your current bridge settings Use(SelectItem), - /// Show which access method is currently used to access the Mullvad API. - Status, + /// Try to reach the Mullvad API using a specific access method + Test(SelectItem), } impl ApiAccess { @@ -54,8 +58,8 @@ impl ApiAccess { ApiAccess::Use(cmd) => { Self::set(cmd).await?; } - ApiAccess::Status => { - Self::status().await?; + ApiAccess::Get => { + Self::get().await?; } }; Ok(()) @@ -173,34 +177,75 @@ impl ApiAccess { /// Test an access method to see if it successfully reaches the Mullvad API. async fn test(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; + // Retrieve the currently used access method. We will reset to this + // after we are done testing. + let previous_access_method = rpc.get_current_api_access_method().await?; let access_method = Self::get_access_method(&mut rpc, &item).await?; + + println!("Testing access method \"{}\"", access_method.name); rpc.set_access_method(access_method.get_id()).await?; // Make the daemon perform an network request which involves talking to the Mullvad API. - match rpc.get_api_addresses().await { - Ok(_) => println!("Connected to the Mullvad API!"), - Err(_) => println!( - "Could *not* connect to the Mullvad API using access method \"{}\"", - access_method.name - ), - } - - Ok(()) + let result = match rpc.get_api_addresses().await { + Ok(_) => { + println!("Success!"); + Ok(()) + } + Err(_) => Err(anyhow!("Could not reach the Mullvad API")), + }; + // In any case, switch back to the previous access method. + rpc.set_access_method(previous_access_method.get_id()) + .await?; + result } - /// 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). + /// Try to use of a specific [`AccessMethodSetting`] for subsequent calls to + /// the Mullvad API. + /// + /// First, a test will be performed to check that the new + /// [`AccessMethodSetting`] is able to reach the API. If it can, the daemon + /// will set this [`AccessMethodSetting`] to be used by the API runtime. + /// + /// If the new [`AccessMethodSetting`] fails, the daemon will perform a + /// roll-back to the previously used [`AccessMethodSetting`]. If that never + /// worked, or has recently stopped working, the daemon will start to + /// automatically try to find a working [`AccessMethodSetting`] among the + /// configured ones. async fn set(item: SelectItem) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let access_method = Self::get_access_method(&mut rpc, &item).await?; - rpc.set_access_method(access_method.get_id()).await?; + let previous_access_method = rpc.get_current_api_access_method().await?; + let mut new_access_method = Self::get_access_method(&mut rpc, &item).await?; + // Try to reach the API with the newly selected access method. + rpc.set_access_method(new_access_method.get_id()).await?; + match rpc.get_api_addresses().await { + Ok(_) => (), + Err(_) => { + // Roll-back to the previous access method + rpc.set_access_method(previous_access_method.get_id()) + .await?; + return Err(anyhow!( + "Could not reach the Mullvad API using access method \"{}\". Rolling back to \"{}\"", + new_access_method.get_name(), + previous_access_method.get_name() + )); + } + }; + // It worked! Let the daemon keep using this access method. + let display_name = new_access_method.get_name(); + // Toggle the enabled status if needed + if !new_access_method.enabled() { + new_access_method.enable(); + rpc.update_access_method(new_access_method).await?; + } + println!("Using access method \"{}\"", display_name); Ok(()) } - async fn status() -> Result<()> { + async fn get() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let current = rpc.get_current_api_access_method().await?; - println!("{}", pp::ApiAccessMethodFormatter::new(¤t)); + let mut access_method_formatter = pp::ApiAccessMethodFormatter::new(¤t); + access_method_formatter.settings.write_enabled = false; + println!("{}", access_method_formatter); Ok(()) } @@ -218,16 +263,16 @@ impl ApiAccess { #[derive(Subcommand, Debug, Clone)] pub enum AddCustomCommands { - /// Configure a local SOCKS5 proxy + /// Configure a SOCKS5 proxy #[clap(subcommand)] Socks5(AddSocks5Commands), - /// Configure Shadowsocks proxy + /// Configure a custom Shadowsocks proxy to use as an API access method Shadowsocks { /// An easy to remember name for this custom proxy name: String, - /// The IP of the remote Shadowsocks server + /// The IP of the remote Shadowsocks-proxy remote_ip: IpAddr, - /// The port of the remote Shadowsocks server + /// Port on which the remote Shadowsocks-proxy listens for traffic #[arg(default_value = "443")] remote_port: u16, /// Password for authentication @@ -249,9 +294,9 @@ pub enum AddSocks5Commands { Remote { /// An easy to remember name for this custom proxy name: String, - /// The IP of the remote proxy server + /// IP of the remote SOCKS5-proxy remote_ip: IpAddr, - /// The port of the remote proxy server + /// Port on which the remote SOCKS5-proxy listens for traffic remote_port: u16, #[clap(flatten)] authentication: Option, @@ -386,16 +431,15 @@ mod conversions { name: _, disabled: _, } => { - println!("Adding Local SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); - let socks_proxy = daemon_types::Socks5::Local( - daemon_types::Socks5Local::from_args( - remote_ip.to_string(), - remote_port, - local_port, - ) - .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?, - ); - daemon_types::AccessMethod::from(socks_proxy) + println!("Adding SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}"); + daemon_types::Socks5Local::from_args( + remote_ip.to_string(), + remote_port, + local_port, + ) + .map(daemon_types::Socks5::Local) + .map(daemon_types::AccessMethod::from) + .ok_or(anyhow!("Could not create a local Socks5 access method"))? } AddSocks5Commands::Remote { remote_ip, @@ -424,7 +468,7 @@ mod conversions { } .map(daemon_types::Socks5::Remote) .map(daemon_types::AccessMethod::from) - .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))? + .ok_or(anyhow!("Could not create a remote Socks5 access method"))? } }, AddCustomCommands::Shadowsocks { @@ -438,14 +482,14 @@ mod conversions { println!( "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" ); - let shadowsocks_proxy = daemon_types::Shadowsocks::from_args( + daemon_types::Shadowsocks::from_args( remote_ip.to_string(), remote_port, cipher, password, ) - .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?; - daemon_types::AccessMethod::from(shadowsocks_proxy) + .map(daemon_types::AccessMethod::from) + .ok_or(anyhow!("Could not create a Shadowsocks access method"))? } }) } @@ -460,11 +504,29 @@ mod pp { pub struct ApiAccessMethodFormatter<'a> { api_access_method: &'a AccessMethodSetting, + pub settings: FormatterSettings, + } + + pub struct FormatterSettings { + /// If the formatter should print the enabled status of an + /// [`AcessMethodSetting`] (*) next to its name. + pub write_enabled: bool, + } + + impl Default for FormatterSettings { + fn default() -> Self { + Self { + write_enabled: true, + } + } } impl<'a> ApiAccessMethodFormatter<'a> { pub fn new(api_access_method: &'a AccessMethodSetting) -> ApiAccessMethodFormatter<'a> { - ApiAccessMethodFormatter { api_access_method } + ApiAccessMethodFormatter { + api_access_method, + settings: Default::default(), + } } } @@ -483,12 +545,17 @@ mod pp { match &self.api_access_method.access_method { AccessMethod::BuiltIn(method) => { write!(f, "{}", method.canonical_name())?; - write_status(f, self.api_access_method.enabled()) + if self.settings.write_enabled { + write_status(f, self.api_access_method.enabled())?; + } + Ok(()) } AccessMethod::Custom(method) => match &method { CustomAccessMethod::Shadowsocks(shadowsocks) => { write!(f, "{}", self.api_access_method.get_name())?; - write_status(f, self.api_access_method.enabled())?; + if self.settings.write_enabled { + write_status(f, self.api_access_method.enabled())?; + } writeln!(f)?; print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher)); print_option!("Peer", shadowsocks.peer); @@ -498,7 +565,9 @@ mod pp { CustomAccessMethod::Socks5(socks) => match socks { Socks5::Remote(remote) => { write!(f, "{}", self.api_access_method.get_name())?; - write_status(f, self.api_access_method.enabled())?; + if self.settings.write_enabled { + write_status(f, self.api_access_method.enabled())?; + } writeln!(f)?; print_option!("Protocol", "Socks5"); print_option!("Peer", remote.peer); @@ -513,7 +582,9 @@ mod pp { } Socks5::Local(local) => { write!(f, "{}", self.api_access_method.get_name())?; - write_status(f, self.api_access_method.enabled())?; + if self.settings.write_enabled { + write_status(f, self.api_access_method.enabled())?; + } writeln!(f)?; print_option!("Protocol", "Socks5 (local)"); print_option!("Peer", local.peer); diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index a79465f9658d..7a09a4eebdf7 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -71,9 +71,20 @@ enum Cli { #[clap(subcommand)] Relay(relay::Relay), - /// Manage use of access methods for reaching the Mullvad API. - /// Can be used to connect to the the Mullvad API via one of the - /// Mullvad bridge servers or a custom proxy (SOCKS5 & Shadowsocks). + /// Manage Mullvad API access methods. + /// + /// Access methods are used to connect to the the Mullvad API via one of + /// Mullvad's bridge servers or a custom proxy (SOCKS5 & Shadowsocks) when + /// and where establishing a direct connection does not work. + /// + /// If the Mullvad daemon is unable to connect to the Mullvad API, it will + /// automatically try to use any other configured access method and re-try + /// the API call. If it succeeds, all subsequent API calls are made using + /// the new access method. Otherwise it will re-try using yet another access + /// method. + /// + /// The Mullvad API is used for logging in, accessing the relay list, + /// rotating Wireguard keys and more. #[clap(subcommand)] ApiAccess(api_access::ApiAccess), diff --git a/mullvad-daemon/src/access_method.rs b/mullvad-daemon/src/access_method.rs index ea096cf5ba64..013cf9d7ce51 100644 --- a/mullvad-daemon/src/access_method.rs +++ b/mullvad-daemon/src/access_method.rs @@ -2,7 +2,10 @@ use crate::{ settings::{self, MadeChanges}, Daemon, EventListener, }; -use mullvad_types::access_method::{self, AccessMethod, AccessMethodSetting}; +use mullvad_types::{ + access_method::{self, AccessMethod, AccessMethodSetting}, + settings::Settings, +}; #[derive(err_derive::Error, Debug)] pub enum Error { @@ -19,15 +22,35 @@ pub enum Error { /// the user should do a factory reset. #[error(display = "No access methods are configured")] NoMethodsExist, + /// Access method could not be rotate + #[error(display = "Access method could not be rotated")] + RotationError, /// Access methods settings error #[error(display = "Settings error")] Settings(#[error(source)] settings::Error), } +/// A tiny datastructure used for signaling whether the daemon should force a +/// rotation of the currently used [`AccessMethodSetting`] or not, and if so: +/// how it should do it. +pub enum Command { + /// There is no need to force a rotation of [`AccessMethodSetting`] + Nothing, + /// Select the next available [`AccessMethodSetting`], whichever that is + Rotate, + /// Select the [`AccessMethodSetting`] with a certain [`access_method::Id`] + Set(access_method::Id), +} + impl Daemon where L: EventListener + Clone + Send + 'static, { + /// Add a [`AccessMethod`] to the daemon's settings. + /// + /// If the daemon settings are successfully updated, the + /// [`access_method::Id`] of the newly created [`AccessMethodSetting`] + /// (which has been derived from the [`AccessMethod`]) is returned. pub async fn add_access_method( &mut self, name: String, @@ -44,71 +67,119 @@ where .map_err(Error::Settings) } + /// Remove a [`AccessMethodSetting`] from the daemon's saved settings. + /// + /// If the [`AccessMethodSetting`] which is currently in use happens to be + /// removed, the daemon should force a rotation of the active API endpoint. pub async fn remove_access_method( &mut self, access_method: access_method::Id, ) -> Result<(), Error> { // Make sure that we are not trying to remove a built-in API access // method - match self.settings.api_access_methods.find(&access_method) { - None => return Ok(()), + let command = match self.settings.api_access_methods.find(&access_method) { Some(api_access_method) => { if api_access_method.is_builtin() { - return Err(Error::RemoveBuiltIn); + Err(Error::RemoveBuiltIn) + } else if api_access_method.get_id() == self.get_current_access_method()?.get_id() { + Ok(Command::Rotate) + } else { + Ok(Command::Nothing) } } - }; + None => Ok(Command::Nothing), + }?; self.settings .update(|settings| settings.api_access_methods.remove(&access_method)) .await .map(|did_change| self.notify_on_change(did_change)) - .map_err(Error::Settings) + .map_err(Error::Settings)? + .process_command(command) + .await } - pub fn set_api_access_method(&mut self, access_method: access_method::Id) -> Result<(), Error> { - if let Some(access_method) = self.settings.api_access_methods.find(&access_method) { - { - let mut connection_modes = self.connection_modes.lock().unwrap(); - connection_modes.set_access_method(access_method.clone()); - } - // Force a rotation of Access Methods. - let _ = self.api_handle.service().next_api_endpoint(); - Ok(()) - } else { - Err(Error::NoSuchMethod(access_method)) + /// Set a [`AccessMethodSetting`] as the current API access method. + /// + /// If successful, the daemon will force a rotation of the active API access + /// method, which means that subsequent API calls will use the new + /// [`AccessMethodSetting`] to figure out the API endpoint. + pub async fn set_api_access_method( + &mut self, + access_method: access_method::Id, + ) -> Result<(), Error> { + let access_method = self + .settings + .api_access_methods + .find(&access_method) + .ok_or(Error::NoSuchMethod(access_method))?; + { + let mut connection_modes = self.connection_modes.lock().unwrap(); + connection_modes.set_access_method(access_method.clone()); } + // Force a rotation of Access Methods. + // + // This is not a call to `process_command` due to the restrictions on + // recursively calling async functions. + self.force_api_endpoint_rotation().await } /// "Updates" an [`AccessMethodSetting`] by replacing the existing entry /// with the argument `access_method_update` if an existing entry with - /// matching UUID is found. + /// matching [`access_method::Id`] is found. + /// + /// If the currently active [`AccessMethodSetting`] is updated, the daemon + /// will automatically use this updated [`AccessMethodSetting`] when + /// performing subsequent API calls. pub async fn update_access_method( &mut self, access_method_update: AccessMethodSetting, ) -> Result<(), Error> { - self.settings - .update(|settings| { - let access_methods = &mut settings.api_access_methods; - if let Some(access_method) = access_methods.find_mut(&access_method_update.get_id()) - { - *access_method = access_method_update + let current = self.get_current_access_method()?; + let mut command = Command::Nothing; + let settings_update = |settings: &mut Settings| { + if let Some(access_method) = settings + .api_access_methods + .find_mut(&access_method_update.get_id()) + { + *access_method = access_method_update; + if access_method.get_id() == current.get_id() { + command = Command::Set(access_method.get_id()) } - }) + } + }; + + self.settings + .update(settings_update) .await .map(|did_change| self.notify_on_change(did_change)) - .map_err(Error::Settings) + .map_err(Error::Settings)? + .process_command(command) + .await } /// Return the [`AccessMethodSetting`] which is currently used to access the /// Mullvad API. - pub fn get_current_access_method(&mut self) -> Result { + pub fn get_current_access_method(&self) -> Result { let connections_modes = self.connection_modes.lock().unwrap(); Ok(connections_modes.peek()) } + /// Change which [`AccessMethodSetting`] which will be used to figure out + /// the Mullvad API endpoint. + async fn force_api_endpoint_rotation(&self) -> Result<(), Error> { + self.api_handle + .service() + .next_api_endpoint() + .await + .map_err(|error| { + log::error!("Failed to rotate API endpoint: {}", error); + Error::RotationError + }) + } + /// If settings were changed due to an update, notify all listeners. - fn notify_on_change(&mut self, settings_changed: MadeChanges) { + fn notify_on_change(&mut self, settings_changed: MadeChanges) -> &mut Self { if settings_changed { self.event_listener .notify_settings(self.settings.to_settings()); @@ -124,5 +195,15 @@ where .collect(), ) }; + self + } + + /// The semantics of the [`Command`] datastructure. + async fn process_command(&mut self, command: Command) -> Result<(), Error> { + match command { + Command::Nothing => Ok(()), + Command::Rotate => self.force_api_endpoint_rotation().await, + Command::Set(id) => self.set_api_access_method(id).await, + } } } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 3def0855c207..f7343acc877a 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1077,7 +1077,7 @@ where RemoveApiAccessMethod(tx, method) => self.on_remove_api_access_method(tx, method).await, 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), + SetApiAccessMethod(tx, method) => self.on_set_api_access_method(tx, method).await, 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), @@ -1970,7 +1970,7 @@ where .notify_settings(self.settings.to_settings()); self.relay_selector .set_config(new_selector_config(&self.settings)); - if let Err(error) = self.api_handle.service().next_api_endpoint() { + if let Err(error) = self.api_handle.service().next_api_endpoint().await { log::error!("Failed to rotate API endpoint: {}", error); } self.reconnect_tunnel(); @@ -2284,13 +2284,14 @@ where Self::oneshot_send(tx, result, "remove_api_access_method response"); } - fn on_set_api_access_method( + async fn on_set_api_access_method( &mut self, tx: ResponseTx<(), Error>, access_method: mullvad_types::access_method::Id, ) { let result = self .set_api_access_method(access_method) + .await .map_err(Error::AccessMethodError); Self::oneshot_send(tx, result, "set_api_access_method response"); } From fbefa3f172cf324de8790a383ceb3f5329e94c7c Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Fri, 29 Sep 2023 15:02:31 +0200 Subject: [PATCH 29/29] Add `GetApiAddresses` now returns a list of API addresses --- mullvad-daemon/src/management_interface.rs | 4 ++-- .../proto/management_interface.proto | 4 +++- mullvad-management-interface/src/client.rs | 12 ++++++++--- .../src/types/conversions/net.rs | 21 +++++++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index f92b895ba8fd..993f0f9ece0b 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -700,13 +700,13 @@ impl ManagementService for ManagementServiceImpl { .map_err(map_daemon_error) } - async fn get_api_addresses(&self, _: Request<()>) -> ServiceResult<()> { + async fn get_api_addresses(&self, _: Request<()>) -> ServiceResult { log::debug!("get_api_addresses"); let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::GetApiAddresses(tx))?; self.wait_for_result(rx) .await? - .map(drop) + .map(types::ApiAddresses::from) .map(Response::new) .map_err(map_daemon_error) } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 1bbb7f501281..24bfe2284b7f 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -22,7 +22,7 @@ service ManagementService { rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {} rpc GetVersionInfo(google.protobuf.Empty) returns (AppVersionInfo) {} - rpc GetApiAddresses(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GetApiAddresses(google.protobuf.Empty) returns (ApiAddresses) {} rpc IsPerformingPostUpgrade(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} @@ -112,6 +112,8 @@ message AccountData { google.protobuf.Timestamp expiry = 1; } message AccountHistory { google.protobuf.StringValue token = 1; } +message ApiAddresses { repeated google.protobuf.StringValue api_addresses = 1; } + message VoucherSubmission { uint64 seconds_added = 1; google.protobuf.Timestamp new_expiry = 2; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index a4d56dd4450b..417083e16123 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -202,9 +202,15 @@ impl MullvadProxyClient { }) } - pub async fn get_api_addresses(&mut self) -> Result<()> { - self.0.get_api_addresses(()).await.map_err(Error::Rpc)?; - Ok(()) + pub async fn get_api_addresses(&mut self) -> Result> { + self.0 + .get_api_addresses(()) + .await + .map_err(Error::Rpc) + .map(tonic::Response::into_inner) + .and_then(|api_addresses| { + Vec::::try_from(api_addresses).map_err(Error::InvalidResponse) + }) } pub async fn update_relay_locations(&mut self) -> Result<()> { diff --git a/mullvad-management-interface/src/types/conversions/net.rs b/mullvad-management-interface/src/types/conversions/net.rs index d0dcc975d0fc..ea5dcf99a53d 100644 --- a/mullvad-management-interface/src/types/conversions/net.rs +++ b/mullvad-management-interface/src/types/conversions/net.rs @@ -174,6 +174,27 @@ impl From for proto::IpVersionConstraint { } } +impl TryFrom for Vec { + type Error = FromProtobufTypeError; + + fn try_from(value: proto::ApiAddresses) -> Result { + value + .api_addresses + .iter() + .map(|api_address| api_address.parse::()) + .collect::>() + .map_err(|_| FromProtobufTypeError::InvalidArgument("Invalid socket address")) + } +} + +impl From> for proto::ApiAddresses { + fn from(value: Vec) -> Self { + Self { + api_addresses: value.iter().map(SocketAddr::to_string).collect(), + } + } +} + pub fn try_tunnel_type_from_i32( tunnel_type: i32, ) -> Result {