diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-06-16 15:13:32 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-08-20 14:41:41 +0200 |
| commit | 8cf02b29a718a7856c80323ee0cf496b9ee24648 (patch) | |
| tree | 16f378f2cc3d3101d01d58435bf54824e57683a6 /mullvad-cli/src/cmds | |
| parent | c2e9303cc7aff29df7941fc08df19b8ffcffa48f (diff) | |
| download | mullvadvpn-8cf02b29a718a7856c80323ee0cf496b9ee24648.tar.xz mullvadvpn-8cf02b29a718a7856c80323ee0cf496b9ee24648.zip | |
Use gRPC for management interface in backend and CLI
Diffstat (limited to 'mullvad-cli/src/cmds')
| -rw-r--r-- | mullvad-cli/src/cmds/account.rs | 82 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/auto_connect.rs | 21 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/beta_program.rs | 13 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/block_when_disconnected.rs | 26 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/bridge.rs | 218 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/connect.rs | 9 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/disconnect.rs | 9 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/lan.rs | 21 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/reconnect.rs | 9 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 387 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/reset.rs | 9 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/split_tunnel/linux.rs | 32 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 252 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/tunnel.rs | 206 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/version.rs | 20 |
15 files changed, 840 insertions, 474 deletions
diff --git a/mullvad-cli/src/cmds/account.rs b/mullvad-cli/src/cmds/account.rs index e35699605c..a0a223eaf2 100644 --- a/mullvad-cli/src/cmds/account.rs +++ b/mullvad-cli/src/cmds/account.rs @@ -1,9 +1,10 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Error, Result}; use clap::value_t_or_exit; use mullvad_types::account::{AccountToken, VoucherError}; pub struct Account; +#[async_trait::async_trait] impl Command for Account { fn name(&self) -> &'static str { "account" @@ -49,21 +50,21 @@ impl Command for Account { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let token = value_t_or_exit!(set_matches.value_of("token"), String); - self.set(Some(token)) + self.set(Some(token)).await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else if let Some(_matches) = matches.subcommand_matches("unset") { - self.set(None) + self.set(None).await } else if let Some(_matches) = matches.subcommand_matches("clear-history") { - self.clear_history() + self.clear_history().await } else if let Some(_matches) = matches.subcommand_matches("create") { - self.create() + self.create().await } else if let Some(matches) = matches.subcommand_matches("redeem") { let voucher = value_t_or_exit!(matches.value_of("voucher"), String); - self.redeem_voucher(voucher) + self.redeem_voucher(voucher).await } else { unreachable!("No account command given"); } @@ -71,9 +72,9 @@ impl Command for Account { } impl Account { - fn set(&self, token: Option<AccountToken>) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_account(token.clone())?; + async fn set(&self, token: Option<AccountToken>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_account(token.clone().unwrap_or_default()).await?; if let Some(token) = token { println!("Mullvad account \"{}\" set", token); } else { @@ -82,45 +83,55 @@ impl Account { Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let settings = rpc.get_settings()?; - if let Some(account_token) = settings.get_account_token() { - println!("Mullvad account: {}", account_token); - let expiry = rpc.get_account_data(account_token)?; - println!("Expires at : {}", expiry.expiry); + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + if settings.account_token != "" { + println!("Mullvad account: {}", settings.account_token); + let expiry = rpc + .get_account_data(settings.account_token) + .await? + .into_inner(); + println!( + "Expires at : {}", + Self::format_expiry(&expiry.expiry.unwrap()) + ); } else { println!("No account configured"); } Ok(()) } - fn create(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.create_new_account()?; + async fn create(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.create_new_account(()).await?; println!("New account created!"); - self.get() + self.get().await } - fn redeem_voucher(&self, mut voucher: String) -> Result<()> { - let mut rpc = new_rpc_client()?; + async fn redeem_voucher(&self, mut voucher: String) -> Result<()> { + let mut rpc = new_grpc_client().await?; voucher.retain(|c| c.is_alphanumeric()); - match rpc.submit_voucher(voucher) { + match rpc.submit_voucher(voucher).await { Ok(submission) => { + let submission = submission.into_inner(); println!( "Added {} to the account", - Self::format_duration(submission.time_added) + Self::format_duration(submission.seconds_added) + ); + println!( + "New expiry date: {}", + Self::format_expiry(&submission.new_expiry.unwrap()) ); - println!("New expiry date: {}", submission.new_expiry); Ok(()) } Err(err) => { eprintln!( "Failed to submit voucher.\n{}", - VoucherError::from_rpc_error_code(Self::get_redeem_rpc_error_code(&err)) + VoucherError::from_rpc_error_code(err.code() as i64) ); - Err(err.into()) + Err(Error::GrpcClientError(err)) } } } @@ -138,16 +149,13 @@ impl Account { } } - fn get_redeem_rpc_error_code(error: &mullvad_ipc_client::Error) -> i64 { - match error.kind() { - mullvad_ipc_client::ErrorKind::JsonRpcError(ref rpc_error) => rpc_error.code.code(), - _ => 0, - } + fn format_expiry(expiry: &prost_types::Timestamp) -> String { + chrono::NaiveDateTime::from_timestamp(expiry.seconds, expiry.nanos as u32).to_string() } - fn clear_history(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.clear_account_history()?; + async fn clear_history(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.clear_account_history(()).await?; println!("Removed account history and all associated keys"); Ok(()) } diff --git a/mullvad-cli/src/cmds/auto_connect.rs b/mullvad-cli/src/cmds/auto_connect.rs index 385a2de7b6..e934e8e37c 100644 --- a/mullvad-cli/src/cmds/auto_connect.rs +++ b/mullvad-cli/src/cmds/auto_connect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct AutoConnect; +#[async_trait::async_trait] impl Command for AutoConnect { fn name(&self) -> &'static str { "auto-connect" @@ -27,12 +28,12 @@ impl Command for AutoConnect { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let auto_connect = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(auto_connect == "on") + self.set(auto_connect == "on").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No auto-connect command given"); } @@ -40,16 +41,16 @@ impl Command for AutoConnect { } impl AutoConnect { - fn set(&self, auto_connect: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_auto_connect(auto_connect)?; + async fn set(&self, auto_connect: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_auto_connect(auto_connect).await?; println!("Changed auto-connect sharing setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let auto_connect = rpc.get_settings()?.auto_connect; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let auto_connect = rpc.get_settings(()).await?.into_inner().auto_connect; println!("Autoconnect: {}", if auto_connect { "on" } else { "off" }); Ok(()) } diff --git a/mullvad-cli/src/cmds/beta_program.rs b/mullvad-cli/src/cmds/beta_program.rs index c4cbe6cf21..3950d54433 100644 --- a/mullvad-cli/src/cmds/beta_program.rs +++ b/mullvad-cli/src/cmds/beta_program.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Error, Result, PRODUCT_VERSION}; +use crate::{new_grpc_client, Command, Error, Result, PRODUCT_VERSION}; use clap::value_t_or_exit; pub struct BetaProgram; +#[async_trait::async_trait] impl Command for BetaProgram { fn name(&self) -> &'static str { "beta-program" @@ -24,11 +25,11 @@ impl Command for BetaProgram { .subcommand(clap::SubCommand::with_name("get").about("Get beta notifications setting")) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("get", Some(_)) => { - let mut rpc = new_rpc_client()?; - let settings = rpc.get_settings()?; + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); let enabled_str = if settings.show_beta_releases { "on" } else { @@ -47,8 +48,8 @@ impl Command for BetaProgram { )); } - let mut rpc = new_rpc_client()?; - rpc.set_show_beta_releases(enable)?; + let mut rpc = new_grpc_client().await?; + rpc.set_show_beta_releases(enable).await?; println!("Beta program: {}", enable_str); Ok(()) diff --git a/mullvad-cli/src/cmds/block_when_disconnected.rs b/mullvad-cli/src/cmds/block_when_disconnected.rs index 57239963d7..b66acf168d 100644 --- a/mullvad-cli/src/cmds/block_when_disconnected.rs +++ b/mullvad-cli/src/cmds/block_when_disconnected.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct BlockWhenDisconnected; +#[async_trait::async_trait] impl Command for BlockWhenDisconnected { fn name(&self) -> &'static str { "always-require-vpn" @@ -27,12 +28,12 @@ impl Command for BlockWhenDisconnected { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let block_when_disconnected = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(block_when_disconnected == "on") + self.set(block_when_disconnected == "on").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No block-when-disconnected command given"); } @@ -40,16 +41,21 @@ impl Command for BlockWhenDisconnected { } impl BlockWhenDisconnected { - fn set(&self, block_when_disconnected: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_block_when_disconnected(block_when_disconnected)?; + async fn set(&self, block_when_disconnected: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_block_when_disconnected(block_when_disconnected) + .await?; println!("Changed always require VPN setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let block_when_disconnected = rpc.get_settings()?.block_when_disconnected; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let block_when_disconnected = rpc + .get_settings(()) + .await? + .into_inner() + .block_when_disconnected; println!( "Network traffic will be {} when the VPN is disconnected", if block_when_disconnected { diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs index 0e5c339744..7ad4cf1b22 100644 --- a/mullvad-cli/src/cmds/bridge.rs +++ b/mullvad-cli/src/cmds/bridge.rs @@ -1,13 +1,18 @@ -use crate::{location, new_rpc_client, Command, Result}; +use crate::{location, new_grpc_client, Command, Result}; use clap::value_t; -use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings, BridgeState}; -use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS}; +use crate::proto::{ + bridge_settings::{Type as BridgeSettingsType, *}, + bridge_state::State as BridgeStateType, + BridgeSettings, BridgeState, +}; +use talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS; use std::net::{IpAddr, SocketAddr}; pub struct Bridge; +#[async_trait::async_trait] impl Command for Bridge { fn name(&self) -> &'static str { "bridge" @@ -24,11 +29,11 @@ impl Command for Bridge { .subcommand(clap::SubCommand::with_name("list").about("List bridge relays")) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async 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(), + ("set", Some(set_matches)) => Self::handle_set(set_matches).await, + ("get", _) => Self::handle_get().await, + ("list", _) => Self::list_bridge_relays().await, _ => unreachable!("unhandled command"), } } @@ -145,64 +150,66 @@ fn create_set_state_subcommand() -> clap::App<'static, 'static> { } impl Bridge { - fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_set(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("location", Some(location_matches)) => { - Self::handle_set_bridge_location(location_matches) + Self::handle_set_bridge_location(location_matches).await } ("custom", Some(custom_matches)) => { - Self::handle_bridge_set_custom_settings(custom_matches) + Self::handle_bridge_set_custom_settings(custom_matches).await } - ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches), + ("state", Some(set_matches)) => Self::handle_set_bridge_state(set_matches).await, _ => 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.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) - } - }; + async fn handle_get() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let settings = rpc.get_settings(()).await?.into_inner(); + Self::print_state(settings.bridge_state.unwrap()); + match settings.bridge_settings.unwrap().r#type.unwrap() { + BridgeSettingsType::Local(local_proxy) => Self::print_local_proxy(&local_proxy), + BridgeSettingsType::Remote(remote_proxy) => Self::print_remote_proxy(&remote_proxy), + BridgeSettingsType::Shadowsocks(shadowsocks_proxy) => { + Self::print_shadowsocks_proxy(&shadowsocks_proxy) } - BridgeSettings::Normal(constraints) => { - println!("Bridge constraints: {}", constraints); + BridgeSettingsType::Normal(constraints) => { + println!( + "Bridge constraints - {}", + location::format_location(constraints.location.as_ref()) + ); } }; 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 }))?; + async fn handle_set_bridge_location(matches: &clap::ArgMatches<'_>) -> Result<()> { + let constraints = location::get_constraint(matches); + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Normal(BridgeConstraints { + location: Some(constraints), + })), + }) + .await?; Ok(()) } - fn handle_set_bridge_state(matches: &clap::ArgMatches<'_>) -> Result<()> { + async 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, + "auto" => BridgeStateType::Auto as i32, + "on" => BridgeStateType::On as i32, + "off" => BridgeStateType::Off as i32, _ => unreachable!(), }; - let mut rpc = new_rpc_client()?; - rpc.set_bridge_state(state)?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_state(BridgeState { state }).await?; Ok(()) } - fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_bridge_set_custom_settings(matches: &clap::ArgMatches<'_>) -> Result<()> { + use talpid_types::net::openvpn; + 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()); @@ -211,19 +218,24 @@ impl Bridge { let remote_port = value_t!(args.value_of("remote-port"), u16).unwrap_or_else(|e| e.exit()); - let proxy = openvpn::LocalProxySettings { + let local_proxy = openvpn::LocalProxySettings { port: local_port, peer: SocketAddr::new(remote_ip, remote_port), }; - - let packed_proxy = openvpn::ProxySettings::Local(proxy); - + let prost_proxy = LocalProxySettings { + port: local_proxy.port as u32, + peer: local_proxy.peer.to_string(), + }; + let packed_proxy = openvpn::ProxySettings::Local(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))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Local(prost_proxy)), + }) + .await?; } 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()); @@ -239,20 +251,30 @@ impl Bridge { }), _ => None, }; + let prost_auth = auth.clone().map(|auth| RemoteProxyAuth { + username: auth.username.clone(), + password: auth.password.clone(), + }); let proxy = openvpn::RemoteProxySettings { address: SocketAddr::new(remote_ip, remote_port), auth, }; + let prost_proxy = RemoteProxySettings { + address: proxy.address.to_string(), + auth: prost_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))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Remote(prost_proxy)), + }) + .await?; } 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()); @@ -266,15 +288,22 @@ impl Bridge { password, cipher, }; + let prost_proxy = ShadowsocksProxySettings { + peer: proxy.peer.to_string(), + password: proxy.password.clone(), + cipher: proxy.cipher.clone(), + }; 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))?; + let mut rpc = new_grpc_client().await?; + rpc.set_bridge_settings(BridgeSettings { + r#type: Some(BridgeSettingsType::Shadowsocks(prost_proxy)), + }) + .await?; } else { unreachable!("unhandled proxy type"); } @@ -283,17 +312,24 @@ impl Bridge { Ok(()) } - fn print_local_proxy(proxy: &openvpn::LocalProxySettings) { + fn print_state(state: BridgeState) { + let state = match BridgeStateType::from_i32(state.state).expect("unknown bridge state") { + BridgeStateType::Auto => "auto", + BridgeStateType::On => "on", + BridgeStateType::Off => "off", + }; + println!("Bridge state - {}", state); + } + + fn print_local_proxy(proxy: &LocalProxySettings) { println!("proxy: local"); println!(" local port: {}", proxy.port); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); + println!(" peer address: {}", proxy.peer); } - fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) { + fn print_remote_proxy(proxy: &RemoteProxySettings) { println!("proxy: remote"); - println!(" server IP: {}", proxy.address.ip()); - println!(" server port: {}", proxy.address.port()); + println!(" server address: {}", proxy.address); if let Some(ref auth) = proxy.auth { println!(" auth username: {}", auth.username); @@ -303,47 +339,43 @@ impl Bridge { } } - fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) { + fn print_shadowsocks_proxy(proxy: &ShadowsocksProxySettings) { println!("proxy: Shadowsocks"); - println!(" peer IP: {}", proxy.peer.ip()); - println!(" peer port: {}", proxy.peer.port()); + println!(" peer address: {}", proxy.peer); 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()?; + async fn list_bridge_relays() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let mut locations = rpc.get_relay_locations(()).await?.into_inner(); - 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.active && !relay.bridges.is_empty()); - if !city.relays.is_empty() { - Some(city) - } else { - None - } - }) - .collect(); - if !country.cities.is_empty() { - Some(country) - } else { - None - } - }) - .collect(); + let mut countries = Vec::new(); + + while let Some(mut country) = locations.message().await? { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays.retain(|relay| { + relay.active + && relay.bridges.is_some() + && !relay.bridges.as_ref().unwrap().shadowsocks.is_empty() + }); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + countries.push(country); + } + } - locations - .countries - .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); - for mut country in locations.countries { + countries.sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); + for mut country in countries { country .cities .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); diff --git a/mullvad-cli/src/cmds/connect.rs b/mullvad-cli/src/cmds/connect.rs index 89cc0f2d2e..899a450547 100644 --- a/mullvad-cli/src/cmds/connect.rs +++ b/mullvad-cli/src/cmds/connect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use talpid_types::ErrorExt; pub struct Connect; +#[async_trait::async_trait] impl Command for Connect { fn name(&self) -> &'static str { "connect" @@ -13,9 +14,9 @@ impl Command for Connect { .about("Command the client to start establishing a VPN tunnel") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - if let Err(e) = rpc.connect() { + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + if let Err(e) = rpc.connect_tunnel(()).await { eprintln!("{}", e.display_chain()); } Ok(()) diff --git a/mullvad-cli/src/cmds/disconnect.rs b/mullvad-cli/src/cmds/disconnect.rs index bf85f80310..a3d6698fc9 100644 --- a/mullvad-cli/src/cmds/disconnect.rs +++ b/mullvad-cli/src/cmds/disconnect.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; pub struct Disconnect; +#[async_trait::async_trait] impl Command for Disconnect { fn name(&self) -> &'static str { "disconnect" @@ -12,9 +13,9 @@ impl Command for Disconnect { .about("Command the client to disconnect the VPN tunnel") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.disconnect()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.disconnect_tunnel(()).await?; Ok(()) } } diff --git a/mullvad-cli/src/cmds/lan.rs b/mullvad-cli/src/cmds/lan.rs index 15d30cde50..05f4f867d2 100644 --- a/mullvad-cli/src/cmds/lan.rs +++ b/mullvad-cli/src/cmds/lan.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct Lan; +#[async_trait::async_trait] impl Command for Lan { fn name(&self) -> &'static str { "lan" @@ -27,12 +28,12 @@ impl Command for Lan { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { let allow_lan = value_t_or_exit!(set_matches.value_of("policy"), String); - self.set(allow_lan == "allow") + self.set(allow_lan == "allow").await } else if let Some(_matches) = matches.subcommand_matches("get") { - self.get() + self.get().await } else { unreachable!("No lan command given"); } @@ -40,16 +41,16 @@ impl Command for Lan { } impl Lan { - fn set(&self, allow_lan: bool) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_allow_lan(allow_lan)?; + async fn set(&self, allow_lan: bool) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_allow_lan(allow_lan).await?; println!("Changed local network sharing setting"); Ok(()) } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let allow_lan = rpc.get_settings()?.allow_lan; + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let allow_lan = rpc.get_settings(()).await?.into_inner().allow_lan; println!( "Local network sharing setting: {}", if allow_lan { "allow" } else { "block" } diff --git a/mullvad-cli/src/cmds/reconnect.rs b/mullvad-cli/src/cmds/reconnect.rs index 0cc7f6bfea..d281266e11 100644 --- a/mullvad-cli/src/cmds/reconnect.rs +++ b/mullvad-cli/src/cmds/reconnect.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use talpid_types::ErrorExt; pub struct Reconnect; +#[async_trait::async_trait] impl Command for Reconnect { fn name(&self) -> &'static str { "reconnect" @@ -12,9 +13,9 @@ impl Command for Reconnect { clap::SubCommand::with_name(self.name()).about("Command the client to reconnect") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - if let Err(e) = rpc.reconnect() { + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + if let Err(e) = rpc.reconnect_tunnel(()).await { eprintln!("{}", e.display_chain()); } Ok(()) diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index c4e8c72c81..07d07fc447 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -1,4 +1,4 @@ -use crate::{location, new_rpc_client, Command, Error, Result}; +use crate::{location, new_grpc_client, proto, Command, Error, Result}; use clap::{value_t, values_t}; use std::{ io::{self, BufRead}, @@ -6,19 +6,18 @@ use std::{ str::FromStr, }; -use mullvad_types::{ - relay_constraints::{ - Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettingsUpdate, - WireguardConstraints, - }, - ConnectionConfig, CustomTunnelEndpoint, -}; -use talpid_types::net::{ - all_of_the_internet, openvpn, wireguard, Endpoint, TransportProtocol, TunnelType, +use mullvad_types::relay_constraints::Constraint; +use proto::{ + connection_config::{self, OpenvpnConfig, WireguardConfig}, + relay_settings, relay_settings_update, ConnectionConfig, CustomRelaySettings, + NormalRelaySettingsUpdate, OpenvpnConstraints, RelaySettingsUpdate, TransportProtocol, + TunnelType, TunnelTypeUpdate, WireguardConstraints, }; +use talpid_types::net::all_of_the_internet; pub struct Relay; +#[async_trait::async_trait] impl Command for Relay { fn name(&self) -> &'static str { "relay" @@ -155,15 +154,15 @@ impl Command for Relay { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(set_matches) = matches.subcommand_matches("set") { - self.set(set_matches) + self.set(set_matches).await } else if matches.subcommand_matches("get").is_some() { - self.get() + self.get().await } else if matches.subcommand_matches("list").is_some() { - self.list() + self.list().await } else if matches.subcommand_matches("update").is_some() { - self.update() + self.update().await } else { unreachable!("No relay command given"); } @@ -171,54 +170,73 @@ impl Command for Relay { } impl Relay { - fn update_constraints(&self, update: RelaySettingsUpdate) -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.update_relay_settings(update)?; + async fn update_constraints(&self, update: RelaySettingsUpdate) -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.update_relay_settings(update).await?; println!("Relay constraints updated"); Ok(()) } - fn set(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { if let Some(custom_matches) = matches.subcommand_matches("custom") { - self.set_custom(custom_matches) + self.set_custom(custom_matches).await } else if let Some(location_matches) = matches.subcommand_matches("location") { - self.set_location(location_matches) + self.set_location(location_matches).await } else if let Some(tunnel_matches) = matches.subcommand_matches("tunnel") { - self.set_tunnel(tunnel_matches) + self.set_tunnel(tunnel_matches).await } else if let Some(tunnel_matches) = matches.subcommand_matches("tunnel-protocol") { - self.set_tunnel_protocol(tunnel_matches) + self.set_tunnel_protocol(tunnel_matches).await } else { unreachable!("No set relay command given"); } } - fn set_custom(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_custom(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let custom_endpoint = match matches.subcommand() { ("openvpn", Some(openvpn_matches)) => Self::read_custom_openvpn_relay(openvpn_matches), ("wireguard", Some(wg_matches)) => Self::read_custom_wireguard_relay(wg_matches), (_unknown_tunnel, _) => unreachable!("No set relay command given"), }; - self.update_constraints(RelaySettingsUpdate::CustomTunnelEndpoint(custom_endpoint)) + + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Custom(custom_endpoint)), + }) + .await } - fn read_custom_openvpn_relay(matches: &clap::ArgMatches<'_>) -> CustomTunnelEndpoint { + fn read_custom_openvpn_relay(matches: &clap::ArgMatches<'_>) -> CustomRelaySettings { let host = value_t!(matches.value_of("host"), String).unwrap_or_else(|e| e.exit()); let port = value_t!(matches.value_of("port"), u16).unwrap_or_else(|e| e.exit()); let username = value_t!(matches.value_of("username"), String).unwrap_or_else(|e| e.exit()); let password = value_t!(matches.value_of("password"), String).unwrap_or_else(|e| e.exit()); - let protocol = - value_t!(matches.value_of("protocol"), TransportProtocol).unwrap_or_else(|e| e.exit()); - CustomTunnelEndpoint::new( + let protocol = value_t!(matches.value_of("protocol"), String).unwrap_or_else(|e| e.exit()); + + let protocol = match protocol.as_str() { + "udp" => TransportProtocol::Udp, + "tcp" => TransportProtocol::Tcp, + _ => clap::Error::with_description( + "unknown transport protocol", + clap::ErrorKind::ValueValidation, + ) + .exit(), + }; + + CustomRelaySettings { host, - ConnectionConfig::OpenVpn(openvpn::ConnectionConfig { - endpoint: Endpoint::new(Ipv4Addr::UNSPECIFIED, port, protocol), - username, - password, + config: Some(ConnectionConfig { + config: Some(connection_config::Config::Openvpn(OpenvpnConfig { + address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port).to_string(), + protocol: protocol as i32, + username, + password, + })), }), - ) + } } - fn read_custom_wireguard_relay(matches: &clap::ArgMatches<'_>) -> CustomTunnelEndpoint { + fn read_custom_wireguard_relay(matches: &clap::ArgMatches<'_>) -> CustomRelaySettings { + use connection_config::wireguard_config; + let host = value_t!(matches.value_of("host"), String).unwrap_or_else(|e| e.exit()); let port = value_t!(matches.value_of("port"), u16).unwrap_or_else(|e| e.exit()); let addresses = values_t!(matches.values_of("addr"), IpAddr).unwrap_or_else(|e| e.exit()); @@ -239,26 +257,37 @@ impl Relay { if private_key_str.trim().is_empty() { eprintln!("Expected to read private key from standard input"); } - let private_key = Self::validate_wireguard_key(&private_key_str).into(); - let peer_public_key = Self::validate_wireguard_key(&peer_key_str).into(); - + let private_key = Self::validate_wireguard_key(&private_key_str); + let peer_public_key = Self::validate_wireguard_key(&peer_key_str); - CustomTunnelEndpoint::new( + CustomRelaySettings { host, - ConnectionConfig::Wireguard(wireguard::ConnectionConfig { - tunnel: wireguard::TunnelConfig { - private_key, - addresses, - }, - peer: wireguard::PeerConfig { - public_key: peer_public_key, - allowed_ips: all_of_the_internet(), - endpoint: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port), - }, - ipv4_gateway, - ipv6_gateway, + config: Some(ConnectionConfig { + config: Some(connection_config::Config::Wireguard(WireguardConfig { + tunnel: Some(wireguard_config::TunnelConfig { + private_key: private_key.to_vec(), + addresses: addresses + .iter() + .map(|address| address.to_string()) + .collect(), + }), + peer: Some(wireguard_config::PeerConfig { + public_key: peer_public_key.to_vec(), + allowed_ips: all_of_the_internet() + .iter() + .map(|address| address.to_string()) + .collect(), + endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port) + .to_string(), + }), + ipv4_gateway: ipv4_gateway.to_string(), + ipv6_gateway: ipv6_gateway + .as_ref() + .map(|addr| addr.to_string()) + .unwrap_or_default(), + })), }), - ) + } } fn validate_wireguard_key(key_str: &str) -> [u8; 32] { @@ -280,16 +309,21 @@ impl Relay { key } - fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_location(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let location_constraint = location::get_constraint(matches); - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: Some(location_constraint), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + location: Some(location_constraint), + ..Default::default() + }, + )), + }) + .await } - fn set_tunnel(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn set_tunnel(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let vpn_protocol = matches.value_of("vpn protocol").unwrap(); let port = parse_port_constraint(matches.value_of("port").unwrap())?; let protocol = parse_protocol_constraint(matches.value_of("transport protocol").unwrap()); @@ -299,79 +333,157 @@ impl Relay { if let Constraint::Only(TransportProtocol::Tcp) = protocol { return Err(Error::InvalidCommand("WireGuard does not support TCP")); } - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: None, - tunnel_protocol: None, - wireguard_constraints: Some(WireguardConstraints { port }), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + wireguard_constraints: Some(WireguardConstraints { + port: port.unwrap_or(0) as u32, + }), + ..Default::default() + }, + )), + }) + .await } "openvpn" => { - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - location: None, - tunnel_protocol: None, - openvpn_constraints: Some(OpenVpnConstraints { port, protocol }), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + openvpn_constraints: Some(OpenvpnConstraints { + port: port.unwrap_or(0) as u32, + protocol: protocol.unwrap_or(TransportProtocol::AnyProtocol) as i32, + }), + ..Default::default() + }, + )), + }) + .await } _ => unreachable!(), } } - fn set_tunnel_protocol(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let tunnel_protocol = match matches.value_of("tunnel protocol").unwrap() { - "wireguard" => Constraint::Only(TunnelType::Wireguard), - "openvpn" => Constraint::Only(TunnelType::OpenVpn), - "any" => Constraint::Any, + async fn set_tunnel_protocol(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + let tunnel_type = match matches.value_of("tunnel protocol").unwrap() { + "wireguard" => TunnelType::Wireguard, + "openvpn" => TunnelType::Openvpn, + "any" => TunnelType::AnyTunnel, _ => unreachable!(), }; - self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate { - tunnel_protocol: Some(tunnel_protocol), - ..Default::default() - })) + self.update_constraints(RelaySettingsUpdate { + r#type: Some(relay_settings_update::Type::Normal( + NormalRelaySettingsUpdate { + tunnel_type: Some(TunnelTypeUpdate { + tunnel_type: tunnel_type as i32, + }), + ..Default::default() + }, + )), + }) + .await } - fn get(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let constraints = rpc.get_settings()?.get_relay_settings(); - println!("Current constraints: {}", constraints); + async fn get(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let constraints = rpc + .get_settings(()) + .await? + .into_inner() + .relay_settings + .unwrap(); + + print!("Current constraints: "); + + match constraints.endpoint.unwrap() { + relay_settings::Endpoint::Normal(settings) => { + match TunnelType::from_i32(settings.tunnel_type).expect("unknown tunnel type") { + TunnelType::AnyTunnel => { + println!( + "Any tunnel protocol with OpenVPN over {} and WireGuard over {} in {}", + Self::format_openvpn_constraints(settings.openvpn_constraints.as_ref()), + Self::format_wireguard_constraints( + settings.wireguard_constraints.as_ref() + ), + location::format_location(settings.location.as_ref()) + ); + } + TunnelType::Wireguard => { + println!( + "WireGuard over {} in {}", + Self::format_wireguard_constraints( + settings.wireguard_constraints.as_ref() + ), + location::format_location(settings.location.as_ref()) + ); + } + TunnelType::Openvpn => { + println!( + "OpenVPN over {} in {}", + Self::format_openvpn_constraints(settings.openvpn_constraints.as_ref()), + location::format_location(settings.location.as_ref()) + ); + } + } + } + + relay_settings::Endpoint::Custom(settings) => { + let config = settings.config.unwrap(); + match config.config.unwrap() { + connection_config::Config::Openvpn(config) => { + println!( + "custom OpenVPN relay - {} {}", + config.address, + Self::format_transport_protocol( + TransportProtocol::from_i32(config.protocol).unwrap() + ), + ); + } + connection_config::Config::Wireguard(config) => { + let peer = config.peer.unwrap(); + println!( + "custom WireGuard relay - {} with public key {}", + peer.endpoint, + base64::encode(&peer.public_key), + ); + } + } + } + } Ok(()) } - fn list(&self) -> Result<()> { - let mut rpc = new_rpc_client()?; - let mut locations = rpc.get_relay_locations()?; + async fn list(&self) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let mut locations = rpc.get_relay_locations(()).await?.into_inner(); - 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.active && !relay.tunnels.is_empty()); - if !city.relays.is_empty() { - Some(city) - } else { - None - } - }) - .collect(); - if !country.cities.is_empty() { - Some(country) - } else { - None - } - }) - .collect(); + let mut countries = Vec::new(); - locations - .countries - .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); - for mut country in locations.countries { + while let Some(mut country) = locations.message().await? { + country.cities = country + .cities + .into_iter() + .filter_map(|mut city| { + city.relays.retain(|relay| { + relay.active + && relay.tunnels.is_some() + && !(relay.tunnels.as_ref().unwrap().openvpn.is_empty() + && relay.tunnels.as_ref().unwrap().wireguard.is_empty()) + }); + if !city.relays.is_empty() { + Some(city) + } else { + None + } + }) + .collect(); + if !country.cities.is_empty() { + countries.push(country); + } + } + + countries.sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); + for mut country in countries { country .cities .sort_by(|c1, c2| natord::compare_ignore_case(&c1.name, &c2.name)); @@ -384,8 +496,9 @@ impl Relay { city.name, city.code, city.latitude, city.longitude ); for relay in &city.relays { - let supports_openvpn = !relay.tunnels.openvpn.is_empty(); - let supports_wireguard = !relay.tunnels.wireguard.is_empty(); + let tunnels = relay.tunnels.as_ref().unwrap(); + let supports_openvpn = !tunnels.openvpn.is_empty(); + let supports_wireguard = !tunnels.wireguard.is_empty(); let support_msg = match (supports_openvpn, supports_wireguard) { (true, true) => "OpenVPN and WireGuard", (true, false) => "OpenVPN", @@ -403,11 +516,49 @@ impl Relay { Ok(()) } - fn update(&self) -> Result<()> { - new_rpc_client()?.update_relay_locations()?; + async fn update(&self) -> Result<()> { + new_grpc_client().await?.update_relay_locations(()).await?; println!("Updating relay list in the background..."); Ok(()) } + + fn format_transport_protocol(protocol: TransportProtocol) -> &'static str { + match protocol { + TransportProtocol::AnyProtocol => "any transport protocol", + TransportProtocol::Udp => "UDP", + TransportProtocol::Tcp => "TCP", + } + } + + fn format_port(port: u32) -> String { + if port != 0 { + format!("port {}", port) + } else { + "any port".to_string() + } + } + + fn format_openvpn_constraints(constraints: Option<&OpenvpnConstraints>) -> String { + if let Some(constraints) = constraints { + format!( + "{} over {}", + Self::format_port(constraints.port), + Self::format_transport_protocol( + TransportProtocol::from_i32(constraints.protocol).unwrap() + ) + ) + } else { + "any port over any transport protocol".to_string() + } + } + + fn format_wireguard_constraints(constraints: Option<&WireguardConstraints>) -> String { + if let Some(constraints) = constraints { + Self::format_port(constraints.port) + } else { + "any port".to_string() + } + } } diff --git a/mullvad-cli/src/cmds/reset.rs b/mullvad-cli/src/cmds/reset.rs index d859d1e436..2461e86ad5 100644 --- a/mullvad-cli/src/cmds/reset.rs +++ b/mullvad-cli/src/cmds/reset.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use std::io::stdin; pub struct Reset; +#[async_trait::async_trait] impl Command for Reset { fn name(&self) -> &'static str { "factory-reset" @@ -11,10 +12,10 @@ impl Command for Reset { clap::SubCommand::with_name(self.name()).about("Reset settings, caches and logs") } - fn run(&self, _matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; if Self::receive_confirmation() { - if rpc.factory_reset().is_err() { + if rpc.factory_reset(()).await.is_err() { eprintln!("FAILED TO PERFORM FACTORY RESET"); } else { #[cfg(target_os = "linux")] diff --git a/mullvad-cli/src/cmds/split_tunnel/linux.rs b/mullvad-cli/src/cmds/split_tunnel/linux.rs index 95b172eb6b..5e8a9b9644 100644 --- a/mullvad-cli/src/cmds/split_tunnel/linux.rs +++ b/mullvad-cli/src/cmds/split_tunnel/linux.rs @@ -1,8 +1,9 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; use clap::value_t_or_exit; pub struct SplitTunnel; +#[async_trait::async_trait] impl Command for SplitTunnel { fn name(&self) -> &'static str { "split-tunnel" @@ -15,9 +16,9 @@ impl Command for SplitTunnel { .subcommand(create_pid_subcommand()) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("pid", Some(pid_matches)) => Self::handle_pid_cmd(pid_matches), + ("pid", Some(pid_matches)) => Self::handle_pid_cmd(pid_matches).await, _ => unreachable!("unhandled comand"), } } @@ -38,27 +39,40 @@ fn create_pid_subcommand() -> clap::App<'static, 'static> { } impl SplitTunnel { - fn handle_pid_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_pid_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("add", Some(matches)) => { let pid = value_t_or_exit!(matches.value_of("pid"), i32); - new_rpc_client()?.add_split_tunnel_process(pid)?; + new_grpc_client() + .await? + .add_split_tunnel_process(pid) + .await?; Ok(()) } ("delete", Some(matches)) => { let pid = value_t_or_exit!(matches.value_of("pid"), i32); - new_rpc_client()?.remove_split_tunnel_process(pid)?; + new_grpc_client() + .await? + .remove_split_tunnel_process(pid) + .await?; Ok(()) } ("clear", Some(_)) => { - new_rpc_client()?.clear_split_tunnel_processes()?; + new_grpc_client() + .await? + .clear_split_tunnel_processes(()) + .await?; Ok(()) } ("list", Some(_)) => { - let pids = new_rpc_client()?.get_split_tunnel_processes()?; + let mut pids_stream = new_grpc_client() + .await? + .get_split_tunnel_processes(()) + .await? + .into_inner(); println!("Excluded PIDs:"); - for pid in pids.iter() { + while let Some(pid) = pids_stream.message().await? { println!(" {}", pid); } diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index ffa834134e..ec05492c46 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -1,11 +1,19 @@ -use crate::{new_rpc_client, Command, Error, Result}; -use futures::{Future, Stream}; -use mullvad_ipc_client::DaemonRpcClient; -use mullvad_types::{auth_failed::AuthFailed, states::TunnelState, DaemonEvent}; -use talpid_types::tunnel::{ErrorState, ErrorStateCause}; +use crate::{format::print_keygen_event, new_grpc_client, proto, Command, Error, Result}; +use mullvad_types::auth_failed::AuthFailed; +use proto::{ + daemon_event::Event as EventType, + error_state::{ + firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause, + FirewallPolicyError, GenerationError, + }, + management_service_client::ManagementServiceClient, + ErrorState, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType, +}; +use std::fmt::Write; pub struct Status; +#[async_trait::async_trait] impl Command for Status { fn name(&self) -> &'static str { "status" @@ -31,127 +39,237 @@ impl Command for Status { ) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - let state = rpc.get_state()?; + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let state = rpc.get_tunnel_state(()).await?.into_inner(); print_state(&state); if matches.is_present("location") { - print_location(&mut rpc)?; + print_location(&mut rpc).await?; } if let Some(listen_matches) = matches.subcommand_matches("listen") { let verbose = listen_matches.is_present("verbose"); - let subscription = rpc - .daemon_event_subscribe() - .wait() - .map_err(Error::CantSubscribe)?; - for event in subscription.wait() { - match event? { - DaemonEvent::TunnelState(new_state) => { + + let mut events = rpc.events_listen(()).await?.into_inner(); + + while let Some(event) = events.message().await? { + match event.event.unwrap() { + EventType::TunnelState(new_state) => { print_state(&new_state); - use self::TunnelState::*; - match new_state { - Connected { .. } | Disconnected => { + use proto::tunnel_state::State::*; + match new_state.state.unwrap() { + Connected(..) | Disconnected(..) => { if matches.is_present("location") { - print_location(&mut rpc)?; + print_location(&mut rpc).await?; } } _ => {} } } - DaemonEvent::Settings(settings) => { + EventType::Settings(settings) => { if verbose { println!("New settings: {:#?}", settings); } } - DaemonEvent::RelayList(relay_list) => { + EventType::RelayList(relay_list) => { if verbose { println!("New relay list: {:#?}", relay_list); } } - DaemonEvent::AppVersionInfo(app_version_info) => { + EventType::VersionInfo(app_version_info) => { if verbose { println!("New app version info: {:#?}", app_version_info); } } - DaemonEvent::WireguardKey(key_event) => { + EventType::KeyEvent(key_event) => { if verbose { - println!("{}", key_event); + print!("Key event: "); + print_keygen_event(&key_event); } } } } } + Ok(()) } } fn print_state(state: &TunnelState) { - use self::TunnelState::*; + use proto::{tunnel_state, tunnel_state::State::*}; + print!("Tunnel status: "); - match state { - Error(reason) => print_error_state(reason), - Connected { endpoint, .. } => { - println!("Connected to {}", endpoint); + match state.state.as_ref().unwrap() { + Error(error) => print_error_state(error.error_state.as_ref().unwrap()), + Connected(tunnel_state::Connected { relay_info }) => { + let endpoint = relay_info + .as_ref() + .unwrap() + .tunnel_endpoint + .as_ref() + .unwrap(); + println!("Connected to {}", format_endpoint(&endpoint)); } - Connecting { endpoint, .. } => println!("Connecting to {}...", endpoint), - Disconnected => println!("Disconnected"), + Connecting(tunnel_state::Connecting { relay_info }) => { + let endpoint = relay_info + .as_ref() + .unwrap() + .tunnel_endpoint + .as_ref() + .unwrap(); + println!("Connecting to {}...", format_endpoint(&endpoint)); + } + Disconnected(_) => println!("Disconnected"), Disconnecting(_) => println!("Disconnecting..."), } } +fn format_endpoint(endpoint: &TunnelEndpoint) -> String { + let mut out = format!( + "{} {} over {}", + match TunnelType::from_i32(endpoint.tunnel_type).expect("unknown tunnel protocol") { + TunnelType::Wireguard => "WireGuard", + TunnelType::Openvpn => "OpenVPN", + TunnelType::AnyTunnel => panic!("unexpected tunnel protocol"), + }, + endpoint.address, + format_protocol( + TransportProtocol::from_i32(endpoint.protocol).expect("unknown transport protocol") + ), + ); + + if let Some(ref proxy) = endpoint.proxy { + write!( + &mut out, + " via {} {} over {}", + match ProxyType::from_i32(proxy.proxy_type).expect("unknown proxy type") { + ProxyType::Shadowsocks => "Shadowsocks", + ProxyType::Custom => "custom bridge", + }, + proxy.address, + format_protocol( + TransportProtocol::from_i32(proxy.protocol).expect("unknown transport protocol") + ), + ) + .unwrap(); + } + + out +} + fn print_error_state(error_state: &ErrorState) { - if !error_state.is_blocking() { + if !error_state.is_blocking { eprintln!("Mullvad daemon failed to setup firewall rules!"); eprintln!("Deamon cannot block traffic from flowing, non-local traffic will leak"); } - print_blocked_reason(error_state.cause()); -} - -fn print_blocked_reason(reason: &ErrorStateCause) { - match reason { - ErrorStateCause::AuthFailed(ref auth_failure) => { - let auth_failure_str = auth_failure - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("Account authentication failed"); - println!("Blocked: {}", AuthFailed::from(auth_failure_str)); - } - #[cfg(target_os = "linux")] - ErrorStateCause::SetFirewallPolicyError(error) => { + match ErrorStateCause::from_i32(error_state.cause) { + Some(ErrorStateCause::AuthFailed) => { println!( "Blocked: {}", - ErrorStateCause::SetFirewallPolicyError(error.clone()) + AuthFailed::from(error_state.auth_fail_reason.as_ref()) ); + } + #[cfg(target_os = "linux")] + Some(ErrorStateCause::SetFirewallPolicyError) => { + println!("Blocked: {}", error_state_to_string(error_state)); println!("Your kernel might be terribly out of date or missing nftables"); } - other => println!("Blocked: {}", other), + _ => println!("Blocked: {}", error_state_to_string(error_state)), } } -fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> { - let location = match rpc.get_current_location()? { - Some(loc) => loc, - None => { - println!("Location data unavailable"); - return Ok(()); +fn error_state_to_string(error_state: &ErrorState) -> String { + use ErrorStateCause::*; + + let error_str = match ErrorStateCause::from_i32(error_state.cause).expect("unknown error cause") + { + AuthFailed => { + return if error_state.auth_fail_reason.is_empty() { + "Authentication with remote server failed".to_string() + } else { + format!( + "Authentication with remote server failed: {}", + error_state.auth_fail_reason + ) + }; + } + Ipv6Unavailable => "Failed to configure IPv6 because it's disabled in the platform", + SetFirewallPolicyError => { + return policy_error_to_string(error_state.policy_error.as_ref().unwrap()) } + SetDnsError => "Failed to set system DNS server", + StartTunnelError => "Failed to start connection to remote server", + TunnelParameterError => { + return format!( + "Failure to generate tunnel parameters: {}", + tunnel_parameter_error_to_string(error_state.parameter_error) + ); + } + IsOffline => "This device is offline, no tunnels can be established", + TapAdapterProblem => "A problem with the TAP adapter has been detected", + #[cfg(target_os = "android")] + VpnPermissionDenied => "The Android VPN permission was denied when creating the tunnel", + #[cfg(not(target_os = "android"))] + _ => unreachable!("unknown error cause"), }; - if let Some(hostname) = location.hostname { - println!("Relay: {}", hostname); + + error_str.to_string() +} + +fn tunnel_parameter_error_to_string(parameter_error: i32) -> &'static str { + match GenerationError::from_i32(parameter_error).expect("unknown generation error") { + GenerationError::NoMatchingRelay => "Failure to select a matching tunnel relay", + GenerationError::NoMatchingBridgeRelay => "Failure to select a matching bridge relay", + GenerationError::NoWireguardKey => "No wireguard key available", + GenerationError::CustomTunnelHostResolutionError => { + "Can't resolve hostname for custom tunnel host" + } } - if let Some(ipv4) = location.ipv4 { - println!("IPv4: {}", ipv4); +} + +fn policy_error_to_string(policy_error: &FirewallPolicyError) -> String { + let cause = match FirewallPolicyErrorType::from_i32(policy_error.r#type) + .expect("unknown policy error") + { + FirewallPolicyErrorType::Generic => return "Failed to set firewall policy".to_string(), + FirewallPolicyErrorType::Locked => format!( + "An application prevented the firewall policy from being set: {} (pid {})", + policy_error.lock_name, policy_error.lock_pid + ), + }; + format!("Failed to set firewall policy: {}", cause) +} + +async fn print_location( + rpc: &mut ManagementServiceClient<tonic::transport::Channel>, +) -> Result<()> { + let location = rpc.get_current_location(()).await; + let location = match location { + Ok(response) => response.into_inner(), + Err(status) => { + if status.code() == tonic::Code::NotFound { + println!("Location data unavailable"); + return Ok(()); + } else { + return Err(Error::GrpcClientError(status)); + } + } + }; + if !location.hostname.is_empty() { + println!("Relay: {}", location.hostname); + } + if !location.ipv4.is_empty() { + println!("IPv4: {}", location.ipv4); } - if let Some(ipv6) = location.ipv6 { - println!("IPv6: {}", ipv6); + if !location.ipv6.is_empty() { + println!("IPv6: {}", location.ipv6); } print!("Location: "); - if let Some(city) = location.city { - print!("{}, ", city); + if !location.city.is_empty() { + print!("{}, ", location.city); } println!("{}", location.country); @@ -161,3 +279,11 @@ fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> { ); Ok(()) } + +fn format_protocol(protocol: TransportProtocol) -> &'static str { + match protocol { + TransportProtocol::Udp => "UDP", + TransportProtocol::Tcp => "TCP", + TransportProtocol::AnyProtocol => panic!("unexpected transport protocol"), + } +} diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 5349174d4c..3c3d55092f 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -1,10 +1,10 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{format::print_keygen_event, new_grpc_client, proto, Command, Error, Result}; use clap::value_t; - -use mullvad_types::settings::TunnelOptions; +use proto::TunnelOptions; pub struct Tunnel; +#[async_trait::async_trait] impl Command for Tunnel { fn name(&self) -> &'static str { "tunnel" @@ -19,11 +19,11 @@ impl Command for Tunnel { .subcommand(create_ipv6_subcommand()) } - fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("openvpn", Some(openvpn_matches)) => Self::handle_openvpn_cmd(openvpn_matches), - ("wireguard", Some(wg_matches)) => Self::handle_wireguard_cmd(wg_matches), - ("ipv6", Some(ipv6_matches)) => Self::handle_ipv6_cmd(ipv6_matches), + ("openvpn", Some(openvpn_matches)) => Self::handle_openvpn_cmd(openvpn_matches).await, + ("wireguard", Some(wg_matches)) => Self::handle_wireguard_cmd(wg_matches).await, + ("ipv6", Some(ipv6_matches)) => Self::handle_ipv6_cmd(ipv6_matches).await, _ => { unreachable!("unhandled comand"); } @@ -104,40 +104,42 @@ fn create_ipv6_subcommand() -> clap::App<'static, 'static> { } impl Tunnel { - fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_openvpn_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("mssfix", Some(mssfix_matches)) => Self::handle_openvpn_mssfix_cmd(mssfix_matches), + ("mssfix", Some(mssfix_matches)) => { + Self::handle_openvpn_mssfix_cmd(mssfix_matches).await + } _ => unreachable!("unhandled command"), } } - fn handle_openvpn_mssfix_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_openvpn_mssfix_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { - ("get", Some(_)) => Self::process_openvpn_mssfix_get(), - ("unset", Some(_)) => Self::process_openvpn_mssfix_unset(), - ("set", Some(set_matches)) => Self::process_openvpn_mssfix_set(set_matches), + ("get", Some(_)) => Self::process_openvpn_mssfix_get().await, + ("unset", Some(_)) => Self::process_openvpn_mssfix_unset().await, + ("set", Some(set_matches)) => Self::process_openvpn_mssfix_set(set_matches).await, _ => unreachable!("unhandled command"), } } - fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_wireguard_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { match matches.subcommand() { ("mtu", Some(matches)) => match matches.subcommand() { - ("get", _) => Self::process_wireguard_mtu_get(), - ("set", Some(matches)) => Self::process_wireguard_mtu_set(matches), - ("unset", _) => Self::process_wireguard_mtu_unset(), + ("get", _) => Self::process_wireguard_mtu_get().await, + ("set", Some(matches)) => Self::process_wireguard_mtu_set(matches).await, + ("unset", _) => Self::process_wireguard_mtu_unset().await, _ => unreachable!("unhandled command"), }, ("key", Some(matches)) => match matches.subcommand() { - ("check", _) => Self::process_wireguard_key_check(), - ("regenerate", _) => Self::process_wireguard_key_generate(), + ("check", _) => Self::process_wireguard_key_check().await, + ("regenerate", _) => Self::process_wireguard_key_generate().await, ("rotation-interval", Some(matches)) => match matches.subcommand() { - ("get", _) => Self::process_wireguard_rotation_interval_get(), + ("get", _) => Self::process_wireguard_rotation_interval_get().await, ("set", Some(matches)) => { - Self::process_wireguard_rotation_interval_set(matches) + Self::process_wireguard_rotation_interval_set(matches).await } - ("reset", _) => Self::process_wireguard_rotation_interval_reset(), + ("reset", _) => Self::process_wireguard_rotation_interval_reset().await, _ => unreachable!("unhandled command"), }, _ => unreachable!("unhandled command"), @@ -147,140 +149,150 @@ impl Tunnel { } } - fn process_wireguard_mtu_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_wireguard_mtu_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; + let mtu = tunnel_options.wireguard.unwrap().mtu; println!( "mtu: {}", - tunnel_options - .wireguard - .mtu - .map(|mtu| mtu.to_string()) - .unwrap_or_else(|| "unset".to_owned()) + if mtu != 0 { + mtu.to_string() + } else { + "unset".to_string() + }, ); Ok(()) } - fn process_wireguard_mtu_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_wireguard_mtu_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let mtu = value_t!(matches.value_of("mtu"), u16).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_mtu(Some(mtu))?; + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_mtu(mtu as u32).await?; println!("Wireguard MTU has been updated"); Ok(()) } - fn process_wireguard_mtu_unset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_mtu(None)?; + async fn process_wireguard_mtu_unset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_mtu(0).await?; println!("Wireguard MTU has been unset"); Ok(()) } - fn process_wireguard_key_check() -> Result<()> { - let mut rpc = new_rpc_client()?; - match rpc.get_wireguard_key()? { - Some(key) => { - println!("Current key : {}", &key.key); - println!( - "Key created on : {}", - &key.created.with_timezone(&chrono::offset::Local) - ); - } - None => { - println!("No key is set"); - return Ok(()); + async fn process_wireguard_key_check() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let key = rpc.get_wireguard_key(()).await; + let key = match key { + Ok(response) => Some(response.into_inner()), + Err(status) => { + if status.code() == tonic::Code::NotFound { + None + } else { + return Err(Error::GrpcClientError(status)); + } } }; + if let Some(key) = key { + println!("Current key : {}", base64::encode(&key.key)); + println!( + "Key created on : {}", + Self::format_key_timestamp(&key.created.unwrap()) + ); + } else { + println!("No key is set"); + return Ok(()); + } - let is_valid = rpc.verify_wireguard_key()?; + let is_valid = rpc.verify_wireguard_key(()).await?.into_inner(); println!("Key is valid for use with current account: {}", is_valid); Ok(()) } - fn process_wireguard_key_generate() -> Result<()> { - let mut rpc = new_rpc_client()?; - let result = rpc - .generate_wireguard_key() - .map_err(|e| crate::Error::RpcClientError(e))?; - println!("{}", result); + async fn process_wireguard_key_generate() -> Result<()> { + let mut rpc = new_grpc_client().await?; + let keygen_event = rpc.generate_wireguard_key(()).await?; + print_keygen_event(&keygen_event.into_inner()); Ok(()) } - fn process_wireguard_rotation_interval_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_wireguard_rotation_interval_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; println!( "Rotation interval: {} hour(s)", - tunnel_options - .wireguard - .automatic_rotation - .map(|interval| interval.to_string()) - .unwrap_or_else(|| "default".to_owned()) + tunnel_options.wireguard.unwrap().automatic_rotation ); Ok(()) } - fn process_wireguard_rotation_interval_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_wireguard_rotation_interval_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let rotate_interval = value_t!(matches.value_of("interval"), u32).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_rotation_interval(Some(rotate_interval))?; + let mut rpc = new_grpc_client().await?; + rpc.set_wireguard_rotation_interval(rotate_interval).await?; println!("Set key rotation interval: {} hour(s)", rotate_interval); Ok(()) } - fn process_wireguard_rotation_interval_reset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_wireguard_rotation_interval(None)?; + async fn process_wireguard_rotation_interval_reset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.reset_wireguard_rotation_interval(()).await?; println!("Set key rotation interval: default"); Ok(()) } - fn handle_ipv6_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn handle_ipv6_cmd(matches: &clap::ArgMatches<'_>) -> Result<()> { if matches.subcommand_matches("get").is_some() { - Self::process_ipv6_get() + Self::process_ipv6_get().await } else if let Some(m) = matches.subcommand_matches("set") { - Self::process_ipv6_set(m) + Self::process_ipv6_set(m).await } else { unreachable!("unhandled command"); } } - fn process_openvpn_mssfix_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_openvpn_mssfix_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; + let mssfix = tunnel_options.openvpn.unwrap().mssfix; println!( "mssfix: {}", - tunnel_options - .openvpn - .mssfix - .map_or_else(|| "unset".to_owned(), |v| v.to_string()) + if mssfix != 0 { + mssfix.to_string() + } else { + "unset".to_string() + }, ); Ok(()) } - fn get_tunnel_options() -> Result<TunnelOptions> { - let mut rpc = new_rpc_client()?; - Ok(rpc.get_settings()?.tunnel_options) + async fn get_tunnel_options() -> Result<TunnelOptions> { + let mut rpc = new_grpc_client().await?; + Ok(rpc + .get_settings(()) + .await? + .into_inner() + .tunnel_options + .unwrap()) } - fn process_openvpn_mssfix_unset() -> Result<()> { - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_mssfix(None)?; + async fn process_openvpn_mssfix_unset() -> Result<()> { + let mut rpc = new_grpc_client().await?; + rpc.set_openvpn_mssfix(0).await?; println!("mssfix parameter has been unset"); Ok(()) } - fn process_openvpn_mssfix_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_openvpn_mssfix_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let new_value = value_t!(matches.value_of("mssfix"), u16).unwrap_or_else(|e| e.exit()); - let mut rpc = new_rpc_client()?; - rpc.set_openvpn_mssfix(Some(new_value))?; + let mut rpc = new_grpc_client().await?; + rpc.set_openvpn_mssfix(new_value as u32).await?; println!("mssfix parameter has been updated"); Ok(()) } - fn process_ipv6_get() -> Result<()> { - let tunnel_options = Self::get_tunnel_options()?; + async fn process_ipv6_get() -> Result<()> { + let tunnel_options = Self::get_tunnel_options().await?; println!( "IPv6: {}", - if tunnel_options.generic.enable_ipv6 { + if tunnel_options.generic.unwrap().enable_ipv6 { "on" } else { "off" @@ -289,12 +301,20 @@ impl Tunnel { Ok(()) } - fn process_ipv6_set(matches: &clap::ArgMatches<'_>) -> Result<()> { + async fn process_ipv6_set(matches: &clap::ArgMatches<'_>) -> Result<()> { let enabled = matches.value_of("enable").unwrap() == "on"; - let mut rpc = new_rpc_client()?; - rpc.set_enable_ipv6(enabled)?; - println!("IPv6 setting has been updated"); + let mut rpc = new_grpc_client().await?; + rpc.set_enable_ipv6(enabled).await?; + if enabled { + println!("Enabled IPv6"); + } else { + println!("Disabled IPv6"); + } Ok(()) } + + fn format_key_timestamp(timestamp: &prost_types::Timestamp) -> String { + chrono::NaiveDateTime::from_timestamp(timestamp.seconds, timestamp.nanos as u32).to_string() + } } diff --git a/mullvad-cli/src/cmds/version.rs b/mullvad-cli/src/cmds/version.rs index 4e0f3a0d8b..48b3e621ed 100644 --- a/mullvad-cli/src/cmds/version.rs +++ b/mullvad-cli/src/cmds/version.rs @@ -1,7 +1,8 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{new_grpc_client, Command, Result}; pub struct Version; +#[async_trait::async_trait] impl Command for Version { fn name(&self) -> &'static str { "version" @@ -12,23 +13,24 @@ impl Command for Version { .about("Shows current version, and the currently supported versions") } - fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { - let mut rpc = new_rpc_client()?; - let current_version = rpc.get_current_version()?; + async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + let mut rpc = new_grpc_client().await?; + let current_version = rpc.get_current_version(()).await?.into_inner(); println!("Current version: {}", current_version); - let version_info = rpc.get_version_info()?; + let version_info = rpc.get_version_info(()).await?.into_inner(); println!("\tIs supported: {}", version_info.supported); - match version_info.suggested_upgrade { - Some(version) => println!("\tSuggested update: {}", version), - None => println!("\tNo newer version is available"), + if !version_info.suggested_upgrade.is_empty() { + println!("\tSuggested update: {}", version_info.suggested_upgrade); + } else { + println!("\tNo newer version is available"); } if !version_info.latest_stable.is_empty() { println!("\tLatest stable version: {}", version_info.latest_stable); } - let settings = rpc.get_settings()?; + let settings = rpc.get_settings(()).await?.into_inner(); if settings.show_beta_releases { println!("\t Latest beta version: {}", version_info.latest_beta); }; |
