use super::error::{NetworkError, Result};
use super::namespace::NetNamespace;
use super::types::Route;
use std::process::Command;
pub fn add_route(route: &Route, ns: Option<&NetNamespace>) -> Result<()> {
let is_ipv6 =
route.gateway.is_ipv6() || route.destination.as_ref().map_or(false, |d| d.is_ipv6());
let mut args = vec![];
if is_ipv6 {
args.push("-6");
}
args.push("route");
args.push("add");
let dest_str;
if let Some(dest) = &route.destination {
dest_str = dest.to_string();
args.push(&dest_str);
} else {
args.push("default");
}
args.push("via");
let gw_str = route.gateway.to_string();
args.push(&gw_str);
if let Some(ref iface) = route.interface {
args.push("dev");
args.push(iface);
}
let output = if let Some(namespace) = ns {
let mut ns_args = vec!["netns", "exec", &namespace.name, "ip"];
ns_args.extend_from_slice(&args);
Command::new("ip")
.args(&ns_args)
.output()
.map_err(|e| NetworkError::Route(format!("Failed to add route: {}", e)))?
} else {
Command::new("ip")
.args(&args)
.output()
.map_err(|e| NetworkError::Route(format!("Failed to add route: {}", e)))?
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("File exists") {
return Err(NetworkError::Route(format!(
"Failed to add route: {}",
stderr
)));
}
}
Ok(())
}
pub fn delete_route(route: &Route, ns: Option<&NetNamespace>) -> Result<()> {
let is_ipv6 =
route.gateway.is_ipv6() || route.destination.as_ref().map_or(false, |d| d.is_ipv6());
let mut args = vec![];
if is_ipv6 {
args.push("-6");
}
args.push("route");
args.push("del");
let dest_str;
if let Some(dest) = &route.destination {
dest_str = dest.to_string();
args.push(&dest_str);
} else {
args.push("default");
}
args.push("via");
let gw_str = route.gateway.to_string();
args.push(&gw_str);
if let Some(ref iface) = route.interface {
args.push("dev");
args.push(iface);
}
let output = if let Some(namespace) = ns {
let mut ns_args = vec!["netns", "exec", &namespace.name, "ip"];
ns_args.extend_from_slice(&args);
Command::new("ip")
.args(&ns_args)
.output()
.map_err(|e| NetworkError::Route(format!("Failed to delete route: {}", e)))?
} else {
Command::new("ip")
.args(&args)
.output()
.map_err(|e| NetworkError::Route(format!("Failed to delete route: {}", e)))?
};
if !output.status.success() {
return Err(NetworkError::Route(format!(
"Failed to delete route: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::Route;
use ipnetwork::IpNetwork;
use std::net::IpAddr;
use std::str::FromStr;
#[test]
fn test_route_with_all_fields() {
let route = Route {
destination: Some(IpNetwork::from_str("10.0.0.0/24").unwrap()),
gateway: IpAddr::from_str("10.0.0.1").unwrap(),
interface: Some("eth0".to_string()),
};
assert!(route.destination.is_some());
assert_eq!(route.gateway.to_string(), "10.0.0.1");
assert_eq!(route.interface, Some("eth0".to_string()));
}
#[test]
fn test_default_route() {
let route = Route {
destination: None,
gateway: IpAddr::from_str("192.168.1.1").unwrap(),
interface: None,
};
assert!(route.destination.is_none());
assert_eq!(route.gateway.to_string(), "192.168.1.1");
}
#[test]
fn test_ipv6_route() {
let route = Route {
destination: Some(IpNetwork::from_str("2001:db8::/64").unwrap()),
gateway: IpAddr::from_str("2001:db8::1").unwrap(),
interface: Some("eth0".to_string()),
};
assert!(route.destination.is_some());
assert!(route.gateway.to_string().contains("2001:db8"));
}
}