diff options
| author | David Lönnhager <david.l@mullvad.net> | 2023-09-12 22:02:45 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2023-09-19 17:37:26 +0200 |
| commit | 5774ce6d49f284f0a8c95bf76ca9e5896621eb64 (patch) | |
| tree | 5ee804e7b319364c7da1ebb35fd4c225ce4161fc /talpid-routing | |
| parent | 0c0969fc246bf1078f1b925c306b8afc31e57baa (diff) | |
| download | mullvadvpn-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.rs | 71 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/mod.rs | 99 |
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(); |
