narrowlink_custom_dependencies/
lib.rs1use 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
39pub struct Handle(PlatformHandle);
41
42impl Handle {
43 pub fn new() -> io::Result<Self> {
44 Ok(Self(PlatformHandle::new()?))
45 }
46
47 pub async fn add(&self, route: &Route) -> io::Result<()> {
49 self.0.add(route).await
50 }
51
52 pub fn route_listen_stream(&self) -> impl futures::Stream<Item = RouteChange> {
54 self.0.route_listen_stream()
55 }
56
57 pub async fn list(&self) -> io::Result<Vec<Route>> {
59 self.0.list().await
60 }
61
62 pub async fn default_route(&self) -> io::Result<Option<Route>> {
64 self.0.default_route().await
65 }
66
67 pub async fn delete(&self, route: &Route) -> io::Result<()> {
69 self.0.delete(route).await
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Route {
76 pub destination: IpAddr,
78
79 pub prefix: u8,
81
82 pub gateway: Option<IpAddr>,
86
87 pub ifindex: Option<u32>,
91
92 #[cfg(target_os = "linux")]
93 pub table: u8,
95
96 #[cfg(target_os = "windows")]
97 pub metric: Option<u32>,
99
100 #[cfg(target_os = "windows")]
101 pub luid: Option<u64>,
105}
106
107impl Route {
108 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 table: 254,
120 #[cfg(target_os = "windows")]
121 metric: None,
122 #[cfg(target_os = "windows")]
123 luid: None,
124 }
125 }
126
127 pub fn with_gateway(mut self, gateway: IpAddr) -> Self {
129 self.gateway = Some(gateway);
130 self
131 }
132
133 pub fn with_ifindex(mut self, ifindex: u32) -> Self {
135 self.ifindex = Some(ifindex);
136 self
137 }
138
139 #[cfg(target_os = "linux")]
141 pub fn with_table(mut self, table: u8) -> Self {
142 self.table = table;
143 self
144 }
145
146 #[cfg(target_os = "windows")]
148 pub fn with_metric(mut self, metric: u32) -> Self {
149 self.metric = Some(metric);
150 self
151 }
152
153 #[cfg(target_os = "windows")]
155 pub fn with_luid(mut self, luid: u64) -> Self {
156 self.luid = Some(luid);
157 self
158 }
159
160 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}