diff options
| author | David Lönnhager <david.l@mullvad.net> | 2021-06-21 15:18:09 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2021-06-22 12:46:22 +0200 |
| commit | 927ec7f1d40ff002a9773178525073bc3f774ea5 (patch) | |
| tree | 67d7a840dca6c964edeaa96413187153e92f9aba | |
| parent | 10db393d7e588dbf2f12be6c8f4384db0fe3b562 (diff) | |
| download | mullvadvpn-927ec7f1d40ff002a9773178525073bc3f774ea5.tar.xz mullvadvpn-927ec7f1d40ff002a9773178525073bc3f774ea5.zip | |
Add get_destination_route to the Linux route manager
| -rw-r--r-- | talpid-core/src/routing/linux.rs | 76 | ||||
| -rw-r--r-- | talpid-core/src/routing/unix.rs | 42 |
2 files changed, 110 insertions, 8 deletions
diff --git a/talpid-core/src/routing/linux.rs b/talpid-core/src/routing/linux.rs index d0878cf3b8..a63430fed3 100644 --- a/talpid-core/src/routing/linux.rs +++ b/talpid-core/src/routing/linux.rs @@ -17,7 +17,7 @@ use futures::{ use ipnetwork::IpNetwork; use lazy_static::lazy_static; use netlink_packet_route::{ - constants::{ARPHRD_LOOPBACK, FIB_RULE_INVERT, FR_ACT_TO_TBL}, + constants::{ARPHRD_LOOPBACK, FIB_RULE_INVERT, FR_ACT_TO_TBL, NLM_F_REQUEST}, link::{nlas::Nla as LinkNla, LinkMessage}, route::{nlas::Nla as RouteNla, RouteHeader, RouteMessage}, rtnl::{ @@ -33,7 +33,7 @@ use netlink_packet_route::{ use rtnetlink::{ constants::{RTMGRP_IPV4_ROUTE, RTMGRP_IPV6_ROUTE, RTMGRP_LINK, RTMGRP_NOTIFY}, sys::SocketAddr, - Handle, + Handle, IpVersion, }; use libc::{AF_INET, AF_INET6}; @@ -109,6 +109,12 @@ pub enum Error { #[error(display = "Unknown device index - {}", _0)] UnknownDeviceIndex(u32), + #[error(display = "Failed to get a route for the given IP address")] + GetRouteError(#[error(source)] rtnetlink::Error), + + #[error(display = "No netlink response for route query")] + NoRouteError, + /// Unable to create routing table for tagged connections and packets. #[error(display = "Cannot find a free routing table ID")] NoFreeRoutingTableId, @@ -359,6 +365,9 @@ impl RouteManagerImpl { RouteManagerCommand::NewChangeListener(result_tx) => { let _ = result_tx.send(self.listen()); } + RouteManagerCommand::GetDestinationRoute(destination, set_mark, result_tx) => { + let _ = result_tx.send(self.get_destination_route(&destination, set_mark).await); + } RouteManagerCommand::ClearRoutes => { log::debug!("Clearing routes"); self.cleanup_routes().await; @@ -715,6 +724,36 @@ impl RouteManagerImpl { ); } } + + async fn get_destination_route( + &self, + destination: &IpAddr, + set_mark: bool, + ) -> Result<Option<Route>> { + let mut request = self.handle.route().get(get_ip_version(destination)); + let octets = match destination { + IpAddr::V4(address) => address.octets().to_vec(), + IpAddr::V6(address) => address.octets().to_vec(), + }; + let message = request.message_mut(); + if set_mark { + message + .nlas + .push(RouteNla::Mark(crate::linux::TUNNEL_FW_MARK)); + } + message.header.destination_prefix_length = 8u8 * (octets.len() as u8); + message.header.flags = RouteFlags::RTM_F_FIB_MATCH; + message.nlas.push(RouteNla::Destination(octets)); + let mut stream = execute_route_get_request(self.handle.clone(), message.clone()); + match stream.try_next().await { + Ok(Some(route_msg)) => self.parse_route_message(route_msg), + Ok(None) => Err(Error::NoRouteError), + Err(rtnetlink::Error::NetlinkError(nl_err)) if nl_err.code == -libc::ENETUNREACH => { + Ok(None) + } + Err(err) => Err(Error::GetRouteError(err)), + } + } } fn ip_to_bytes(addr: IpAddr) -> Vec<u8> { @@ -733,6 +772,39 @@ fn compat_table_id(id: u32) -> u8 { } } +fn get_ip_version(addr: &IpAddr) -> IpVersion { + if addr.is_ipv4() { + IpVersion::V4 + } else { + IpVersion::V6 + } +} + +fn execute_route_get_request( + mut handle: Handle, + message: RouteMessage, +) -> impl TryStream<Ok = RouteMessage, Error = rtnetlink::Error> { + use futures::future::{self, Either}; + use rtnetlink::Error; + + let mut req = NetlinkMessage::from(RtnlMessage::GetRoute(message)); + req.header.flags = NLM_F_REQUEST; + + match handle.request(req) { + Ok(response) => Either::Left(response.map(move |msg| { + let (header, payload) = msg.into_parts(); + match payload { + NetlinkPayload::InnerMessage(RtnlMessage::NewRoute(msg)) => Ok(msg), + NetlinkPayload::Error(err) => Err(Error::NetlinkError(err)), + _ => Err(Error::UnexpectedMessage(NetlinkMessage::new( + header, payload, + ))), + } + })), + Err(e) => Either::Right(future::err::<RouteMessage, Error>(e).into_stream()), + } +} + #[derive(Debug)] struct NetworkInterface { name: String, diff --git a/talpid-core/src/routing/unix.rs b/talpid-core/src/routing/unix.rs index 8806ee3ab4..dca1ca4808 100644 --- a/talpid-core/src/routing/unix.rs +++ b/talpid-core/src/routing/unix.rs @@ -3,15 +3,18 @@ // TODO: remove the allow(dead_code) for android once it's up to scratch. use super::{RequiredRoute, Route}; -use futures::{ - channel::{ - mpsc::{self, UnboundedSender}, - oneshot, - }, - stream::Stream, +use futures::channel::{ + mpsc::{self, UnboundedSender}, + oneshot, }; use std::{collections::HashSet, io}; +#[cfg(target_os = "linux")] +use futures::stream::Stream; + +#[cfg(target_os = "linux")] +use std::net::IpAddr; + #[cfg(target_os = "macos")] #[path = "macos.rs"] mod imp; @@ -103,6 +106,27 @@ impl RouteManagerHandle { .map_err(|_| Error::RouteManagerDown)?; response_rx.await.map_err(|_| Error::ManagerChannelDown) } + + /// Listen for route changes. + #[cfg(target_os = "linux")] + pub async fn get_destination_route( + &self, + destination: IpAddr, + set_mark: bool, + ) -> Result<Option<Route>, Error> { + let (response_tx, response_rx) = oneshot::channel(); + self.tx + .unbounded_send(RouteManagerCommand::GetDestinationRoute( + destination, + set_mark, + response_tx, + )) + .map_err(|_| Error::RouteManagerDown)?; + response_rx + .await + .map_err(|_| Error::ManagerChannelDown)? + .map_err(Error::PlatformError) + } } /// Commands for the underlying route manager object. @@ -120,6 +144,12 @@ pub(crate) enum RouteManagerCommand { ClearRoutingRules(oneshot::Sender<Result<(), PlatformError>>), #[cfg(target_os = "linux")] NewChangeListener(oneshot::Sender<mpsc::UnboundedReceiver<CallbackMessage>>), + #[cfg(target_os = "linux")] + GetDestinationRoute( + IpAddr, + bool, + oneshot::Sender<Result<Option<Route>, PlatformError>>, + ), } #[derive(Debug, Clone)] |
