summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'mullvad-cli/src')
-rw-r--r--mullvad-cli/src/cmds/bridge.rs354
-rw-r--r--mullvad-cli/src/cmds/mod.rs4
-rw-r--r--mullvad-cli/src/cmds/relay.rs82
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs240
-rw-r--r--mullvad-cli/src/location.rs69
-rw-r--r--mullvad-cli/src/main.rs1
6 files changed, 435 insertions, 315 deletions
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
new file mode 100644
index 0000000000..3c686154eb
--- /dev/null
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -0,0 +1,354 @@
+use crate::{location, new_rpc_client, Command, Result};
+use clap::value_t;
+
+use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings, BridgeState};
+use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS};
+
+use std::net::{IpAddr, SocketAddr};
+
+pub struct Bridge;
+
+impl Command for Bridge {
+ fn name(&self) -> &'static str {
+ "bridge"
+ }
+
+ fn clap_subcommand(&self) -> clap::App<'static, 'static> {
+ clap::SubCommand::with_name(self.name())
+ .about("Manage use of bridges")
+ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
+ .subcommand(create_bridge_set_subcommand())
+ .subcommand(
+ clap::SubCommand::with_name("get").about("Get current bridge settings and state"),
+ )
+ .subcommand(clap::SubCommand::with_name("list").about("List brigde relays"))
+ }
+
+ fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> {
+ match matches.subcommand() {
+ ("set", Some(set_matches)) => Self::handle_set(set_matches),
+ ("get", _) => Self::handle_get(),
+ ("list", _) => Self::list_bridge_relays(),
+ _ => unreachable!("unhandled command"),
+ }
+ }
+}
+
+fn create_bridge_set_subcommand() -> clap::App<'static, 'static> {
+ clap::SubCommand::with_name("set")
+ .about("Set bridge state and settings")
+ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
+ .subcommand(create_set_state_subcommand())
+ .subcommand(create_set_custom_settings_subcommand())
+ .subcommand(location::get_subcommand().about(
+ "Set country or city to select bridge relays from. Use the 'list' \
+ command to show available alternatives.",
+ ))
+}
+
+
+fn create_set_custom_settings_subcommand() -> clap::App<'static, 'static> {
+ clap::SubCommand::with_name("custom")
+ .about("Configure a SOCKS5 proxy")
+ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
+ .subcommand(
+ clap::SubCommand::with_name("local")
+ .about("Registers a local SOCKS5 proxy")
+ .arg(
+ clap::Arg::with_name("local-port")
+ .help("Specifies the port the local proxy server is listening on")
+ .required(true)
+ .index(1),
+ )
+ .arg(
+ clap::Arg::with_name("remote-ip")
+ .help("Specifies the IP of the proxy server peer")
+ .required(true)
+ .index(2),
+ )
+ .arg(
+ clap::Arg::with_name("remote-port")
+ .help("Specifies the port of the proxy server peer")
+ .required(true)
+ .index(3),
+ ),
+ )
+ .subcommand(
+ clap::SubCommand::with_name("remote")
+ .about("Registers a remote SOCKS5 proxy")
+ .arg(
+ clap::Arg::with_name("remote-ip")
+ .help("Specifies the IP of the remote proxy server")
+ .required(true)
+ .index(1),
+ )
+ .arg(
+ clap::Arg::with_name("remote-port")
+ .help("Specifies the port the remote proxy server is listening on")
+ .required(true)
+ .index(2),
+ )
+ .arg(
+ clap::Arg::with_name("username")
+ .help("Specifies the username for remote authentication")
+ .required(true)
+ .index(3),
+ )
+ .arg(
+ clap::Arg::with_name("password")
+ .help("Specifies the password for remote authentication")
+ .required(true)
+ .index(4),
+ ),
+ )
+ .subcommand(
+ clap::SubCommand::with_name("shadowsocks")
+ .about("Configure bundled Shadowsocks proxy")
+ .arg(
+ clap::Arg::with_name("remote-ip")
+ .help("Specifies the IP of the remote Shadowsocks server")
+ .required(true)
+ .index(1),
+ )
+ .arg(
+ clap::Arg::with_name("remote-port")
+ .help("Specifies the port of the remote Shadowsocks server")
+ .default_value("443")
+ .index(2),
+ )
+ .arg(
+ clap::Arg::with_name("password")
+ .help("Specifies the password on the remote Shadowsocks server")
+ .default_value("23#dfsbbb")
+ .index(3),
+ )
+ .arg(
+ clap::Arg::with_name("cipher")
+ .help("Specifies the cipher to use")
+ .default_value("chacha20")
+ .possible_values(SHADOWSOCKS_CIPHERS)
+ .index(4),
+ ),
+ )
+}
+
+fn create_set_state_subcommand() -> clap::App<'static, 'static> {
+ clap::SubCommand::with_name("state")
+ .about("Set bridge state")
+ .arg(
+ clap::Arg::with_name("state")
+ .help("Specifies whether a bridge should be used")
+ .index(1)
+ .possible_values(&["auto", "on", "off"]),
+ )
+}
+
+impl Bridge {
+ fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> {
+ match matches.subcommand() {
+ ("location", Some(location_matches)) => {
+ Self::handle_set_bridge_location(location_matches)
+ }
+ ("custom", Some(custom_matches)) => {
+ Self::handle_bridge_set_custom_settings(custom_matches)
+ }
+ ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches),
+ _ => unreachable!("unhandled command"),
+ }
+ }
+
+ fn handle_get() -> Result<()> {
+ let mut rpc = new_rpc_client()?;
+ let settings = rpc.get_settings()?;
+ println!("Bridge state - {}", settings.get_bridge_state());
+ match settings.get_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(constraints) => {
+ println!("Bridge constraints: {}", constraints);
+ }
+ };
+ Ok(())
+ }
+
+ fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> {
+ let location = location::get_constraint(matches);
+ let mut rpc = new_rpc_client()?;
+ rpc.set_bridge_settings(BridgeSettings::Normal(BridgeConstraints { location }))?;
+ Ok(())
+ }
+
+ fn handle_set_bridge_state(matches: &clap::ArgMatches<'_>) -> Result<()> {
+ let state = match matches.value_of("state").unwrap() {
+ "auto" => BridgeState::Auto,
+ "on" => BridgeState::On,
+ "off" => BridgeState::Off,
+ _ => unreachable!(),
+ };
+ let mut rpc = new_rpc_client()?;
+ rpc.set_bridge_state(state)?;
+ Ok(())
+ }
+
+ fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> {
+ if let Some(args) = matches.subcommand_matches("local") {
+ let local_port =
+ value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit());
+ let remote_ip =
+ value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
+ let remote_port =
+ value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
+
+ let proxy = openvpn::LocalProxySettings {
+ port: local_port,
+ peer: SocketAddr::new(remote_ip, remote_port),
+ };
+
+ let packed_proxy = openvpn::ProxySettings::Local(proxy);
+
+ if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
+ panic!(error);
+ }
+
+ let mut rpc = new_rpc_client()?;
+ rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?;
+ } else if let Some(args) = matches.subcommand_matches("remote") {
+ let remote_ip =
+ value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
+ let remote_port =
+ value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
+ let username = args.value_of("username");
+ let password = args.value_of("password");
+
+ let auth = match (username, password) {
+ (Some(username), Some(password)) => Some(openvpn::ProxyAuth {
+ username: username.to_string(),
+ password: password.to_string(),
+ }),
+ _ => 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 = new_rpc_client()?;
+ rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?;
+ } else if let Some(args) = matches.subcommand_matches("shadowsocks") {
+ let remote_ip =
+ value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
+ let remote_port =
+ value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
+ let password = args.value_of("password").unwrap().to_string();
+ let cipher = args.value_of("cipher").unwrap().to_string();
+
+ let proxy = openvpn::ShadowsocksProxySettings {
+ peer: SocketAddr::new(remote_ip, remote_port),
+ password,
+ cipher,
+ };
+
+ let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy);
+
+ if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
+ panic!(error);
+ }
+
+ let mut rpc = new_rpc_client()?;
+ rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))?;
+ } else {
+ unreachable!("unhandled proxy type");
+ }
+
+ println!("proxy details have been updated");
+ Ok(())
+ }
+
+ fn print_local_proxy(proxy: &openvpn::LocalProxySettings) {
+ println!("proxy: local");
+ println!(" local port: {}", proxy.port);
+ println!(" peer IP: {}", proxy.peer.ip());
+ println!(" peer port: {}", proxy.peer.port());
+ }
+
+ fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) {
+ println!("proxy: remote");
+ println!(" server IP: {}", proxy.address.ip());
+ println!(" server port: {}", proxy.address.port());
+
+ if let Some(ref auth) = proxy.auth {
+ println!(" auth username: {}", auth.username);
+ println!(" auth password: {}", auth.password);
+ } else {
+ println!(" auth: none");
+ }
+ }
+
+ fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) {
+ println!("proxy: Shadowsocks");
+ println!(" peer IP: {}", proxy.peer.ip());
+ println!(" peer port: {}", proxy.peer.port());
+ println!(" password: {}", proxy.password);
+ println!(" cipher: {}", proxy.cipher);
+ }
+
+ fn list_bridge_relays() -> Result<()> {
+ let mut rpc = new_rpc_client()?;
+ let mut locations = rpc.get_relay_locations()?;
+
+ locations.countries = locations
+ .countries
+ .into_iter()
+ .filter_map(|mut country| {
+ country.cities = country
+ .cities
+ .into_iter()
+ .filter_map(|mut city| {
+ city.relays.retain(|relay| !relay.bridges.is_empty());
+ if !city.relays.is_empty() {
+ Some(city)
+ } else {
+ None
+ }
+ })
+ .collect();
+ if !country.cities.is_empty() {
+ Some(country)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ for mut country in locations.countries {
+ country.cities.sort_by(|c1, c2| c1.name.cmp(&c2.name));
+ println!("{} ({})", country.name, country.code);
+ for city in &country.cities {
+ println!(
+ "\t{} ({}) @ {:.5}°N, {:.5}°W",
+ city.name, city.code, city.latitude, city.longitude
+ );
+ }
+ println!();
+ }
+ Ok(())
+ }
+}
diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs
index 43e1968065..83bfd47883 100644
--- a/mullvad-cli/src/cmds/mod.rs
+++ b/mullvad-cli/src/cmds/mod.rs
@@ -7,6 +7,9 @@ pub use self::account::Account;
mod auto_connect;
pub use self::auto_connect::AutoConnect;
+mod bridge;
+pub use self::bridge::Bridge;
+
mod status;
pub use self::status::Status;
@@ -37,6 +40,7 @@ pub fn get_commands() -> HashMap<&'static str, Box<dyn Command>> {
Box::new(Account),
Box::new(AutoConnect),
Box::new(BlockWhenDisconnected),
+ Box::new(Bridge),
Box::new(Connect),
Box::new(Disconnect),
Box::new(Lan),
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index 5cc8f2815b..965107057d 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -1,4 +1,4 @@
-use crate::{new_rpc_client, Command, Error, Result};
+use crate::{location, new_rpc_client, Command, Error, Result};
use clap::{value_t, values_t};
use std::{
io::{self, BufRead},
@@ -8,8 +8,8 @@ use std::{
use mullvad_types::{
relay_constraints::{
- Constraint, LocationConstraint, OpenVpnConstraints, RelayConstraintsUpdate,
- RelaySettingsUpdate, TunnelConstraints, WireguardConstraints,
+ Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettingsUpdate,
+ TunnelConstraints, WireguardConstraints,
},
ConnectionConfig, CustomTunnelEndpoint,
};
@@ -109,31 +109,9 @@ impl Command for Relay {
)
)
.subcommand(
- clap::SubCommand::with_name("location")
- .about(
- "Set country or city to select relays from. Use the 'list' \
- command to show available alternatives.",
- )
- .arg(
- clap::Arg::with_name("country")
- .help(
- "The two letter country code, or 'any' for no preference.",
- )
- .required(true)
- .index(1)
- .validator(country_code_validator),
- )
- .arg(
- clap::Arg::with_name("city")
- .help("The three letter city code")
- .index(2)
- .validator(city_code_validator),
- )
- .arg(
- clap::Arg::with_name("hostname")
- .help("The relay hostname")
- .index(3),
- ),
+ location::get_subcommand()
+ .about("Set country or city to select relays from. Use the 'list' \
+ command to show available alternatives.")
)
.subcommand(
clap::SubCommand::with_name("tunnel")
@@ -289,37 +267,7 @@ impl Relay {
}
fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> {
- let country = matches.value_of("country").unwrap();
- let city = matches.value_of("city");
- let hostname = matches.value_of("hostname");
-
- let location_constraint = match (country, city, hostname) {
- ("any", None, None) => Constraint::Any,
- ("any", ..) => clap::Error::with_description(
- "City can't be given when selecting 'any' country",
- clap::ErrorKind::InvalidValue,
- )
- .exit(),
- (country, None, None) => {
- Constraint::Only(LocationConstraint::Country(country.to_owned()))
- }
- (country, Some(city), None) => Constraint::Only(LocationConstraint::City(
- country.to_owned(),
- city.to_owned(),
- )),
- (country, Some(city), Some(hostname)) => {
- Constraint::Only(LocationConstraint::Hostname(
- country.to_owned(),
- city.to_owned(),
- hostname.to_owned(),
- ))
- }
- (..) => clap::Error::with_description(
- "Invalid country, city and hostname combination given",
- clap::ErrorKind::InvalidValue,
- )
- .exit(),
- };
+ let location_constraint = location::get_constraint(matches);
self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate {
location: Some(location_constraint),
@@ -409,19 +357,3 @@ fn parse_protocol_constraint(raw_protocol: &str) -> Constraint<TransportProtocol
_ => unreachable!(),
}
}
-
-fn country_code_validator(code: String) -> ::std::result::Result<(), String> {
- if code.len() == 2 || code == "any" {
- Ok(())
- } else {
- Err(String::from("Country codes must be two letters, or 'any'."))
- }
-}
-
-fn city_code_validator(code: String) -> ::std::result::Result<(), String> {
- if code.len() == 3 {
- Ok(())
- } else {
- Err(String::from("City codes must be three letters"))
- }
-}
diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs
index c02a88d03a..136290d5bb 100644
--- a/mullvad-cli/src/cmds/tunnel.rs
+++ b/mullvad-cli/src/cmds/tunnel.rs
@@ -2,9 +2,6 @@ use crate::{new_rpc_client, Command, Result};
use clap::value_t;
use mullvad_types::settings::TunnelOptions;
-use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS};
-
-use std::net::{IpAddr, SocketAddr};
pub struct Tunnel;
@@ -67,7 +64,6 @@ fn create_openvpn_subcommand() -> clap::App<'static, 'static> {
.about("Manage options for OpenVPN tunnels")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
.subcommand(create_openvpn_mssfix_subcommand())
- .subcommand(create_openvpn_proxy_subcommand())
}
fn create_openvpn_mssfix_subcommand() -> clap::App<'static, 'static> {
@@ -81,97 +77,6 @@ fn create_openvpn_mssfix_subcommand() -> clap::App<'static, 'static> {
)
}
-fn create_openvpn_proxy_subcommand() -> clap::App<'static, 'static> {
- clap::SubCommand::with_name("proxy")
- .about("Configure a SOCKS5 proxy")
- .setting(clap::AppSettings::SubcommandRequiredElseHelp)
- .subcommand(clap::SubCommand::with_name("get"))
- .subcommand(clap::SubCommand::with_name("unset"))
- .subcommand(
- clap::SubCommand::with_name("set")
- .setting(clap::AppSettings::SubcommandRequiredElseHelp)
- .subcommand(
- clap::SubCommand::with_name("local")
- .about("Registers a local SOCKS5 proxy")
- .arg(
- clap::Arg::with_name("local-port")
- .help("Specifies the port the local proxy server is listening on")
- .required(true)
- .index(1),
- )
- .arg(
- clap::Arg::with_name("remote-ip")
- .help("Specifies the IP of the proxy server peer")
- .required(true)
- .index(2),
- )
- .arg(
- clap::Arg::with_name("remote-port")
- .help("Specifies the port of the proxy server peer")
- .required(true)
- .index(3),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("remote")
- .about("Registers a remote SOCKS5 proxy")
- .arg(
- clap::Arg::with_name("remote-ip")
- .help("Specifies the IP of the remote proxy server")
- .required(true)
- .index(1),
- )
- .arg(
- clap::Arg::with_name("remote-port")
- .help("Specifies the port the remote proxy server is listening on")
- .required(true)
- .index(2),
- )
- .arg(
- clap::Arg::with_name("username")
- .help("Specifies the username for remote authentication")
- .required(true)
- .index(3),
- )
- .arg(
- clap::Arg::with_name("password")
- .help("Specifies the password for remote authentication")
- .required(true)
- .index(4),
- ),
- )
- .subcommand(
- clap::SubCommand::with_name("shadowsocks")
- .about("Configure bundled Shadowsocks proxy")
- .arg(
- clap::Arg::with_name("remote-ip")
- .help("Specifies the IP of the remote Shadowsocks server")
- .required(true)
- .index(1),
- )
- .arg(
- clap::Arg::with_name("remote-port")
- .help("Specifies the port of the remote Shadowsocks server")
- .default_value("443")
- .index(2),
- )
- .arg(
- clap::Arg::with_name("password")
- .help("Specifies the password on the remote Shadowsocks server")
- .default_value("23#dfsbbb")
- .index(3),
- )
- .arg(
- clap::Arg::with_name("cipher")
- .help("Specifies the cipher to use")
- .default_value("chacha20")
- .possible_values(SHADOWSOCKS_CIPHERS)
- .index(4),
- ),
- ),
- )
-}
-
fn create_ipv6_subcommand() -> clap::App<'static, 'static> {
clap::SubCommand::with_name("ipv6")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
@@ -190,7 +95,6 @@ impl Tunnel {
fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> {
match matches.subcommand() {
("mssfix", Some(mssfix_matches)) => Self::handle_openvpn_mssfix_cmd(mssfix_matches),
- ("proxy", Some(proxy_matches)) => Self::handle_openvpn_proxy_cmd(proxy_matches),
_ => unreachable!("unhandled command"),
}
}
@@ -204,15 +108,6 @@ impl Tunnel {
}
}
- fn handle_openvpn_proxy_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> {
- match matches.subcommand() {
- ("get", Some(_)) => Self::process_openvpn_proxy_get(),
- ("unset", Some(_)) => Self::process_openvpn_proxy_unset(),
- ("set", Some(set_matches)) => Self::process_openvpn_proxy_set(set_matches),
- _ => unreachable!("unhandled command"),
- }
- }
-
fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> {
match matches.subcommand() {
("mtu", Some(matches)) => match matches.subcommand() {
@@ -323,141 +218,6 @@ impl Tunnel {
Ok(())
}
- fn process_openvpn_proxy_get() -> Result<()> {
- let tunnel_options = Self::get_tunnel_options()?;
- if let Some(proxy) = tunnel_options.openvpn.proxy {
- if let openvpn::ProxySettings::Local(local_proxy) = proxy {
- Self::print_local_proxy(&local_proxy)
- } else if let openvpn::ProxySettings::Remote(remote_proxy) = proxy {
- Self::print_remote_proxy(&remote_proxy)
- } else if let openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) = proxy {
- Self::print_shadowsocks_proxy(&shadowsocks_proxy)
- } else {
- unreachable!("unhandled proxy type");
- }
- } else {
- println!("proxy: unset");
- }
- Ok(())
- }
-
- fn print_local_proxy(proxy: &openvpn::LocalProxySettings) {
- println!("proxy: local");
- println!(" local port: {}", proxy.port);
- println!(" peer IP: {}", proxy.peer.ip());
- println!(" peer port: {}", proxy.peer.port());
- }
-
- fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) {
- println!("proxy: remote");
- println!(" server IP: {}", proxy.address.ip());
- println!(" server port: {}", proxy.address.port());
-
- if let Some(ref auth) = proxy.auth {
- println!(" auth username: {}", auth.username);
- println!(" auth password: {}", auth.password);
- } else {
- println!(" auth: none");
- }
- }
-
- fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) {
- println!("proxy: Shadowsocks");
- println!(" peer IP: {}", proxy.peer.ip());
- println!(" peer port: {}", proxy.peer.port());
- println!(" password: {}", proxy.password);
- println!(" cipher: {}", proxy.cipher);
- }
-
- fn process_openvpn_proxy_unset() -> Result<()> {
- let mut rpc = new_rpc_client()?;
- rpc.set_openvpn_proxy(None)?;
- println!("proxy details have been unset");
- Ok(())
- }
-
- fn process_openvpn_proxy_set(matches: &clap::ArgMatches<'_>) -> Result<()> {
- if let Some(args) = matches.subcommand_matches("local") {
- let local_port =
- value_t!(args.value_of("local-port"), u16).unwrap_or_else(|e| e.exit());
- let remote_ip =
- value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
- let remote_port =
- value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
-
- let proxy = openvpn::LocalProxySettings {
- port: local_port,
- peer: SocketAddr::new(remote_ip, remote_port),
- };
-
- let packed_proxy = openvpn::ProxySettings::Local(proxy);
-
- if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
- panic!(error);
- }
-
- let mut rpc = new_rpc_client()?;
- rpc.set_openvpn_proxy(Some(packed_proxy))?;
- } else if let Some(args) = matches.subcommand_matches("remote") {
- let remote_ip =
- value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
- let remote_port =
- value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
- let username = args.value_of("username");
- let password = args.value_of("password");
-
- let auth = match (username, password) {
- (Some(username), Some(password)) => Some(openvpn::ProxyAuth {
- username: username.to_string(),
- password: password.to_string(),
- }),
- _ => 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 = new_rpc_client()?;
- rpc.set_openvpn_proxy(Some(packed_proxy))?;
- } else if let Some(args) = matches.subcommand_matches("shadowsocks") {
- let remote_ip =
- value_t!(args.value_of("remote-ip"), IpAddr).unwrap_or_else(|e| e.exit());
- let remote_port =
- value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit());
- let password = args.value_of("password").unwrap().to_string();
- let cipher = args.value_of("cipher").unwrap().to_string();
-
- let proxy = openvpn::ShadowsocksProxySettings {
- peer: SocketAddr::new(remote_ip, remote_port),
- password,
- cipher,
- };
-
- let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy);
-
- if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
- panic!(error);
- }
-
- let mut rpc = new_rpc_client()?;
- rpc.set_openvpn_proxy(Some(packed_proxy))?;
- } else {
- unreachable!("unhandled proxy type");
- }
-
- println!("proxy details have been updated");
- println!("note: The OpenVPN tunnel constraints have been updated to use TCP");
- Ok(())
- }
-
fn process_ipv6_get() -> Result<()> {
let tunnel_options = Self::get_tunnel_options()?;
println!(
diff --git a/mullvad-cli/src/location.rs b/mullvad-cli/src/location.rs
new file mode 100644
index 0000000000..10b27b61d8
--- /dev/null
+++ b/mullvad-cli/src/location.rs
@@ -0,0 +1,69 @@
+use mullvad_types::relay_constraints::{Constraint, LocationConstraint};
+
+pub fn get_subcommand() -> clap::App<'static, 'static> {
+ clap::SubCommand::with_name("location")
+ .arg(
+ clap::Arg::with_name("country")
+ .help("The two letter country code, or 'any' for no preference.")
+ .required(true)
+ .index(1)
+ .validator(country_code_validator),
+ )
+ .arg(
+ clap::Arg::with_name("city")
+ .help("The three letter city code")
+ .index(2)
+ .validator(city_code_validator),
+ )
+ .arg(
+ clap::Arg::with_name("hostname")
+ .help("The hostname")
+ .index(3),
+ )
+}
+
+pub fn get_constraint(matches: &clap::ArgMatches<'_>) -> Constraint<LocationConstraint> {
+ let country = matches.value_of("country").unwrap();
+ let city = matches.value_of("city");
+ let hostname = matches.value_of("hostname");
+
+ match (country, city, hostname) {
+ ("any", None, None) => Constraint::Any,
+ ("any", ..) => clap::Error::with_description(
+ "City can't be given when selecting 'any' country",
+ clap::ErrorKind::InvalidValue,
+ )
+ .exit(),
+ (country, None, None) => Constraint::Only(LocationConstraint::Country(country.to_owned())),
+ (country, Some(city), None) => Constraint::Only(LocationConstraint::City(
+ country.to_owned(),
+ city.to_owned(),
+ )),
+ (country, Some(city), Some(hostname)) => Constraint::Only(LocationConstraint::Hostname(
+ country.to_owned(),
+ city.to_owned(),
+ hostname.to_owned(),
+ )),
+ (..) => clap::Error::with_description(
+ "Invalid country, city and hostname combination given",
+ clap::ErrorKind::InvalidValue,
+ )
+ .exit(),
+ }
+}
+
+fn country_code_validator(code: String) -> ::std::result::Result<(), String> {
+ if code.len() == 2 || code == "any" {
+ Ok(())
+ } else {
+ Err(String::from("Country codes must be two letters, or 'any'."))
+ }
+}
+
+fn city_code_validator(code: String) -> ::std::result::Result<(), String> {
+ if code.len() == 3 {
+ Ok(())
+ } else {
+ Err(String::from("City codes must be three letters"))
+ }
+}
diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs
index 3dc1879fb8..4abe16224c 100644
--- a/mullvad-cli/src/main.rs
+++ b/mullvad-cli/src/main.rs
@@ -14,6 +14,7 @@ use std::io;
use talpid_types::ErrorExt;
mod cmds;
+mod location;
pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt"));