don't remove namespaces on daemon exit

This commit is contained in:
Robin Appelman 2025-11-01 15:58:25 +01:00
commit 3a8b684600
6 changed files with 52 additions and 56 deletions

View file

@ -2,6 +2,7 @@
name = "netnsd" name = "netnsd"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
rust-version = "1.87.0"
[dependencies] [dependencies]
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "signal", "net", "io-util"] } tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "signal", "net", "io-util"] }

32
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1742394900, "lastModified": 1760924934,
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=", "narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd", "rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -22,11 +22,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1748868585, "lastModified": 1761636064,
"narHash": "sha256-DrrbahOQAwvNM8l5EuGxxkVS7X5/S59zcG0N9ZWQFhk=", "narHash": "sha256-xAbEYL5gqPnBfXOIGToPjF6E0n9RSe8YJ6E2/ePrPaM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "flakelight", "repo": "flakelight",
"rev": "dfbecd12d99c1bf82906521a6a7d5b75d2aa1ca2", "rev": "2c621bc66465c457b64b016bb00d575ff2e51094",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -44,11 +44,11 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1748205441, "lastModified": 1762008387,
"narHash": "sha256-W+UUBT/l1DSTZo5G43494mRNNspJ2i9jW2QELC9JuMQ=", "narHash": "sha256-Rss/bk95oJB8lcOp5ZbfzVUmRushUCWUGhfZLz9C/Lw=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "dac3b74a89cebbeb21cc6602e4a346604adbee8b", "rev": "ae534f62fd0a32f8e983d0a9fbfc8124d099b341",
"revCount": 49, "revCount": 64,
"type": "git", "type": "git",
"url": "https://codeberg.org/icewind/mill-scale" "url": "https://codeberg.org/icewind/mill-scale"
}, },
@ -59,11 +59,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1748708770, "lastModified": 1761597516,
"narHash": "sha256-q8jG2HJWgooWa9H0iatZqBPF3bp0504e05MevFmnFLY=", "narHash": "sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8+ON/0Yy8+a5vsDU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a59eb7800787c926045d51b70982ae285faa2346", "rev": "daf6dc47aa4b44791372d6139ab7b25269184d55",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -88,11 +88,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1742697269, "lastModified": 1761964689,
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=", "narHash": "sha256-Zo3LQQDz+64EQ9zor/WmeNTFLoZkjmhp0UY3G0D3seE=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316", "rev": "63d22578600f70d293aede6bc737efef60ebd97f",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -40,6 +40,10 @@ impl Config {
pub fn reload(&self) -> Result<Config, ConfigError> { pub fn reload(&self) -> Result<Config, ConfigError> {
Self::load(&self.path) Self::load(&self.path)
} }
pub fn get_namespace<'a>(&'a self, name: &NamespaceName) -> Option<&'a NamespaceConfig> {
self.namespaces.iter().find(|namespace| &namespace.name == name)
}
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View file

