summaryrefslogtreecommitdiffhomepage
path: root/mullvad-cli/src/cmds/tunnel_state.rs
blob: 11c57aa324e7441140adaa5ac58cc5fa30f5b745 (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
use crate::format;
use anyhow::{Result, anyhow, bail};
use futures::{Stream, StreamExt};
use mullvad_management_interface::{MullvadProxyClient, client::DaemonEvent};
use mullvad_types::{device::DeviceState, states::TunnelState};

pub async fn connect(wait: bool) -> Result<()> {
    let mut rpc = MullvadProxyClient::new().await?;

    let device_state = rpc.get_device().await?;
    print_account_loggedout(&device_state);

    let listener = if wait {
        Some(rpc.events_listen().await?)
    } else {
        None
    };

    if rpc.connect_tunnel().await?
        && let Some(receiver) = listener
    {
        wait_for_tunnel_state(receiver, |state| match state {
            TunnelState::Connected { .. } => Ok(true),
            TunnelState::Error(_) => Err(anyhow!("Failed to connect")),
            _ => Ok(false),
        })
        .await?;
    }

    Ok(())
}

pub async fn disconnect(wait: bool) -> Result<()> {
    let mut rpc = MullvadProxyClient::new().await?;

    let listener = if wait {
        Some(rpc.events_listen().await?)
    } else {
        None
    };

    if rpc.disconnect_tunnel().await?
        && let Some(receiver) = listener
    {
        wait_for_tunnel_state(receiver, |state| Ok(state.is_disconnected())).await?;
    }

    Ok(())
}

pub async fn reconnect(wait: bool) -> Result<()> {
    let mut rpc = MullvadProxyClient::new().await?;

    let device_state = rpc.get_device().await?;
    print_account_loggedout(&device_state);

    let listener = if wait {
        Some(rpc.events_listen().await?)
    } else {
        None
    };

    let reconnecting = rpc.reconnect_tunnel().await?;
    if !reconnecting {
        bail!("Not reconnecting due to being in disconnected state")
    }
    if let Some(receiver) = listener {
        wait_for_tunnel_state(receiver, |state| match state {
            TunnelState::Connected { .. } => Ok(true),
            TunnelState::Error(_) => Err(anyhow!("Failed to reconnect")),
            _ => Ok(false),
        })
        .await?;
    }

    Ok(())
}

async fn wait_for_tunnel_state(
    mut event_stream: impl Stream<
        Item = std::result::Result<DaemonEvent, mullvad_management_interface::Error>,
    > + Unpin,
    matches_event: impl Fn(&TunnelState) -> Result<bool>,
) -> Result<()> {
    while let Some(state) = event_stream.next().await {
        if let DaemonEvent::TunnelState(new_state) = state? {
            format::print_state(&new_state, None, false);
            if matches_event(&new_state)? {
                return Ok(());
            }
        }
    }
    Err(anyhow!("Failed to wait for expected tunnel state"))
}

/// Checks the if the user is logged in. If not, we print a warning to get their
/// attention.
///
/// When using the CLI, the user could potentially end up in a situation where
/// they try to connect to a Mullvad relay without having successfully logged in
/// to their account. In this case, we at least want to issue a warning to guide
/// the user when they inevitably will go troubleshooting.
fn print_account_loggedout(state: &DeviceState) {
    match state {
        DeviceState::LoggedOut => println!("Warning: You are not logged in to an account."),
        DeviceState::Revoked => println!("Warning: This device has been revoked"),
        DeviceState::LoggedIn(_) => return, // Normal case, do nothing.
    };

    println!(
        "Mullvad is blocking all network traffic until you perform one of the following actions:

1. Login to a Mullvad account with available time/credits.
2. Disconnect from Mullvad VPN. This can either be done from the CLI or the Mullvad App.

For more information, try 'mullvad account -h' or 'mullvad disconnect -h'"
    );
}