1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
use anyhow::Result;
use clap::{Args, Subcommand};
use futures::StreamExt;
use mullvad_management_interface::{client::DaemonEvent, MullvadProxyClient};
use mullvad_types::{device::DeviceState, states::TunnelState};
use crate::format;
#[derive(Subcommand, Debug, PartialEq)]
pub enum Status {
/// Listen for tunnel state changes
Listen,
}
#[derive(Args, Debug)]
pub struct StatusArgs {
/// Enable verbose output
#[arg(long, short = 'v')]
verbose: bool,
/// Print the current location and IP, based on GeoIP lookups
#[arg(long, short = 'l')]
location: bool,
/// Enable debug output
#[arg(long, short = 'd')]
debug: bool,
}
impl Status {
pub async fn listen(mut rpc: MullvadProxyClient, args: StatusArgs) -> Result<()> {
while let Some(event) = rpc.events_listen().await?.next().await {
match event? {
DaemonEvent::TunnelState(new_state) => {
if args.debug {
println!("New tunnel state: {new_state:#?}");
} else {
format::print_state(&new_state, args.verbose);
}
match new_state {
TunnelState::Connected { .. } | TunnelState::Disconnected => {
if args.location {
print_location(&mut rpc).await?;
}
}
_ => {}
}
}
DaemonEvent::Settings(settings) => {
if args.debug {
println!("New settings: {settings:#?}");
}
}
DaemonEvent::RelayList(relay_list) => {
if args.debug {
println!("New relay list: {relay_list:#?}");
}
}
DaemonEvent::AppVersionInfo(app_version_info) => {
if args.debug {
println!("New app version info: {app_version_info:#?}");
}
}
DaemonEvent::Device(device) => {
if args.debug {
println!("Device event: {device:#?}");
}
}
DaemonEvent::RemoveDevice(device) => {
if args.debug {
println!("Remove device event: {device:#?}");
}
}
}
}
Ok(())
}
}
pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let state = rpc.get_tunnel_state().await?;
let device = rpc.get_device().await?;
print_account_loggedout(&state, &device);
if args.debug {
println!("Tunnel state: {state:#?}");
} else {
format::print_state(&state, args.verbose);
}
if args.location {
print_location(&mut rpc).await?;
}
if cmd == Some(Status::Listen) {
Status::listen(rpc, args).await?;
}
Ok(())
}
async fn print_location(rpc: &mut MullvadProxyClient) -> Result<()> {
let location = match rpc.get_current_location().await {
Ok(location) => location,
Err(error) => match &error {
mullvad_management_interface::Error::NoLocationData => {
println!("Location data unavailable");
return Ok(());
}
_ => return Err(error.into()),
},
};
if let Some(ipv4) = location.ipv4 {
println!("IPv4: {ipv4}");
}
if let Some(ipv6) = location.ipv6 {
println!("IPv6: {ipv6}");
}
println!(
"Position: {:.5}°N, {:.5}°W",
location.latitude, location.longitude
);
Ok(())
}
fn print_account_loggedout(state: &TunnelState, device: &DeviceState) {
match state {
TunnelState::Connecting { .. } | TunnelState::Connected { .. } | TunnelState::Error(_) => {
match device {
DeviceState::LoggedOut => {
println!("Warning: You are not logged in to an account.")
}
DeviceState::Revoked => println!("Warning: This device has been revoked."),
DeviceState::LoggedIn(_) => (),
}
}
TunnelState::Disconnected | TunnelState::Disconnecting(_) => (),
}
}
|