diff options
Diffstat (limited to 'mullvad-cli/src')
| -rw-r--r-- | mullvad-cli/src/cmds/api_access.rs | 286 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/bridge.rs | 350 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/mod.rs | 1 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/proxies.rs | 214 |
4 files changed, 462 insertions, 389 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(()) + } } } } diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 4abccb99e8..329921868b 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -1,17 +1,22 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use clap::Subcommand; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::{ relay_constraints::{ - BridgeConstraints, BridgeConstraintsFormatter, BridgeSettings, BridgeState, Constraint, - LocationConstraint, Ownership, Provider, Providers, + BridgeConstraintsFormatter, BridgeState, BridgeType, Constraint, LocationConstraint, + Ownership, Provider, Providers, }, relay_list::RelayEndpointData, }; -use std::net::{IpAddr, SocketAddr}; -use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; +use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote}; -use super::{relay::resolve_location_constraint, relay_constraints::LocationArgs}; +use crate::cmds::proxies::pp::CustomProxyFormatter; + +use super::{ + proxies::{ProxyEditParams, ShadowsocksAdd, Socks5LocalAdd, Socks5RemoteAdd}, + relay::resolve_location_constraint, + relay_constraints::LocationArgs, +}; #[derive(Subcommand, Debug)] pub enum Bridge { @@ -73,73 +78,48 @@ pub enum SetCommands { /// Configure a SOCKS5 proxy #[clap(subcommand)] - Custom(SetCustomCommands), + Custom(CustomCommands), +} + +#[derive(Subcommand, Debug, Clone)] +pub enum CustomCommands { + /// Create or update and enable the custom bridge configuration. + #[clap(subcommand)] + Set(AddCustomCommands), + /// Edit an existing custom bridge configuration. + Edit(ProxyEditParams), + /// Use an existing custom bridge configuration. + Use, + /// Stop using the custom bridge configuration. + Disable, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum AddCustomCommands { + #[clap(subcommand)] + Socks5(AddSocks5Commands), + /// Configure bundled Shadowsocks proxy + Shadowsocks { + #[clap(flatten)] + add: ShadowsocksAdd, + }, } #[derive(Subcommand, Debug, Clone)] -pub enum SetCustomCommands { +pub enum AddSocks5Commands { /// Configure a local SOCKS5 proxy - #[cfg_attr( - target_os = "linux", - clap( - about = "Registers a local SOCKS5 proxy. The server must be excluded using \ - 'mullvad-exclude', or `SO_MARK` must be set to '0x6d6f6c65', in order \ - to bypass firewall restrictions" - ) - )] - #[cfg_attr( - target_os = "windows", - clap( - about = "Registers a local SOCKS5 proxy. The server must be excluded using \ - split tunneling in order to bypass firewall restrictions" - ) - )] - #[cfg_attr( - target_os = "macos", - clap( - about = "Registers a local SOCKS5 proxy. The server must run as root to bypass \ - firewall restrictions" - ) + #[clap( + about = "Registers a local SOCKS5 proxy. Will allow all local programs to leak traffic *only* to the remote endpoint." )] 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, + #[clap(flatten)] + add: Socks5LocalAdd, }, /// 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<String>, - /// Password for authentication - #[arg(requires = "username")] - password: Option<String>, - }, - - /// 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, + #[clap(flatten)] + add: Socks5RemoteAdd, }, } @@ -188,133 +168,35 @@ impl Bridge { }; Self::update_bridge_settings(&mut rpc, None, Some(providers), None).await } - SetCommands::Custom(subcmd) => Self::set_custom(subcmd).await, + SetCommands::Custom(subcmd) => Self::handle_custom(subcmd).await, } } - async fn set_custom(subcmd: SetCustomCommands) -> Result<()> { - match subcmd { - SetCustomCommands::Local { - local_port, - remote_ip, - remote_port, - } => { - let local_proxy = openvpn::LocalProxySettings { - port: local_port, - peer: SocketAddr::new(remote_ip, remote_port), - }; - let packed_proxy = openvpn::ProxySettings::Local(local_proxy); - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!("{}", error); - } - - let mut rpc = MullvadProxyClient::new().await?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy)) - .await?; - } - SetCustomCommands::Remote { - remote_ip, - remote_port, - username, - password, - } => { - let auth = match (username, password) { - (Some(username), Some(password)) => { - Some(openvpn::ProxyAuth { username, password }) - } - _ => None, - }; - let proxy = openvpn::RemoteProxySettings { - address: SocketAddr::new(remote_ip, remote_port), - auth, - }; - let packed_proxy = openvpn::ProxySettings::Remote(proxy); - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!("{}", error); - } - - let mut rpc = MullvadProxyClient::new().await?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy)) - .await?; - } - SetCustomCommands::Shadowsocks { - remote_ip, - remote_port, - password, - cipher, - } => { - let proxy = openvpn::ShadowsocksProxySettings { - peer: SocketAddr::new(remote_ip, remote_port), - password, - cipher, - #[cfg(target_os = "linux")] - fwmark: None, - }; - let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy); - if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) { - panic!("{}", error); - } - - let mut rpc = MullvadProxyClient::new().await?; - rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy)) - .await?; - } - } - - println!("Updated bridge settings"); - Ok(()) - } - async fn get() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; let settings = rpc.get_settings().await?; println!("Bridge state: {}", settings.bridge_state); - match settings.bridge_settings { - BridgeSettings::Custom(proxy) => match proxy { - openvpn::ProxySettings::Local(local_proxy) => Self::print_local_proxy(&local_proxy), - openvpn::ProxySettings::Remote(remote_proxy) => { - Self::print_remote_proxy(&remote_proxy) - } - openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) => { - Self::print_shadowsocks_proxy(&shadowsocks_proxy) - } - }, - BridgeSettings::Normal(ref constraints) => { - println!( - "Bridge constraints: {}", - BridgeConstraintsFormatter { - constraints, - custom_lists: &settings.custom_lists - } - ) - } - }; - Ok(()) - } - - fn print_local_proxy(proxy: &openvpn::LocalProxySettings) { - println!("proxy: local"); - println!(" local port: {}", proxy.port); - println!(" peer address: {}", proxy.peer); - } + println!( + "Active bridge type: {}", + settings.bridge_settings.bridge_type + ); - fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) { - println!("proxy: remote"); - println!(" server address: {}", proxy.address); + println!("Normal constraints"); + println!( + "{:<4}{}", + "", + BridgeConstraintsFormatter { + constraints: &settings.bridge_settings.normal, + custom_lists: &settings.custom_lists + } + ); - if let Some(ref auth) = proxy.auth { - println!(" auth username: {}", auth.username); - println!(" auth password: {}", auth.password); - } else { - println!(" auth: none"); + if let Some(ref custom_proxy) = settings.bridge_settings.custom { + println!("Custom proxy"); + println!("{}", CustomProxyFormatter { custom_proxy }); } - } - fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) { - println!("proxy: Shadowsocks"); - println!(" peer address: {}", proxy.peer); - println!(" password: {}", proxy.password); - println!(" cipher: {}", proxy.cipher); + Ok(()) } async fn list() -> Result<()> { @@ -379,33 +261,99 @@ impl Bridge { providers: Option<Constraint<Providers>>, ownership: Option<Constraint<Ownership>>, ) -> Result<()> { - let constraints = match rpc.get_settings().await?.bridge_settings { - BridgeSettings::Normal(mut constraints) => { - if let Some(new_location) = location { - constraints.location = new_location; - } - if let Some(new_providers) = providers { - constraints.providers = new_providers; - } - if let Some(new_ownership) = ownership { - constraints.ownership = new_ownership; - } - constraints + let mut settings = rpc.get_settings().await?.bridge_settings; + if let Some(new_location) = location { + settings.normal.location = new_location; + } + if let Some(new_providers) = providers { + settings.normal.providers = new_providers; + } + if let Some(new_ownership) = ownership { + settings.normal.ownership = new_ownership; + } + + settings.bridge_type = BridgeType::Normal; + + rpc.set_bridge_settings(settings).await?; + + println!("Updated bridge settings"); + + Ok(()) + } + + pub async fn handle_custom(subcmd: CustomCommands) -> Result<()> { + match subcmd { + CustomCommands::Set(set_custom_commands) => { + Self::custom_bridge_set(set_custom_commands).await } - _ => BridgeConstraints { - location: location - .unwrap_or(Constraint::Any) - .map(LocationConstraint::from), - providers: providers.unwrap_or(Constraint::Any), - ownership: ownership.unwrap_or(Constraint::Any), - }, + CustomCommands::Edit(edit) => Self::custom_bridge_edit(edit).await, + CustomCommands::Use => Self::custom_bridge_use().await, + CustomCommands::Disable => Self::custom_bridge_disable().await, + } + } + + async fn custom_bridge_edit(edit: ProxyEditParams) -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + let mut settings = rpc.get_settings().await?; + + let Some(ref mut custom_bridge) = settings.bridge_settings.custom else { + bail!("Can not edit as there is no currently saved custom bridge"); + }; + + match custom_bridge { + CustomProxy::Shadowsocks(ss) => *ss = edit.merge_shadowsocks(ss), + CustomProxy::Socks5Local(local) => *local = edit.merge_socks_local(local), + CustomProxy::Socks5Remote(remote) => *remote = edit.merge_socks_remote(remote), }; - rpc.set_bridge_settings(BridgeSettings::Normal(constraints)) - .await?; + rpc.set_bridge_settings(settings.bridge_settings) + .await + .map_err(anyhow::Error::from) + } - println!("Updated bridge settings"); + async fn custom_bridge_use() -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + + let mut settings = rpc.get_settings().await?; + if settings.bridge_settings.custom.is_none() { + bail!("Cannot enable custom bridge as there are no settings"); + } + settings.bridge_settings.bridge_type = BridgeType::Custom; + rpc.set_bridge_settings(settings.bridge_settings).await?; + + Ok(()) + } + async fn custom_bridge_disable() -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + let mut settings = rpc.get_settings().await?; + + settings.bridge_settings.bridge_type = BridgeType::Normal; + + rpc.set_bridge_settings(settings.bridge_settings).await?; Ok(()) } + + async fn custom_bridge_set(set_commands: AddCustomCommands) -> Result<()> { + let mut rpc = MullvadProxyClient::new().await?; + let mut settings = rpc.get_settings().await?; + + settings.bridge_settings.custom = Some(match set_commands { + AddCustomCommands::Socks5(AddSocks5Commands::Local { add }) => { + CustomProxy::Socks5Local(Socks5Local::from(add)) + } + AddCustomCommands::Socks5(AddSocks5Commands::Remote { add }) => { + CustomProxy::Socks5Remote(Socks5Remote::from(add)) + } + AddCustomCommands::Shadowsocks { add } => { + CustomProxy::Shadowsocks(Shadowsocks::from(add)) + } + }); + + settings.bridge_settings.bridge_type = BridgeType::Custom; + + rpc.set_bridge_settings(settings.bridge_settings) + .await + .map_err(anyhow::Error::from) + } } diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs index 1984f1493b..29e0508d80 100644 --- a/mullvad-cli/src/cmds/mod.rs +++ b/mullvad-cli/src/cmds/mod.rs @@ -13,6 +13,7 @@ pub mod import_settings; pub mod lan; pub mod lockdown; pub mod obfuscation; +pub mod proxies; pub mod relay; pub mod relay_constraints; pub mod reset; diff --git a/mullvad-cli/src/cmds/proxies.rs b/mullvad-cli/src/cmds/proxies.rs new file mode 100644 index 0000000000..6e32836a25 --- /dev/null +++ b/mullvad-cli/src/cmds/proxies.rs @@ -0,0 +1,214 @@ +use std::net::{IpAddr, SocketAddr}; +use talpid_types::net::{ + proxy::{Shadowsocks, Socks5Local, Socks5Remote, SocksAuth, SHADOWSOCKS_CIPHERS}, + Endpoint, TransportProtocol, +}; + +use clap::Args; + +#[derive(Args, Debug, Clone)] +pub struct Socks5LocalAdd { + /// The port that the server on localhost is listening on + pub local_port: u16, + /// The IP of the remote peer + pub remote_ip: IpAddr, + /// The port of the remote peer + pub 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)] + pub transport_protocol: TransportProtocol, +} + +impl From<Socks5LocalAdd> for Socks5Local { + fn from(add: Socks5LocalAdd) -> Self { + Self { + remote_endpoint: Endpoint { + address: SocketAddr::new(add.remote_ip, add.remote_port), + protocol: add.transport_protocol, + }, + local_port: add.local_port, + } + } +} + +// We do not support setting the protocol as anything other than tcp for remote socks5 servers +#[derive(Args, Debug, Clone)] +pub struct Socks5RemoteAdd { + /// The IP of the remote proxy server + pub remote_ip: IpAddr, + /// The port of the remote proxy server + pub remote_port: u16, + + #[clap(flatten)] + pub authentication: Option<SocksAuthentication>, +} + +impl From<Socks5RemoteAdd> for Socks5Remote { + fn from(add: Socks5RemoteAdd) -> Self { + Self { + endpoint: SocketAddr::new(add.remote_ip, add.remote_port), + auth: add.authentication.map(|auth| SocksAuth { + username: auth.username, + password: auth.password, + }), + } + } +} + +#[derive(Args, Debug, Clone)] +pub struct ShadowsocksAdd { + /// The IP of the remote Shadowsocks-proxy + pub remote_ip: IpAddr, + /// Port on which the remote Shadowsocks-proxy listens for traffic + pub remote_port: u16, + /// Password for authentication + pub password: String, + /// Cipher to use + #[arg(long, value_parser = SHADOWSOCKS_CIPHERS)] + pub cipher: String, +} + +impl From<ShadowsocksAdd> for Shadowsocks { + fn from(add: ShadowsocksAdd) -> Self { + Self { + endpoint: SocketAddr::new(add.remote_ip, add.remote_port), + password: add.password, + cipher: add.cipher, + } + } +} + +#[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)] + pub username: String, + /// Password for authentication against a remote SOCKS5 proxy + #[arg(short, long, required = false)] + pub password: String, +} + +#[derive(Args, Debug, Clone)] +pub struct ProxyEditParams { + /// Username for authentication [Socks5 (Remote proxy)] + #[arg(long)] + pub username: Option<String>, + /// Password for authentication [Socks5 (Remote proxy), Shadowsocks] + #[arg(long)] + pub password: Option<String>, + /// Cipher to use [Shadowsocks] + #[arg(value_parser = SHADOWSOCKS_CIPHERS, long)] + pub cipher: Option<String>, + /// The IP of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] + #[arg(long)] + pub ip: Option<IpAddr>, + /// The port of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks] + #[arg(long)] + pub port: Option<u16>, + /// The port that the server on localhost is listening on [Socks5 (Local proxy)] + #[arg(long)] + pub local_port: Option<u16>, + /// The transport protocol used by the remote proxy [Socks5 (Local proxy)] + #[arg(long)] + pub transport_protocol: Option<TransportProtocol>, +} + +impl ProxyEditParams { + pub fn merge_socks_local(self, local: &Socks5Local) -> Socks5Local { + let remote_ip = self.ip.unwrap_or(local.remote_endpoint.address.ip()); + let remote_port = self.port.unwrap_or(local.remote_endpoint.address.port()); + let local_port = self.local_port.unwrap_or(local.local_port); + let remote_peer_transport_protocol = self + .transport_protocol + .unwrap_or(local.remote_endpoint.protocol); + Socks5Local::new_with_transport_protocol( + (remote_ip, remote_port), + local_port, + remote_peer_transport_protocol, + ) + } + + pub fn merge_socks_remote(self, remote: &Socks5Remote) -> Socks5Remote { + let ip = self.ip.unwrap_or(remote.endpoint.ip()); + let port = self.port.unwrap_or(remote.endpoint.port()); + match &remote.auth { + None => match (self.username, self.password) { + (Some(username), Some(password)) => { + let auth = SocksAuth { username, password }; + Socks5Remote::new_with_authentication((ip, port), auth) + } + (None, None) => Socks5Remote::new((ip, port)), + _ => { + println!("Remote SOCKS5 proxy does not have a username and password set already, so you must provide both or neither when you edit."); + Socks5Remote::new((ip, port)) + } + }, + Some(SocksAuth { username, password }) => { + let username = self.username.unwrap_or(username.to_owned()); + let password = self.password.unwrap_or(password.to_owned()); + let auth = SocksAuth { username, password }; + Socks5Remote::new_with_authentication((ip, port), auth) + } + } + } + + pub fn merge_shadowsocks(self, shadowsocks: &Shadowsocks) -> Shadowsocks { + let ip = self.ip.unwrap_or(shadowsocks.endpoint.ip()); + let port = self.port.unwrap_or(shadowsocks.endpoint.port()); + let password = self.password.unwrap_or(shadowsocks.password.to_owned()); + let cipher = self.cipher.unwrap_or(shadowsocks.cipher.to_owned()); + Shadowsocks::new((ip, port), cipher, password) + } +} + +pub mod pp { + use crate::print_option; + use talpid_types::net::proxy::{CustomProxy, SocksAuth}; + + pub struct CustomProxyFormatter<'a> { + pub custom_proxy: &'a CustomProxy, + } + + impl<'a> std::fmt::Display for CustomProxyFormatter<'a> { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.custom_proxy { + CustomProxy::Shadowsocks(shadowsocks) => { + print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher)); + print_option!("Peer", shadowsocks.endpoint); + print_option!("Password", shadowsocks.password); + Ok(()) + } + CustomProxy::Socks5Remote(remote) => { + print_option!("Protocol", "Socks5"); + print_option!("Peer", remote.endpoint); + match &remote.auth { + Some(SocksAuth { username, password }) => { + print_option!("Username", username); + print_option!("Password", password); + } + None => (), + } + Ok(()) + } + CustomProxy::Socks5Local(local) => { + 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(()) + } + } + } + } +} |
