use std::{
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
mod platform_impl;
use platform_impl::PlatformHandle;
#[cfg(all(target_os = "macos", not(doc)))]
pub use platform_impl::ifname_to_index;
pub struct Handle(PlatformHandle);
impl Handle {
pub fn new() -> io::Result<Self> {
Ok(Self(PlatformHandle::new()?))
}
pub async fn add(&self, route: &Route) -> io::Result<()> {
self.0.add(route).await
}
pub fn route_listen_stream(&self) -> impl futures::Stream<Item = RouteChange> {
self.0.route_listen_stream()
}
pub async fn list(&self) -> io::Result<Vec<Route>> {
self.0.list().await
}
pub async fn default_route(&self) -> io::Result<Option<Route>> {
self.0.default_route().await
}
pub async fn delete(&self, route: &Route) -> io::Result<()> {
self.0.delete(route).await
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Route {
pub destination: IpAddr,
pub prefix: u8,
pub gateway: Option<IpAddr>,
pub ifindex: Option<u32>,
#[cfg(target_os = "linux")]
pub table: u8,
#[cfg(target_os = "linux")]
pub source: Option<IpAddr>,
#[cfg(target_os = "linux")]
pub source_prefix: u8,
#[cfg(target_os = "linux")]
pub source_hint: Option<IpAddr>,
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub metric: Option<u32>,
#[cfg(target_os = "windows")]
pub luid: Option<u64>,
}
impl Route {
pub fn new(destination: IpAddr, prefix: u8) -> Self {
Self {
destination,
prefix,
gateway: None,
ifindex: None,
#[cfg(target_os = "linux")]
table: 254,
#[cfg(target_os = "linux")]
source: None,
#[cfg(target_os = "linux")]
source_prefix: 0,
#[cfg(target_os = "linux")]
source_hint: None,
#[cfg(any(target_os = "windows", target_os = "linux"))]
metric: None,
#[cfg(target_os = "windows")]
luid: None,
}
}
pub fn with_gateway(mut self, gateway: IpAddr) -> Self {
self.gateway = Some(gateway);
self
}
pub fn with_ifindex(mut self, ifindex: u32) -> Self {
self.ifindex = Some(ifindex);
self
}
#[cfg(target_os = "linux")]
pub fn with_table(mut self, table: u8) -> Self {
self.table = table;
self
}
#[cfg(target_os = "linux")]
pub fn with_source(mut self, source: IpAddr, prefix: u8) -> Self {
self.source = Some(source);
self.source_prefix = prefix;
self
}
#[cfg(target_os = "linux")]
pub fn with_source_hint(mut self, hint: IpAddr) -> Self {
self.source_hint = Some(hint);
self
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn with_metric(mut self, metric: u32) -> Self {
self.metric = Some(metric);
self
}
#[cfg(target_os = "windows")]
pub fn with_luid(mut self, luid: u64) -> Self {
self.luid = Some(luid);
self
}
pub fn mask(&self) -> IpAddr {
match self.destination {
IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::from(
u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0),
)),
IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::from(
u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0),
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteChange {
Add(Route),
Delete(Route),
Change(Route),
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv6Addr};
use crate::Route;
#[test]
fn it_calculates_v4_netmask() {
let mut route = Route::new("10.10.0.0".parse().unwrap(), 32);
assert_eq!(route.mask(), "255.255.255.255".parse::<IpAddr>().unwrap());
route.prefix = 29;
assert_eq!(route.mask(), "255.255.255.248".parse::<IpAddr>().unwrap());
route.prefix = 25;
assert_eq!(route.mask(), "255.255.255.128".parse::<IpAddr>().unwrap());
route.prefix = 2;
assert_eq!(route.mask(), "192.0.0.0".parse::<IpAddr>().unwrap());
}
#[test]
fn it_calculates_v6_netmask() {
let route = Route::new(
"77ca:838b:9ec0:fc97:eedc:236a:9d41:31e5".parse().unwrap(),
32,
);
assert_eq!(
route.mask(),
Ipv6Addr::new(0xffff, 0xffff, 0, 0, 0, 0, 0, 0)
);
}
}