summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src/offline/linux.rs
blob: 1cc9901784348c9e9eeda2660d3bc37f149e9488 (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
use futures::{StreamExt, channel::mpsc::UnboundedSender};
use std::{
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
    sync::Arc,
};
use talpid_routing::RouteManagerHandle;
use talpid_types::{ErrorExt, net::Connectivity};

pub type Result<T> = std::result::Result<T, Error>;

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("The route manager returned an error")]
    RouteManagerError(#[source] talpid_routing::Error),
}

pub struct MonitorHandle {
    route_manager: RouteManagerHandle,
    fwmark: Option<u32>,
    _notify_tx: Arc<UnboundedSender<Connectivity>>,
}

/// A non-local IPv4 address.
const PUBLIC_INTERNET_ADDRESS_V4: IpAddr = IpAddr::V4(Ipv4Addr::new(193, 138, 218, 78));
/// A non-local IPv6 address.
const PUBLIC_INTERNET_ADDRESS_V6: IpAddr =
    IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6));

impl MonitorHandle {
    pub async fn connectivity(&self) -> Connectivity {
        check_connectivity(&self.route_manager, self.fwmark).await
    }
}

pub async fn spawn_monitor(
    notify_tx: UnboundedSender<Connectivity>,
    route_manager: RouteManagerHandle,
    fwmark: Option<u32>,
) -> Result<MonitorHandle> {
    let mut connectivity = check_connectivity(&route_manager, fwmark).await;

    let mut listener = route_manager
        .change_listener()
        .await
        .map_err(Error::RouteManagerError)?;

    let notify_tx = Arc::new(notify_tx);
    let sender = Arc::downgrade(&notify_tx);
    let monitor_handle = MonitorHandle {
        route_manager: route_manager.clone(),
        fwmark,
        _notify_tx: notify_tx,
    };

    tokio::spawn(async move {
        while let Some(_event) = listener.next().await {
            match sender.upgrade() {
                Some(sender) => {
                    let new_connectivity = check_connectivity(&route_manager, fwmark).await;
                    if new_connectivity != connectivity {
                        connectivity = new_connectivity;
                        let _ = sender.unbounded_send(connectivity);
                    }
                }
                None => return,
            }
        }
    });

    Ok(monitor_handle)
}

async fn check_connectivity(handle: &RouteManagerHandle, fwmark: Option<u32>) -> Connectivity {
    let route_exists = |destination| async move {
        handle
            .get_destination_route(destination, fwmark)
            .await
            .map(|route| route.is_some())
    };

    match (
        route_exists(PUBLIC_INTERNET_ADDRESS_V4).await,
        route_exists(PUBLIC_INTERNET_ADDRESS_V6).await,
    ) {
        (Ok(ipv4), Ok(ipv6)) => Connectivity::new(ipv4, ipv6),
        // If we fail to retrieve the IPv4 route, always assume we're connected
        (Err(err), _) => {
            log::error!(
                "Failed to verify offline state: {}. Presuming connectivity",
                err
            );
            Connectivity::PresumeOnline
        }
        // Errors for IPv6 likely mean it's disabled, so assume it's unavailable
        (Ok(ipv4), Err(err)) => {
            log::trace!(
                "{}",
                err.display_chain_with_msg(
                    "Failed to infer offline state for IPv6. Assuming it's unavailable"
                )
            );
            Connectivity::new(ipv4, false)
        }
    }
}