diff options
| author | Emīls <emils@mullvad.net> | 2022-04-28 14:50:42 +0100 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2022-05-05 10:16:17 +0100 |
| commit | ba95533d864ec2ba0de6fc8284fa53841a55e8bb (patch) | |
| tree | 969bab227f7ccdd97830f5c8c5bee584f0fe1987 /mullvad-cli/src | |
| parent | 3a19900e51ca874e8202bfbd2853662a0652863e (diff) | |
| download | mullvadvpn-ba95533d864ec2ba0de6fc8284fa53841a55e8bb.tar.xz mullvadvpn-ba95533d864ec2ba0de6fc8284fa53841a55e8bb.zip | |
Rework output of status subcommand in CLI
The output of the status command is reworked to show hostnames instead
of IP addresses and trim the fat. The extra information (tunnel
protocols, IP addrresses) are now available with the verbose flag.
Diffstat (limited to 'mullvad-cli/src')
| -rw-r--r-- | mullvad-cli/src/cmds/connect.rs | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/disconnect.rs | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/reconnect.rs | 2 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 26 | ||||
| -rw-r--r-- | mullvad-cli/src/format.rs | 200 |
5 files changed, 136 insertions, 96 deletions
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) { |
