summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2021-06-21 15:18:09 +0200
committerDavid Lönnhager <david.l@mullvad.net>2021-06-22 12:46:22 +0200
commit927ec7f1d40ff002a9773178525073bc3f774ea5 (patch)
tree67d7a840dca6c964edeaa96413187153e92f9aba
parent10db393d7e588dbf2f12be6c8f4384db0fe3b562 (diff)
downloadmullvadvpn-927ec7f1d40ff002a9773178525073bc3f774ea5.tar.xz
mullvadvpn-927ec7f1d40ff002a9773178525073bc3f774ea5.zip
Add get_destination_route to the Linux route manager
-rw-r--r--talpid-core/src/routing/linux.rs76
-rw-r--r--talpid-core/src/routing/unix.rs42
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)]