//! Manage routing tables on various platforms. #![allow(rustdoc::private_intra_doc_links)] #![deny(missing_docs)] use ipnetwork::IpNetwork; use std::{fmt, net::IpAddr}; #[cfg(any(target_os = "windows", target_os = "macos"))] /// Burst guard pub mod debounce; #[cfg(target_os = "windows")] #[path = "windows/mod.rs"] mod imp; #[cfg(target_os = "windows")] pub use imp::{CallbackHandle, EventType, InterfaceAndGateway, get_best_default_route}; #[cfg(not(target_os = "windows"))] #[path = "unix/mod.rs"] mod imp; #[cfg(target_os = "macos")] pub use imp::{ PlatformError, imp::{DefaultRouteEvent, RouteError, RoutingTable, data}, }; pub use imp::{Error, RouteManagerHandle}; /// Link-layer/MAC address #[cfg(target_os = "macos")] #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct MacAddress(pub [u8; 6]); #[cfg(target_os = "macos")] impl MacAddress { /// Consume bytes that make up the link address pub fn into_bytes(self) -> [u8; 6] { self.0 } } #[cfg(target_os = "macos")] impl From<[u8; 6]> for MacAddress { fn from(addr: [u8; 6]) -> Self { Self(addr) } } #[cfg(target_os = "macos")] impl fmt::Display for MacAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{:<02X}{:<02X}{:<02X}{:<02X}{:<02X}{:<02X}", self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] ) } } /// Gateway, including IP address and MAC address #[cfg(target_os = "macos")] #[derive(Debug, Eq, PartialEq, Clone)] pub struct Gateway { /// Network layer address for the gateway pub ip_address: IpAddr, /// Link layer address for the gateway pub mac_address: MacAddress, } /// A network route with a specific network node, destination and an optional metric. #[cfg(not(target_os = "android"))] #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct Route { node: Node, prefix: IpNetwork, metric: Option, #[cfg(target_os = "linux")] table_id: u32, #[cfg(any(target_os = "linux", target_os = "macos"))] mtu: Option, } /// A network route with a specific network node, destination and an optional metric. #[cfg(target_os = "android")] #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct Route(IpNetwork); impl Route { /// Construct a new Route #[cfg(not(target_os = "android"))] pub fn new(node: Node, prefix: IpNetwork) -> Self { Self { node, prefix, metric: None, #[cfg(target_os = "linux")] table_id: u32::from(libc::RT_TABLE_MAIN), #[cfg(any(target_os = "linux", target_os = "macos"))] mtu: None, } } /// Construct a new Route #[cfg(target_os = "android")] pub fn new(prefix: IpNetwork) -> Self { Self(prefix) } #[cfg(target_os = "linux")] fn table(mut self, new_id: u32) -> Self { self.table_id = new_id; self } /// Returns the network node of the route. #[cfg(target_os = "linux")] pub fn get_node(&self) -> &Node { &self.node } } #[cfg(target_os = "linux")] impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} via {}", self.prefix, self.node)?; if let Some(metric) = &self.metric { write!(f, " metric {}", *metric)?; } #[cfg(target_os = "linux")] write!(f, " table {}", self.table_id)?; #[cfg(target_os = "linux")] if let Some(mtu) = self.mtu { write!(f, " mtu {mtu}")?; } Ok(()) } } #[cfg(target_os = "android")] impl From<&talpid_types::android::RouteInfo> for Route { fn from(route_info: &talpid_types::android::RouteInfo) -> Self { let network = IpNetwork::new( route_info.destination.address, route_info.destination.prefix_length as u8, ) .unwrap(); Self::new(network) } } /// A network route that should be applied by the route manager. /// It can either be routed through a specific network node or it can be routed through the current /// default route. #[cfg(not(target_os = "android"))] #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct RequiredRoute { /// Route's prefix pub prefix: IpNetwork, node: NetNode, /// Specifies whether the route should be added to the main routing table or not. #[cfg(target_os = "linux")] main_table: bool, /// Specifies route MTU #[cfg(any(target_os = "linux", target_os = "macos"))] mtu: Option, } #[cfg(not(target_os = "android"))] impl RequiredRoute { /// Constructs a new required route. pub fn new(prefix: IpNetwork, node: impl Into) -> Self { Self { node: node.into(), prefix, #[cfg(target_os = "linux")] main_table: true, #[cfg(any(target_os = "linux", target_os = "macos"))] mtu: None, } } /// Sets the routing table ID of the route. #[cfg(target_os = "linux")] pub fn use_main_table(mut self, main_table: bool) -> Self { self.main_table = main_table; self } /// Set route MTU to the given value. #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn with_mtu(mut self, mtu: u16) -> Self { self.mtu = Some(mtu); self } } /// A NetNode represents a network node - either a real one or a symbolic default one. /// A route with a symbolic default node will be changed whenever a new default route is created. #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum NetNode { /// A real node will be used to set a regular route that will remain unchanged for the lifetime /// of the route manager RealNode(Node), /// A default node is a symbolic node that will resolve to the network node used in the current /// most preferable default route #[cfg(not(target_os = "linux"))] DefaultNode, } impl From for NetNode { fn from(node: Node) -> NetNode { NetNode::RealNode(node) } } /// Node represents a real network node - it can be identified by a network interface name, an IP /// address or both. #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub struct Node { ip: Option, device: Option, } impl Node { /// Construct an Node with both an IP address and an interface name. pub fn new(address: IpAddr, iface_name: String) -> Self { Self { ip: Some(address), device: Some(iface_name), } } /// Construct an Node from an IP address. pub fn address(address: IpAddr) -> Node { Self { ip: Some(address), device: None, } } /// Construct a Node from a network interface name. pub fn device(iface_name: String) -> Node { Self { ip: None, device: Some(iface_name), } } /// Retrieve a node's IP address pub fn get_address(&self) -> Option { self.ip } /// Retrieve a node's network interface name pub fn get_device(&self) -> Option<&str> { self.device.as_ref().map(|s| s.as_ref()) } } impl fmt::Display for Node { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ip) = &self.ip { write!(f, "{ip}")?; } if let Some(device) = &self.device { let extra_space = if self.ip.is_some() { " " } else { "" }; write!(f, "{extra_space}dev {device}")?; } Ok(()) } }