mirror of
https://codeberg.org/icewind/netnsd.git
synced 2026-06-03 09:04:07 +02:00
218 lines
No EOL
7 KiB
Rust
218 lines
No EOL
7 KiB
Rust
mod raw;
|
|
|
|
use crate::config::NamespaceName;
|
|
use crate::link::{LinkError, link_up_ns};
|
|
use crate::namespace::raw::{NamespaceSetupError, create_network_namespace};
|
|
use either::Either;
|
|
use nix::errno::Errno;
|
|
use nix::mount::{MntFlags, MsFlags, mount, umount2};
|
|
use std::fs::{File, create_dir, read_dir, remove_file};
|
|
use std::io::{Error as IoError, ErrorKind};
|
|
use std::iter::empty;
|
|
use std::os::unix::fs::symlink;
|
|
use std::path::{Path, PathBuf};
|
|
use thiserror::Error;
|
|
use tracing::{debug, error, info};
|
|
|
|
pub struct NetNs {
|
|
name: NamespaceName,
|
|
path: PathBuf,
|
|
nsd_path: PathBuf,
|
|
}
|
|
|
|
impl NetNs {
|
|
pub fn existing(include_broken: bool) -> Result<impl Iterator<Item = NamespaceName>, NamespaceError> {
|
|
let dir = match read_dir("/var/run/netnsd") {
|
|
Ok(dir) => Ok(dir),
|
|
Err(error) if error.kind() == ErrorKind::NotFound => {
|
|
return Ok(Either::Left(empty()));
|
|
}
|
|
Err(error) => Err(NamespaceError::Scan {
|
|
path: "/var/run/netnsd".into(),
|
|
error,
|
|
}),
|
|
}?;
|
|
Ok(Either::Right(
|
|
dir.flatten()
|
|
.filter(move |entry| include_broken || entry.path().is_symlink())
|
|
.flat_map(|entry| NamespaceName::try_from(entry.file_name()).ok()),
|
|
))
|
|
}
|
|
|
|
/// Create a new named network namespace
|
|
pub fn new(name: NamespaceName) -> Result<Self, NamespaceError> {
|
|
let parent = Path::new("/var/run/netns");
|
|
let nsd_parent = Path::new("/var/run/netnsd");
|
|
setup_tmpfs(parent)?;
|
|
setup_tmpfs(nsd_parent)?;
|
|
|
|
let path = parent.join(&name);
|
|
let nsd_path = nsd_parent.join(&name);
|
|
|
|
remove_non_mount(&path).map_err(|error| NamespaceError::Delete {
|
|
error,
|
|
path: nsd_path.clone(),
|
|
})?;
|
|
|
|
match File::create_new(&path) {
|
|
Ok(_) => {}
|
|
Err(e) if e.kind() == ErrorKind::AlreadyExists => {
|
|
info!(%name, "using existing network namespace");
|
|
|
|
if !nsd_path.is_symlink() {
|
|
remove_file_if_exists(&nsd_path).map_err(|error| NamespaceError::Delete {
|
|
error,
|
|
path: nsd_path.clone(),
|
|
})?;
|
|
|
|
symlink(&path, &nsd_path).map_err(|error| NamespaceError::Symlink {
|
|
error,
|
|
path: nsd_path.clone(),
|
|
})?;
|
|
}
|
|
|
|
return Ok(NetNs {
|
|
name,
|
|
nsd_path,
|
|
path,
|
|
});
|
|
}
|
|
Err(e) => return Err(NamespaceError::from_create(path.clone(), e)),
|
|
}
|
|
info!(%name, "creating network namespace");
|
|
|
|
let ns = create_network_namespace(move |ns| {
|
|
bind_namespace(&ns, &path)?;
|
|
if !nsd_path.is_symlink() {
|
|
symlink(&path, &nsd_path).map_err(|error| NamespaceError::Symlink {
|
|
error,
|
|
path: nsd_path.clone(),
|
|
})?;
|
|
}
|
|
Result::<_, NamespaceError>::Ok(NetNs {
|
|
name,
|
|
path,
|
|
nsd_path,
|
|
})
|
|
})?;
|
|
|
|
ns.setup_interfaces()?;
|
|
|
|
Ok(ns)
|
|
}
|
|
}
|
|
|
|
fn setup_tmpfs(path: &Path) -> Result<(), NamespaceError> {
|
|
let created = match create_dir(path) {
|
|
Ok(_) => Ok(true),
|
|
Err(error) if error.kind() == ErrorKind::AlreadyExists => Ok(false),
|
|
Err(error) => Err(NamespaceError::Parent {
|
|
path: path.into(),
|
|
error,
|
|
}),
|
|
}?;
|
|
if created {
|
|
mount(
|
|
Some("tmpfs"),
|
|
path.as_os_str(),
|
|
Some("tmpfs"),
|
|
MsFlags::empty(),
|
|
Some("size=16k,mode=1755"),
|
|
)
|
|
.map_err(|error| NamespaceError::ParentTmpfs {
|
|
path: path.into(),
|
|
error,
|
|
})?
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn bind_namespace(namespace: &Path, path: &Path) -> Result<(), NamespaceError> {
|
|
debug!(namespace = %namespace.display(), path = %path.display(), "mounting namespace");
|
|
mount(
|
|
Some(namespace.as_os_str()),
|
|
path.as_os_str(),
|
|
Option::<&str>::None,
|
|
MsFlags::MS_BIND,
|
|
Option::<&str>::None,
|
|
)
|
|
.map_err(NamespaceError::Mount)
|
|
}
|
|
|
|
impl NetNs {
|
|
pub fn name(&self) -> &NamespaceName {
|
|
&self.name
|
|
}
|
|
|
|
fn setup_interfaces(&self) -> Result<(), NamespaceError> {
|
|
link_up_ns(&self.path, "lo")?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn delete(self) -> Result<(), NamespaceError> {
|
|
let name = self.path.file_name().unwrap().to_str().unwrap();
|
|
info!(name, "deleting network namespace");
|
|
match umount2(&self.path, MntFlags::MNT_DETACH) {
|
|
Err(Errno::EINVAL) => Ok(()), // not a mountpoint, namespace doesn't exist
|
|
rest => rest,
|
|
}
|
|
.map_err(NamespaceError::UnMount)?;
|
|
remove_file_if_exists(&self.path).map_err(|error| NamespaceError::Delete {
|
|
error,
|
|
path: self.path,
|
|
})?;
|
|
remove_file_if_exists(&self.nsd_path).map_err(|error| NamespaceError::Delete {
|
|
error,
|
|
path: self.nsd_path,
|
|
})?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum NamespaceError {
|
|
#[error("Failed to create parent directory for namespaces {}: {error:#}", path.display())]
|
|
Parent { path: PathBuf, error: IoError },
|
|
#[error("Failed to create parent tmpfs for namespaces {}: {error:#}", path.display())]
|
|
ParentTmpfs { path: PathBuf, error: Errno },
|
|
#[error("Failed to create namespace file {}: {error:#}", path.display())]
|
|
Create { path: PathBuf, error: IoError },
|
|
#[error("Failed to delete namespace file {}: {error:#}", path.display())]
|
|
Delete { path: PathBuf, error: IoError },
|
|
#[error("Failed to register namespace symlink {}: {error:#}", path.display())]
|
|
Symlink { path: PathBuf, error: IoError },
|
|
#[error("Failed to setup namespace: {0:#}")]
|
|
Setup(#[from] NamespaceSetupError),
|
|
#[error("Failed to bind-mount netns handle: {0:?}")]
|
|
Mount(Errno),
|
|
#[error("Failed to unmount netns handle: {0:?}")]
|
|
UnMount(Errno),
|
|
#[error("Failed to setup loopback inside namespace: {0:#}")]
|
|
Link(#[from] LinkError),
|
|
#[error("Failed to scan {} for namespaces: {error:#}", path.display())]
|
|
Scan { path: PathBuf, error: IoError },
|
|
}
|
|
|
|
impl NamespaceError {
|
|
fn from_create(path: PathBuf, error: IoError) -> Self {
|
|
NamespaceError::Create { path, error }
|
|
}
|
|
}
|
|
|
|
/// `remove_file`, but ignore "file not found" errors
|
|
fn remove_file_if_exists<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
|
|
match remove_file(path) {
|
|
Err(err) if err.kind() == ErrorKind::NotFound => Ok(()),
|
|
rest => rest,
|
|
}
|
|
}
|
|
|
|
|
|
/// `remove_file`, but ignore errors if the file doesn't exist or is a mount point
|
|
fn remove_non_mount<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
|
|
match remove_file(path) {
|
|
Err(err) if err.kind() == ErrorKind::NotFound => Ok(()),
|
|
Err(err) if err.kind() == ErrorKind::ResourceBusy => Ok(()),
|
|
rest => rest,
|
|
}
|
|
} |