diff options
| author | Emīls <emils@mullvad.net> | 2020-11-06 16:23:29 +0000 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2020-11-11 10:16:23 +0000 |
| commit | 142cdbe7b1f09ac505d3b06d5d3b0a8839241e6d (patch) | |
| tree | a0dbf878bfb8079f5b35b3d6768778eaf4912865 | |
| parent | e21e8e98cd82c0ba005754a71182867b317a8f6a (diff) | |
| download | mullvadvpn-142cdbe7b1f09ac505d3b06d5d3b0a8839241e6d.tar.xz mullvadvpn-142cdbe7b1f09ac505d3b06d5d3b0a8839241e6d.zip | |
Divide default route if necessary when building exclsuion table
| -rw-r--r-- | talpid-core/src/routing/linux.rs | 42 | ||||
| -rw-r--r-- | talpid-core/src/routing/mod.rs | 13 |
2 files changed, 52 insertions, 3 deletions
diff --git a/talpid-core/src/routing/linux.rs b/talpid-core/src/routing/linux.rs index 8667026294..9cf1126558 100644 --- a/talpid-core/src/routing/linux.rs +++ b/talpid-core/src/routing/linux.rs @@ -73,6 +73,18 @@ pub enum Error { Shutdown, } +impl Error { + /// Returns true only if it's a netlink error with a code ENETUNREACH + fn is_network_unreachable(&self) -> bool { + match self { + Error::NetlinkError(rtnetlink::Error::NetlinkError(err)) => { + err.code == -libc::ENETUNREACH + } + _ => false, + } + } +} + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] struct RequiredDefaultRoute { table_id: u32, @@ -203,10 +215,33 @@ impl RouteManagerImpl { let mut main_routes = self.get_routes(None).await?.into_iter().collect::<Vec<_>>(); main_routes.sort_by(|a, b| a.prefix.prefix().cmp(&b.prefix.prefix())); + main_routes.sort_by(|a, b| a.prefix.is_ipv4().cmp(&b.prefix.is_ipv4())); for mut route in main_routes { route.table_id = self.split_table_id; - self.add_route_direct(route).await?; + if let Err(err) = self.add_route_direct(route.clone()).await { + // If a rotue can't be added because parts of it's next-hop are unreachable, then + // a gateway route should be added first. Seemingly there's an ARP table per + // routing table, and a route that specifies both a gateway and an output interface + // depends on a route that instructs how to reach the gateway. + if err.is_network_unreachable() { + match (route.device_only_route(), route.node.get_address()) { + (Some(mut gateway_route), Some(address)) => { + gateway_route.prefix = + IpNetwork::new(address, if address.is_ipv4() { 32 } else { 128 }) + .unwrap(); + let add_gateway_route = self.add_route_direct(gateway_route).await; + let add_route_result = self.add_route_direct(route).await; + if let Err(err) = add_gateway_route.and_then(|_| add_route_result) { + log::error!("Failed to add route to split-routing table: {}", err); + }; + continue; + } + _ => (), + }; + } + log::error!("Failed to add route to split-routing table: {}", err); + } } Ok(()) } @@ -869,12 +904,13 @@ impl RouteManagerImpl { device: device.map(|dev| dev.name.clone()), }; - Ok(Some(Route { + let result = Ok(Some(Route { node, prefix, metric, table_id, - })) + })); + result } fn map_interface(msg: LinkMessage) -> Option<(u32, NetworkInterface)> { diff --git a/talpid-core/src/routing/mod.rs b/talpid-core/src/routing/mod.rs index 15bf1b04ae..6ab4240fd1 100644 --- a/talpid-core/src/routing/mod.rs +++ b/talpid-core/src/routing/mod.rs @@ -40,6 +40,19 @@ impl Route { } } + /// Returns route that only contains the device node if a device node exists. + #[cfg(target_os = "linux")] + fn device_only_route(&self) -> Option<Self> { + if let Some(device) = self.node.get_device() { + Some(Self { + node: Node::device(device.to_string()), + ..self.clone() + }) + } else { + None + } + } + #[cfg(target_os = "linux")] fn table(mut self, new_id: u32) -> Self { self.table_id = new_id; |
