summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-10-09 15:38:29 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-10-09 15:38:29 +0200
commit633d4c4184441f51ba44c43e27ddb3c68178a8d2 (patch)
tree794be2073e83a2501b7afc3823508d84b21ed898
parentd6d010e40eae0fbba4fed8381a7c887aceee79ce (diff)
parent112431d06c1adfd25538b6bd444c62cf3844dc99 (diff)
downloadmullvadvpn-633d4c4184441f51ba44c43e27ddb3c68178a8d2.tar.xz
mullvadvpn-633d4c4184441f51ba44c43e27ddb3c68178a8d2.zip
Merge branch 'add-wait-flag-to-cli' into master
-rw-r--r--CHANGELOG.md2
-rw-r--r--mullvad-cli/src/cmds/connect.rs36
-rw-r--r--mullvad-cli/src/cmds/disconnect.rs34
-rw-r--r--mullvad-cli/src/cmds/reconnect.rs39
-rw-r--r--mullvad-cli/src/cmds/status.rs170
-rw-r--r--mullvad-cli/src/format.rs162
-rw-r--r--mullvad-cli/src/main.rs7
-rw-r--r--mullvad-cli/src/state.rs41
-rw-r--r--mullvad-daemon/src/lib.rs26
-rw-r--r--mullvad-daemon/src/management_interface.rs21
-rw-r--r--mullvad-jni/src/daemon_interface.rs8
-rw-r--r--mullvad-management-interface/proto/management_interface.proto6
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