diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-11-09 22:32:01 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-11-24 12:29:47 +0100 |
| commit | ec4bfb027808b91746223715377c24ec78a9d015 (patch) | |
| tree | f60803848c13bab562ff2c84a7a7daa092eaf1c6 | |
| parent | 44bd239dcb61e7c8fb773c875f79819e0755dca7 (diff) | |
| download | mullvadvpn-ec4bfb027808b91746223715377c24ec78a9d015.tar.xz mullvadvpn-ec4bfb027808b91746223715377c24ec78a9d015.zip | |
Use separate routing policies for tunnel routes
| -rw-r--r-- | talpid-core/src/linux/mod.rs | 1 | ||||
| -rw-r--r-- | talpid-core/src/routing/linux.rs | 122 | ||||
| -rw-r--r-- | talpid-core/src/routing/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/routing/unix.rs | 51 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/openvpn.rs | 9 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/mod.rs | 11 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 7 |
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( |
