1use std::{
39 io,
40 net::{IpAddr, Ipv4Addr, Ipv6Addr},
41};
42
43mod platform_impl;
44use platform_impl::PlatformHandle;
45
46#[cfg(all(target_os = "macos", not(doc)))]
47pub use platform_impl::ifname_to_index;
48
49pub struct Handle(PlatformHandle);
51
52impl Handle {
53 pub fn new() -> io::Result<Self> {
54 Ok(Self(PlatformHandle::new()?))
55 }
56
57 pub async fn add(&self, route: &Route) -> io::Result<()> {
59 self.0.add(route).await
60 }
61
62 pub fn route_listen_stream(&self) -> impl futures::Stream<Item = RouteChange> {
64 self.0.route_listen_stream()
65 }
66
67 pub async fn list(&self) -> io::Result<Vec<Route>> {
69 self.0.list().await
70 }
71
72 pub async fn default_route(&self) -> io::Result<Option<Route>> {
74 self.0.default_route().await
75 }
76
77 pub async fn delete(&self, route: &Route) -> io::Result<()> {
79 self.0.delete(route).await
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct Route {
86 pub destination: IpAddr,
88
89 pub prefix: u8,
91
92 pub gateway: Option<IpAddr>,
96
97 pub ifindex: Option<u32>,
101
102 #[cfg(target_os = "linux")]
103 pub table: u8,
105
106 #[cfg(target_os = "linux")]
108 pub source: Option<IpAddr>,
109
110 #[cfg(target_os = "linux")]
112 pub source_prefix: u8,
113
114 #[cfg(target_os = "linux")]
116 pub source_hint: Option<IpAddr>,
117
118 #[cfg(any(target_os = "windows", target_os = "linux"))]
119 pub metric: Option<u32>,
121
122 #[cfg(target_os = "windows")]
123 pub luid: Option<u64>,
127}
128
129impl Route {
130 pub fn new(destination: IpAddr, prefix: u8) -> Self {
134 Self {
135 destination,
136 prefix,
137 gateway: None,
138 ifindex: None,
139 #[cfg(target_os = "linux")]
140 table: 254,
142 #[cfg(target_os = "linux")]
143 source: None,
144 #[cfg(target_os = "linux")]
145 source_prefix: 0,
146 #[cfg(target_os = "linux")]
147 source_hint: None,
148 #[cfg(any(target_os = "windows", target_os = "linux"))]
149 metric: None,
150 #[cfg(target_os = "windows")]
151 luid: None,
152 }
153 }
154
155 pub fn with_gateway(mut self, gateway: IpAddr) -> Self {
157 self.gateway = Some(gateway);
158 self
159 }
160
161 pub fn with_ifindex(mut self, ifindex: u32) -> Self {
163 self.ifindex = Some(ifindex);
164 self
165 }
166
167 #[cfg(target_os = "linux")]
169 pub fn with_table(mut self, table: u8) -> Self {
170 self.table = table;
171 self
172 }
173
174 #[cfg(target_os = "linux")]
176 pub fn with_source(mut self, source: IpAddr, prefix: u8) -> Self {
177 self.source = Some(source);
178 self.source_prefix = prefix;
179 self
180 }
181
182 #[cfg(target_os = "linux")]
184 pub fn with_source_hint(mut self, hint: IpAddr) -> Self {
185 self.source_hint = Some(hint);
186 self
187 }
188
189 #[cfg(any(target_os = "windows", target_os = "linux"))]
191 pub fn with_metric(mut self, metric: u32) -> Self {
192 self.metric = Some(metric);
193 self
194 }
195
196 #[cfg(target_os = "windows")]
198 pub fn with_luid(mut self, luid: u64) -> Self {
199 self.luid = Some(luid);
200 self
201 }
202
203 pub fn mask(&self) -> IpAddr {
205 match self.destination {
206 IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::from(
207 u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0),
208 )),
209 IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::from(
210 u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0),
211 )),
212 }
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub enum RouteChange {
218 Add(Route),
219 Delete(Route),
220 Change(Route),
221}
222
223#[cfg(test)]
224mod tests {
225 use std::net::{IpAddr, Ipv6Addr};
226
227 use crate::Route;
228
229 #[test]
230 fn it_calculates_v4_netmask() {
231 let mut route = Route::new("10.10.0.0".parse().unwrap(), 32);
232
233 assert_eq!(route.mask(), "255.255.255.255".parse::<IpAddr>().unwrap());
234
235 route.prefix = 29;
236 assert_eq!(route.mask(), "255.255.255.248".parse::<IpAddr>().unwrap());
237
238 route.prefix = 25;
239 assert_eq!(route.mask(), "255.255.255.128".parse::<IpAddr>().unwrap());
240
241 route.prefix = 2;
242 assert_eq!(route.mask(), "192.0.0.0".parse::<IpAddr>().unwrap());
243 }
244
245 #[test]
246 fn it_calculates_v6_netmask() {
247 let route = Route::new(
248 "77ca:838b:9ec0:fc97:eedc:236a:9d41:31e5".parse().unwrap(),
249 32,
250 );
251 assert_eq!(
252 route.mask(),
253 Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0)
254 );
255 }
256}