From 9dd802050c4244181c43616b0befad5485a0db5c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 13 Feb 2026 23:22:45 +0100 Subject: [PATCH] make managing namespaces more resilient against inconsitent states --- src/daemon.rs | 2 +- src/down.rs | 2 +- src/namespace/mod.rs | 58 +++++++++++++++++++++++++++++++++++--------- src/up.rs | 2 +- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/daemon.rs b/src/daemon.rs index 57d6a0b..d655bb5 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -101,7 +101,7 @@ struct State { impl State { pub fn new() -> Result { - let namespaces = NetNs::existing()? + let namespaces = NetNs::existing(false)? .map(ActiveNamespace::new) .collect::, _>>()?; Ok(State { namespaces }) diff --git a/src/down.rs b/src/down.rs index f29f50d..fed2891 100644 --- a/src/down.rs +++ b/src/down.rs @@ -2,7 +2,7 @@ use crate::namespace::NetNs; use main_error::MainResult; pub fn down() -> MainResult { - for name in NetNs::existing()? { + for name in NetNs::existing(true)? { let ns = NetNs::new(name)?; ns.delete()? } diff --git a/src/namespace/mod.rs b/src/namespace/mod.rs index b10c635..5287afb 100644 --- a/src/namespace/mod.rs +++ b/src/namespace/mod.rs @@ -1,12 +1,12 @@ mod raw; use crate::config::NamespaceName; -use crate::link::{link_up_ns, LinkError}; -use crate::namespace::raw::{create_network_namespace, NamespaceSetupError}; +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::{mount, umount2, MntFlags, MsFlags}; -use std::fs::{create_dir, read_dir, remove_file, File}; +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; @@ -21,7 +21,7 @@ pub struct NetNs { } impl NetNs { - pub fn existing() -> Result, NamespaceError> { + pub fn existing(include_broken: bool) -> Result, NamespaceError> { let dir = match read_dir("/var/run/netnsd") { Ok(dir) => Ok(dir), Err(error) if error.kind() == ErrorKind::NotFound => { @@ -32,12 +32,14 @@ impl NetNs { error, }), }?; - Ok(Either::Right(dir.flatten().flat_map(|entry| { - NamespaceName::try_from(entry.file_name()).ok() - }))) + 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 that will be removed when dropped + /// Create a new named network namespace pub fn new(name: NamespaceName) -> Result { let parent = Path::new("/var/run/netns"); let nsd_parent = Path::new("/var/run/netnsd"); @@ -47,12 +49,22 @@ impl NetNs { 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(), @@ -140,12 +152,16 @@ impl NetNs { pub fn delete(self) -> Result<(), NamespaceError> { let name = self.path.file_name().unwrap().to_str().unwrap(); info!(name, "deleting network namespace"); - umount2(&self.path, MntFlags::MNT_DETACH).map_err(NamespaceError::UnMount)?; - remove_file(&self.path).map_err(|error| NamespaceError::Delete { + 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(&self.nsd_path).map_err(|error| NamespaceError::Delete { + remove_file_if_exists(&self.nsd_path).map_err(|error| NamespaceError::Delete { error, path: self.nsd_path, })?; @@ -182,3 +198,21 @@ impl NamespaceError { NamespaceError::Create { path, error } } } + +/// `remove_file`, but ignore "file not found" errors +fn remove_file_if_exists>(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>(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, + } +} \ No newline at end of file diff --git a/src/up.rs b/src/up.rs index a7c65b4..ad6e763 100644 --- a/src/up.rs +++ b/src/up.rs @@ -3,7 +3,7 @@ use crate::namespace::NetNs; use main_error::MainResult; pub fn up(config: Config) -> MainResult { - let mut namespaces = NetNs::existing()? + let mut namespaces = NetNs::existing(false)? .map(NetNs::new) .collect::, _>>()?;