diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-10-09 15:38:29 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-10-09 15:38:29 +0200 |
| commit | 633d4c4184441f51ba44c43e27ddb3c68178a8d2 (patch) | |
| tree | 794be2073e83a2501b7afc3823508d84b21ed898 | |
| parent | d6d010e40eae0fbba4fed8381a7c887aceee79ce (diff) | |
| parent | 112431d06c1adfd25538b6bd444c62cf3844dc99 (diff) | |
| download | mullvadvpn-633d4c4184441f51ba44c43e27ddb3c68178a8d2.tar.xz mullvadvpn-633d4c4184441f51ba44c43e27ddb3c68178a8d2.zip | |
Merge branch 'add-wait-flag-to-cli' into master
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/connect.rs | 36 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/disconnect.rs | 34 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/reconnect.rs | 39 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 170 | ||||
| -rw-r--r-- | mullvad-cli/src/format.rs | 162 | ||||
| -rw-r--r-- | mullvad-cli/src/main.rs | 7 | ||||
| -rw-r--r-- | mullvad-cli/src/state.rs | 41 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 26 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 21 | ||||
| -rw-r--r-- | mullvad-jni/src/daemon_interface.rs | 8 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 6 |
12 files changed, 346 insertions, 206 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c61314e47..6b4dd94b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ Line wrap the file at 100 chars. Th ## [Unreleased] ### Added - Improve accessibility in the desktop app. +- Add `--wait` flag to `connect`, `disconnect` and `reconnect` CLI subcommands to make the CLI wait + for the target state to be reached before exiting. ### Changed - Use the API to fetch API IP addresses instead of DNS. diff --git a/mullvad-cli/src/cmds/connect.rs b/mullvad-cli/src/cmds/connect.rs index 6fc7072580..c93ed8c2e2 100644 --- a/mullvad-cli/src/cmds/connect.rs +++ b/mullvad-cli/src/cmds/connect.rs @@ -1,5 +1,6 @@ -use crate::{new_rpc_client, Command, Result}; -use talpid_types::ErrorExt; +use crate::{format, new_rpc_client, state, Command, Error, Result}; +use futures::StreamExt; +use mullvad_management_interface::types::tunnel_state::State; pub struct Connect; @@ -12,13 +13,38 @@ impl Command for Connect { fn clap_subcommand(&self) -> clap::App<'static, 'static> { clap::SubCommand::with_name(self.name()) .about("Command the client to start establishing a VPN tunnel") + .arg( + clap::Arg::with_name("wait") + .long("wait") + .short("w") + .help("Wait until connected before exiting"), + ) } - async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let mut rpc = new_rpc_client().await?; - if let Err(e) = rpc.connect_tunnel(()).await { - eprintln!("{}", e.display_chain()); + + let receiver_option = if matches.is_present("wait") { + Some(state::state_listen(rpc.clone())) + } else { + None + }; + + if rpc.connect_tunnel(()).await?.into_inner() { + if let Some(mut receiver) = receiver_option { + while let Some(state) = receiver.next().await { + let state = state?; + format::print_state(&state); + match state.state.unwrap() { + State::Connected(_) => return Ok(()), + State::Error(_) => return Err(Error::CommandFailed("connect")), + _ => {} + } + } + return Err(Error::StatusListenerFailed); + } } + Ok(()) } } diff --git a/mullvad-cli/src/cmds/disconnect.rs b/mullvad-cli/src/cmds/disconnect.rs index f976b96fab..96d5843d20 100644 --- a/mullvad-cli/src/cmds/disconnect.rs +++ b/mullvad-cli/src/cmds/disconnect.rs @@ -1,4 +1,6 @@ -use crate::{new_rpc_client, Command, Result}; +use crate::{format, new_rpc_client, state, Command, Error, Result}; +use futures::StreamExt; +use mullvad_management_interface::types::tunnel_state::State::Disconnected; pub struct Disconnect; @@ -11,11 +13,37 @@ impl Command for Disconnect { fn clap_subcommand(&self) -> clap::App<'static, 'static> { clap::SubCommand::with_name(self.name()) .about("Command the client to disconnect the VPN tunnel") + .arg( + clap::Arg::with_name("wait") + .long("wait") + .short("w") + .help("Wait until disconnected before exiting"), + ) } - async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let mut rpc = new_rpc_client().await?; - rpc.disconnect_tunnel(()).await?; + + let receiver_option = if matches.is_present("wait") { + Some(state::state_listen(rpc.clone())) + } else { + None + }; + + if rpc.disconnect_tunnel(()).await?.into_inner() { + if let Some(mut receiver) = receiver_option { + while let Some(state) = receiver.next().await { + let state = state?; + format::print_state(&state); + match state.state.unwrap() { + Disconnected(_) => return Ok(()), + _ => {} + } + } + return Err(Error::StatusListenerFailed); + } + } + Ok(()) } } diff --git a/mullvad-cli/src/cmds/reconnect.rs b/mullvad-cli/src/cmds/reconnect.rs index ecc0b089c0..87de63144f 100644 --- a/mullvad-cli/src/cmds/reconnect.rs +++ b/mullvad-cli/src/cmds/reconnect.rs @@ -1,5 +1,6 @@ -use crate::{new_rpc_client, Command, Result}; -use talpid_types::ErrorExt; +use crate::{format, new_rpc_client, state, Command, Error, Result}; +use futures::StreamExt; +use mullvad_management_interface::types::tunnel_state::State; pub struct Reconnect; @@ -10,14 +11,40 @@ impl Command for Reconnect { } fn clap_subcommand(&self) -> clap::App<'static, 'static> { - clap::SubCommand::with_name(self.name()).about("Command the client to reconnect") + clap::SubCommand::with_name(self.name()) + .about("Command the client to reconnect") + .arg( + clap::Arg::with_name("wait") + .long("wait") + .short("w") + .help("Wait until reconnected before exiting"), + ) } - async fn run(&self, _: &clap::ArgMatches<'_>) -> Result<()> { + async fn run(&self, matches: &clap::ArgMatches<'_>) -> Result<()> { let mut rpc = new_rpc_client().await?; - if let Err(e) = rpc.reconnect_tunnel(()).await { - eprintln!("{}", e.display_chain()); + + let receiver_option = if matches.is_present("wait") { + Some(state::state_listen(rpc.clone())) + } else { + None + }; + + if rpc.reconnect_tunnel(()).await?.into_inner() { + if let Some(mut receiver) = receiver_option { + while let Some(state) = receiver.next().await { + let state = state?; + format::print_state(&state); + match state.state.unwrap() { + State::Connected(_) => return Ok(()), + State::Error(_) => return Err(Error::CommandFailed("reconnect")), + _ => {} + } + } + return Err(Error::StatusListenerFailed); + } } + Ok(()) } } diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index b0c4d40ad0..76a4ed2e67 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -1,17 +1,7 @@ -use crate::{format::print_keygen_event, new_rpc_client, Command, Error, Result}; +use crate::{format, format::print_keygen_event, new_rpc_client, Command, Error, Result}; use mullvad_management_interface::{ - types::{ - daemon_event::Event as EventType, - error_state::{ - firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause, - FirewallPolicyError, GenerationError, - }, - ErrorState, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType, - }, - ManagementServiceClient, + types::daemon_event::Event as EventType, ManagementServiceClient, }; -use mullvad_types::auth_failed::AuthFailed; -use std::fmt::Write; pub struct Status; @@ -45,7 +35,7 @@ impl Command for Status { let mut rpc = new_rpc_client().await?; let state = rpc.get_tunnel_state(()).await?.into_inner(); - print_state(&state); + format::print_state(&state); if matches.is_present("location") { print_location(&mut rpc).await?; } @@ -58,7 +48,7 @@ impl Command for Status { while let Some(event) = events.message().await? { match event.event.unwrap() { EventType::TunnelState(new_state) => { - print_state(&new_state); + format::print_state(&new_state); use mullvad_management_interface::types::tunnel_state::State::*; match new_state.state.unwrap() { Connected(..) | Disconnected(..) => { @@ -98,151 +88,6 @@ impl Command for Status { } } -fn print_state(state: &TunnelState) { - use mullvad_management_interface::types::{tunnel_state, tunnel_state::State::*}; - - print!("Tunnel status: "); - 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(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", - }, - 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.blocking_error.is_some() { - eprintln!("Mullvad daemon failed to setup firewall rules!"); - eprintln!("Deamon cannot block traffic from flowing, non-local traffic will leak"); - } - - match ErrorStateCause::from_i32(error_state.cause) { - Some(ErrorStateCause::AuthFailed) => { - println!( - "Blocked: {}", - 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"); - } - _ => println!("Blocked: {}", error_state_to_string(error_state)), - } -} - -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"), - }; - - 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" - } - } -} - -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) -> Result<()> { let location = rpc.get_current_location(()).await; let location = match location { @@ -278,10 +123,3 @@ async fn print_location(rpc: &mut ManagementServiceClient) -> Result<()> { ); Ok(()) } - -fn format_protocol(protocol: TransportProtocol) -> &'static str { - match protocol { - TransportProtocol::Udp => "UDP", - TransportProtocol::Tcp => "TCP", - } -} diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index 4f69d05317..92f6076e66 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -1,4 +1,14 @@ -use mullvad_management_interface::types::KeygenEvent; +use mullvad_management_interface::types::{ + error_state::{ + firewall_policy_error::ErrorType as FirewallPolicyErrorType, Cause as ErrorStateCause, + FirewallPolicyError, GenerationError, + }, + tunnel_state, + tunnel_state::State::*, + ErrorState, KeygenEvent, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState, TunnelType, +}; +use mullvad_types::auth_failed::AuthFailed; +use std::fmt::Write; pub fn print_keygen_event(key_event: &KeygenEvent) { use mullvad_management_interface::types::keygen_event::KeygenEvent as EventType; @@ -18,3 +28,153 @@ pub fn print_keygen_event(key_event: &KeygenEvent) { } } } + +pub fn print_state(state: &TunnelState) { + print!("Tunnel status: "); + 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(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", + }, + 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.blocking_error.is_some() { + eprintln!("Mullvad daemon failed to setup firewall rules!"); + eprintln!("Deamon cannot block traffic from flowing, non-local traffic will leak"); + } + + match ErrorStateCause::from_i32(error_state.cause) { + Some(ErrorStateCause::AuthFailed) => { + println!( + "Blocked: {}", + 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"); + } + _ => println!("Blocked: {}", error_state_to_string(error_state)), + } +} + +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"), + }; + + 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" + } + } +} + +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) +} + +fn format_protocol(protocol: TransportProtocol) -> &'static str { + match protocol { + TransportProtocol::Udp => "UDP", + TransportProtocol::Tcp => "TCP", + } +} diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 356a50ce69..17019c0ab2 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -10,6 +10,7 @@ pub use mullvad_management_interface::{self, new_rpc_client}; mod cmds; mod format; mod location; +mod state; pub const BIN_NAME: &str = "mullvad"; pub const PRODUCT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt")); @@ -30,6 +31,12 @@ pub enum Error { /// The given command is not correct in some way #[error(display = "Invalid command: {}", _0)] InvalidCommand(&'static str), + + #[error(display = "Command failed: {}", _0)] + CommandFailed(&'static str), + + #[error(display = "Failed to listen for status updates")] + StatusListenerFailed, } #[tokio::main] diff --git a/mullvad-cli/src/state.rs b/mullvad-cli/src/state.rs new file mode 100644 index 0000000000..9890218545 --- /dev/null +++ b/mullvad-cli/src/state.rs @@ -0,0 +1,41 @@ +use crate::{Error, Result}; +use futures::{ + channel::{mpsc, mpsc::Receiver}, + SinkExt, +}; +use mullvad_management_interface::{ + types::{daemon_event::Event as EventType, TunnelState}, + ManagementServiceClient, +}; + +// Spawns a new task that listens for tunnel state changes and forwards it through the returned +// channel. Panics if called from outside of the Tokio runtime. +pub fn state_listen(mut rpc: ManagementServiceClient) -> Receiver<Result<TunnelState>> { + let (mut sender, receiver) = mpsc::channel::<Result<TunnelState>>(1); + tokio::spawn(async move { + match rpc.events_listen(()).await { + Ok(events) => { + let mut events = events.into_inner(); + loop { + let forward = match events.message().await { + Ok(Some(event)) => match event.event.unwrap() { + EventType::TunnelState(new_state) => Ok(new_state), + _ => continue, + }, + Ok(None) => break, + Err(status) => Err(Error::GrpcClientError(status)), + }; + + if let Err(_) = sender.send(forward).await { + break; + } + } + } + Err(status) => { + let _ = sender.send(Err(Error::GrpcClientError(status))).await; + } + } + }); + + receiver +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index d8ce28893f..21242e2736 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -141,9 +141,9 @@ pub enum Error { /// Enum representing commands that can be sent to the daemon. pub enum DaemonCommand { /// Set target state. Does nothing if the daemon already has the state that is being set. - SetTargetState(oneshot::Sender<()>, TargetState), + SetTargetState(oneshot::Sender<bool>, TargetState), /// Reconnect the tunnel, if one is connecting/connected. - Reconnect, + Reconnect(oneshot::Sender<bool>), /// Request the current state. GetState(oneshot::Sender<TunnelState>), /// Get the current geographical location. @@ -967,7 +967,8 @@ where let (future, abort_handle) = abortable(Box::pin(async move { tokio::time::delay_for(delay).await; log::debug!("Attempting to reconnect"); - let _ = tunnel_command_tx.send(DaemonCommand::Reconnect); + let (tx, _) = oneshot::channel(); + let _ = tunnel_command_tx.send(DaemonCommand::Reconnect(tx)); })); tokio::spawn(future); @@ -989,7 +990,7 @@ where } match command { SetTargetState(tx, state) => self.on_set_target_state(tx, state), - Reconnect => self.on_reconnect(), + Reconnect(tx) => self.on_reconnect(tx), GetState(tx) => self.on_get_state(tx), GetCurrentLocation(tx) => self.on_get_current_location(tx).await, CreateNewAccount(tx) => self.on_create_new_account(tx).await, @@ -1152,20 +1153,22 @@ where self.event_listener.notify_app_version(app_version_info); } - fn on_set_target_state(&mut self, tx: oneshot::Sender<()>, new_target_state: TargetState) { + fn on_set_target_state(&mut self, tx: oneshot::Sender<bool>, new_target_state: TargetState) { if self.state.is_running() { - self.set_target_state(new_target_state); + let state_change_initated = self.set_target_state(new_target_state); + Self::oneshot_send(tx, state_change_initated, "state change initiated"); } else { warn!("Ignoring target state change request due to shutdown"); } - Self::oneshot_send(tx, (), "target state"); } - fn on_reconnect(&mut self) { + fn on_reconnect(&mut self, tx: oneshot::Sender<bool>) { if self.target_state == TargetState::Secured || self.tunnel_state.is_in_error_state() { self.connect_tunnel(); + Self::oneshot_send(tx, true, "reconnect issued"); } else { debug!("Ignoring reconnect command. Currently not in secured state"); + Self::oneshot_send(tx, false, "reconnect issued"); } } @@ -1892,8 +1895,8 @@ where /// Set the target state of the client. If it changed trigger the operations needed to /// progress towards that state. - /// Returns an error if trying to set secured state, but no account token is present. - fn set_target_state(&mut self, new_state: TargetState) { + /// Returns a bool representing whether or not a state change was initiated. + fn set_target_state(&mut self, new_state: TargetState) -> bool { if new_state != self.target_state || self.tunnel_state.is_in_error_state() { debug!("Target state {:?} => {:?}", self.target_state, new_state); self.target_state = new_state; @@ -1901,6 +1904,9 @@ where TargetState::Secured => self.connect_tunnel(), TargetState::Unsecured => self.disconnect_tunnel(), } + true + } else { + false } } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index cba6e96b43..e3533b28e2 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -60,27 +60,30 @@ impl ManagementService for ManagementServiceImpl { // Control and get the tunnel state // - async fn connect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + async fn connect_tunnel(&self, _: Request<()>) -> ServiceResult<bool> { log::debug!("connect_tunnel"); let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Secured))?; - rx.await.map_err(|_| Status::internal("internal error"))?; - Ok(Response::new(())) + let connect_issued = rx.await.map_err(|_| Status::internal("internal error"))?; + Ok(Response::new(connect_issued)) } - async fn disconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + async fn disconnect_tunnel(&self, _: Request<()>) -> ServiceResult<bool> { log::debug!("disconnect_tunnel"); - let (tx, _) = oneshot::channel(); + let (tx, rx) = oneshot::channel(); self.send_command_to_daemon(DaemonCommand::SetTargetState(tx, TargetState::Unsecured))?; - Ok(Response::new(())) + let disconnect_issued = rx.await.map_err(|_| Status::internal("internal error"))?; + Ok(Response::new(disconnect_issued)) } - async fn reconnect_tunnel(&self, _: Request<()>) -> ServiceResult<()> { + async fn reconnect_tunnel(&self, _: Request<()>) -> ServiceResult<bool> { log::debug!("reconnect_tunnel"); - self.send_command_to_daemon(DaemonCommand::Reconnect)?; - Ok(Response::new(())) + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::Reconnect(tx))?; + let reconnect_issued = rx.await.map_err(|_| Status::internal("internal error"))?; + Ok(Response::new(reconnect_issued)) } async fn get_tunnel_state(&self, _: Request<()>) -> ServiceResult<types::TunnelState> { diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs index 40d9e9cc35..b00a1b6b50 100644 --- a/mullvad-jni/src/daemon_interface.rs +++ b/mullvad-jni/src/daemon_interface.rs @@ -42,7 +42,7 @@ impl DaemonInterface { self.send_command(DaemonCommand::SetTargetState(tx, TargetState::Secured))?; - block_on(rx).map_err(|_| Error::NoResponse) + block_on(rx).map(|_| ()).map_err(|_| Error::NoResponse) } pub fn create_new_account(&self) -> Result<String> { @@ -60,7 +60,7 @@ impl DaemonInterface { self.send_command(DaemonCommand::SetTargetState(tx, TargetState::Unsecured))?; - block_on(rx).map_err(|_| Error::NoResponse) + block_on(rx).map(|_| ()).map_err(|_| Error::NoResponse) } pub fn generate_wireguard_key(&self) -> Result<KeygenEvent> { @@ -148,7 +148,9 @@ impl DaemonInterface { } pub fn reconnect(&self) -> Result<()> { - self.send_command(DaemonCommand::Reconnect)?; + let (tx, _) = oneshot::channel(); + + self.send_command(DaemonCommand::Reconnect(tx))?; Ok(()) } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 857499afd4..995bd0d346 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -8,9 +8,9 @@ import "google/protobuf/wrappers.proto"; service ManagementService { // Control and get tunnel state - rpc ConnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} - rpc DisconnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} - rpc ReconnectTunnel(google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc ConnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} + rpc DisconnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} + rpc ReconnectTunnel(google.protobuf.Empty) returns (google.protobuf.BoolValue) {} rpc GetTunnelState(google.protobuf.Empty) returns (TunnelState) {} // Control the daemon and receive events |