@ -1,20 +1,20 @@
use crate::config::{Config, ForwardConfig, NamespaceConfig, NamespaceName}; use crate::config::{Config, ForwardConfig, NamespaceConfig, NamespaceName};
use crate::namespace::{NamespaceError, NetNs}; use crate::namespace::{NamespaceError, NetNs};
use crate::proxy::{ActiveProxy, ProxyError};
use futures::FutureExt; use futures::FutureExt;
use futures::StreamExt; use futures::StreamExt;
use futures_concurrency::stream::Merge; use futures_concurrency::stream::Merge;
use humansize::{BINARY, SizeFormatter};
use main_error::MainResult; use main_error::MainResult;
use sd_notify::{NotifyState, notify}; use sd_notify::{NotifyState, notify};
use std::io::Error as IoError; use std::io::Error as IoError;
use std::pin::pin; use std::pin::pin;
use humansize::{SizeFormatter, BINARY};
use thiserror::Error; use thiserror::Error;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::signal::ctrl_c; use tokio::signal::ctrl_c;
use tokio::signal::unix::{SignalKind, signal}; use tokio::signal::unix::{SignalKind, signal};
use tokio_stream::wrappers::SignalStream; use tokio_stream::wrappers::SignalStream;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use crate::proxy::{ActiveProxy, ProxyError};
pub fn daemon(config: Config) -> MainResult { pub fn daemon(config: Config) -> MainResult {
let rt = Runtime::new()?; let rt = Runtime::new()?;
@ -111,20 +111,11 @@ struct State {
impl State { impl State {
pub fn update(&mut self, config: &Config) -> Result<(), DaemonError> { pub fn update(&mut self, config: &Config) -> Result<(), DaemonError> {
self.namespaces.retain_mut(|existing| { for removed in self.namespaces.extract_if(.., |namespace| {
if let Some(new_config) = config.get_namespace(namespace.name()).is_none()
config }) {
.namespaces removed.ns.delete()?;
.iter() }
.find(|new| &new.name == existing.name()) {
if let Err(error) = existing.update_proxies(new_config) {
error!(%error, "Failed to update proxies for namespace");
}
true
} else {
false
}
});
for new in &config.namespaces { for new in &config.namespaces {
if !self.has_namespace(&new.name) { if !self.has_namespace(&new.name) {
@ -132,6 +123,11 @@ impl State {
} }
} }
for namespace in &mut self.namespaces {
let config = config.get_namespace(namespace.name()).unwrap();
namespace.update_proxies(config)?;
}
Ok(()) Ok(())
} }
@ -161,7 +157,8 @@ impl ActiveNamespace {
} }
pub fn update_proxies(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> { pub fn update_proxies(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> {
self.proxies.retain(|existing| config.forward.iter().any(|new| existing == new)); self.proxies
.retain(|existing| config.forward.iter().any(|new| existing == new));
for new in &config.forward { for new in &config.forward {
if !self.has_forward(new) { if !self.has_forward(new) {

View file

@ -2,11 +2,9 @@ mod raw;
use crate::config::NamespaceName; use crate::config::NamespaceName;
use crate::link::{LinkError, link_up_ns}; use crate::link::{LinkError, link_up_ns};
use crate::namespace::raw::{ use crate::namespace::raw::{NamespaceSetupError, create_network_namespace};
NamespaceSetupError, create_network_namespace,
};
use nix::errno::Errno; use nix::errno::Errno;
use nix::mount::{MsFlags, mount, umount2, MntFlags}; use nix::mount::{MntFlags, MsFlags, mount, umount2};
use std::fs::{File, create_dir_all, remove_file}; use std::fs::{File, create_dir_all, remove_file};
use std::io::{Error as IoError, ErrorKind}; use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -40,10 +38,7 @@ impl NetNs {
let ns = create_network_namespace(move |ns| { let ns = create_network_namespace(move |ns| {
bind_namespace(&ns, &path)?; bind_namespace(&ns, &path)?;
Result::<_, NamespaceError>::Ok(NetNs { Result::<_, NamespaceError>::Ok(NetNs { name, path })
name,
path,
})
})?; })?;
ns.setup_interfaces()?; ns.setup_interfaces()?;
@ -73,18 +68,16 @@ impl NetNs {
link_up_ns(&self.path, "lo")?; link_up_ns(&self.path, "lo")?;
Ok(()) Ok(())
} }
}
impl Drop for NetNs { pub fn delete(self) -> Result<(), NamespaceError> {
fn drop(&mut self) {
let name = self.path.file_name().unwrap().to_str().unwrap(); let name = self.path.file_name().unwrap().to_str().unwrap();
info!(name, "deleting network namespace"); info!(name, "deleting network namespace");
if let Err(error) = umount2(&self.path, MntFlags::MNT_DETACH) { umount2(&self.path, MntFlags::MNT_DETACH).map_err(NamespaceError::UnMount)?;
error!(%error, path = %self.path.display(), "Failed to unmount network namespace"); remove_file(&self.path).map_err(|error| NamespaceError::Delete {
} error,
if let Err(error) = remove_file(&self.path) { path: self.path
error!(%error, path = %self.path.display(), "Failed to remove namespace file"); })?;
} Ok(())
} }
} }
@ -94,19 +87,20 @@ pub enum NamespaceError {
Parent(IoError), Parent(IoError),
#[error("Failed to create namespace file {}: {error:#}", path.display())] #[error("Failed to create namespace file {}: {error:#}", path.display())]
Create { path: PathBuf, error: IoError }, Create { path: PathBuf, error: IoError },
#[error("Failed to delete namespace file {}: {error:#}", path.display())]
Delete { path: PathBuf, error: IoError },
#[error("Failed to setup namespace: {0:#}")] #[error("Failed to setup namespace: {0:#}")]
Setup(#[from] NamespaceSetupError), Setup(#[from] NamespaceSetupError),
#[error("Failed to bind-mount netns handle: {0:?}")] #[error("Failed to bind-mount netns handle: {0:?}")]
Mount(Errno), Mount(Errno),
#[error("Failed to unmount netns handle: {0:?}")]
UnMount(Errno),
#[error("Failed to setup loopback inside namespace: {0:#}")] #[error("Failed to setup loopback inside namespace: {0:#}")]
Link(#[from] LinkError), Link(#[from] LinkError),
} }
impl NamespaceError { impl NamespaceError {
fn from_create(path: PathBuf, error: IoError) -> Self { fn from_create(path: PathBuf, error: IoError) -> Self {
NamespaceError::Create { NamespaceError::Create { path, error }
path,
error,
}
} }
} }

View file

@ -1,6 +1,6 @@
mod tcp; mod tcp;
use crate::config::{ForwardConfig, ForwardTarget, ForwardSource, NamespaceName}; use crate::config::{ForwardConfig, ForwardSource, ForwardTarget, NamespaceName};
use crate::proxy::tcp::Proxy; use crate::proxy::tcp::Proxy;
use futures::future::AbortHandle; use futures::future::AbortHandle;
use nix::sched::{CloneFlags, setns}; use nix::sched::{CloneFlags, setns};