summaryrefslogtreecommitdiffhomepage
path: root/talpid-routing
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2023-09-12 22:02:45 +0200
committerDavid Lönnhager <david.l@mullvad.net>2023-09-19 17:37:26 +0200
commit5774ce6d49f284f0a8c95bf76ca9e5896621eb64 (patch)
tree5ee804e7b319364c7da1ebb35fd4c225ce4161fc /talpid-routing
parent0c0969fc246bf1078f1b925c306b8afc31e57baa (diff)
downloadmullvadvpn-5774ce6d49f284f0a8c95bf76ca9e5896621eb64.tar.xz
mullvadvpn-5774ce6d49f284f0a8c95bf76ca9e5896621eb64.zip
Use unscoped route as best route if there's no tun
Diffstat (limited to 'talpid-routing')
-rw-r--r--talpid-routing/src/unix/macos/interface.rs71
-rw-r--r--talpid-routing/src/unix/macos/mod.rs99
2 files changed, 103 insertions, 67 deletions
diff --git a/talpid-routing/src/unix/macos/interface.rs b/talpid-routing/src/unix/macos/interface.rs
index bf08317e74..05b23752b8 100644
--- a/talpid-routing/src/unix/macos/interface.rs
+++ b/talpid-routing/src/unix/macos/interface.rs
@@ -1,6 +1,8 @@
+use ipnetwork::IpNetwork;
+use libc::{if_indextoname, IFNAMSIZ};
use nix::net::if_::{if_nametoindex, InterfaceFlags};
use std::{
- ffi::CString,
+ ffi::{CStr, CString},
io,
net::{Ipv4Addr, Ipv6Addr},
};
@@ -21,18 +23,69 @@ pub enum Family {
V6,
}
-/// Attempt to retrieve the best current default route.
-/// Note: The tunnel interface is not even listed in the service order, so it will be skipped.
-pub async fn get_best_default_route(
+impl From<Family> for IpNetwork {
+ fn from(fam: Family) -> Self {
+ match fam {
+ Family::V4 => IpNetwork::new(Ipv4Addr::UNSPECIFIED.into(), 0).unwrap(),
+ Family::V6 => IpNetwork::new(Ipv6Addr::UNSPECIFIED.into(), 0).unwrap(),
+ }
+ }
+}
+
+/// Retrieve the current unscoped default route. That is the only default route that does not have
+/// the IF_SCOPE flag set, if such a route exists.
+///
+/// # Note
+///
+/// For some reason, the socket sometimes returns a route with the IF_SCOPE flag set, if there also
+/// exists a scoped route for the same interface. This does not occur if there is no unscoped route,
+/// so we can still rely on it.
+pub async fn get_unscoped_default_route(
routing_table: &mut RoutingTable,
family: Family,
) -> Option<RouteMessage> {
- let destination = match family {
- Family::V4 => super::v4_default(),
- Family::V6 => super::v6_default(),
- };
+ let mut msg = RouteMessage::new_route(Destination::Network(IpNetwork::from(family)));
+ msg = msg.set_gateway_route(true);
+
+ let route = routing_table
+ .get_route(&msg)
+ .await
+ .unwrap_or_else(|error| {
+ log::error!("Failed to retrieve unscoped default route: {error}");
+ None
+ })?;
+
+ let idx = u32::from(route.interface_index());
+ if idx != 0 {
+ let mut ifname = [0u8; IFNAMSIZ];
- let mut msg = RouteMessage::new_route(Destination::Network(destination));
+ // SAFETY: The buffer is large to contain any interface name.
+ if !unsafe { if_indextoname(idx, ifname.as_mut_ptr() as _) }.is_null() {
+ let ifname = CStr::from_bytes_until_nul(&ifname).unwrap();
+ let name = ifname.to_str().expect("expected ascii");
+
+ // Ignore the unscoped route if its interface is not "active"
+ if !is_active_interface(name, family).unwrap_or(true) {
+ return None;
+ }
+ }
+ }
+
+ Some(route)
+}
+
+/// Retrieve the best current default route. That is the first scoped default route, ordered by
+/// network service order, and with interfaces filtered out if they do not have valid IP addresses
+/// assigned.
+///
+/// # Note
+///
+/// The tunnel interface is not even listed in the service order, so it will be skipped.
+pub async fn get_best_default_route(
+ routing_table: &mut RoutingTable,
+ family: Family,
+) -> Option<RouteMessage> {
+ let mut msg = RouteMessage::new_route(Destination::Network(IpNetwork::from(family)));
msg = msg.set_gateway_route(true);
for iface in network_service_order() {
diff --git a/talpid-routing/src/unix/macos/mod.rs b/talpid-routing/src/unix/macos/mod.rs
index 2f2a86d9b1..02c86faa9a 100644
--- a/talpid-routing/src/unix/macos/mod.rs
+++ b/talpid-routing/src/unix/macos/mod.rs
@@ -10,7 +10,6 @@ use nix::sys::socket::{AddressFamily, SockaddrLike, SockaddrStorage};
use std::pin::Pin;
use std::{
collections::{BTreeMap, HashSet},
- net::{Ipv4Addr, Ipv6Addr},
time::Duration,
};
use talpid_types::ErrorExt;
@@ -159,7 +158,7 @@ impl RouteManagerImpl {
device: None,
ip: route.gateway_ip(),
},
- prefix: v4_default(),
+ prefix: IpNetwork::from(interface::Family::V4),
metric: None,
}
});
@@ -169,7 +168,7 @@ impl RouteManagerImpl {
device: None,
ip: route.gateway_ip(),
},
- prefix: v6_default(),
+ prefix: IpNetwork::from(interface::Family::V6),
metric: None,
}
});
@@ -332,8 +331,26 @@ impl RouteManagerImpl {
self.apply_non_tunnel_routes().await
}
+ /// Figure out what the best default routes to use are, and send updates to default route change
+ /// subscribers. The "best routes" are used by the tunnel device to send packets to the VPN
+ /// relay.
+ ///
+ /// If there is a tunnel device, the "best route" is the first ifscope default route found,
+ /// ordered after network service order (after filtering out interfaces without valid IP
+ /// addresses).
+ ///
+ /// If there is no tunnel device, the "best route" is the unscoped default route, whatever it
+ /// is.
async fn update_best_default_route(&mut self, family: interface::Family) -> Result<()> {
- let best_route = interface::get_best_default_route(&mut self.routing_table, family).await;
+ let use_scoped_route = (family == interface::Family::V4
+ && self.v4_tunnel_default_route.is_some())
+ || (family == interface::Family::V6 && self.v6_tunnel_default_route.is_some());
+
+ let best_route = if use_scoped_route {
+ interface::get_best_default_route(&mut self.routing_table, family).await
+ } else {
+ interface::get_unscoped_default_route(&mut self.routing_table, family).await
+ };
log::trace!("Best route ({family:?}): {best_route:?}");
let default_route = match family {
@@ -592,68 +609,34 @@ impl RouteManagerImpl {
/// Add back unscoped default routes, if they are still missing. This function returns
/// true when no routes had to be added.
async fn try_restore_default_routes(&mut self) -> bool {
- let message = RouteMessage::new_route(v4_default().into());
- let v4_done = if matches!(self.routing_table.get_route(&message).await, Ok(Some(_))) {
- true
- } else {
- let new_route =
- interface::get_best_default_route(&mut self.routing_table, interface::Family::V4)
- .await;
- let old_route = std::mem::replace(&mut self.v4_default_route, new_route);
- if old_route != self.v4_default_route {
- self.notify_default_route_listeners(
- interface::Family::V4,
- self.v4_default_route.is_some(),
- );
- }
- if let Some(route) = &mut self.v4_default_route {
- let _ = std::mem::replace(route, route.clone().set_ifscope(0));
- if let Err(error) = self.routing_table.add_route(route).await {
- log::trace!("Failed to add non-ifscope v4 route: {error}");
- }
- false
- } else {
+ let restore_route = |family, current_route: &mut RouteMessage| {
+ let message = RouteMessage::new_route(IpNetwork::from(family).into());
+ if matches!(self.routing_table.get_route(&message).await, Ok(Some(_))) {
true
- }
- };
-
- let message = RouteMessage::new_route(v6_default().into());
- let v6_done = if matches!(self.routing_table.get_route(&message).await, Ok(Some(_))) {
- true
- } else {
- let new_route =
- interface::get_best_default_route(&mut self.routing_table, interface::Family::V6)
- .await;
- let old_route = std::mem::replace(&mut self.v6_default_route, new_route);
- if old_route != self.v6_default_route {
- self.notify_default_route_listeners(
- interface::Family::V6,
- self.v6_default_route.is_some(),
- );
- }
- if let Some(route) = &mut self.v6_default_route {
- let _ = std::mem::replace(route, route.clone().set_ifscope(0));
- if let Err(error) = self.routing_table.add_route(route).await {
- log::trace!("Failed to add non-ifscope v6 route: {error}");
- }
- false
} else {
- true
+ let new_route =
+ interface::get_best_default_route(&mut self.routing_table, family).await;
+ let old_route = std::mem::replace(current_route, new_route);
+ if old_route != current_route {
+ self.notify_default_route_listeners(family, current_route.is_some());
+ }
+ if let Some(route) = current_route {
+ let _ = std::mem::replace(route, route.clone().set_ifscope(0));
+ if let Err(error) = self.routing_table.add_route(route).await {
+ log::trace!("Failed to add non-ifscope {family} route: {error}");
+ }
+ false
+ } else {
+ true
+ }
}
};
- v4_done && v6_done
+ restore_route(interface::Family::V4, &mut self.v4_default_route)
+ && restore_route(interface::Family::V6, &mut self.v6_default_route)
}
}
-fn v4_default() -> IpNetwork {
- IpNetwork::new(Ipv4Addr::UNSPECIFIED.into(), 0).unwrap()
-}
-
-fn v6_default() -> IpNetwork {
- IpNetwork::new(Ipv6Addr::UNSPECIFIED.into(), 0).unwrap()
-}
-
/// Return a map from interface name to link addresses (AF_LINK)
fn get_interface_link_addresses() -> Result<BTreeMap<String, SockaddrStorage>> {
let mut gateway_link_addrs = BTreeMap::new();