narrowlink_custom_dependencies/
lib.rs

1// SPDX-License-Identifier: MIT
2
3//! This project aims to provide a high level interface for manipulating and observing
4//! the routing table on a variety of platforms.
5//!
6//!
7//! ## Examples
8//! #### Adding a route
9//! ```
10//! // route traffic destined for 10.14.0.0/24 to 192.1.2.1 using interface 9
11//! let handle = Handle::new()?;
12//! let route = Route::new("10.14.0.0".parse().unwrap(), 24)
13//!     .with_ifindex(9)
14//!     .with_gateway("192.1.2.1".parse.unwrap());
15//! handle.add(&route).await
16//! ```
17//!
18//! #### Listening to changes in the routing table
19//! ```
20//! let handle = Handle::new()?;
21//! let stream = handle.route_listen_stream();
22//! futures::pin_mut!(stream);
23//! while let Some(event) = stream.next().await {
24//!     println!("{:?}", value);
25//! }
26//! ```
27
28use std::{
29    io,
30    net::{IpAddr, Ipv4Addr, Ipv6Addr},
31};
32
33mod platform_impl;
34use platform_impl::PlatformHandle;
35
36#[cfg(all(target_os = "macos", not(doc)))]
37pub use platform_impl::ifname_to_index;
38
39/// Handle that abstracts initialization and cleanup of resources needed to operate on the routing table.
40pub struct Handle(PlatformHandle);
41
42impl Handle {
43    pub fn new() -> io::Result<Self> {
44        Ok(Self(PlatformHandle::new()?))
45    }
46
47    /// Add route to the system's routing table.
48    pub async fn add(&self, route: &Route) -> io::Result<()> {
49        self.0.add(route).await
50    }
51
52    /// Returns a `Stream` which will yield a `RouteChange` event whenever a route is added, removed, or changed from the system's routing table.
53    pub fn route_listen_stream(&self) -> impl futures::Stream<Item = RouteChange> {
54        self.0.route_listen_stream()
55    }
56
57    /// Returns a `Vec<Route>` containing a list of both ipv4 and v6 routes on the system.
58    pub async fn list(&self) -> io::Result<Vec<Route>> {
59        self.0.list().await
60    }
61
62    /// Get one of the default routes on the system if there is at least one.
63    pub async fn default_route(&self) -> io::Result<Option<Route>> {
64        self.0.default_route().await
65    }
66
67    /// Remove a route from the system's routing table.
68    pub async fn delete(&self, route: &Route) -> io::Result<()> {
69        self.0.delete(route).await
70    }
71}
72
73/// Contains information that describes a route in the local computer's Ipv4 or Ipv6 routing table.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Route {
76    /// Network address of the destination. `0.0.0.0` with a prefix of `0` is considered a default route.
77    pub destination: IpAddr,
78
79    /// Length of network prefix in the destination address.
80    pub prefix: u8,
81
82    /// The address of the next hop of this route.
83    ///
84    /// On macOS, this must be `Some` if ifindex is `None`
85    pub gateway: Option<IpAddr>,
86
87    /// The index of the local interface through which the next hop of this route may be reached.
88    ///
89    /// On macOS, this must be `Some` if gateway is `None`
90    pub ifindex: Option<u32>,
91
92    #[cfg(target_os = "linux")]
93    /// The routing table this route belongs to.
94    pub table: u8,
95
96    #[cfg(target_os = "windows")]
97    /// The route metric offset value for this route.
98    pub metric: Option<u32>,
99
100    #[cfg(target_os = "windows")]
101    /// Luid of the local interface through which the next hop of this route may be reached.
102    ///
103    /// If luid is specified, ifindex is optional.
104    pub luid: Option<u64>,
105}
106
107impl Route {
108    /// Create a route that matches a given destination network.
109    ///
110    /// Either the gateway or interface should be set before attempting to add to a routing table.
111    pub fn new(destination: IpAddr, prefix: u8) -> Self {
112        Self {
113            destination,
114            prefix,
115            gateway: None,
116            ifindex: None,
117            #[cfg(target_os = "linux")]
118            // default to main table
119            table: 254,
120            #[cfg(target_os = "windows")]
121            metric: None,
122            #[cfg(target_os = "windows")]
123            luid: None,
124        }
125    }
126
127    /// Set the next next hop gateway for this route.
128    pub fn with_gateway(mut self, gateway: IpAddr) -> Self {
129        self.gateway = Some(gateway);
130        self
131    }
132
133    /// Set the index of the local interface through which the next hop of this route should be reached.
134    pub fn with_ifindex(mut self, ifindex: u32) -> Self {
135        self.ifindex = Some(ifindex);
136        self
137    }
138
139    /// Set table the route will be installed in.
140    #[cfg(target_os = "linux")]
141    pub fn with_table(mut self, table: u8) -> Self {
142        self.table = table;
143        self
144    }
145
146    /// Set route metric.
147    #[cfg(target_os = "windows")]
148    pub fn with_metric(mut self, metric: u32) -> Self {
149        self.metric = Some(metric);
150        self
151    }
152
153    /// Set luid of the local interface through which the next hop of this route should be reached.
154    #[cfg(target_os = "windows")]
155    pub fn with_luid(mut self, luid: u64) -> Self {
156        self.luid = Some(luid);
157        self
158    }
159
160    /// Get the netmask covering the network portion of the destination address.
161    pub fn mask(&self) -> IpAddr {
162        match self.destination {
163            IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::from(
164                u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0),
165            )),
166            IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::from(
167                u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0),
168            )),
169        }
170    }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum RouteChange {
175    Add(Route),
176    Delete(Route),
177    Change(Route),
178}
179
180#[cfg(test)]
181mod tests {
182    use std::net::{IpAddr, Ipv6Addr};
183
184    use crate::Route;
185
186    #[test]
187    fn it_calculates_v4_netmask() {
188        let mut route = Route::new("10.10.0.0".parse().unwrap(), 32);
189
190        assert_eq!(route.mask(), "255.255.255.255".parse::<IpAddr>().unwrap());
191
192        route.prefix = 29;
193        assert_eq!(route.mask(), "255.255.255.248".parse::<IpAddr>().unwrap());
194
195        route.prefix = 25;
196        assert_eq!(route.mask(), "255.255.255.128".parse::<IpAddr>().unwrap());
197
198        route.prefix = 2;
199        assert_eq!(route.mask(), "192.0.0.0".parse::<IpAddr>().unwrap());
200    }
201
202    #[test]
203    fn it_calculates_v6_netmask() {
204        let route = Route::new(
205            "77ca:838b:9ec0:fc97:eedc:236a:9d41:31e5".parse().unwrap(),
206            32,
207        );
208        assert_eq!(
209            route.mask(),
210            Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0)
211        );
212    }
213}