summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-11-09 22:32:01 +0100
committerDavid Lönnhager <david.l@mullvad.net>2020-11-24 12:29:47 +0100
commitec4bfb027808b91746223715377c24ec78a9d015 (patch)
treef60803848c13bab562ff2c84a7a7daa092eaf1c6
parent44bd239dcb61e7c8fb773c875f79819e0755dca7 (diff)
downloadmullvadvpn-ec4bfb027808b91746223715377c24ec78a9d015.tar.xz
mullvadvpn-ec4bfb027808b91746223715377c24ec78a9d015.zip
Use separate routing policies for tunnel routes
-rw-r--r--talpid-core/src/linux/mod.rs1
-rw-r--r--talpid-core/src/routing/linux.rs122
-rw-r--r--talpid-core/src/routing/mod.rs2
-rw-r--r--talpid-core/src/routing/unix.rs51
-rw-r--r--talpid-core/src/tunnel/openvpn.rs9
-rw-r--r--talpid-core/src/tunnel/wireguard/mod.rs11
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs7
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs7
8 files changed, 189 insertions, 21 deletions
diff --git a/talpid-core/src/linux/mod.rs b/talpid-core/src/linux/mod.rs
index d54656309e..0441e1f7bd 100644
--- a/talpid-core/src/linux/mod.rs
+++ b/talpid-core/src/linux/mod.rs
@@ -31,3 +31,4 @@ pub enum IfaceIndexLookupError {
// b"mole" is [ 0x6d, 0x6f 0x6c, 0x65 ]
pub const TUNNEL_FW_MARK: u32 = 0x6d6f6c65;
+pub const TUNNEL_TABLE_ID: u32 = 0x6d6f6c65;
diff --git a/talpid-core/src/routing/linux.rs b/talpid-core/src/routing/linux.rs
index 9cf1126558..858247ced3 100644
--- a/talpid-core/src/routing/linux.rs
+++ b/talpid-core/src/routing/linux.rs
@@ -2,21 +2,18 @@ use crate::{
routing::{imp::RouteManagerCommand, NetNode, Node, RequiredRoute, Route},
split_tunnel,
};
-
-use talpid_types::ErrorExt;
-
-use ipnetwork::IpNetwork;
use std::{
collections::{BTreeMap, HashSet},
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
+use talpid_types::ErrorExt;
use futures::{channel::mpsc::UnboundedReceiver, future::FutureExt, StreamExt, TryStreamExt};
-
-
+use ipnetwork::IpNetwork;
+use lazy_static::lazy_static;
use netlink_packet_route::{
- constants::ARPHRD_LOOPBACK,
+ constants::{ARPHRD_LOOPBACK, FIB_RULE_INVERT, FR_ACT_TO_TBL},
link::{nlas::Nla as LinkNla, LinkMessage},
route::{nlas::Nla as RouteNla, RouteHeader, RouteMessage},
rtnl::{
@@ -38,6 +35,43 @@ use rtnetlink::{
use libc::{AF_INET, AF_INET6};
+lazy_static! {
+ static ref SUPPRESS_RULE_V4: RuleMessage = RuleMessage {
+ header: RuleHeader {
+ family: AF_INET as u8,
+ action: FR_ACT_TO_TBL,
+ ..RuleHeader::default()
+ },
+ nlas: vec![
+ RuleNla::SuppressPrefixLen(0),
+ RuleNla::Table(RT_TABLE_MAIN as u32),
+ ],
+ };
+ static ref SUPPRESS_RULE_V6: RuleMessage = {
+ let mut v6_rule = SUPPRESS_RULE_V4.clone();
+ v6_rule.header.family = AF_INET6 as u8;
+ v6_rule
+ };
+ static ref NO_FWMARK_RULE_V4: RuleMessage = RuleMessage {
+ header: RuleHeader {
+ family: AF_INET as u8,
+ action: FR_ACT_TO_TBL,
+ flags: FIB_RULE_INVERT,
+ ..RuleHeader::default()
+ },
+ nlas: vec![
+ RuleNla::FwMark(crate::linux::TUNNEL_FW_MARK),
+ RuleNla::Table(crate::linux::TUNNEL_TABLE_ID),
+ ],
+ };
+ static ref NO_FWMARK_RULE_V6: RuleMessage = {
+ let mut v6_rule = NO_FWMARK_RULE_V4.clone();
+ v6_rule.header.family = AF_INET6 as u8;
+ v6_rule
+ };
+}
+
+
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can happen in the Linux routing integration
@@ -149,6 +183,7 @@ impl RouteManagerImpl {
};
monitor.initialize_exclusions_routes().await?;
+ monitor.clear_routing_rules().await?;
monitor.default_routes = monitor.get_default_routes().await?;
monitor.best_default_node_v4 =
@@ -246,6 +281,61 @@ impl RouteManagerImpl {
Ok(())
}
+ async fn create_routing_rules(&mut self) -> Result<()> {
+ use netlink_packet_route::constants::*;
+
+ self.clear_routing_rules().await?;
+
+ for rule in &[
+ &*NO_FWMARK_RULE_V4,
+ &*NO_FWMARK_RULE_V6,
+ &*SUPPRESS_RULE_V4,
+ &*SUPPRESS_RULE_V6,
+ ] {
+ let mut req = NetlinkMessage::from(RtnlMessage::NewRule((*rule).clone()));
+ req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE;
+
+ let mut response = self.handle.request(req).map_err(Error::NetlinkError)?;
+
+ while let Some(message) = response.next().await {
+ if let NetlinkPayload::Error(error) = message.payload {
+ return Err(Error::NetlinkError(rtnetlink::Error::NetlinkError(error)));
+ }
+ }
+ }
+ Ok(())
+ }
+
+ async fn clear_routing_rules(&mut self) -> Result<()> {
+ for rule in &[
+ &*NO_FWMARK_RULE_V4,
+ &*NO_FWMARK_RULE_V6,
+ &*SUPPRESS_RULE_V4,
+ &*SUPPRESS_RULE_V6,
+ ] {
+ self.delete_rule_if_exists((*rule).clone()).await?;
+ }
+ Ok(())
+ }
+
+ async fn delete_rule_if_exists(&mut self, rule: RuleMessage) -> Result<()> {
+ use netlink_packet_route::constants::*;
+
+ let mut req = NetlinkMessage::from(RtnlMessage::DelRule(rule));
+ req.header.flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ let mut response = self.handle.request(req).map_err(Error::NetlinkError)?;
+
+ while let Some(message) = response.next().await {
+ if let NetlinkPayload::Error(error) = message.payload {
+ if error.to_io().kind() != io::ErrorKind::NotFound {
+ return Err(Error::NetlinkError(rtnetlink::Error::NetlinkError(error)));
+ }
+ }
+ }
+ Ok(())
+ }
+
/// Route PID-associated packets through the physical interface.
async fn enable_exclusions_routes(&mut self) -> Result<()> {
// TODO: IPv6
@@ -722,7 +812,10 @@ impl RouteManagerImpl {
}
- pub async fn run(mut self, manage_rx: UnboundedReceiver<RouteManagerCommand>) -> Result<()> {
+ pub(crate) async fn run(
+ mut self,
+ manage_rx: UnboundedReceiver<RouteManagerCommand>,
+ ) -> Result<()> {
let mut manage_rx = manage_rx.fuse();
loop {
futures::select! {
@@ -751,6 +844,12 @@ impl RouteManagerImpl {
log::debug!("Adding routes: {:?}", routes);
let _ = result_tx.send(self.add_required_routes(routes.clone()).await);
}
+ RouteManagerCommand::CreateRoutingRules(result_tx) => {
+ let _ = result_tx.send(self.create_routing_rules().await);
+ }
+ RouteManagerCommand::ClearRoutingRules(result_tx) => {
+ let _ = result_tx.send(self.clear_routing_rules().await);
+ }
RouteManagerCommand::EnableExclusionsRoutes(result_tx) => {
let _ = result_tx.send(self.enable_exclusions_routes().await);
}
@@ -1122,6 +1221,13 @@ impl RouteManagerImpl {
);
}
self.cleanup_routes().await;
+
+ if let Err(error) = self.clear_routing_rules().await {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to remove routing rules")
+ );
+ }
}
}
diff --git a/talpid-core/src/routing/mod.rs b/talpid-core/src/routing/mod.rs
index 6ab4240fd1..9fa5d28737 100644
--- a/talpid-core/src/routing/mod.rs
+++ b/talpid-core/src/routing/mod.rs
@@ -95,7 +95,7 @@ impl RequiredRoute {
node: node.into(),
prefix,
#[cfg(target_os = "linux")]
- table_id: u32::from(RT_TABLE_MAIN),
+ table_id: u32::from(crate::linux::TUNNEL_TABLE_ID),
}
}
diff --git a/talpid-core/src/routing/unix.rs b/talpid-core/src/routing/unix.rs
index 147a33a1a5..4db258b3a5 100644
--- a/talpid-core/src/routing/unix.rs
+++ b/talpid-core/src/routing/unix.rs
@@ -81,30 +81,53 @@ impl RouteManagerHandle {
.block_on(response_rx)
.map_err(|_| Error::ManagerChannelDown)?)
}
+
+ /// Ensure that packets are routed using the correct tables.
+ #[cfg(target_os = "linux")]
+ pub fn create_routing_rules(&self) -> Result<(), Error> {
+ let (response_tx, response_rx) = oneshot::channel();
+ self.tx
+ .unbounded_send(RouteManagerCommand::CreateRoutingRules(response_tx))
+ .map_err(|_| Error::RouteManagerDown)?;
+ self.runtime
+ .block_on(response_rx)
+ .map_err(|_| Error::ManagerChannelDown)?
+ .map_err(Error::PlatformError)
+ }
+
+ /// Remove any routing rules created by [`create_routing_rules`].
+ #[cfg(target_os = "linux")]
+ pub fn clear_routing_rules(&self) -> Result<(), Error> {
+ let (response_tx, response_rx) = oneshot::channel();
+ self.tx
+ .unbounded_send(RouteManagerCommand::ClearRoutingRules(response_tx))
+ .map_err(|_| Error::RouteManagerDown)?;
+ self.runtime
+ .block_on(response_rx)
+ .map_err(|_| Error::ManagerChannelDown)?
+ .map_err(Error::PlatformError)
+ }
}
/// Commands for the underlying route manager object.
#[derive(Debug)]
-pub enum RouteManagerCommand {
- /// Adds required routes
+pub(crate) enum RouteManagerCommand {
AddRoutes(
HashSet<RequiredRoute>,
oneshot::Sender<Result<(), PlatformError>>,
),
- /// Clears required routes
ClearRoutes,
- /// Shuts down the route manager
Shutdown(oneshot::Sender<()>),
- /// Routes traffic with correct fwmark using the exclusions table
+ #[cfg(target_os = "linux")]
+ CreateRoutingRules(oneshot::Sender<Result<(), PlatformError>>),
+ #[cfg(target_os = "linux")]
+ ClearRoutingRules(oneshot::Sender<Result<(), PlatformError>>),
#[cfg(target_os = "linux")]
EnableExclusionsRoutes(oneshot::Sender<Result<(), PlatformError>>),
- /// Removes rule for routing marked traffic differently.
#[cfg(target_os = "linux")]
DisableExclusionsRoutes,
- /// Adds link to ignore in the exclusions table.
#[cfg(target_os = "linux")]
SetTunnelLink(String, oneshot::Sender<()>),
- /// Adds exclusions table route for sending DNS requests via the tunnel.
#[cfg(target_os = "linux")]
RouteExclusionsDns(
String,
@@ -191,6 +214,18 @@ impl RouteManager {
}
}
+ /// Ensure that packets are routed using the correct tables.
+ #[cfg(target_os = "linux")]
+ pub fn create_routing_rules(&mut self) -> Result<(), Error> {
+ self.handle()?.create_routing_rules()
+ }
+
+ /// Remove any routing rules created by [`create_routing_rules`].
+ #[cfg(target_os = "linux")]
+ pub fn clear_routing_rules(&mut self) -> Result<(), Error> {
+ self.handle()?.clear_routing_rules()
+ }
+
/// Route PID-associated packets through the physical interface.
#[cfg(target_os = "linux")]
pub fn enable_exclusions_routes(&mut self) -> Result<(), Error> {
diff --git a/talpid-core/src/tunnel/openvpn.rs b/talpid-core/src/tunnel/openvpn.rs
index a327628037..312572e9ae 100644
--- a/talpid-core/src/tunnel/openvpn.rs
+++ b/talpid-core/src/tunnel/openvpn.rs
@@ -220,7 +220,14 @@ impl OpenVpnMonitor<OpenVpnCommand> {
.set_tunnel_link(interface)
.unwrap();
let routes = extract_routes(&env).unwrap();
- if let Err(error) = route_manager_handle.clone().add_routes(routes) {
+ let route_manager_handle = route_manager_handle.clone();
+ if let Err(error) = route_manager_handle.add_routes(routes) {
+ log::error!("{}", error.display_chain());
+ panic!("Failed to add routes");
+ }
+
+ #[cfg(target_os = "linux")]
+ if let Err(error) = route_manager_handle.create_routing_rules() {
log::error!("{}", error.display_chain());
panic!("Failed to add routes");
}
diff --git a/talpid-core/src/tunnel/wireguard/mod.rs b/talpid-core/src/tunnel/wireguard/mod.rs
index 24223b7603..f74fd22a1c 100644
--- a/talpid-core/src/tunnel/wireguard/mod.rs
+++ b/talpid-core/src/tunnel/wireguard/mod.rs
@@ -84,9 +84,14 @@ impl WireguardMonitor {
let iface_name = tunnel.get_interface_name().to_string();
#[cfg(target_os = "linux")]
- route_manager
- .set_tunnel_link(&iface_name)
- .map_err(Error::SetupRoutingError)?;
+ {
+ route_manager
+ .set_tunnel_link(&iface_name)
+ .map_err(Error::SetupRoutingError)?;
+ route_manager
+ .create_routing_rules()
+ .map_err(Error::SetupRoutingError)?;
+ }
route_manager
.add_routes(Self::get_routes(&iface_name, &config))
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index f0bd874530..4034092f84 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -151,6 +151,13 @@ impl ConnectedState {
if let Err(error) = shared_values.route_manager.clear_routes() {
log::error!("{}", error.display_chain_with_msg("Failed to clear routes"));
}
+ #[cfg(target_os = "linux")]
+ if let Err(error) = shared_values.route_manager.clear_routing_rules() {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to clear routing rules")
+ );
+ }
}
fn disconnect(
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index 8689cc05bf..85ddb87617 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -194,6 +194,13 @@ impl ConnectingState {
if let Err(error) = shared_values.route_manager.clear_routes() {
log::error!("{}", error.display_chain_with_msg("Failed to clear routes"));
}
+ #[cfg(target_os = "linux")]
+ if let Err(error) = shared_values.route_manager.clear_routing_rules() {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to clear routing rules")
+ );
+ }
}
fn disconnect(