use super::error::{NetworkError, Result};
use std::path::PathBuf;
use std::process::Command;
const NETNS_PATH: &str = "/var/run/netns";
#[derive(Debug, Clone)]
pub struct NetNamespace {
pub name: String,
pub path: PathBuf,
}
pub fn create_namespace(name: &str) -> Result<NetNamespace> {
let ns_path = PathBuf::from(NETNS_PATH).join(name);
if ns_path.exists() {
return get_namespace_by_name(name);
}
let output = Command::new("ip")
.args(["netns", "add", name])
.output()
.map_err(|e| NetworkError::Namespace(format!("Failed to execute ip netns add: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Namespace(format!(
"Failed to create namespace {}: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(NetNamespace {
name: name.to_string(),
path: ns_path,
})
}
pub fn get_namespace_by_name(name: &str) -> Result<NetNamespace> {
let ns_path = PathBuf::from(NETNS_PATH).join(name);
if !ns_path.exists() {
return Err(NetworkError::NotFound(format!(
"Namespace {} not found",
name
)));
}
Ok(NetNamespace {
name: name.to_string(),
path: ns_path,
})
}
pub fn namespace_exists(name: &str) -> bool {
PathBuf::from(NETNS_PATH).join(name).exists()
}
pub fn delete_namespace(ns: NetNamespace) -> Result<()> {
let output = Command::new("ip")
.args(["netns", "delete", &ns.name])
.output()
.map_err(|e| NetworkError::Namespace(format!("Failed to execute ip netns delete: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("No such file") {
return Err(NetworkError::Namespace(format!(
"Failed to delete namespace {}: {}",
ns.name, stderr
)));
}
}
Ok(())
}
pub fn exec_in_ns(ns: &NetNamespace, cmd: &str, args: &[&str]) -> Result<std::process::Output> {
let mut command = Command::new("ip");
command.args(["netns", "exec", &ns.name, cmd]);
command.args(args);
let output = command
.output()
.map_err(|e| NetworkError::Namespace(format!("Failed to exec in namespace: {}", e)))?;
Ok(output)
}
pub fn exec_in_ns_by_name(name: &str, cmd: &str, args: &[&str]) -> Result<std::process::Output> {
if !namespace_exists(name) {
return Err(NetworkError::NotFound(format!(
"Namespace {} not found",
name
)));
}
let mut command = Command::new("ip");
command.args(["netns", "exec", name, cmd]);
command.args(args);
let output = command
.output()
.map_err(|e| NetworkError::Namespace(format!("Failed to exec in namespace: {}", e)))?;
Ok(output)
}
pub fn list_namespaces() -> Result<Vec<String>> {
let output = Command::new("ip")
.args(["netns", "list"])
.output()
.map_err(|e| NetworkError::Namespace(format!("Failed to list namespaces: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Namespace(format!(
"Failed to list namespaces: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let namespaces: Vec<String> = stdout
.lines()
.filter_map(|line| {
line.split_whitespace().next().map(|s| s.to_string())
})
.collect();
Ok(namespaces)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_netnamespace_struct() {
let ns = NetNamespace {
name: "test_ns".to_string(),
path: PathBuf::from("/var/run/netns/test_ns"),
};
assert_eq!(ns.name, "test_ns");
assert_eq!(ns.path.to_str().unwrap(), "/var/run/netns/test_ns");
}
#[test]
fn test_netns_path_constant() {
assert_eq!(NETNS_PATH, "/var/run/netns");
}
#[test]
fn test_namespace_path_construction() {
let name = "my_namespace";
let expected_path = PathBuf::from(NETNS_PATH).join(name);
assert_eq!(
expected_path.to_str().unwrap(),
"/var/run/netns/my_namespace"
);
}
#[test]
fn test_namespace_exists_false() {
assert!(!namespace_exists("nonexistent_ns_12345"));
}
}