summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-04-15 22:00:32 +0200
committerDavid Lönnhager <david.l@mullvad.net>2020-06-02 10:05:02 +0200
commit7731393aae9f476b4e64593b4357a17bab9c4f68 (patch)
treeb3b0e02c4a16dcecab6bca7b733fb9d08a051b7e
parenta612c583a3a0524ecac09c78f740d4eadad2960a (diff)
downloadmullvadvpn-7731393aae9f476b4e64593b4357a17bab9c4f68.tar.xz
mullvadvpn-7731393aae9f476b4e64593b4357a17bab9c4f68.zip
Set up routes for excluded applications using the route manager
-rw-r--r--talpid-core/src/firewall/linux.rs1
-rw-r--r--talpid-core/src/split.rs130
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs24
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs6
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs8
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs1
6 files changed, 53 insertions, 117 deletions
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index c9e35f9c2f..4d0a13b9e8 100644
--- a/talpid-core/src/firewall/linux.rs
+++ b/talpid-core/src/firewall/linux.rs
@@ -305,7 +305,6 @@ impl<'a> PolicyBatch<'a> {
let mut rule = Rule::new(&self.nat_chain);
rule.add_expr(&nft_expr!(ct mark));
rule.add_expr(&nft_expr!(cmp == split::MARK));
- // TODO: match oif interface
rule.add_expr(&nft_expr!(masquerade));
add_verdict(&mut rule, &Verdict::Accept);
self.batch.add(&rule, nftnl::MsgType::Add);
diff --git a/talpid-core/src/split.rs b/talpid-core/src/split.rs
index 7ae0f365c5..bf0ec8e7b0 100644
--- a/talpid-core/src/split.rs
+++ b/talpid-core/src/split.rs
@@ -1,12 +1,14 @@
#![cfg(target_os = "linux")]
+use crate::routing::{NetNode, Node, RequiredRoute, RouteManager};
+use ipnetwork::IpNetwork;
use regex::Regex;
use std::{
+ collections::HashSet,
fs,
io::{self, BufRead, BufReader, BufWriter, Read, Seek, Write},
- net::{AddrParseError, IpAddr},
+ net::{IpAddr, Ipv4Addr},
path::Path,
process::Command,
- str::FromStr,
};
use talpid_types::SPLIT_TUNNEL_CGROUP_NAME;
@@ -26,18 +28,6 @@ const RT_TABLES_PATH: &str = "/etc/iproute2/rt_tables";
#[derive(err_derive::Error, Debug)]
#[error(no_from)]
pub enum Error {
- /// Unable to list routing table entries.
- #[error(display = "Failed to enumerate routes")]
- EnumerateRoutes(#[error(source)] io::Error),
-
- /// Unable to find the interface/ip pair used by the physical interface.
- #[error(display = "No default route found")]
- NoDefaultRoute,
-
- /// Failed to parse string containing an IP address. May be invalid.
- #[error(display = "Failed to parse IP address")]
- ParseIpError(#[error(source)] AddrParseError),
-
/// Failed to run the process.
#[error(display = "Unable to execute process")]
ExecFailed(#[error(source)] io::Error),
@@ -74,41 +64,13 @@ pub enum Error {
#[error(display = "Unable to obtain PIDs from cgroup.procs")]
ListCGroupPids(#[error(source)] io::Error),
+ /// Unable to add route to the exclusions table.
+ #[error(display = "Failed to add routing table entry")]
+ SetupRouting(#[error(source)] crate::routing::Error),
+
/// Unable to add setup DNS routing.
#[error(display = "Failed to add routing table DNS rules")]
- SetDns(#[error(source)] io::Error),
-}
-
-struct DefaultRoute {
- interface: String,
- address: IpAddr,
-}
-
-/// Obtain the IP/interface of the physical interface
-fn get_default_route() -> Result<DefaultRoute, Error> {
- // FIXME: use netlink
- let mut cmd = Command::new("ip");
- cmd.args(&["-4", "route", "list", "table", "main"]);
- log::trace!("running cmd - {:?}", &cmd);
- let out = cmd.output().map_err(Error::EnumerateRoutes)?;
- let out_str = String::from_utf8_lossy(&out.stdout);
-
- // Find "default" row
- let expression = Regex::new(r"^default via ([0-9.]+) dev (\w+)").unwrap();
-
- for line in out_str.lines() {
- if let Some(captures) = expression.captures(&line) {
- let ip_str = captures.get(1).unwrap().as_str();
- let interface = captures.get(2).unwrap().as_str().to_string();
-
- return Ok(DefaultRoute {
- interface,
- address: IpAddr::from_str(ip_str).map_err(Error::ParseIpError)?,
- });
- }
- }
-
- Err(Error::NoDefaultRoute)
+ SetDns(#[error(source)] crate::routing::Error),
}
/// Manage routing for split tunneling cgroup.
@@ -185,28 +147,8 @@ impl SplitTunnel {
writeln!(file, "{} {}", self.table_id, ROUTING_TABLE_NAME).map_err(Error::RoutingTableSetup)
}
- /// Reset the split-tunneling routing table to its default state
- fn reset_table() -> Result<(), Error> {
- let _ = exec_ip(&["-4", "route", "flush", "table", ROUTING_TABLE_NAME]);
-
- // Force routing through the physical interface
- let default_route = get_default_route()?;
- exec_ip(&[
- "-4",
- "route",
- "add",
- "default",
- "via",
- &default_route.address.to_string(),
- "dev",
- &default_route.interface,
- "table",
- ROUTING_TABLE_NAME,
- ])
- }
-
/// Route PID-associated packets through the physical interface.
- pub fn enable_routing(&self) -> Result<(), Error> {
+ pub fn enable_routing(&self, route_manager: &mut RouteManager) -> Result<(), Error> {
// TODO: IPv6
// Create the rule if it does not exist
@@ -231,11 +173,20 @@ impl SplitTunnel {
])?;
}
- Self::reset_table()
+ // Add default route for the exclusions table
+ let zero_network =
+ ipnetwork::IpNetwork::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0).unwrap();
+ let mut required_routes = HashSet::new();
+ required_routes.insert(
+ RequiredRoute::new(zero_network, NetNode::DefaultNode).table(self.table_id as u8),
+ );
+ route_manager
+ .add_routes(required_routes)
+ .map_err(Error::SetupRouting)
}
/// Stop routing PID-associated packets through the physical interface.
- pub fn disable_routing(&self) -> Result<(), Error> {
+ pub fn disable_routing(&self) {
// TODO: IPv6
if let Err(e) = exec_ip(&[
@@ -251,35 +202,28 @@ impl SplitTunnel {
]) {
log::warn!("Failed to delete routing policy: {}", e);
}
-
- Ok(())
}
/// Route DNS requests through the tunnel interface.
- pub fn route_dns(&self, tunnel_alias: &str, dns_servers: &[IpAddr]) -> Result<(), Error> {
+ pub fn route_dns(
+ &self,
+ route_manager: &mut RouteManager,
+ tunnel_alias: &str,
+ dns_servers: &[IpAddr],
+ ) -> Result<(), Error> {
+ let mut dns_routes = HashSet::new();
+
for server in dns_servers {
- if let IpAddr::V4(addr) = server {
- let addr = addr.to_string();
- exec_ip(&[
- "-4",
- "route",
- "replace",
- &addr,
- "dev",
- tunnel_alias,
- "table",
- ROUTING_TABLE_NAME,
- ])?;
- }
+ dns_routes.insert(
+ RequiredRoute::new(
+ IpNetwork::from(*server),
+ Node::device(tunnel_alias.to_string()),
+ )
+ .table(self.table_id as u8),
+ );
}
- Ok(())
- }
-
- /// Reset DNS rules.
- pub fn flush_dns(&self) -> Result<(), Error> {
- // For now, simply flush it
- Self::reset_table()
+ route_manager.add_routes(dns_routes).map_err(Error::SetDns)
}
}
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index 3c99d5b098..ef5362d635 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -75,30 +75,28 @@ impl ConnectedState {
dns_ips.push(ipv6_gateway.into());
};
+ shared_values
+ .dns_monitor
+ .set(&self.metadata.interface, &dns_ips)
+ .map_err(BoxedError::new)?;
+
#[cfg(target_os = "linux")]
shared_values
.split_tunnel
- .route_dns(&self.metadata.interface, &dns_ips)
+ .route_dns(
+ &mut shared_values.route_manager,
+ &self.metadata.interface,
+ &dns_ips,
+ )
.map_err(BoxedError::new)?;
- shared_values
- .dns_monitor
- .set(&self.metadata.interface, &dns_ips)
- .map_err(BoxedError::new)
+ Ok(())
}
fn reset_dns(shared_values: &mut SharedTunnelStateValues) {
if let Err(error) = shared_values.dns_monitor.reset() {
log::error!("{}", error.display_chain_with_msg("Unable to reset DNS"));
}
-
- #[cfg(target_os = "linux")]
- if let Err(error) = shared_values.split_tunnel.flush_dns() {
- log::error!(
- "{}",
- error.display_chain_with_msg("Unable to update split-tunnel route")
- );
- }
}
fn reset_routes(shared_values: &mut SharedTunnelStateValues) {
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index 2c895fb707..3a34f7fb0f 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -6,7 +6,6 @@ use super::{
use crate::{
firewall::FirewallPolicy,
routing::RouteManager,
- split,
tunnel::{
self, tun_provider::TunProvider, CloseHandle, TunnelEvent, TunnelMetadata, TunnelMonitor,
},
@@ -360,7 +359,10 @@ impl TunnelState for ConnectingState {
ErrorState::enter(shared_values, ErrorStateCause::StartTunnelError)
} else {
#[cfg(target_os = "linux")]
- if let Err(error) = shared_values.split_tunnel.enable_routing() {
+ if let Err(error) = shared_values
+ .split_tunnel
+ .enable_routing(&mut shared_values.route_manager)
+ {
error!(
"{}",
error.display_chain_with_msg("Failed to set up split tunneling")
diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
index 4cb0a97e20..ac3b33aed5 100644
--- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
@@ -40,13 +40,7 @@ impl TunnelState for DisconnectedState {
_: Self::Bootstrap,
) -> (TunnelStateWrapper, TunnelStateTransition) {
#[cfg(target_os = "linux")]
- if let Err(error) = shared_values.split_tunnel.disable_routing() {
- log::error!(
- "{}",
- error.display_chain_with_msg("Failed to update routing")
- );
- }
-
+ shared_values.split_tunnel.disable_routing();
Self::set_firewall_policy(shared_values);
#[cfg(target_os = "android")]
shared_values.tun_provider.close_tun();
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index bf4a17bad4..0393be3418 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -22,7 +22,6 @@ use crate::{
mpsc::Sender,
offline,
routing::RouteManager,
- split,
tunnel::tun_provider::TunProvider,
};