diff options
| author | Jonathan <jonathan@mullvad.net> | 2023-12-05 10:03:08 +0100 |
|---|---|---|
| committer | Jonathan <jonathan@mullvad.net> | 2024-01-03 14:38:41 +0100 |
| commit | 4fdc34acbba60d5092e45ce3e513d30ec996c317 (patch) | |
| tree | 80d3a23c1a96bd3d80e05ac66b530e39c252d48a /mullvad-cli/src/cmds/api_access.rs | |
| parent | c510df96772b1e4ab7998e739ced42806c78e931 (diff) | |
| download | mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.tar.xz mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.zip | |
Allow app to use custom socks5 and shadwosocks proxies
This PR has a couple of different purposes
- Allow users to use socks5 local proxies with the CLI without
having to be root nor use split-tunneling. This only works for
OpenVPN.
- Unify the types used by different proxy parts of the codebase,
such as the Access Methods as well as some already existing
OpenVPN proxy code.
This PR changes the firewall on all desktop platforms as well as changes
the routing table slightly on MacOS and Windows.
On Linux the firewall code is modified to apply the appropriate firewall
marks to all packages that go to a remote endpoint corresponding to the
remote part of a local socks5 proxy. The firewall marks will allow the
routing to be done without having to modify the routing table.
On MacOS and Windows the routing table is modified to allow packages to
go to that same endpoint to pass outside the VPN tunnel, it will
additionally punch a hole in the firewall.
The PR also migrates the settings file from version 7 to version 8 in order
to properly and neatly unify Proxy related types.
Finally it provides some slight extensions to the gRPC interface in
order to allow for control over the custom proxy settings.
Diffstat (limited to 'mullvad-cli/src/cmds/api_access.rs')
| -rw-r--r-- | mullvad-cli/src/cmds/api_access.rs | 286 |
1 files changed, 98 insertions, 188 deletions
diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs index c6e01c52d6..887d378fa4 100644 --- a/mullvad-cli/src/cmds/api_access.rs +++ b/mullvad-cli/src/cmds/api_access.rs @@ -1,10 +1,11 @@ use anyhow::{anyhow, Result}; use mullvad_management_interface::MullvadProxyClient; -use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, CustomAccessMethod}; -use std::net::IpAddr; +use mullvad_types::access_method::{AccessMethod, AccessMethodSetting}; +use talpid_types::net::proxy::CustomProxy; use clap::{Args, Subcommand}; -use talpid_types::net::{openvpn::SHADOWSOCKS_CIPHERS, TransportProtocol}; + +use super::proxies::{ProxyEditParams, ShadowsocksAdd, Socks5LocalAdd, Socks5RemoteAdd}; #[derive(Subcommand, Debug, Clone)] pub enum ApiAccess { @@ -99,9 +100,7 @@ impl ApiAccess { /// Edit the data of an API access method. async fn edit(cmd: EditCustomCommands) -> Result<()> { - use mullvad_types::access_method::{ - Shadowsocks, Socks5, Socks5Local, Socks5Remote, SocksAuth, - }; + use talpid_types::net::proxy::{Shadowsocks, Socks5Local, Socks5Remote, SocksAuth}; let mut rpc = MullvadProxyClient::new().await?; let mut api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?; @@ -109,49 +108,47 @@ impl ApiAccess { 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()); - let port = cmd.params.port.unwrap_or(shadowsocks.peer.port()); + CustomProxy::Shadowsocks(shadowsocks) => { + let ip = cmd.params.ip.unwrap_or(shadowsocks.endpoint.ip()); + let port = cmd.params.port.unwrap_or(shadowsocks.endpoint.port()); let password = cmd.params.password.unwrap_or(shadowsocks.password); let cipher = cmd.params.cipher.unwrap_or(shadowsocks.cipher); AccessMethod::from(Shadowsocks::new((ip, port), cipher, password)) } - CustomAccessMethod::Socks5(socks) => match socks { - Socks5::Local(local) => { - let remote_ip = cmd.params.ip.unwrap_or(local.remote_endpoint.address.ip()); - let remote_port = cmd - .params - .port - .unwrap_or(local.remote_endpoint.address.port()); - let local_port = cmd.params.local_port.unwrap_or(local.local_port); - let remote_peer_transport_protocol = cmd - .params - .transport_protocol - .unwrap_or(local.remote_endpoint.protocol); - AccessMethod::from(Socks5Local::new_with_transport_protocol( - (remote_ip, remote_port), - local_port, - remote_peer_transport_protocol, - )) - } - Socks5::Remote(remote) => { - let ip = cmd.params.ip.unwrap_or(remote.peer.ip()); - let port = cmd.params.port.unwrap_or(remote.peer.port()); - AccessMethod::from(match remote.authentication { - None => Socks5Remote::new((ip, port)), - Some(SocksAuth { username, password }) => { - let username = cmd.params.username.unwrap_or(username); - let password = cmd.params.password.unwrap_or(password); - let auth = SocksAuth { username, password }; - Socks5Remote::new_with_authentication((ip, port), auth) - } - }) - } - }, + CustomProxy::Socks5Local(local) => { + let remote_ip = cmd.params.ip.unwrap_or(local.remote_endpoint.address.ip()); + let remote_port = cmd + .params + .port + .unwrap_or(local.remote_endpoint.address.port()); + let local_port = cmd.params.local_port.unwrap_or(local.local_port); + let remote_peer_transport_protocol = cmd + .params + .transport_protocol + .unwrap_or(local.remote_endpoint.protocol); + AccessMethod::from(Socks5Local::new_with_transport_protocol( + (remote_ip, remote_port), + local_port, + remote_peer_transport_protocol, + )) + } + CustomProxy::Socks5Remote(remote) => { + let ip = cmd.params.ip.unwrap_or(remote.endpoint.ip()); + let port = cmd.params.port.unwrap_or(remote.endpoint.port()); + AccessMethod::from(match remote.auth { + None => Socks5Remote::new((ip, port)), + Some(SocksAuth { username, password }) => { + let username = cmd.params.username.unwrap_or(username); + let password = cmd.params.password.unwrap_or(password); + let auth = SocksAuth { username, password }; + Socks5Remote::new_with_authentication((ip, port), auth) + } + }) + } }, }; - if let Some(name) = cmd.params.name { + if let Some(name) = cmd.name { api_access_method.name = name; }; api_access_method.access_method = access_method; @@ -259,19 +256,12 @@ pub enum AddCustomCommands { Shadowsocks { /// An easy to remember name for this custom proxy name: String, - /// The IP of the remote Shadowsocks-proxy - remote_ip: IpAddr, - /// Port on which the remote Shadowsocks-proxy listens for traffic - remote_port: u16, - /// Password for authentication - password: String, - /// Cipher to use - #[arg(long, value_parser = SHADOWSOCKS_CIPHERS)] - 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, + #[clap(flatten)] + add: ShadowsocksAdd, }, } @@ -281,53 +271,26 @@ pub enum AddSocks5Commands { Remote { /// An easy to remember name for this custom proxy name: String, - /// IP of the remote SOCKS5-proxy - remote_ip: IpAddr, - /// Port on which the remote SOCKS5-proxy listens for traffic - remote_port: u16, - #[clap(flatten)] - authentication: Option<SocksAuthentication>, /// 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, + #[clap(flatten)] + add: Socks5RemoteAdd, }, /// 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 - remote_ip: IpAddr, - /// The port of the remote peer - remote_port: u16, - /// The Mullvad App can not know which transport protocol that the - /// remote peer accepts, but it needs to know this in order to correctly - /// exempt the connection traffic in the firewall. - /// - /// By default, the transport protocol is assumed to be `TCP`, but it - /// can optionally be set to `UDP` as well. - #[arg(long, default_value_t = TransportProtocol::Tcp)] - transport_protocol: TransportProtocol, /// 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, + #[clap(flatten)] + add: Socks5LocalAdd, }, } -#[derive(Args, Debug, Clone)] -#[group(requires_all = ["username", "password"])] // https://github.com/clap-rs/clap/issues/5092 -pub struct SocksAuthentication { - /// Username for authentication against a remote SOCKS5 proxy - #[arg(short, long, required = false)] - username: String, - /// Password for authentication against a remote SOCKS5 proxy - #[arg(short, long, required = false)] - password: String, -} - impl AddCustomCommands { fn name(&self) -> &str { match self { @@ -374,9 +337,12 @@ pub struct EditCustomCommands { /// Which API access method to edit #[clap(flatten)] item: SelectItem, + /// Name of the API access method in the Mullvad client [All] + #[arg(long)] + name: Option<String>, /// Editing parameters #[clap(flatten)] - params: EditParams, + params: ProxyEditParams, } #[derive(Args, Debug, Clone)] @@ -384,27 +350,8 @@ pub struct EditParams { /// Name of the API access method in the Mullvad client [All] #[arg(long)] name: Option<String>, - /// Username for authentication [Socks5 (Remote proxy)] - #[arg(long)] - username: Option<String>, - /// Password for authentication [Socks5 (Remote proxy), Shadowsocks] - #[arg(long)] - password: Option<String>, - /// Cipher to use [Shadowsocks] - #[arg(value_parser = SHADOWSOCKS_CIPHERS, long)] - cipher: Option<String>, - /// The IP of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] - #[arg(long)] - ip: Option<IpAddr>, - /// The port of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] - #[arg(long)] - port: Option<u16>, - /// The port that the server on localhost is listening on [Socks5 (Local proxy)] - #[arg(long)] - local_port: Option<u16>, - /// The transport protocol used by the remote proxy [Socks5 (Local proxy)] - #[arg(long)] - transport_protocol: Option<TransportProtocol>, + #[clap(flatten)] + edit_params: ProxyEditParams, } /// Implement conversions from CLI types to Daemon types. @@ -412,23 +359,29 @@ pub struct EditParams { /// Since these are not supposed to be used outside of the CLI, /// we define them in a hidden-away module. mod conversions { - use super::{AddCustomCommands, AddSocks5Commands, SocksAuthentication}; + use crate::cmds::proxies::SocksAuthentication; + + use super::{AddCustomCommands, AddSocks5Commands}; use mullvad_types::access_method as daemon_types; + use talpid_types::net::proxy as talpid_types; impl From<AddCustomCommands> for daemon_types::AccessMethod { fn from(value: AddCustomCommands) -> Self { match value { AddCustomCommands::Socks5(socks) => match socks { AddSocks5Commands::Local { - local_port, - remote_ip, - remote_port, name: _, disabled: _, - transport_protocol, + add, } => { + let (local_port, remote_ip, remote_port, transport_protocol) = ( + add.local_port, + add.remote_ip, + add.remote_port, + add.transport_protocol, + ); println!("Adding SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}/{transport_protocol}"); - daemon_types::Socks5Local::new_with_transport_protocol( + talpid_types::Socks5Local::new_with_transport_protocol( (remote_ip, remote_port), local_port, transport_protocol, @@ -436,41 +389,38 @@ mod conversions { .into() } AddSocks5Commands::Remote { - remote_ip, - remote_port, - authentication, + add, name: _, disabled: _, - } => daemon_types::AccessMethod::from(daemon_types::Socks5::Remote( - match authentication { - Some(SocksAuthentication { username, password }) => { - println!("Adding SOCKS5-proxy: {username}:{password}@{remote_ip}:{remote_port}"); - let auth = - mullvad_types::access_method::SocksAuth { username, password }; - daemon_types::Socks5Remote::new_with_authentication( - (remote_ip, remote_port), - auth, - ) - } - None => { - println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}"); - daemon_types::Socks5Remote::new((remote_ip, remote_port)) - } - }, - )), + } => daemon_types::AccessMethod::from(match add.authentication { + Some(SocksAuthentication { username, password }) => { + println!( + "Adding SOCKS5-proxy: {username}:{password}@{}:{}", + add.remote_ip, add.remote_port + ); + let auth = talpid_types::SocksAuth { username, password }; + talpid_types::Socks5Remote::new_with_authentication( + (add.remote_ip, add.remote_port), + auth, + ) + } + None => { + println!("Adding SOCKS5-proxy: {}:{}", add.remote_ip, add.remote_port); + talpid_types::Socks5Remote::new((add.remote_ip, add.remote_port)) + } + }), }, AddCustomCommands::Shadowsocks { - remote_ip, - remote_port, - password, - cipher, + add, name: _, disabled: _, } => { + let (password, cipher, remote_ip, remote_port) = + (add.password, add.cipher, add.remote_ip, add.remote_port); println!( "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}" ); - daemon_types::AccessMethod::from(daemon_types::Shadowsocks::new( + daemon_types::AccessMethod::from(talpid_types::Shadowsocks::new( (remote_ip, remote_port), cipher, password, @@ -483,9 +433,8 @@ mod conversions { /// Pretty printing of [`ApiAccessMethod`]s mod pp { - use mullvad_types::access_method::{ - AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, SocksAuth, - }; + use crate::cmds::proxies::pp::CustomProxyFormatter; + use mullvad_types::access_method::{AccessMethod, AccessMethodSetting}; pub struct ApiAccessMethodFormatter<'a> { api_access_method: &'a AccessMethodSetting, @@ -517,8 +466,6 @@ mod pp { impl<'a> std::fmt::Display for ApiAccessMethodFormatter<'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, " *") @@ -535,55 +482,18 @@ mod pp { } Ok(()) } - AccessMethod::Custom(method) => match &method { - CustomAccessMethod::Shadowsocks(shadowsocks) => { - write!(f, "{}", self.api_access_method.get_name())?; - 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); - print_option!("Password", shadowsocks.password); - Ok(()) + AccessMethod::Custom(method) => { + write!(f, "{}", self.api_access_method.get_name())?; + if self.settings.write_enabled { + write_status(f, self.api_access_method.enabled())?; } - CustomAccessMethod::Socks5(socks) => match socks { - Socks5::Remote(remote) => { - write!(f, "{}", self.api_access_method.get_name())?; - if self.settings.write_enabled { - write_status(f, self.api_access_method.enabled())?; - } - 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) => { - write!(f, "{}", self.api_access_method.get_name())?; - if self.settings.write_enabled { - write_status(f, self.api_access_method.enabled())?; - } - writeln!(f)?; - print_option!("Protocol", "Socks5 (local)"); - print_option!( - "Peer", - format!( - "{}/{}", - local.remote_endpoint.address, local.remote_endpoint.protocol - ) - ); - print_option!("Local port", local.local_port); - Ok(()) - } - }, - }, + writeln!(f)?; + let formatter = CustomProxyFormatter { + custom_proxy: method, + }; + write!(f, "{}", formatter)?; + Ok(()) + } } } } |
