summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src/cmds
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-06-16 15:13:32 +0200
committerDavid Lönnhager <david.l@mullvad.net>2020-08-20 14:41:41 +0200
commit8cf02b29a718a7856c80323ee0cf496b9ee24648 (patch)
tree16f378f2cc3d3101d01d58435bf54824e57683a6 /mullvad-cli/src/cmds
parentc2e9303cc7aff29df7941fc08df19b8ffcffa48f (diff)
downloadmullvadvpn-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.rs82
-rw-r--r--mullvad-cli/src/cmds/auto_connect.rs21
-rw-r--r--mullvad-cli/src/cmds/beta_program.rs13
-rw-r--r--mullvad-cli/src/cmds/block_when_disconnected.rs26
-rw-r--r--mullvad-cli/src/cmds/bridge.rs218
-rw-r--r--mullvad-cli/src/cmds/connect.rs9
-rw-r--r--mullvad-cli/src/cmds/disconnect.rs9
-rw-r--r--mullvad-cli/src/cmds/lan.rs21
-rw-r--r--mullvad-cli/src/cmds/reconnect.rs9
-rw-r--r--mullvad-cli/src/cmds/relay.rs387
-rw-r--r--mullvad-cli/src/cmds/reset.rs9
-rw-r--r--mullvad-cli/src/cmds/split_tunnel/linux.rs32
-rw-r--r--mullvad-cli/src/cmds/status.rs252
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs206
-rw-r--r--mullvad-cli/src/cmds/version.rs20
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);
};