summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src/cmds/status.rs
blob: 15b0d10dfecf82408482ae4aa7e26c09dc95bcfd (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
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 IP information. When it arrives, we will get another
                        // tunnel state of the same enum type, but with the IP filled in. This
                        // match statement checks for duplicate tunnel states and skips the second
                        // print to avoid spamming the user.
                        match (&previous_tunnel_state, &new_state) {
                            (Some(TunnelState::Disconnected(_)), TunnelState::Disconnected(_))
                            | (
                                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_loggedout(&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_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(_) => (),
    }
}