From e3643b1005915b04952119e32a20fd54483d3fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C4=ABls=20Pi=C5=86=C4=B7is?= Date: Wed, 6 Jan 2021 12:53:19 +0000 Subject: [PATCH] Query route for an IP to infer offline state --- CHANGELOG.md | 3 + talpid-core/src/offline/linux.rs | 108 +++++++++++++++---------------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d7fb1c063f..87c87f167449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ Line wrap the file at 100 chars. Th - Prefer the last used API endpoint when the service starts back up, as well as in other tools such as the problem report tool. +#### Linux +- Improve offline check to query the routing table to allow users to use a bridged adapter as their + primary interface. ## [2020.8-beta2] - 2020-12-11 This release is for desktop only. diff --git a/talpid-core/src/offline/linux.rs b/talpid-core/src/offline/linux.rs index c581b9b0d3cc..b00c8f79f3a6 100644 --- a/talpid-core/src/offline/linux.rs +++ b/talpid-core/src/offline/linux.rs @@ -3,17 +3,13 @@ use futures::{ channel::{mpsc::UnboundedSender, oneshot}, FutureExt, StreamExt, TryStreamExt, }; -use netlink_packet_route::{ - constants::{ARPHRD_LOOPBACK, ARPHRD_NONE, IFF_LOWER_UP, IFF_UP}, - rtnl::link::nlas::{Info as LinkInfo, InfoKind, Nla as LinkNla}, - LinkMessage, -}; +use netlink_packet_route::rtnl::route::nlas::Nla as RouteNla; use rtnetlink::{ constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK, RTMGRP_NOTIFY}, sys::SocketAddr, - Handle, + Handle, IpVersion, }; -use std::{collections::BTreeSet, io, sync::Weak}; +use std::{io, net::Ipv4Addr, sync::Weak}; pub type Result = std::result::Result; @@ -26,6 +22,9 @@ pub enum Error { #[error(display = "Failed to get list of IP addresses")] GetAddressesError(#[error(source)] failure::Compat), + #[error(display = "Failed to get a route for an arbitrary IP address")] + GetRouteError(#[error(source)] failure::Compat), + #[error(display = "Failed to connect to netlink socket")] NetlinkConnectionError(#[error(source)] io::Error), @@ -50,9 +49,11 @@ pub struct MonitorHandle { _stop_connection_tx: oneshot::Sender<()>, } +const PUBLIC_INTERNET_ADDRESS: Ipv4Addr = Ipv4Addr::new(193, 138, 218, 78); + impl MonitorHandle { pub async fn is_offline(&mut self) -> bool { - match check_offline_state(&self.handle).await { + match public_ip_not_reachable(&self.handle).await { Ok(is_offline) => is_offline, Err(err) => { log::error!( @@ -86,7 +87,7 @@ pub async fn spawn_monitor(sender: Weak>) -> Resu _ = stop_rx.fuse() => (), } }); - let mut is_offline = check_offline_state(&handle).await?; + let mut is_offline = public_ip_not_reachable(&handle).await?; let monitor_handle = MonitorHandle { handle: handle.clone(), @@ -98,7 +99,7 @@ pub async fn spawn_monitor(sender: Weak>) -> Resu while let Some(_new_message) = messages.next().await { match sender.upgrade() { Some(sender) => { - let new_offline_state = check_offline_state(&handle).await.unwrap_or(false); + let new_offline_state = public_ip_not_reachable(&handle).await.unwrap_or(false); if new_offline_state != is_offline { is_offline = new_offline_state; let _ = sender.unbounded_send(TunnelCommand::IsOffline(is_offline)); @@ -113,63 +114,58 @@ pub async fn spawn_monitor(sender: Weak>) -> Resu Ok(monitor_handle) } -async fn check_offline_state(handle: &Handle) -> Result { - let mut link_request = handle.link().get().execute(); - let mut links = BTreeSet::new(); - while let Some(link) = link_request - .try_next() - .await - .map_err(failure::Fail::compat) - .map_err(Error::GetLinksError)? - { - if link_provides_connectivity(&link) { - links.insert(link.header.index); - } - } - - if links.is_empty() { - return Ok(true); - } - - let mut address_request = handle.address().get().execute(); - while let Some(address) = address_request +async fn public_ip_not_reachable(handle: &Handle) -> Result { + let mut request = handle.route().get(IpVersion::V4); + let message = request.message_mut(); + message + .nlas + .push(RouteNla::Mark(crate::linux::TUNNEL_FW_MARK)); + message.nlas.push(RouteNla::Destination( + PUBLIC_INTERNET_ADDRESS.octets().to_vec(), + )); + message.header.destination_prefix_length = 32; + let mut stream = request.execute(); + while let Some(message) = stream .try_next() .await .map_err(failure::Fail::compat) - .map_err(Error::GetAddressesError)? + .map_err(Error::GetRouteError)? { - if links.contains(&address.header.index) { - return Ok(false); + for nla in message.nlas.iter() { + if let RouteNla::Gateway(_) = nla { + return Ok(false); + } } } Ok(true) } +#[cfg(test)] +mod test { + use super::*; + use rtnetlink::{ + constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK, RTMGRP_NOTIFY}, + sys::SocketAddr, + }; -// TODO: Improve by allowing bridge links to provide connectivity, will require route checking. -fn link_provides_connectivity(link: &LinkMessage) -> bool { - // Some tunnels have the link layer type set to None - link.header.link_layer_type != ARPHRD_NONE - && link.header.link_layer_type != ARPHRD_LOOPBACK - && (link.header.flags & IFF_UP > 0 || link.header.flags & IFF_LOWER_UP > 0) - && !is_virtual_interface(link) -} + #[test] + fn test_route_table_query() { + let mut runtime = tokio::runtime::Runtime::new().expect("failed to initialize runtime"); + let (mut connection, handle, _) = runtime.block_on(async { + rtnetlink::new_connection() + .map_err(Error::NetlinkConnectionError) + .expect("Failed to create a netlink connection") + }); -fn is_virtual_interface(link: &LinkMessage) -> bool { - for nla in link.nlas.iter() { - if let LinkNla::Info(info_nlas) = nla { - for info in info_nlas.iter() { - // LinkInfo::Kind seems to only be set when the link is actually virtual - if let LinkInfo::Kind(ref kind) = info { - use InfoKind::*; - return match kind { - Dummy | Bridge | Tun | Nlmon | IpTun => true, - _ => false, - }; - } - } - } + let mgroup_flags = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK | RTMGRP_NOTIFY; + let addr = SocketAddr::new(0, mgroup_flags); + + connection.socket_mut().bind(&addr).unwrap(); + runtime.spawn(connection); + + runtime + .block_on(public_ip_not_reachable(&handle)) + .expect("Failed to query routing table"); } - false }