summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src/cmds/status.rs
blob: 8880c4c69c85e414e3e0356bf6f59e7feb319137 (plain)
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
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,

    /// Enable debug output
    #[arg(long, short = 'd')]
    debug: bool,
}

impl Status {
    pub async fn listen(mut rpc: MullvadProxyClient, args: StatusArgs) -> Result<()> {
        let mut previous_tunnel_state = None;

        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 {
                        // When we enter the connected or disconnected state, am.i.mullvad.net will
                        // be polled to get exit location. When it arrives, we will get another
                        // tunnel state of the same enum type, but with the location filled in. This
                        // match statement checks if the new state is an updated version of the old
                        // one and if so skips the print to avoid spamming the user. Note that for
                        // graphical frontends updating the drawn state with an identical one is
                        // invisible, so this is only an issue for the CLI.
                        match (&previous_tunnel_state, &new_state) {
                            (
                                Some(TunnelState::Disconnected {
                                    location: _,
                                    locked_down: was_locked_down,
                                }),
                                TunnelState::Disconnected {
                                    location: _,
                                    locked_down,
                                },
                                // Do print an updated state if the lockdown setting was changed
                            ) if was_locked_down == locked_down => continue,
                            (
                                Some(TunnelState::Connected { .. }),
                                TunnelState::Connected { .. },
                            ) => continue,
                            _ => {}
                        }
                        format::print_state(&new_state, args.verbose);
                        previous_tunnel_state = Some(new_state);
                    }
                }
                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:#?}");
                    }
                }
                DaemonEvent::NewAccessMethod(access_method) => {
                    if args.debug {
                        println!("New access method: {access_method:#?}");
                    }
                }
            }
        }
        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_logged_out(&state, &device);

    if args.debug {
        println!("Tunnel state: {state:#?}");
    } else {
        format::print_state(&state, args.verbose);
        format::print_location(&state);
    }

    if cmd == Some(Status::Listen) {
        Status::listen(rpc, args).await?;
    }
    Ok(())
}

fn print_account_logged_out(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(_) => (),
    }
}