summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--mullvad-cli/src/cmds/connect.rs2
-rw-r--r--mullvad-cli/src/cmds/disconnect.rs2
-rw-r--r--mullvad-cli/src/cmds/reconnect.rs2
-rw-r--r--mullvad-cli/src/cmds/status.rs26
-rw-r--r--mullvad-cli/src/format.rs200
6 files changed, 137 insertions, 96 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4413af951f..2892b02b14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,7 @@ Line wrap the file at 100 chars. Th
- Make login field keep previous value when submitting an incorrect account number in desktop app.
- Decrease the time it takes to connect to WireGuard relays by sending an ICMP packet immediately.
- Pause API interactions when the daemon has not been used for 3 days.
+- Simplified output of `mullvad status` command.
### Fixed
- Fix the sometimes incorrect time added text after adding time to the account.
diff --git a/mullvad-cli/src/cmds/connect.rs b/mullvad-cli/src/cmds/connect.rs
index a184e38244..3a879fc6a8 100644
--- a/mullvad-cli/src/cmds/connect.rs
+++ b/mullvad-cli/src/cmds/connect.rs
@@ -34,7 +34,7 @@ impl Command for Connect {
if let Some(mut receiver) = receiver_option {
while let Some(state) = receiver.next().await {
let state = state?;
- format::print_state(&state);
+ format::print_state(&state, false);
match state.state.unwrap() {
State::Connected(_) => return Ok(()),
State::Error(_) => return Err(Error::CommandFailed("connect")),
diff --git a/mullvad-cli/src/cmds/disconnect.rs b/mullvad-cli/src/cmds/disconnect.rs
index 9ac051ca57..a906eed22d 100644
--- a/mullvad-cli/src/cmds/disconnect.rs
+++ b/mullvad-cli/src/cmds/disconnect.rs
@@ -34,7 +34,7 @@ impl Command for Disconnect {
if let Some(mut receiver) = receiver_option {
while let Some(state) = receiver.next().await {
let state = state?;
- format::print_state(&state);
+ format::print_state(&state, false);
match state.state.unwrap() {
Disconnected(_) => return Ok(()),
_ => {}
diff --git a/mullvad-cli/src/cmds/reconnect.rs b/mullvad-cli/src/cmds/reconnect.rs
index c2a577a212..53ca666482 100644
--- a/mullvad-cli/src/cmds/reconnect.rs
+++ b/mullvad-cli/src/cmds/reconnect.rs
@@ -34,7 +34,7 @@ impl Command for Reconnect {
if let Some(mut receiver) = receiver_option {
while let Some(state) = receiver.next().await {
let state = state?;
- format::print_state(&state);
+ format::print_state(&state, false);
match state.state.unwrap() {
State::Connected(_) => return Ok(()),
State::Error(_) => return Err(Error::CommandFailed("reconnect")),
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs
index 3d5cfc8b7d..71f31f5fef 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -15,6 +15,11 @@ impl Command for Status {
clap::App::new(self.name())
.about("View the state of the VPN tunnel")
.arg(
+ clap::Arg::new("verbose")
+ .short('v')
+ .help("Enables verbose output"),
+ )
+ .arg(
clap::Arg::new("location")
.long("location")
.short('l')
@@ -24,13 +29,15 @@ impl Command for Status {
clap::Arg::new("debug")
.long("debug")
.global(true)
- .help("Enables verbose output"),
+ .help("Enables debug output"),
)
.subcommand(clap::App::new("listen").about("Listen for VPN tunnel state changes"))
}
async fn run(&self, matches: &clap::ArgMatches) -> Result<()> {
let debug = matches.is_present("debug");
+ let verbose = matches.is_present("verbose");
+ let show_full_location = matches.is_present("location");
let mut rpc = new_rpc_client().await?;
let state = rpc.get_tunnel_state(()).await?.into_inner();
@@ -38,10 +45,10 @@ impl Command for Status {
if debug {
println!("Tunnel state: {:#?}", state);
} else {
- format::print_state(&state);
+ format::print_state(&state, verbose);
}
- if matches.is_present("location") {
+ if show_full_location {
print_location(&mut rpc).await?;
}
@@ -54,13 +61,13 @@ impl Command for Status {
if debug {
println!("New tunnel state: {:#?}", new_state);
} else {
- format::print_state(&new_state);
+ format::print_state(&new_state, verbose);
}
use mullvad_management_interface::types::tunnel_state::State::*;
match new_state.state.unwrap() {
Connected(..) | Disconnected(..) => {
- if matches.is_present("location") {
+ if show_full_location {
print_location(&mut rpc).await?;
}
}
@@ -113,9 +120,6 @@ async fn print_location(rpc: &mut ManagementServiceClient) -> Result<()> {
}
}
};
- if !location.hostname.is_empty() {
- println!("Relay: {}", location.hostname);
- }
if !location.ipv4.is_empty() {
println!("IPv4: {}", location.ipv4);
}
@@ -123,12 +127,6 @@ async fn print_location(rpc: &mut ManagementServiceClient) -> Result<()> {
println!("IPv6: {}", location.ipv6);
}
- print!("Location: ");
- if !location.city.is_empty() {
- print!("{}, ", location.city);
- }
- println!("{}", location.country);
-
println!(
"Position: {:.5}°N, {:.5}°W",
location.latitude, location.longitude
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs
index 13e084707a..8a4fc95742 100644
--- a/mullvad-cli/src/format.rs
+++ b/mullvad-cli/src/format.rs
@@ -5,107 +5,149 @@ use mullvad_management_interface::types::{
},
tunnel_state,
tunnel_state::State::*,
- ErrorState, ObfuscationType, ProxyType, TransportProtocol, TunnelEndpoint, TunnelState,
+ ErrorState, ObfuscationType, ProxyType, TransportProtocol, TunnelState, TunnelStateRelayInfo,
TunnelType,
};
use mullvad_types::auth_failed::AuthFailed;
-use std::fmt::Write;
-pub fn print_state(state: &TunnelState) {
- print!("Tunnel status: ");
+pub fn print_state(state: &TunnelState, verbose: bool) {
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));
+ println!(
+ "Connected to {}",
+ format_relay_connection(relay_info.as_ref().unwrap(), verbose)
+ );
}
Connecting(tunnel_state::Connecting { relay_info }) => {
- let endpoint = relay_info
- .as_ref()
- .unwrap()
- .tunnel_endpoint
- .as_ref()
- .unwrap();
- println!("Connecting to {}...", format_endpoint(&endpoint));
+ let ellipsis = if !verbose { "..." } else { "" };
+ println!(
+ "Connecting to {}{ellipsis}",
+ format_relay_connection(relay_info.as_ref().unwrap(), verbose)
+ );
}
Disconnected(_) => println!("Disconnected"),
Disconnecting(_) => println!("Disconnecting..."),
}
}
-fn format_endpoint(endpoint: &TunnelEndpoint) -> String {
- let tunnel_type = TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel protocol");
- let mut out = format!(
- "{} {}/{}",
- match tunnel_type {
+fn format_relay_connection(relay_info: &TunnelStateRelayInfo, verbose: bool) -> String {
+ let endpoint = relay_info.tunnel_endpoint.as_ref().unwrap();
+ let location = &relay_info.location.as_ref().unwrap();
+
+ let prefix_separator = if verbose { "\n\t" } else { " " };
+ let mut obfuscator_overlaps = false;
+
+ let exit_endpoint = {
+ let mut address = endpoint.address.as_str();
+ let mut protocol = endpoint.protocol;
+ if let Some(obfuscator) = endpoint.obfuscation.as_ref() {
+ if location.hostname == location.obfuscator_hostname {
+ obfuscator_overlaps = true;
+ address = &obfuscator.address;
+ protocol = obfuscator.protocol;
+ }
+ };
+
+ let exit = format_endpoint(
+ &location.hostname,
+ protocol,
+ Some(address).filter(|_| verbose),
+ );
+ format!("{exit} in {}, {}", &location.city, &location.country)
+ };
+
+ let first_hop = endpoint.entry_endpoint.as_ref().map(|entry| {
+ let mut address = entry.address.as_str();
+ let mut protocol = entry.protocol;
+ if let Some(obfuscator) = endpoint.obfuscation.as_ref() {
+ obfuscator_overlaps = true;
+ if location.entry_hostname == location.obfuscator_hostname {
+ address = &obfuscator.address;
+ protocol = obfuscator.protocol;
+ }
+ };
+
+ let endpoint = format_endpoint(
+ &location.entry_hostname,
+ protocol,
+ Some(address).filter(|_| verbose),
+ );
+ format!("{prefix_separator}via {endpoint}")
+ });
+
+ let obfuscator = endpoint.obfuscation.as_ref().map(|obfuscator| {
+ if !obfuscator_overlaps {
+ let endpoint_str = format_endpoint(
+ &location.obfuscator_hostname,
+ obfuscator.protocol,
+ Some(obfuscator.address.as_str()).filter(|_| verbose),
+ );
+ format!("{prefix_separator}obfuscated via {endpoint_str}")
+ } else {
+ String::new()
+ }
+ });
+
+ let bridge = endpoint.proxy.as_ref().map(|proxy| {
+ let proxy_endpoint = format_endpoint(
+ &location.bridge_hostname,
+ proxy.protocol,
+ Some(proxy.address.as_str()).filter(|_| verbose),
+ );
+
+ format!("{prefix_separator}via {proxy_endpoint}")
+ });
+ let tunnel_type = if verbose {
+ let tunnel = match TunnelType::from_i32(endpoint.tunnel_type).expect("invalid tunnel type")
+ {
TunnelType::Wireguard => "WireGuard",
TunnelType::Openvpn => "OpenVPN",
- },
- endpoint.address,
- format_protocol(
- TransportProtocol::from_i32(endpoint.protocol).expect("invalid transport protocol")
- ),
- );
+ };
- match tunnel_type {
- TunnelType::Openvpn => {
- if let Some(ref proxy) = endpoint.proxy {
- write!(
- &mut out,
- " via {} {}/{}",
- match ProxyType::from_i32(proxy.proxy_type).expect("invalid proxy type") {
- ProxyType::Shadowsocks => "Shadowsocks",
- ProxyType::Custom => "custom bridge",
- },
- proxy.address,
- format_protocol(
- TransportProtocol::from_i32(proxy.protocol)
- .expect("invalid transport protocol")
- ),
- )
- .unwrap();
- }
+ format!("\nTunnel type: {tunnel}")
+ } else {
+ String::new()
+ };
+
+ let mut bridge_type = String::new();
+ let mut obfuscator_type = String::new();
+ if verbose {
+ if let Some(bridge) = endpoint.proxy.as_ref() {
+ let bridge = match ProxyType::from_i32(bridge.proxy_type).expect("invalid proxy type") {
+ ProxyType::Shadowsocks => "Shadowsocks",
+ ProxyType::Custom => "custom bridge",
+ };
+ bridge_type = format!("\nBridge type: {}", bridge);
}
- TunnelType::Wireguard => {
- if let Some(ref entry_endpoint) = endpoint.entry_endpoint {
- write!(
- &mut out,
- " via {}/{}",
- entry_endpoint.address,
- format_protocol(
- TransportProtocol::from_i32(entry_endpoint.protocol)
- .expect("invalid transport protocol")
- )
- )
- .unwrap();
- }
- if let Some(ref obfuscation) = endpoint.obfuscation {
- write!(
- &mut out,
- " via {} {}:{}/{}",
- match ObfuscationType::from_i32(obfuscation.obfuscation_type)
- .expect("invalid obfuscation type")
- {
- ObfuscationType::Udp2tcp => "Udp2Tcp",
- },
- obfuscation.address,
- obfuscation.port,
- format_protocol(
- TransportProtocol::from_i32(obfuscation.protocol)
- .expect("invalid transport protocol")
- )
- )
- .unwrap();
- }
+ if let Some(obfuscator) = endpoint.obfuscation.as_ref() {
+ let obfuscation = convert_obfuscator_type(obfuscator.obfuscation_type);
+ obfuscator_type = format!("\nObfuscator: {obfuscation}");
}
}
- out
+ format!(
+ "{exit_endpoint}{first_hop}{bridge}{obfuscator}{tunnel_type}{bridge_type}{obfuscator_type}",
+ first_hop = first_hop.unwrap_or(String::new()),
+ bridge = bridge.unwrap_or(String::new()),
+ obfuscator = obfuscator.unwrap_or(String::new()),
+ )
+}
+
+fn convert_obfuscator_type(obfuscator: i32) -> &'static str {
+ match ObfuscationType::from_i32(obfuscator).expect("invalid obfuscator type") {
+ ObfuscationType::Udp2tcp => "Udp2Tcp",
+ }
+}
+
+fn format_endpoint(hostname: &String, protocol_enum: i32, addr: Option<&str>) -> String {
+ let protocol = format_protocol(
+ TransportProtocol::from_i32(protocol_enum).expect("invalid transport protocol"),
+ );
+ let sockaddr_suffix = addr
+ .map(|addr| format!(" ({addr}/{protocol})"))
+ .unwrap_or_else(String::new);
+ format!("{hostname}{sockaddr_suffix}")
}
fn print_error_state(error_state: &ErrorState) {