use std::cmp::Ordering;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::{fmt, io};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RouteChange {
Add(Route),
Delete(Route),
Change(Route),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Route {
pub(crate) destination: IpAddr,
pub(crate) prefix: u8,
pub(crate) gateway: Option<IpAddr>,
pub(crate) if_name: Option<String>,
pub(crate) if_index: Option<u32>,
#[cfg(target_os = "linux")]
pub(crate) table: u8,
#[cfg(target_os = "linux")]
pub(crate) source: Option<IpAddr>,
#[cfg(target_os = "linux")]
pub(crate) source_prefix: u8,
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pub(crate) pref_source: Option<IpAddr>,
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub(crate) metric: Option<u32>,
#[cfg(target_os = "windows")]
pub(crate) luid: Option<u64>,
}
impl Route {
pub fn destination(&self) -> IpAddr {
self.destination
}
pub fn prefix(&self) -> u8 {
self.prefix
}
pub fn gateway(&self) -> Option<IpAddr> {
self.gateway
}
pub fn if_name(&self) -> Option<&String> {
self.if_name.as_ref()
}
pub fn if_index(&self) -> Option<u32> {
self.if_index
}
#[cfg(target_os = "linux")]
pub fn table(&self) -> u8 {
self.table
}
#[cfg(target_os = "linux")]
pub fn source(&self) -> Option<IpAddr> {
self.source
}
#[cfg(target_os = "linux")]
pub fn source_prefix(&self) -> u8 {
self.source_prefix
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pub fn pref_source(&self) -> Option<IpAddr> {
self.pref_source
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn metric(&self) -> Option<u32> {
self.metric
}
#[cfg(target_os = "windows")]
pub fn luid(&self) -> Option<u64> {
self.luid
}
}
impl Route {
pub fn new(destination: IpAddr, prefix: u8) -> Self {
Self {
destination,
prefix,
gateway: None,
if_name: None,
if_index: None,
#[cfg(target_os = "linux")]
table: 0,
#[cfg(target_os = "linux")]
source: None,
#[cfg(target_os = "linux")]
source_prefix: 0,
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pref_source: 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_if_name(mut self, if_name: String) -> Self {
self.if_name = Some(if_name);
self
}
pub fn with_if_index(mut self, if_index: u32) -> Self {
self.if_index = Some(if_index);
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(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
pub fn with_pref_source(mut self, pref_source: IpAddr) -> Self {
self.pref_source = Some(pref_source);
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
}
}
impl Route {
pub fn check(&self) -> io::Result<()> {
if self.destination.is_ipv4() {
if self.prefix > 32 {
return Err(io::Error::other("prefix error"));
}
} else if self.prefix > 128 {
return Err(io::Error::other("prefix error"));
}
if let Some(index) = self.if_index {
crate::if_index_to_name(index)?;
}
if let Some(gateway) = self.gateway {
if gateway.is_ipv4() != self.destination.is_ipv4() {
return Err(io::Error::other("gateway error"));
}
}
if let Some(name) = self.if_name.as_ref() {
let index = crate::if_name_to_index(name)?;
if let Some(if_index) = self.if_index {
if index != if_index {
return Err(io::Error::other("if_index mismatch"));
}
}
}
Ok(())
}
pub fn network(&self) -> IpAddr {
Route::network_addr(self.destination, self.prefix)
}
fn network_addr(ip: IpAddr, prefix: u8) -> IpAddr {
match ip {
IpAddr::V4(ipv4) => {
let ip = u32::from(ipv4);
let mask = if prefix == 0 { 0 } else { !0 << (32 - prefix) };
IpAddr::V4(Ipv4Addr::from(ip & mask))
}
IpAddr::V6(ipv6) => {
let ip = u128::from(ipv6);
let mask = if prefix == 0 {
0
} else {
!0_u128 << (128 - prefix)
};
IpAddr::V6(Ipv6Addr::from(ip & mask))
}
}
}
pub fn contains(&self, dest: &IpAddr) -> bool {
if dest.is_ipv4() != self.destination.is_ipv4() {
return false;
}
let route_network = self.network();
let addr_network = Route::network_addr(*dest, self.prefix);
route_network == addr_network
}
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),
)),
}
}
#[allow(dead_code)]
pub(crate) fn get_index(&self) -> Option<u32> {
self.if_index.or_else(|| {
if let Some(name) = &self.if_name {
crate::if_name_to_index(name).ok()
} else {
None
}
})
}
#[allow(dead_code)]
pub(crate) fn get_name(&self) -> Option<String> {
self.if_name.clone().or_else(|| {
if let Some(index) = &self.if_index {
crate::if_index_to_name(*index).ok()
} else {
None
}
})
}
}
impl PartialOrd<Self> for Route {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Route {
fn cmp(&self, other: &Self) -> Ordering {
match self.prefix.cmp(&other.prefix) {
#[cfg(any(target_os = "windows", target_os = "linux"))]
Ordering::Equal => other.metric.cmp(&self.metric),
v => v,
}
}
}
impl crate::RouteManager {
#[cfg(not(target_os = "windows"))]
pub fn find_route(&mut self, dest: &IpAddr) -> io::Result<Option<Route>> {
let mut list = self.list()?;
list.sort_by(|v1, v2| v2.cmp(v1));
let rs = list
.iter()
.filter(|v| v.destination.is_ipv4() == dest.is_ipv4())
.find(|v| v.contains(dest))
.cloned();
Ok(rs)
}
}
impl fmt::Display for RouteChange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteChange::Add(route) => write!(f, "Add({route})"),
RouteChange::Delete(route) => write!(f, "Delete({route})"),
RouteChange::Change(route) => write!(f, "Change({route})"),
}
}
}
impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Route {{ destination: {}/{}, gateway: ",
self.destination, self.prefix
)?;
match self.gateway {
Some(addr) => write!(f, "{addr}"),
None => write!(f, "None"),
}?;
write!(f, ", if_index: ")?;
match self.if_index {
Some(index) => write!(f, "{index}"),
None => write!(f, "None"),
}?;
write!(f, ", if_name: ")?;
match &self.if_name {
Some(if_name) => write!(f, "{if_name}"),
None => write!(f, "None"),
}?;
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
write!(f, ", metric: ")?;
match self.metric {
Some(m) => write!(f, "{m}"),
None => write!(f, "None"),
}?;
}
#[cfg(target_os = "windows")]
{
write!(f, ", luid: ")?;
match self.luid {
Some(l) => write!(f, "{l}"),
None => write!(f, "None"),
}?;
}
#[cfg(target_os = "linux")]
{
write!(f, ", table: {}", self.table)?;
write!(f, ", source: ")?;
match self.source {
Some(addr) => write!(f, "{}/{}", addr, self.source_prefix)?,
None => write!(f, "None")?,
};
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
{
write!(f, ", pref_source: ")?;
match self.pref_source {
Some(addr) => write!(f, "{addr}"),
None => write!(f, "None"),
}?;
}
write!(f, " }}")
}
}