summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2020-09-22 13:13:38 +0200
committerDavid Lönnhager <david.l@mullvad.net>2020-09-22 13:13:38 +0200
commitf1ec0260733001b316c0180e0a397a35c8dcfddd (patch)
tree6190d7f38c712cd90e39d8978b6e0c1bfc46c329
parentbb1e50525c97f7e597860a6ecfc6d2af82f35dc4 (diff)
parentcdae002978c2f6e225060e022fc20c5968bee8c2 (diff)
downloadmullvadvpn-f1ec0260733001b316c0180e0a397a35c8dcfddd.tar.xz
mullvadvpn-f1ec0260733001b316c0180e0a397a35c8dcfddd.zip
Merge branch 'linux-no-iproute2'
-rw-r--r--talpid-core/src/routing/linux.rs158
-rw-r--r--talpid-core/src/routing/mod.rs12
2 files changed, 71 insertions, 99 deletions
diff --git a/talpid-core/src/routing/linux.rs b/talpid-core/src/routing/linux.rs
index a314c4f112..d960916345 100644
--- a/talpid-core/src/routing/linux.rs
+++ b/talpid-core/src/routing/linux.rs
@@ -6,11 +6,9 @@ use crate::{
use talpid_types::ErrorExt;
use ipnetwork::IpNetwork;
-use regex::Regex;
use std::{
collections::{BTreeMap, HashSet},
- fs,
- io::{self, BufRead, BufReader, Read, Seek, Write},
+ io,
net::{IpAddr, Ipv4Addr},
};
@@ -21,7 +19,9 @@ use netlink_packet_route::{
link::{nlas::Nla as LinkNla, LinkMessage},
route::{nlas::Nla as RouteNla, RouteHeader, RouteMessage},
rtnl::{
- constants::{RTN_UNICAST, RTPROT_STATIC, RT_SCOPE_UNIVERSE, RT_TABLE_MAIN},
+ constants::{
+ RTN_UNICAST, RTPROT_STATIC, RT_SCOPE_UNIVERSE, RT_TABLE_COMPAT, RT_TABLE_MAIN,
+ },
RouteFlags,
},
rule::{nlas::Nla as RuleNla, RuleHeader, RuleMessage},
@@ -35,9 +35,6 @@ use rtnetlink::{
use libc::{AF_INET, AF_INET6};
-const ROUTING_TABLE_NAME: &str = "mullvad_exclusions";
-const RT_TABLES_PATH: &str = "/etc/iproute2/rt_tables";
-
pub type Result<T> = std::result::Result<T, Error>;
@@ -63,35 +60,20 @@ pub enum Error {
#[error(display = "Invalid network prefix")]
InvalidNetworkPrefix(#[error(source)] ipnetwork::IpNetworkError),
- #[error(display = "Failed to initialize event loop")]
- EventLoopError(#[error(source)] io::Error),
-
#[error(display = "Unknown device index - {}", _0)]
UnknownDeviceIndex(u32),
/// Unable to create routing table for tagged connections and packets.
- #[error(display = "Unable to create routing table for split tunneling")]
- ExclusionsRoutingTableSetup(#[error(source)] io::Error),
-
- /// Unable to create routing table for tagged connections and packets.
- #[error(display = "Cannot find a free routing table ID in rt_tables")]
+ #[error(display = "Cannot find a free routing table ID")]
NoFreeRoutingTableId,
#[error(display = "Shutting down route manager")]
Shutdown,
-
- /// Failed to run the process.
- #[error(display = "Unable to execute process")]
- ExecFailed(#[error(source)] io::Error),
-
- /// ip command returned an error status.
- #[error(display = "ip command failed")]
- IpFailed,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct RequiredDefaultRoute {
- table_id: u8,
+ table_id: u32,
destination: IpNetwork,
}
@@ -109,7 +91,7 @@ pub struct RouteManagerImpl {
best_default_node_v4: Option<Node>,
best_default_node_v6: Option<Node>,
- split_table_id: i32,
+ split_table_id: u32,
}
impl RouteManagerImpl {
@@ -127,7 +109,9 @@ impl RouteManagerImpl {
tokio::spawn(connection);
let iface_map = Self::initialize_link_map(&handle).await?;
- let split_table_id = Self::initialize_exclusions_table().await?;
+ let split_table_id = Self::find_free_table_id(&handle).await?;
+
+ log::trace!("Using table id {} for excluded apps", split_table_id);
let mut monitor = Self {
iface_map,
@@ -155,74 +139,39 @@ impl RouteManagerImpl {
Ok(monitor)
}
- /// Set up policy-based routing table for marked packets.
- /// Returns the routing table id.
- async fn initialize_exclusions_table() -> Result<i32> {
- // Add routing table to /etc/iproute2/rt_tables, if it does not exist
-
- let file = fs::OpenOptions::new()
- .read(true)
- .open(RT_TABLES_PATH)
- .map_err(Error::ExclusionsRoutingTableSetup)?;
- let buf_reader = BufReader::new(file);
- let expression = Regex::new(r"^\s*(\d+)\s+(\w+)").unwrap();
+ async fn find_free_table_id(handle: &rtnetlink::Handle) -> Result<u32> {
+ let mut request = handle.route().get(IpVersion::V4).execute();
+ let mut used_ids = HashSet::new();
- let mut used_ids = Vec::<i32>::new();
-
- for line in buf_reader.lines() {
- let line = line.map_err(Error::ExclusionsRoutingTableSetup)?;
- if let Some(captures) = expression.captures(&line) {
- let table_id = captures
- .get(1)
- .unwrap()
- .as_str()
- .parse::<i32>()
- .expect("Table ID does not fit i32");
- let table_name = captures.get(2).unwrap().as_str();
-
- if table_name == ROUTING_TABLE_NAME {
- // The table has already been added
- return Ok(table_id);
+ while let Some(route) = request.try_next().await.map_err(Error::NetlinkError)? {
+ let mut id_found = false;
+ for nla in route.nlas {
+ match nla {
+ RouteNla::Table(id) => {
+ used_ids.insert(id);
+ id_found = true;
+ break;
+ }
+ _ => (),
}
-
- used_ids.push(table_id);
}
- }
-
- used_ids.sort_unstable();
- // Assign a free id to the table
- let mut table_id = 1;
- loop {
- if used_ids.binary_search(&table_id).is_err() {
- break;
- }
-
- table_id += 1;
-
- if table_id >= 256 {
- return Err(Error::NoFreeRoutingTableId);
+ if !id_found {
+ // Use old header ID
+ used_ids.insert(u32::from(route.header.table));
}
}
- let mut file = fs::OpenOptions::new()
- .read(true)
- .append(true)
- .open(RT_TABLES_PATH)
- .map_err(Error::ExclusionsRoutingTableSetup)?;
-
- if let Ok(_) = file.seek(io::SeekFrom::End(-1)) {
- // Append newline if necessary
- let mut buffer = [0u8];
- let _ = file.read_exact(&mut buffer);
- if buffer[0] != b'\n' {
- writeln!(file).map_err(Error::ExclusionsRoutingTableSetup)?;
+ for id in 1..u32::MAX {
+ if id == RT_TABLE_COMPAT as u32 {
+ continue;
+ }
+ if !used_ids.contains(&id) {
+ return Ok(id);
}
}
- writeln!(file, "{} {}", table_id, ROUTING_TABLE_NAME)
- .map_err(Error::ExclusionsRoutingTableSetup)?;
- Ok(table_id)
+ Err(Error::NoFreeRoutingTableId)
}
/// Route PID-associated packets through the physical interface.
@@ -246,7 +195,7 @@ impl RouteManagerImpl {
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.split_table_id as u8),
+ RequiredRoute::new(zero_network, NetNode::DefaultNode).table(self.split_table_id),
);
self.add_required_routes(required_routes).await
}
@@ -306,7 +255,7 @@ impl RouteManagerImpl {
IpNetwork::from(*server),
Node::device(tunnel_alias.to_string()),
)
- .table(self.split_table_id as u8),
+ .table(self.split_table_id),
);
}
@@ -659,6 +608,7 @@ impl RouteManagerImpl {
let destination_length = msg.header.destination_prefix_length;
let af_spec = msg.header.address_family;
+ let mut table_id = u32::from(msg.header.table);
for nla in msg.nlas.iter() {
match nla {
@@ -692,6 +642,10 @@ impl RouteManagerImpl {
RouteNla::Priority(priority) => {
metric = Some(*priority);
}
+
+ RouteNla::Table(id) => {
+ table_id = *id;
+ }
_ => continue,
}
}
@@ -719,7 +673,7 @@ impl RouteManagerImpl {
node,
prefix: prefix.unwrap(),
metric,
- table_id: msg.header.table,
+ table_id,
}))
}
@@ -749,6 +703,7 @@ impl RouteManagerImpl {
}
async fn delete_route(&self, route: &Route) -> Result<()> {
+ let compat_table = compat_table_id(route.table_id);
let mut route_message = RouteMessage {
header: RouteHeader {
address_family: if route.prefix.is_ipv4() {
@@ -759,7 +714,7 @@ impl RouteManagerImpl {
source_prefix_length: 0,
destination_prefix_length: route.prefix.prefix(),
tos: 0u8,
- table: route.table_id,
+ table: compat_table,
protocol: RTPROT_STATIC,
scope: RT_SCOPE_UNIVERSE,
kind: RTN_UNICAST,
@@ -767,6 +722,10 @@ impl RouteManagerImpl {
},
nlas: vec![RouteNla::Destination(ip_to_bytes(route.prefix.ip()))],
};
+ if compat_table == RT_TABLE_COMPAT {
+ route_message.nlas.push(RouteNla::Table(route.table_id));
+ }
+
if let Some(interface_name) = route.node.get_device() {
if let Some(iface_idx) = self.find_iface_idx(interface_name) {
route_message.nlas.push(RouteNla::Oif(iface_idx));
@@ -792,14 +751,13 @@ impl RouteManagerImpl {
}
async fn add_route(&mut self, route: Route) -> Result<()> {
- let add_message = match &route.prefix {
+ let mut add_message = match &route.prefix {
IpNetwork::V4(v4_prefix) => {
let mut add_message = self
.handle
.route()
.add_v4()
- .destination_prefix(v4_prefix.ip(), v4_prefix.prefix())
- .table(route.table_id);
+ .destination_prefix(v4_prefix.ip(), v4_prefix.prefix());
if v4_prefix.prefix() > 0 && v4_prefix.prefix() < 32 {
add_message = add_message.scope(RT_SCOPE_LINK);
@@ -823,8 +781,7 @@ impl RouteManagerImpl {
.handle
.route()
.add_v6()
- .destination_prefix(v6_prefix.ip(), v6_prefix.prefix())
- .table(route.table_id);
+ .destination_prefix(v6_prefix.ip(), v6_prefix.prefix());
if v6_prefix.prefix() > 0 && v6_prefix.prefix() < 128 {
add_message = add_message.scope(RT_SCOPE_LINK);
@@ -844,6 +801,12 @@ impl RouteManagerImpl {
}
};
+ let compat_table = compat_table_id(route.table_id);
+ add_message.header.table = compat_table;
+ if compat_table == RT_TABLE_COMPAT {
+ add_message.nlas.push(RouteNla::Table(route.table_id));
+ }
+
// Need to modify the request in place to set the correct flags to be able to replace any
// existing routes - self.handle.route().add_v4().execute() sets the NLM_F_EXCL flag which
// will make the request fail if a route with the same destination already exists.
@@ -876,6 +839,15 @@ fn ip_to_bytes(addr: IpAddr) -> Vec<u8> {
}
}
+fn compat_table_id(id: u32) -> u8 {
+ // RT_TABLE_COMPAT must be combined with nla Table(id)
+ if id > 255 {
+ RT_TABLE_COMPAT
+ } else {
+ id as u8
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/talpid-core/src/routing/mod.rs b/talpid-core/src/routing/mod.rs
index 764e845ae4..41d2ff1c57 100644
--- a/talpid-core/src/routing/mod.rs
+++ b/talpid-core/src/routing/mod.rs
@@ -24,7 +24,7 @@ pub struct Route {
prefix: IpNetwork,
metric: Option<u32>,
#[cfg(target_os = "linux")]
- table_id: u8,
+ table_id: u32,
}
impl Route {
@@ -34,12 +34,12 @@ impl Route {
prefix,
metric: None,
#[cfg(target_os = "linux")]
- table_id: RT_TABLE_MAIN,
+ table_id: u32::from(RT_TABLE_MAIN),
}
}
#[cfg(target_os = "linux")]
- fn table(mut self, new_id: u8) -> Self {
+ fn table(mut self, new_id: u32) -> Self {
self.table_id = new_id;
self
}
@@ -65,7 +65,7 @@ pub struct RequiredRoute {
prefix: IpNetwork,
node: NetNode,
#[cfg(target_os = "linux")]
- table_id: u8,
+ table_id: u32,
}
impl RequiredRoute {
@@ -75,13 +75,13 @@ impl RequiredRoute {
node: node.into(),
prefix,
#[cfg(target_os = "linux")]
- table_id: RT_TABLE_MAIN,
+ table_id: u32::from(RT_TABLE_MAIN),
}
}
/// Sets the routing table ID of the route.
#[cfg(target_os = "linux")]
- pub fn table(mut self, new_id: u8) -> Self {
+ pub fn table(mut self, new_id: u32) -> Self {
self.table_id = new_id;
self
}