diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-04-23 17:12:52 +0200 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-04-23 17:12:52 +0200 |
| commit | 2260667e3a16214dc0e79dc3d5e640021cb8f765 (patch) | |
| tree | 78a2c90ff1de687b8607417057fdb4f9395d1ef8 | |
| parent | 4e5168734f69ad696a84a8436b5dda2712e0266c (diff) | |
| parent | e947e34ce6e83013f8d5bee86de3d59350efe447 (diff) | |
| download | mullvadvpn-2260667e3a16214dc0e79dc3d5e640021cb8f765.tar.xz mullvadvpn-2260667e3a16214dc0e79dc3d5e640021cb8f765.zip | |
Merge branch 'macos-ipv6-dissapearing-route'
| -rw-r--r-- | talpid-routing/src/unix/macos/data.rs | 80 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/default_routes.rs | 279 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/interface.rs | 334 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/ip_map.rs | 67 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/mod.rs | 378 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/routing_socket.rs | 2 | ||||
| -rw-r--r-- | talpid-routing/src/unix/macos/watch.rs | 4 |
7 files changed, 828 insertions, 316 deletions
diff --git a/talpid-routing/src/unix/macos/data.rs b/talpid-routing/src/unix/macos/data.rs index 66644d6ce3..5ee2b24a06 100644 --- a/talpid-routing/src/unix/macos/data.rs +++ b/talpid-routing/src/unix/macos/data.rs @@ -6,12 +6,14 @@ use nix::{ use std::{ collections::BTreeMap, ffi::{c_int, c_uchar, c_ushort}, + fmt::{self, Debug}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; /// Message that describes a route - either an added, removed, changed or plainly retrieved route. #[derive(Debug, Clone, PartialEq)] pub struct RouteMessage { + // INVARIANT: The `AddressFlag` must match the variant of `RouteSocketAddress`. sockaddrs: BTreeMap<AddressFlag, RouteSocketAddress>, mtu: u32, route_flags: RouteFlag, @@ -789,7 +791,7 @@ bitflags::bitflags! { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub enum RouteSocketAddress { /// Corresponds to RTA_DST Destination(Option<SockaddrStorage>), @@ -809,6 +811,39 @@ pub enum RouteSocketAddress { Broadcast(Option<SockaddrStorage>), } +/// Custom Debug-impl that uses the Display-impl of [SockaddrStorage] since its Debug-impl is +/// basically unreadable. +impl Debug for RouteSocketAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (variant, sockaddr) = match self { + Self::Destination(sockaddr) => ("Destination", sockaddr), + Self::Gateway(sockaddr) => ("Gateway", sockaddr), + Self::Netmask(sockaddr) => ("Netmask", sockaddr), + Self::CloningMask(sockaddr) => ("CloningMask", sockaddr), + Self::IfName(sockaddr) => ("IfName", sockaddr), + Self::IfSockaddr(sockaddr) => ("IfSockaddr", sockaddr), + Self::RedirectAuthor(sockaddr) => ("RedirectAuthor", sockaddr), + Self::Broadcast(sockaddr) => ("Broadcast", sockaddr), + }; + + if let Some(sockaddr) = sockaddr { + if let Some(link_addr) = sockaddr.as_link_addr() { + // The default Display impl for LinkAddrs does not print ifindex + write!(f, "{variant}(")?; + f.debug_struct("LinkAddr") + .field("addr", &link_addr.addr()) + .field("iface", &link_addr.ifindex()) + .finish()?; + write!(f, ")") + } else { + write!(f, "{variant}({sockaddr})") + } + } else { + write!(f, "{variant}(None)") + } + } +} + impl RouteSocketAddress { // Returns a new route socket address and number of bytes read from the buffer pub fn new(flag: AddressFlag, buf: &[u8]) -> Result<(Self, u8)> { @@ -953,17 +988,15 @@ struct sockaddr_hdr { /// routing socket message. pub struct RouteSockAddrIterator<'a> { buffer: &'a [u8], - flags: AddressFlag, - // Cursor used to iterate through address flags - flag_cursor: i32, + /// Iterator over all the set bits in the provided [AddressFlag]. + flags_iter: bitflags::iter::Iter<AddressFlag>, } impl<'a> RouteSockAddrIterator<'a> { fn new(buffer: &'a [u8], flags: AddressFlag) -> Self { Self { buffer, - flags, - flag_cursor: AddressFlag::RTA_DST.bits(), + flags_iter: flags.iter(), } } @@ -995,25 +1028,26 @@ impl Iterator for RouteSockAddrIterator<'_> { type Item = Result<RouteSocketAddress>; fn next(&mut self) -> Option<Self::Item> { - loop { - // If address flags don't contain the current one, try the next one. - // Will return None if it runs out of valid flags. - let current_flag = AddressFlag::from_bits(self.flag_cursor)?; - self.flag_cursor <<= 1; + // Will return None if it runs out of set flags. + let current_flag = self.flags_iter.next()?; + + // Any undefiend flags are all returned as a clump in the final iteration. + let no_undefined_flags = AddressFlag::all().contains(current_flag); + debug_assert!( + no_undefined_flags, + "AddressFlag contained undefined bits! {current_flag:?}. \ + Consider adding them to the definition." + ); - if !self.flags.contains(current_flag) { - continue; + match RouteSocketAddress::new(current_flag, self.buffer) { + Ok((next_addr, addr_len)) => { + self.advance_buffer(addr_len); + Some(Ok(next_addr)) + } + Err(err) => { + self.buffer = &[]; + Some(Err(err)) } - return match RouteSocketAddress::new(current_flag, self.buffer) { - Ok((next_addr, addr_len)) => { - self.advance_buffer(addr_len); - Some(Ok(next_addr)) - } - Err(err) => { - self.buffer = &[]; - Some(Err(err)) - } - }; } } } diff --git a/talpid-routing/src/unix/macos/default_routes.rs b/talpid-routing/src/unix/macos/default_routes.rs new file mode 100644 index 0000000000..e6a68a508d --- /dev/null +++ b/talpid-routing/src/unix/macos/default_routes.rs @@ -0,0 +1,279 @@ +use std::{collections::HashMap, convert::Infallible, future::pending, mem, time::Duration}; + +use futures::{ + channel::mpsc::{self, UnboundedReceiver, UnboundedSender}, + select_biased, FutureExt, StreamExt, +}; +use tokio::{ + runtime, + time::{sleep_until, Instant}, +}; + +use crate::imp::imp::interface::NetworkServiceDetails; + +use super::{ + interface::{Family, InterfaceEvent, PrimaryInterfaceDetails, PrimaryInterfaceMonitor}, + ip_map::IpMap, + DefaultRoute, +}; + +/// Grace time during which we don't act if the best default route dissapears. +// +// 5 seconds seemed to be a reasonable value when testing. +// Increasing this value will increase the time it takes the daemon to realize when there's no +// network connectivity. Decreasing it will increase the risk of unnecessary reconnects when the +// best default route simply goes away for a few seconds. +const NO_ROUTE_GRACE_TIME: Duration = Duration::from_secs(5); + +/// Monitors changes to the primary interface and reports [BestRoute]. +pub struct DefaultRouteMonitor { + monitor: PrimaryInterfaceMonitor, + event_rx: UnboundedReceiver<Vec<InterfaceEvent>>, + + route_tx: IpMap<UnboundedSender<Option<DefaultRoute>>>, + + /// The current best routes. + current_route: IpMap<DefaultRoute>, + + /// The current primary interfaces. + primary_interfaces: IpMap<PrimaryInterfaceDetails>, +} + +impl DefaultRouteMonitor { + /// Start monitoring interfaces for changes to the best route. + /// + /// Returns an IPv4 and an IPv6 channel of [BestRoute] updates. + pub fn start( + monitor: PrimaryInterfaceMonitor, + event_rx: UnboundedReceiver<Vec<InterfaceEvent>>, + ) -> ( + UnboundedReceiver<Option<DefaultRoute>>, + UnboundedReceiver<Option<DefaultRoute>>, + ) { + let (route_v4_tx, route_v4_rx) = mpsc::unbounded(); + let (route_v6_tx, route_v6_rx) = mpsc::unbounded(); + + let mut route_tx = IpMap::new(); + route_tx.insert(Family::V4, route_v4_tx); + route_tx.insert(Family::V6, route_v6_tx); + + let monitor = DefaultRouteMonitor { + monitor, + event_rx, + route_tx, + current_route: IpMap::new(), + primary_interfaces: IpMap::new(), + }; + + tokio::task::spawn_blocking(move || { + runtime::Handle::current().block_on(monitor.run()); + }); + + let route_v4_rx = filter_duplicates(delay_nones(NO_ROUTE_GRACE_TIME, route_v4_rx)); + let route_v6_rx = filter_duplicates(delay_nones(NO_ROUTE_GRACE_TIME, route_v6_rx)); + + (route_v4_rx, route_v6_rx) + } + + async fn run(mut self) { + for family in [Family::V4, Family::V6] { + let route = self.monitor.get_route(family); + + self.current_route.set(family, route.clone()); + if let Some(tx) = self.route_tx.get(family) { + let _ = tx.unbounded_send(route); + } + } + + while let Some(events) = self.event_rx.next().await { + if self.route_tx.is_empty() { + break; + } + + self.handle_events(events); + } + } + + fn handle_events(&mut self, events: Vec<InterfaceEvent>) { + // Split events by address family and handle them seperately. + let mut ipv4_events = vec![]; + let mut ipv6_events = vec![]; + for event in events { + match event.family() { + Family::V4 => ipv4_events.push(event), + Family::V6 => ipv6_events.push(event), + } + } + + self.handle_events_for_family(Family::V4, ipv4_events); + self.handle_events_for_family(Family::V6, ipv6_events); + } + + fn handle_events_for_family(&mut self, family: Family, events: Vec<InterfaceEvent>) { + enum Change<T> { + New(T), + Removed, + } + + // Go through the events and figure out if the primary interface changed. + let mut primary_interface_change: Option<Change<PrimaryInterfaceDetails>> = None; + for event in &events { + let InterfaceEvent::PrimaryInterfaceUpdate { new_value, .. } = event else { + continue; + }; + + primary_interface_change = Some(match new_value { + Some(new_value) => Change::New(new_value.clone()), + None => Change::Removed, + }); + } + + // Collect all NetworkServiceUpdates into a HashMap. + let changed_services: HashMap<String, Change<NetworkServiceDetails>> = events + .into_iter() + .filter_map(|service| { + let InterfaceEvent::NetworkServiceUpdate { + service_id, + new_value, + .. + } = service + else { + return None; + }; + + let change = match new_value { + Some(service) => Change::New(service), + None => Change::Removed, + }; + + Some((service_id, change)) + }) + .collect(); + + // Figure out if anything interesting happened. + // Things we care about: + // - The primary interface changed. + // - The service of the primary interface changed. + // - If we're NOT using the primary interface, we care about whether ANY service changed. + let an_important_service_changed = + if let Some(primary_interface) = self.primary_interfaces.get(family) { + changed_services.contains_key(&primary_interface.service_id) + } else { + !changed_services.is_empty() + }; + + // If nothing interesting has happened, just return. + if primary_interface_change.is_none() && !an_important_service_changed { + return; + } + + // Figure out what the new default route should be. + // Match on the new primary interface, and the previous primary interface + let new_route = match ( + primary_interface_change.as_ref(), + self.primary_interfaces.get(family), + ) { + // This match covers two cases: + // - The primary interface changed. + // - The primary interface didn't change, and we have one from before. + (Some(Change::New(interface)), _) | (None, Some(interface)) => changed_services + .get(&interface.service_id) + .and_then(|change| match change { + Change::New(service) => Some(service), + Change::Removed => None, + }) + .and_then(|service| self.monitor.route_from_service(service)) + .or_else(|| self.monitor.get_route_by_service_order(family)), + + // This match covers the case where the primary interface was removed, or it never + // existed. In this case we iterate over all network services and pick the first good + // one. + _ => self.monitor.get_route_by_service_order(family), + }; + + self.current_route.set(family, new_route.clone()); + if let Some(tx) = self.route_tx.get(family) { + if tx.unbounded_send(new_route).is_err() { + self.route_tx.remove(family); + } + } + } +} + +/// Filter out duplicate messages from a channel. +/// +/// This will always keep a clone of the last value that was sent on the channel. +fn filter_duplicates<T: PartialEq + Clone + Send + 'static>( + unfiltered_rx: UnboundedReceiver<T>, +) -> UnboundedReceiver<T> { + async fn do_filtering<T: PartialEq + Clone + Send + 'static>( + mut unfiltered_rx: UnboundedReceiver<T>, + filtered_tx: UnboundedSender<T>, + ) -> Option<Infallible> { + let mut last_value = unfiltered_rx.next().await?; + filtered_tx.unbounded_send(last_value.clone()).ok()?; + + loop { + let prev_value = mem::replace(&mut last_value, unfiltered_rx.next().await?); + + if last_value != prev_value { + filtered_tx.unbounded_send(last_value.clone()).ok()?; + } + } + } + + let (filtered_tx, filtered_rx) = mpsc::unbounded(); + tokio::task::spawn(do_filtering(unfiltered_rx, filtered_tx)); + filtered_rx +} + +/// Delay `None`-events by `grace_time`. +/// +/// When receiving a `None` on the channel, a timer will start. If no `Some`s are received within +/// the deadline, a `None` will be sent. +/// +/// Some `None`s may be dropped, but `Some`-values are passed along immediately. +fn delay_nones<T: Send + 'static>( + grace_time: Duration, + mut fast_rx: UnboundedReceiver<Option<T>>, +) -> UnboundedReceiver<Option<T>> { + let (slow_tx, slow_rx) = mpsc::unbounded(); + + tokio::task::spawn(async move { + let mut no_route_grace_timeout = None; + + loop { + let no_route_grace_timer = async { + match no_route_grace_timeout { + None => pending().await, + Some(time) => sleep_until(time).await, + }; + }; + + select_biased! { + route = fast_rx.next() => { + let Some(route) = route else { return }; + + if route.is_some() { + no_route_grace_timeout = None; + if slow_tx.unbounded_send(route).is_err() { + return; + }; + + } else if no_route_grace_timeout.is_none() { + no_route_grace_timeout = Some(Instant::now() + grace_time); + } + } + + _ = no_route_grace_timer.fuse() => { + no_route_grace_timeout = None; + if slow_tx.unbounded_send(None).is_err() { + return; + }; + } + } + } + }); + + slow_rx +} diff --git a/talpid-routing/src/unix/macos/interface.rs b/talpid-routing/src/unix/macos/interface.rs index f23870945f..33ddaed9e4 100644 --- a/talpid-routing/src/unix/macos/interface.rs +++ b/talpid-routing/src/unix/macos/interface.rs @@ -10,6 +10,7 @@ use std::{ collections::BTreeMap, io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + ops::Deref, }; use super::data::{Destination, RouteMessage}; @@ -63,13 +64,6 @@ impl Family { } } -#[derive(Debug)] -struct NetworkServiceDetails { - name: String, - router_ip: IpAddr, - first_ip: IpAddr, -} - pub struct PrimaryInterfaceMonitor { store: SCDynamicStore, prefs: SCPreferences, @@ -78,21 +72,68 @@ pub struct PrimaryInterfaceMonitor { // FIXME: Implement Send on SCDynamicStore, if it's safe unsafe impl Send for PrimaryInterfaceMonitor {} +/// Contents of a `/Network/Service/<service_id>/IPvX` key in the [SCDynamicStore]. +#[derive(Clone, Debug)] +pub struct NetworkServiceDetails { + pub interface_name: String, + pub router_ip: IpAddr, + pub first_ip: IpAddr, +} + +/// Contents of the `/Network/Global/IPvX` key in the [SCDynamicStore]. +#[derive(Clone, Debug)] +pub struct PrimaryInterfaceDetails { + #[allow(dead_code)] // this field is useful for debugging + pub name: String, + pub service_id: String, +} + pub enum InterfaceEvent { - Update, + /// The `/Network/Global/IPvX` key in the [SCDynamicStore] was updated. + PrimaryInterfaceUpdate { + /// The IP address family. + family: Family, + + /// The updated [PrimaryInterfaceDetails]. + new_value: Option<PrimaryInterfaceDetails>, + }, + + /// A network service in the [SCDynamicStore] was updated. + NetworkServiceUpdate { + /// The IP address family of the network service. + family: Family, + + /// The ID of the network service. + service_id: String, + + /// The updated [NetworkServiceDetails]. + new_value: Option<NetworkServiceDetails>, + }, +} +impl InterfaceEvent { + pub fn family(&self) -> Family { + match *self { + InterfaceEvent::PrimaryInterfaceUpdate { family, .. } => family, + InterfaceEvent::NetworkServiceUpdate { family, .. } => family, + } + } } -/// Default interface/route +/// The best network route. Either suggested by macOS, or inferred by looking at the available +/// network interfaces. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DefaultRoute { - /// Default interface name + /// Interface name. pub interface: String, - /// Default interface index + + /// Interface index. pub interface_index: u16, - /// Router IP - pub router_ip: IpAddr, - /// Default interface IP address + + /// IP address of the interface. pub ip: IpAddr, + + /// Router IP. + pub router_ip: IpAddr, } impl From<DefaultRoute> for RouteMessage { @@ -111,7 +152,7 @@ impl From<DefaultRoute> for RouteMessage { } impl PrimaryInterfaceMonitor { - pub fn new() -> (Self, UnboundedReceiver<InterfaceEvent>) { + pub fn new() -> (Self, UnboundedReceiver<Vec<InterfaceEvent>>) { let store = SCDynamicStoreBuilder::new("talpid-routing").build(); let prefs = SCPreferences::default(&CFString::new("talpid-routing")); @@ -121,7 +162,7 @@ impl PrimaryInterfaceMonitor { (Self { store, prefs }, rx) } - fn start_listener(tx: UnboundedSender<InterfaceEvent>) { + fn start_listener(tx: UnboundedSender<Vec<InterfaceEvent>>) { std::thread::spawn(|| { let listener_store = SCDynamicStoreBuilder::new("talpid-routing-listener") .callback_context(SCDynamicStoreCallBackContext { @@ -154,46 +195,79 @@ impl PrimaryInterfaceMonitor { } fn store_change_handler( - _store: SCDynamicStore, + store: SCDynamicStore, changed_keys: CFArray<CFString>, - tx: &mut UnboundedSender<InterfaceEvent>, + tx: &mut UnboundedSender<Vec<InterfaceEvent>>, ) { - for k in changed_keys.iter() { - log::debug!("Interface change, key {}", k.to_string()); - } - let _ = tx.unbounded_send(InterfaceEvent::Update); + let events = changed_keys + .iter() + .filter_map(|key| { + let key = key.deref().to_string(); + + let family = match key.as_str() { + STATE_IPV4_KEY => Family::V4, + STATE_IPV6_KEY => Family::V6, + + key => { + let Some((service_id, family)) = service_id_from_service_key(key) else { + log::debug!("Unknown SCDynStore key: {key:?}"); + return None; // skip invalid keys + }; + + let new_value = get_network_service(&store, service_id, family); + return Some(InterfaceEvent::NetworkServiceUpdate { + family, + service_id: service_id.to_string(), + new_value, + }); + } + }; + + let new_value = get_primary_interface(&store, family); + Some(InterfaceEvent::PrimaryInterfaceUpdate { family, new_value }) + }) + .collect(); + + let _ = tx.unbounded_send(events); } /// Retrieve the best current default route. This is based on the primary interface, or else /// the first active interface in the network service order. pub fn get_route(&self, family: Family) -> Option<DefaultRoute> { - let ifaces = self - .get_primary_interface(family) - .map(|iface| { + self.get_primary_interface_service(family) + .map(|service| { log::debug!("Found primary interface for {family}"); - vec![iface] + vec![service] }) - .unwrap_or_else(|| self.network_services(family)); + .unwrap_or_else(|| self.network_services(family)) + .into_iter() + .filter_map(|service| self.route_from_service(&service)) + .next() + } - let (iface, index) = ifaces + /// Iterate through active interfaces in network service order and return a suggested route for + /// the first one with a valid IP and gateway. + pub fn get_route_by_service_order(&self, family: Family) -> Option<DefaultRoute> { + self.network_services(family) .into_iter() - .filter_map(|iface| { - let index = if_nametoindex(iface.name.as_str()) - .inspect_err(|error| { - log::error!( - "Failed to retrieve interface index for \"{}\": {error}", - iface.name - ); - }) - .ok()?; - Some((iface, index)) + .filter_map(|service| self.route_from_service(&service)) + .next() + } + + pub fn route_from_service(&self, service: &NetworkServiceDetails) -> Option<DefaultRoute> { + let index = if_nametoindex(service.interface_name.as_str()) + .inspect_err(|error| { + log::error!( + "Failed to retrieve interface index for \"{}\": {error}", + service.interface_name + ); }) - .next()?; + .ok()?; let index = u16::try_from(index).unwrap(); - let mut router_ip = iface.router_ip; - if let IpAddr::V6(ref mut addr) = router_ip { + let mut router_ip = service.router_ip; + if let IpAddr::V6(addr) = &mut router_ip { if is_link_local_v6(addr) { // The second pair of octets should be set to the scope id // See getaddr() in route.c: @@ -210,37 +284,23 @@ impl PrimaryInterfaceMonitor { } Some(DefaultRoute { - interface: iface.name, + interface: service.interface_name.clone(), interface_index: index, router_ip, - ip: iface.first_ip, + ip: service.first_ip, }) } - fn get_primary_interface(&self, family: Family) -> Option<NetworkServiceDetails> { - let key = if family == Family::V4 { - STATE_IPV4_KEY - } else { - STATE_IPV6_KEY - }; - let global_dict = self - .store - .get(key) - .and_then(|v| v.downcast_into::<CFDictionary>())?; - - let service_id = get_dict_elem_as_string( - &global_dict, - schema_definition!(kSCDynamicStorePropNetPrimaryService), - ) - .or_else(|| { - log::debug!("Missing service ID for primary interface ({family})"); - None - })?; + fn get_primary_interface_service(&self, family: Family) -> Option<NetworkServiceDetails> { + get_primary_interface_service(&self.store, family) + } - self.get_network_service(&service_id, family).or_else(|| { - log::debug!("Invalid service ID for primary interface ({family})"); - None - }) + pub fn get_network_service( + &self, + service_id: &str, + family: Family, + ) -> Option<NetworkServiceDetails> { + get_network_service(&self.store, service_id, family) } fn network_services(&self, family: Family) -> Vec<NetworkServiceDetails> { @@ -250,54 +310,6 @@ impl PrimaryInterfaceMonitor { .filter_map(|service_id| self.get_network_service(&service_id.to_string(), family)) .collect::<Vec<_>>() } - - /// Get details about a specific network interface. - /// - /// Will return `None` and log a message on any error. - fn get_network_service( - &self, - service_id: &str, - family: Family, - ) -> Option<NetworkServiceDetails> { - let service_key = network_service_key(service_id.to_string(), family); - let service_dict = self - .store - .get(CFString::new(&service_key)) - .and_then(|v| v.downcast_into::<CFDictionary>())?; - - let name = get_dict_elem_as_string(&service_dict, schema_definition!(kSCPropInterfaceName)) - .or_else(|| { - log::debug!("Missing name for service {service_key} ({family})"); - None - })?; - let router_ip = get_service_router_ip(&service_dict, family).or_else(|| { - log::debug!("Missing router IP for {service_key} ({name}, {family})"); - None - })?; - let first_ip = get_service_first_ip(&service_dict, family).or_else(|| { - log::debug!("Missing IP for \"{service_key}\" ({name}, {family})"); - None - })?; - - Some(NetworkServiceDetails { - name, - router_ip, - first_ip, - }) - } - - pub fn debug(&self) { - for family in [Family::V4, Family::V6] { - log::debug!( - "Primary interface ({family}): {:?}", - self.get_primary_interface(family) - ); - log::debug!( - "Network services ({family}): {:?}", - self.network_services(family) - ); - } - } } /// Construct the string key for a network service from its ID. @@ -310,6 +322,18 @@ fn network_service_key(service_id: String, family: Family) -> String { format!("State:/Network/Service/{service_id}/{family}") } +fn service_id_from_service_key(key: &str) -> Option<(&str, Family)> { + let id_and_family = key.strip_prefix("State:/Network/Service/")?; + let (id, family) = id_and_family.split_once('/')?; + let family = match family { + "IPv4" => Family::V4, + "IPv6" => Family::V6, + _ => return None, + }; + + Some((id, family)) +} + /// Return a map from interface name to link addresses (AF_LINK) pub fn get_interface_link_addresses() -> io::Result<BTreeMap<String, SockaddrStorage>> { let mut gateway_link_addrs = BTreeMap::new(); @@ -327,6 +351,88 @@ fn is_link_local_v6(addr: &Ipv6Addr) -> bool { (addr.segments()[0] & 0xffc0) == 0xfe80 } +fn get_primary_interface( + store: &SCDynamicStore, + family: Family, +) -> Option<PrimaryInterfaceDetails> { + let key = if family == Family::V4 { + STATE_IPV4_KEY + } else { + STATE_IPV6_KEY + }; + let global_dict = store + .get(key) + .or_else(|| { + log::debug!("{key} is missing!"); + None + }) + .and_then(|v| v.downcast_into::<CFDictionary>())?; + + let service_id = get_dict_elem_as_string( + &global_dict, + schema_definition!(kSCDynamicStorePropNetPrimaryService), + ) + .or_else(|| { + log::debug!("Missing service ID for primary interface ({family})"); + None + })?; + + let name = get_dict_elem_as_string( + &global_dict, + schema_definition!(kSCDynamicStorePropNetPrimaryInterface), + ) + .or_else(|| { + log::debug!("Missing name for primary interface ({family})"); + None + })?; + + Some(PrimaryInterfaceDetails { name, service_id }) +} + +fn get_primary_interface_service( + store: &SCDynamicStore, + family: Family, +) -> Option<NetworkServiceDetails> { + let primary_interface = get_primary_interface(store, family)?; + get_network_service(store, &primary_interface.service_id, family) +} + +/// Get details about a specific network interface. +/// +/// Will return `None` and log a message on any error. +fn get_network_service( + store: &SCDynamicStore, + service_id: &str, + family: Family, +) -> Option<NetworkServiceDetails> { + let service_key = network_service_key(service_id.to_string(), family); + let service_dict = store + .get(CFString::new(&service_key)) + .and_then(|v| v.downcast_into::<CFDictionary>())?; + + let interface_name = + get_dict_elem_as_string(&service_dict, schema_definition!(kSCPropInterfaceName)).or_else( + || { + log::debug!("Missing name for service {service_key} ({family})"); + None + }, + )?; + let router_ip = get_service_router_ip(&service_dict, family).or_else(|| { + log::debug!("Missing router IP for {service_key} ({interface_name}, {family})"); + None + })?; + let first_ip = get_service_first_ip(&service_dict, family).or_else(|| { + log::debug!("Missing IP for \"{service_key}\" ({interface_name}, {family})"); + None + })?; + + Some(NetworkServiceDetails { + interface_name, + router_ip, + first_ip, + }) +} + fn get_service_router_ip(service_dict: &CFDictionary, family: Family) -> Option<IpAddr> { let router_key = if family == Family::V4 { schema_definition!(kSCPropNetIPv4Router) diff --git a/talpid-routing/src/unix/macos/ip_map.rs b/talpid-routing/src/unix/macos/ip_map.rs new file mode 100644 index 0000000000..5d0ad4c61d --- /dev/null +++ b/talpid-routing/src/unix/macos/ip_map.rs @@ -0,0 +1,67 @@ +use super::interface::Family; + +/// A map where the key is [Family]. +#[derive(Clone, Debug)] +pub struct IpMap<T> { + v4: Option<T>, + v6: Option<T>, +} + +impl<T> IpMap<T> { + pub const fn new() -> Self { + Self { v4: None, v6: None } + } + + pub fn get(&self, family: Family) -> Option<&T> { + match family { + Family::V4 => self.v4.as_ref(), + Family::V6 => self.v6.as_ref(), + } + } + + /// Insert an option value and return the old value. + pub fn set(&mut self, family: Family, value: Option<T>) -> Option<T> { + let old_value = self.remove(family); + match family { + Family::V4 => self.v4 = value, + Family::V6 => self.v6 = value, + }; + old_value + } + + /// Insert a value and return the old value. + pub fn insert(&mut self, family: Family, value: T) -> Option<T> { + match family { + Family::V4 => self.v4.replace(value), + Family::V6 => self.v6.replace(value), + } + } + + /// Remove a value and return it. + pub fn remove(&mut self, family: Family) -> Option<T> { + match family { + Family::V4 => self.v4.take(), + Family::V6 => self.v6.take(), + } + } + + pub fn len(&self) -> usize { + self.iter().count() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn iter(&self) -> impl Iterator<Item = (Family, &T)> { + [(Family::V4, &self.v4), (Family::V6, &self.v6)] + .into_iter() + .flat_map(|(family, elem)| Some((family, elem.as_ref()?))) + } + + pub fn drain(&mut self) -> impl Iterator<Item = (Family, T)> { + [(Family::V4, self.v4.take()), (Family::V6, self.v6.take())] + .into_iter() + .flat_map(|(family, elem)| Some((family, elem?))) + } +} diff --git a/talpid-routing/src/unix/macos/mod.rs b/talpid-routing/src/unix/macos/mod.rs index df89767e38..975ebef927 100644 --- a/talpid-routing/src/unix/macos/mod.rs +++ b/talpid-routing/src/unix/macos/mod.rs @@ -1,11 +1,14 @@ use crate::{debounce::BurstGuard, Gateway, MacAddress, NetNode, RequiredRoute, Route}; +use default_routes::DefaultRouteMonitor; use futures::{ channel::mpsc::{self, UnboundedReceiver}, future::FutureExt, stream::{FusedStream, StreamExt}, }; +use ip_map::IpMap; use ipnetwork::IpNetwork; +use nix::net::if_::if_nametoindex; use std::{ collections::{BTreeMap, HashSet}, net::{IpAddr, SocketAddr}, @@ -23,7 +26,9 @@ pub use super::DefaultRouteEvent; pub use interface::DefaultRoute; mod data; +mod default_routes; mod interface; +mod ip_map; mod routing_socket; mod watch; @@ -58,17 +63,6 @@ pub enum Error { InvalidData(#[source] data::Error), } -/// Convenience macro to get the current default route. Macro because I don't want to borrow `self` -/// mutably. -macro_rules! get_current_best_default_route { - ($self:expr, $family:expr) => {{ - match $family { - interface::Family::V4 => &mut $self.v4_default_route, - interface::Family::V6 => &mut $self.v6_default_route, - } - }}; -} - /// Route manager can be in 1 of 4 states - /// - waiting for a route to be added or removed from the route table /// - obtaining default routes @@ -81,21 +75,49 @@ macro_rules! get_current_best_default_route { /// new changes, obtain new default routes and reapply routes that should be routed through the /// default nodes. Once the routes are reapplied, the route table changes are monitored again. pub struct RouteManagerImpl { + /// Interface to the macOS routing table. routing_table: RoutingTable, - // Routes that use the default non-tunnel interface + + /// Routes that use the default non-tunnel interface. non_tunnel_routes: HashSet<IpNetwork>, - v4_tunnel_default_route: Option<data::RouteMessage>, - v6_tunnel_default_route: Option<data::RouteMessage>, + + /// The IPv4 and IPv6 routes that we will add that points at the tun interface. + /// + /// This is populated by [Self::add_required_routes] + /// and used by [Self::apply_tunnel_default_routes]. + tunnel_default_routes: IpMap<data::RouteMessage>, + + /// The list of routes we have added to macOSs routing table in [Self::add_route_with_record]. applied_routes: BTreeMap<RouteDestination, RouteMessage>, - v4_default_route: Option<interface::DefaultRoute>, - v6_default_route: Option<interface::DefaultRoute>, + + /// Callback that fires when we receive an event from our route-monitoring socket, + /// or from [Self::interface_change_rx]. It indicates that *something* has changed with the + /// routing table or with the network interfaces. update_trigger: BurstGuard, + + /// Indicates that `best_default_route` has changed, or that a `/0`-route in the routing table + /// was changed. + unhandled_default_route_changes: bool, + + check_default_routes_restored: Pin<Box<dyn FusedStream<Item = ()> + Send>>, + + /// Channel that receives updates to the best [DefaultRoute] for IPv4. + best_default_route_rx_v4: UnboundedReceiver<Option<DefaultRoute>>, + + /// Channel that receives updates to the best [DefaultRoute] for IPv6. + best_default_route_rx_v6: UnboundedReceiver<Option<DefaultRoute>>, + + /// The best IPv4 and v6 network routes suggested by macOS. + /// + /// Example: Interface "en0" (1) with IP 192.168.1.222, and with router_ip 192.168.1.1. + best_default_route: IpMap<interface::DefaultRoute>, + + /// Message to notify `default_route_listeners` when `best_default_route` changes. + best_default_route_update: IpMap<DefaultRouteEvent>, + default_route_listeners: Vec<mpsc::UnboundedSender<DefaultRouteEvent>>, + interface_change_listeners: Vec<mpsc::UnboundedSender<super::InterfaceEvent>>, - check_default_routes_restored: Pin<Box<dyn FusedStream<Item = ()> + Send>>, - unhandled_default_route_changes: bool, - primary_interface_monitor: interface::PrimaryInterfaceMonitor, - interface_change_rx: UnboundedReceiver<interface::InterfaceEvent>, } impl RouteManagerImpl { @@ -106,61 +128,42 @@ impl RouteManagerImpl { ) -> Result<Self> { let (primary_interface_monitor, interface_change_rx) = interface::PrimaryInterfaceMonitor::new(); + + let (best_route_rx_v4, best_route_rx_v6) = + DefaultRouteMonitor::start(primary_interface_monitor, interface_change_rx); + let routing_table = RoutingTable::new().map_err(Error::RoutingTable)?; let update_trigger = BurstGuard::new( BURST_BUFFER_PERIOD, BURST_LONGEST_BUFFER_PERIOD, move || { - let Some(manage_tx) = manage_tx.upgrade() else { - return; - }; - let _ = manage_tx.unbounded_send(RouteManagerCommand::RefreshRoutes); + if let Some(manage_tx) = manage_tx.upgrade() { + let _ = manage_tx.unbounded_send(RouteManagerCommand::RefreshRoutes); + } }, ); Ok(Self { routing_table, non_tunnel_routes: HashSet::new(), - v4_tunnel_default_route: None, - v6_tunnel_default_route: None, + tunnel_default_routes: IpMap::new(), applied_routes: BTreeMap::new(), - v4_default_route: None, - v6_default_route: None, - update_trigger, + best_default_route: IpMap::new(), + best_default_route_update: IpMap::new(), default_route_listeners: vec![], - interface_change_listeners: vec![], + best_default_route_rx_v4: best_route_rx_v4, + best_default_route_rx_v6: best_route_rx_v6, + update_trigger, check_default_routes_restored: Box::pin(futures::stream::pending()), unhandled_default_route_changes: false, - primary_interface_monitor, - interface_change_rx, + interface_change_listeners: vec![], }) } pub(crate) async fn run(mut self, manage_rx: mpsc::UnboundedReceiver<RouteManagerCommand>) { let mut manage_rx = manage_rx.fuse(); - // Initialize default routes - // NOTE: This isn't race-free, as we're not listening for route changes before initializing - self.update_best_default_route(interface::Family::V4) - .unwrap_or_else(|error| { - log::error!( - "{}", - error.display_chain_with_msg("Failed to get initial default v4 route") - ); - false - }); - self.update_best_default_route(interface::Family::V6) - .unwrap_or_else(|error| { - log::error!( - "{}", - error.display_chain_with_msg("Failed to get initial default v6 route") - ); - false - }); - - self.debug_offline(); - let mut completion_tx = None; loop { @@ -177,14 +180,21 @@ impl RouteManagerImpl { if self.check_default_routes_restored.is_terminated() { continue; } + if self.try_restore_default_routes().await { log::debug!("Unscoped routes were already restored"); self.check_default_routes_restored = Box::pin(futures::stream::pending()); } } - _event = self.interface_change_rx.next() => { - self.update_trigger.trigger(); + new_best_route = self.best_default_route_rx_v4.next() => { + let Some(new_best_route)= new_best_route else { continue }; + self.handle_new_best_default_route(interface::Family::V4, new_best_route); + } + + new_best_route = self.best_default_route_rx_v6.next() => { + let Some(new_best_route)= new_best_route else { continue }; + self.handle_new_best_default_route(interface::Family::V6, new_best_route); } command = manage_rx.next() => { @@ -200,18 +210,18 @@ impl RouteManagerImpl { let _ = tx.send(events_rx); } Some(RouteManagerCommand::GetDefaultRoutes(tx)) => { - let v4_route = self.v4_default_route.clone(); - let v6_route = self.v6_default_route.clone(); + let v4_route = self.best_default_route.get(interface::Family::V4).cloned(); + let v6_route = self.best_default_route.get(interface::Family::V6).cloned(); let _ = tx.send((v4_route, v6_route)); } Some(RouteManagerCommand::GetDefaultGateway(tx)) => { let mut v4_gateway = None; let mut v6_gateway = None; - if let Some(v4_route) = &self.v4_default_route { + if let Some(v4_route) = self.best_default_route.get(interface::Family::V4) { v4_gateway = self.get_gateway_link_address(v4_route.router_ip).await; } - if let Some(v6_route) = &self.v6_default_route { + if let Some(v6_route) = self.best_default_route.get(interface::Family::V6) { v6_gateway = self.get_gateway_link_address(v6_route.router_ip).await; } let _ = tx.send((v4_gateway, v6_gateway)); @@ -303,17 +313,24 @@ impl RouteManagerImpl { // Add routes not using the default interface for route in routes_to_apply { - let mut message = if let Some(ref device) = route.node.device { - // If we specify route by interface name, use the link address of the given - // interface - match interface_link_addrs.get(device) { - Some(link_addr) => RouteMessage::new_route(Destination::from(route.prefix)) - .set_gateway_sockaddr(*link_addr), - None => { - log::error!("Route with unknown device: {route:?}, {device}"); - continue; - } - } + let mut message = if let Some(device) = route.node.get_device() { + // Get the link-address of the provided network interface (device). + // We need the link address to create a route that targets the interface. + let Some(link_addr) = interface_link_addrs.get(device) else { + log::error!("Route with unknown device: {route:?}, {device}"); + continue; + }; + + // Get the index of the network interface. This is not needed to create the route, + // but we use it to later validate that the route is correct. + let Ok(interface_index) = if_nametoindex(device) else { + log::error!("Route with unknown device: {route:?}, {device}"); + continue; + }; + + RouteMessage::new_route(Destination::from(route.prefix)) + .set_gateway_sockaddr(*link_addr) + .set_interface_index(interface_index as u16) } else { log::error!("Specifying gateway by IP rather than device is unimplemented"); continue; @@ -326,19 +343,19 @@ impl RouteManagerImpl { // Default routes are a special case: We must apply it after replacing the current // default route with an ifscope route. if route.prefix.prefix() == 0 { - if route.prefix.is_ipv4() { - self.v4_tunnel_default_route = Some(message); + let family = if route.prefix.is_ipv4() { + interface::Family::V4 } else { - self.v6_tunnel_default_route = Some(message); - } + interface::Family::V6 + }; + self.tunnel_default_routes.insert(family, message); continue; } - // Add route self.add_route_with_record(message).await?; } - self.apply_tunnel_default_route().await?; + self.apply_tunnel_default_routes().await?; // Add routes that use the default interface if let Err(error) = self.apply_non_tunnel_routes().await { @@ -355,6 +372,8 @@ impl RouteManagerImpl { ) { talpid_types::detect_flood!(); + log::trace!("got RouteSocketMessage::{:?}", message.as_ref().unwrap()); + match message { Ok(RouteSocketMessage::DeleteRoute(route)) => { // Forget about applied route, if relevant @@ -374,8 +393,7 @@ impl RouteManagerImpl { } self.update_trigger.trigger(); } - Ok(RouteSocketMessage::AddRoute(route)) - | Ok(RouteSocketMessage::ChangeRoute(route)) => { + Ok(RouteSocketMessage::AddRoute(route) | RouteSocketMessage::ChangeRoute(route)) => { if route.errno() != 0 { return; } @@ -423,23 +441,26 @@ impl RouteManagerImpl { async fn refresh_routes(&mut self) -> Result<()> { talpid_types::detect_flood!(); - self.update_best_default_route(interface::Family::V4)?; - self.update_best_default_route(interface::Family::V6)?; - - self.debug_offline(); + for (_, event) in self.best_default_route_update.drain() { + self.default_route_listeners + .retain(|tx| tx.unbounded_send(event).is_ok()); + } if !self.unhandled_default_route_changes { + self.ensure_default_tunnel_routes_exist().await?; return Ok(()); } - // Remove any existing ifscope route that we've added + log::trace!("Refreshing routes"); + + // Remove any existing ifscoped default route that we've added self.remove_applied_routes(|route| { route.is_ifscope() && route.is_default().unwrap_or(false) }) .await; // Substitute route with a tunnel route - self.apply_tunnel_default_route().await?; + self.apply_tunnel_default_routes().await?; // Update routes using default interface self.apply_non_tunnel_routes().await?; @@ -449,117 +470,68 @@ impl RouteManagerImpl { Ok(()) } - fn debug_offline(&self) { - if self.v4_default_route.is_none() && self.v6_default_route.is_none() { - self.primary_interface_monitor.debug(); - } - } - - /// 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. - /// - /// The "best route" is determined by the first interface in the network service order that has - /// a valid IP address and gateway. - /// - /// On success, the function returns whether the previously known best default changed. - fn update_best_default_route(&mut self, family: interface::Family) -> Result<bool> { - let best_route = self.primary_interface_monitor.get_route(family); - - let current_route = get_current_best_default_route!(self, family); - - log::trace!("Best route ({family:?}): {best_route:?}"); - if best_route == *current_route { - return Ok(false); - } - - self.unhandled_default_route_changes = true; - - let old_pair = current_route - .as_ref() - .map(|r| (r.interface_index, r.router_ip)); - let new_pair = best_route - .as_ref() - .map(|r| (r.interface_index, r.router_ip)); - log::debug!("Best default route ({family}) changed from {old_pair:?} to {new_pair:?}"); - let _ = std::mem::replace(current_route, best_route); - - let changed = current_route.is_some(); - self.notify_default_route_listeners(family, changed); - Ok(true) - } + /// Handle a new [DefaultRoute] received from [DefaultRouteMonitor]. + fn handle_new_best_default_route( + &mut self, + family: interface::Family, + new_best_route: Option<DefaultRoute>, + ) { + log::trace!("Best route ({family:?}): {new_best_route:?}"); - fn notify_default_route_listeners(&mut self, family: interface::Family, changed: bool) { - // Notify default route listeners - let event = match (family, changed) { + let event = match (family, new_best_route.is_some()) { (interface::Family::V4, true) => DefaultRouteEvent::AddedOrChangedV4, - (interface::Family::V6, true) => DefaultRouteEvent::AddedOrChangedV6, (interface::Family::V4, false) => DefaultRouteEvent::RemovedV4, + (interface::Family::V6, true) => DefaultRouteEvent::AddedOrChangedV6, (interface::Family::V6, false) => DefaultRouteEvent::RemovedV6, }; - self.default_route_listeners - .retain(|tx| tx.unbounded_send(event).is_ok()); + self.best_default_route_update.insert(family, event); + self.best_default_route.set(family, new_best_route); + self.unhandled_default_route_changes = true; + self.update_trigger.trigger(); } /// Replace the default routes with an ifscope route, and /// add a new default tunnel route. - async fn apply_tunnel_default_route(&mut self) -> Result<()> { + async fn apply_tunnel_default_routes(&mut self) -> Result<()> { // As long as the relay route has a way of reaching the internet, we'll want to add a tunnel // route for both IPv4 and IPv6. // NOTE: This is incorrect. We're assuming that any "default destination" is used for // tunneling. - let (v4_conn, v6_conn) = self - .non_tunnel_routes - .iter() - .fold((false, false), |(v4, v6), route| { - (v4 || route.is_ipv4(), v6 || route.is_ipv6()) - }); - let relay_route_is_valid = (v4_conn && self.v4_default_route.is_some()) - || (v6_conn && self.v6_default_route.is_some()); + let v4_conn = self.non_tunnel_routes.iter().any(|r| r.is_ipv4()); + let v6_conn = self.non_tunnel_routes.iter().any(|r| r.is_ipv6()); + + let relay_route_is_valid = (v4_conn + && self.best_default_route.get(interface::Family::V4).is_some()) + || (v6_conn && self.best_default_route.get(interface::Family::V6).is_some()); if !relay_route_is_valid { return Ok(()); } - for tunnel_route in [ - self.v4_tunnel_default_route.clone(), - self.v6_tunnel_default_route.clone(), - ] { - let tunnel_route = match tunnel_route { - Some(route) => route, - None => continue, - }; - let family = if tunnel_route.is_ipv4() { - interface::Family::V4 - } else { - interface::Family::V6 - }; - + for (family, tunnel_route) in self.tunnel_default_routes.clone().iter() { // Replace the default route with an ifscope route self.replace_with_scoped_route(family).await?; // Make sure there is really no other unscoped default route - let mut msg = RouteMessage::new_route(family.default_network().into()); - msg = msg.set_gateway_route(true); - let old_route = self.routing_table.get_route(&msg).await; - if let Ok(Some(old_route)) = old_route { - let tun_gateway_link_addr = - tunnel_route.gateway().and_then(|addr| addr.as_link_addr()); - let current_link_addr = old_route.gateway().and_then(|addr| addr.as_link_addr()); - if current_link_addr - .map(|addr| Some(addr) != tun_gateway_link_addr) - .unwrap_or(true) - { + + let actual_default_route = self.get_actual_default_route(family).await.unwrap_or(None); + + if let Some(actual_default_route) = actual_default_route { + debug_assert!(tunnel_route.interface_index() != 0); + if tunnel_route.interface_index() != actual_default_route.interface_index() { log::trace!("Removing existing unscoped default route"); - let _ = self.routing_table.delete_route(&msg).await; - } else if !old_route.is_ifscope() { - // NOTE: Skipping route - continue; + + let _ = self + .routing_table + .delete_route(&default_route_msg(family)) + .await; + } else if !actual_default_route.is_ifscope() { + continue; // Skipping route } } - log::debug!("Adding default route for tunnel"); - self.add_route_with_record(tunnel_route).await?; + log::debug!("Adding default route for tunnel ({family:?})"); + self.add_route_with_record(tunnel_route.clone()).await?; } Ok(()) @@ -569,12 +541,12 @@ impl RouteManagerImpl { /// a default route, this function replaces the non-tunnel default route with an ifscope route. async fn apply_non_tunnel_routes(&mut self) -> Result<()> { let v4_gateway = self - .v4_default_route - .as_ref() + .best_default_route + .get(interface::Family::V4) .map(|route| SocketAddr::new(route.router_ip, 0)); let v6_gateway = self - .v6_default_route - .as_ref() + .best_default_route + .get(interface::Family::V6) .map(|route| SocketAddr::new(route.router_ip, 0)); // Reapply routes that use the default (non-tunnel) node @@ -609,7 +581,7 @@ impl RouteManagerImpl { /// Replace a known default route with an ifscope route. async fn replace_with_scoped_route(&mut self, family: interface::Family) -> Result<()> { - let Some(default_route) = get_current_best_default_route!(self, family) else { + let Some(default_route) = self.best_default_route.get(family) else { return Ok(()); }; @@ -641,8 +613,8 @@ impl RouteManagerImpl { self.remove_applied_routes(|_| true).await; // We have already removed the applied default routes - self.v4_tunnel_default_route = None; - self.v6_tunnel_default_route = None; + self.tunnel_default_routes.remove(interface::Family::V4); + self.tunnel_default_routes.remove(interface::Family::V6); self.try_restore_default_routes().await; @@ -707,24 +679,30 @@ impl RouteManagerImpl { /// Add back unscoped default route for the given `family`, if it is still missing. This /// function returns true when no route had to be added. async fn restore_default_route(&mut self, family: interface::Family) -> bool { - let Some(desired_default_route) = self.primary_interface_monitor.get_route(family) else { + let Some(desired_default_route) = self.best_default_route.get(family).cloned() else { return true; }; let desired_default_route = RouteMessage::from(desired_default_route); let current_default_route = RouteMessage::new_route(family.default_network().into()); + if let Ok(Some(current_default)) = self.routing_table.get_route(¤t_default_route).await { // We're done if the route we're looking for is already here if route_matches_interface(¤t_default, &desired_default_route) { + log::trace!("current default route for {family:?} matches the desired"); return true; } + + log::trace!("current default route for {family:?} does NOT match the desired"); let _ = self .routing_table .delete_route(¤t_default_route) .await; - }; + } else { + log::trace!("no current default route") + } if let Err(error) = self.routing_table.add_route(&desired_default_route).await { log::trace!("Failed to add unscoped default {family} route: {error}"); @@ -734,6 +712,50 @@ impl RouteManagerImpl { false } + + async fn ensure_default_tunnel_routes_exist(&mut self) -> Result<()> { + for (family, _) in self.tunnel_default_routes.clone().iter() { + if !self.default_route_is_tunnel_route(family).await? { + return self.apply_tunnel_default_routes().await; + } + } + + Ok(()) + } + + /// Check if the `0.0.0.0/0`/`::/0`-route goes to our tunnel interface. + async fn default_route_is_tunnel_route(&mut self, family: interface::Family) -> Result<bool> { + let actual_default_route = self.get_actual_default_route(family).await?; + + let Some(actual_default_route) = actual_default_route else { + return Ok(false); + }; + + let Some(tunnel_route) = self.tunnel_default_routes.get(family) else { + return Ok(false); + }; + + Ok(actual_default_route.interface_index() == tunnel_route.interface_index()) + } + + /// Get the route which goes to `0.0.0.0/0`/`::/0`, if any. + async fn get_actual_default_route( + &mut self, + family: interface::Family, + ) -> Result<Option<data::RouteMessage>> { + self.routing_table + .get_route(&default_route_msg(family)) + .await + .map_err(Error::RoutingTable) + } +} + +/// Construct a [RouteMessage] that refers to the `0.0.0.0/0` or `::/0` route with the +/// RTF_GATEAWAY-flag set. Used to reference the default route created by macOS. +fn default_route_msg(family: interface::Family) -> RouteMessage { + let mut msg = RouteMessage::new_route(family.default_network().into()); + msg = msg.set_gateway_route(true); + msg } fn route_matches_interface(default_route: &RouteMessage, interface_route: &RouteMessage) -> bool { diff --git a/talpid-routing/src/unix/macos/routing_socket.rs b/talpid-routing/src/unix/macos/routing_socket.rs index 763dbb9264..cfa0f0973f 100644 --- a/talpid-routing/src/unix/macos/routing_socket.rs +++ b/talpid-routing/src/unix/macos/routing_socket.rs @@ -173,7 +173,7 @@ impl RoutingSocketInner { let mut guard = self.socket.readable().await?; match guard.try_io(|sock| sock.get_ref().read(out)) { Ok(result) => return result, - Err(_err) => continue, + Err(_would_block) => continue, } } } diff --git a/talpid-routing/src/unix/macos/watch.rs b/talpid-routing/src/unix/macos/watch.rs index 61c620b2f3..e3f034a841 100644 --- a/talpid-routing/src/unix/macos/watch.rs +++ b/talpid-routing/src/unix/macos/watch.rs @@ -83,6 +83,8 @@ impl RoutingTable { } } + log::trace!("Add route: {message:?}"); + let msg = self .alter_routing_table(message, MessageType::RTM_ADD) .await; @@ -131,6 +133,8 @@ impl RoutingTable { } pub async fn delete_route(&mut self, message: &RouteMessage) -> Result<()> { + log::trace!("Delete route: {message:?}"); + let response = self .alter_routing_table(message, MessageType::RTM_DELETE) .await?; |
