summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'mullvad-cli/src')
-rw-r--r--mullvad-cli/src/cmds/api_access.rs286
-rw-r--r--mullvad-cli/src/cmds/bridge.rs350
-rw-r--r--mullvad-cli/src/cmds/mod.rs1
-rw-r--r--mullvad-cli/src/cmds/proxies.rs214
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(())
+ }
+ }
+ }
+ }
+}