use std::{
process::Command,
sync::atomic::{AtomicU32, Ordering},
};
use nlink::{
Result, Route,
netlink::{Connection, namespace},
};
static NAMESPACE_COUNTER: AtomicU32 = AtomicU32::new(0);
fn unique_ns_name(prefix: &str) -> String {
let id = NAMESPACE_COUNTER.fetch_add(1, Ordering::SeqCst);
let pid = std::process::id();
format!("nlink-test-{}-{}-{}", prefix, pid, id)
}
pub struct TestNamespace {
name: String,
}
impl TestNamespace {
pub fn new(prefix: &str) -> Result<Self> {
let name = unique_ns_name(prefix);
namespace::create(&name)?;
Ok(Self { name })
}
#[allow(dead_code)]
pub fn name(&self) -> &str {
&self.name
}
pub fn connection(&self) -> Result<Connection<Route>> {
namespace::connection_for(&self.name)
}
pub fn exec(&self, cmd: &str, args: &[&str]) -> Result<String> {
let mut command = Command::new(cmd);
command.args(args);
let output = namespace::spawn_output(&self.name, command)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(nlink::Error::InvalidMessage(format!(
"command failed: {} {:?}: {}",
cmd, args, stderr
)));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
pub fn exec_ignore(&self, cmd: &str, args: &[&str]) {
let mut command = Command::new(cmd);
command.args(args);
let _ = namespace::spawn_output(&self.name, command);
}
pub fn connect_to(
&self,
other: &TestNamespace,
local_name: &str,
remote_name: &str,
) -> Result<()> {
let mut cmd = Command::new("ip");
cmd.args([
"link",
"add",
local_name,
"type",
"veth",
"peer",
"name",
remote_name,
]);
let output = namespace::spawn_output(&self.name, cmd)?;
if !output.status.success() {
return Err(nlink::Error::InvalidMessage(
"failed to create veth pair".into(),
));
}
let mut cmd = Command::new("ip");
cmd.args(["link", "set", remote_name, "netns", &other.name]);
let output = namespace::spawn_output(&self.name, cmd)?;
if !output.status.success() {
return Err(nlink::Error::InvalidMessage(
"failed to move veth peer".into(),
));
}
Ok(())
}
pub fn add_dummy(&self, name: &str) -> Result<()> {
self.exec("ip", &["link", "add", name, "type", "dummy"])?;
Ok(())
}
pub fn link_up(&self, name: &str) -> Result<()> {
self.exec("ip", &["link", "set", name, "up"])?;
Ok(())
}
pub fn add_addr(&self, dev: &str, addr: &str) -> Result<()> {
self.exec("ip", &["addr", "add", addr, "dev", dev])?;
Ok(())
}
}
impl Drop for TestNamespace {
fn drop(&mut self) {
let _ = namespace::delete(&self.name);
}
}
pub fn is_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
#[macro_export]
macro_rules! require_root {
() => {
if !$crate::common::is_root() {
eprintln!("Skipping test: requires root");
return Ok(());
}
};
}
#[macro_export]
macro_rules! require_root_void {
() => {
if !$crate::common::is_root() {
eprintln!("Skipping test: requires root");
return;
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unique_ns_name() {
let name1 = unique_ns_name("test");
let name2 = unique_ns_name("test");
assert_ne!(name1, name2);
assert!(name1.starts_with("nlink-test-test-"));
}
}