mirror of
https://codeberg.org/icewind/netnsd.git
synced 2026-06-03 09:04:07 +02:00
track which namespaces we control
This commit is contained in:
parent
d168b4bf4d
commit
32fc90debe
5 changed files with 59 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -576,6 +576,7 @@ name = "netnsd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"either",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-concurrency",
|
"futures-concurrency",
|
||||||
"humansize",
|
"humansize",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ futures = "0.3.31"
|
||||||
futures-concurrency = "7.6.3"
|
futures-concurrency = "7.6.3"
|
||||||
humansize = { version = "2.1.3", features = ["no_alloc"] }
|
humansize = { version = "2.1.3", features = ["no_alloc"] }
|
||||||
neli = "0.7.1"
|
neli = "0.7.1"
|
||||||
|
either = "1.15.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_test = "1.0.177"
|
serde_test = "1.0.177"
|
||||||
|
|
@ -1,11 +1,25 @@
|
||||||
use serde::de::{Error, Unexpected, Visitor};
|
use serde::de::{Error, Unexpected, Visitor};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct NamespaceName(String);
|
pub struct NamespaceName(String);
|
||||||
|
|
||||||
|
impl TryFrom<OsString> for NamespaceName {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: OsString) -> Result<Self, Self::Error> {
|
||||||
|
let str = value.into_string().map_err(|_| ())?;
|
||||||
|
if validate_name(&str) {
|
||||||
|
Ok(NamespaceName(str))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for NamespaceName {
|
impl Display for NamespaceName {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
|
|
@ -80,7 +94,7 @@ fn validate_name(name: &str) -> bool {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_de() {
|
fn test_de() {
|
||||||
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token};
|
use serde_test::{Token, assert_de_tokens, assert_de_tokens_error};
|
||||||
|
|
||||||
assert_de_tokens(&NamespaceName("foo".into()), &[Token::String("foo")]);
|
assert_de_tokens(&NamespaceName("foo".into()), &[Token::String("foo")]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub fn daemon(config: Config) -> MainResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn daemon_async(mut config: Config) -> Result<(), DaemonError> {
|
async fn daemon_async(mut config: Config) -> Result<(), DaemonError> {
|
||||||
let mut state = State::default();
|
let mut state = State::new()?;
|
||||||
state.update(&config)?;
|
state.update(&config)?;
|
||||||
|
|
||||||
// now the namespaces are setup, we can tell systemd to start any service depending on them
|
// now the namespaces are setup, we can tell systemd to start any service depending on them
|
||||||
|
|
@ -104,12 +104,18 @@ enum Event {
|
||||||
Info,
|
Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct State {
|
struct State {
|
||||||
namespaces: Vec<ActiveNamespace>,
|
namespaces: Vec<ActiveNamespace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
pub fn new() -> Result<Self, DaemonError> {
|
||||||
|
let namespaces = NetNs::existing()?.map(ActiveNamespace::new).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(State {
|
||||||
|
namespaces
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, config: &Config) -> Result<(), DaemonError> {
|
pub fn update(&mut self, config: &Config) -> Result<(), DaemonError> {
|
||||||
for removed in self.namespaces.extract_if(.., |namespace| {
|
for removed in self.namespaces.extract_if(.., |namespace| {
|
||||||
config.get_namespace(namespace.name()).is_none()
|
config.get_namespace(namespace.name()).is_none()
|
||||||
|
|
@ -119,7 +125,7 @@ impl State {
|
||||||
|
|
||||||
for new in &config.namespaces {
|
for new in &config.namespaces {
|
||||||
if !self.has_namespace(&new.name) {
|
if !self.has_namespace(&new.name) {
|
||||||
self.namespaces.push(ActiveNamespace::new(new)?);
|
self.namespaces.push(ActiveNamespace::new(new.name.clone())?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,16 +150,13 @@ struct ActiveNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveNamespace {
|
impl ActiveNamespace {
|
||||||
pub fn new(config: &NamespaceConfig) -> Result<Self, DaemonError> {
|
pub fn new(name: NamespaceName) -> Result<Self, DaemonError> {
|
||||||
let ns = NetNs::new(config.name.clone())?;
|
let ns = NetNs::new(name)?;
|
||||||
|
|
||||||
let mut namespace = ActiveNamespace {
|
Ok(ActiveNamespace {
|
||||||
ns,
|
ns,
|
||||||
proxies: Vec::default(),
|
proxies: Vec::default(),
|
||||||
};
|
})
|
||||||
namespace.update_proxies(config)?;
|
|
||||||
|
|
||||||
Ok(namespace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_proxies(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> {
|
pub fn update_proxies(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ 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::{NamespaceSetupError, create_network_namespace};
|
use crate::namespace::raw::{NamespaceSetupError, create_network_namespace};
|
||||||
|
use either::Either;
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
use nix::mount::{MntFlags, MsFlags, mount, umount2};
|
use nix::mount::{MntFlags, MsFlags, mount, umount2};
|
||||||
use std::fs::{File, create_dir, remove_file};
|
use std::fs::{File, create_dir, read_dir, remove_file};
|
||||||
use std::io::{Error as IoError, ErrorKind};
|
use std::io::{Error as IoError, ErrorKind};
|
||||||
|
use std::iter::empty;
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -15,9 +17,26 @@ use tracing::{debug, error, info};
|
||||||
pub struct NetNs {
|
pub struct NetNs {
|
||||||
name: NamespaceName,
|
name: NamespaceName,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
nsd_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetNs {
|
impl NetNs {
|
||||||
|
pub fn existing() -> 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().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 that will be removed when dropped
|
||||||
pub fn new(name: NamespaceName) -> Result<Self, NamespaceError> {
|
pub fn new(name: NamespaceName) -> Result<Self, NamespaceError> {
|
||||||
let parent = Path::new("/var/run/netns");
|
let parent = Path::new("/var/run/netns");
|
||||||
|
|
@ -41,7 +60,8 @@ impl NetNs {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(NetNs {
|
return Ok(NetNs {
|
||||||
name: name.clone(),
|
name,
|
||||||
|
nsd_path,
|
||||||
path,
|
path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +77,7 @@ impl NetNs {
|
||||||
path: nsd_path.clone(),
|
path: nsd_path.clone(),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Result::<_, NamespaceError>::Ok(NetNs { name, path })
|
Result::<_, NamespaceError>::Ok(NetNs { name, path, nsd_path })
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
ns.setup_interfaces()?;
|
ns.setup_interfaces()?;
|
||||||
|
|
@ -121,6 +141,10 @@ impl NetNs {
|
||||||
error,
|
error,
|
||||||
path: self.path,
|
path: self.path,
|
||||||
})?;
|
})?;
|
||||||
|
remove_file(&self.nsd_path).map_err(|error| NamespaceError::Delete {
|
||||||
|
error,
|
||||||
|
path: self.nsd_path,
|
||||||
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +169,8 @@ pub enum NamespaceError {
|
||||||
UnMount(Errno),
|
UnMount(Errno),
|
||||||
#[error("Failed to setup loopback inside namespace: {0:#}")]
|
#[error("Failed to setup loopback inside namespace: {0:#}")]
|
||||||
Link(#[from] LinkError),
|
Link(#[from] LinkError),
|
||||||
|
#[error("Failed to scan {} for namespaces: {error:#}", path.display())]
|
||||||
|
Scan { path: PathBuf, error: IoError },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamespaceError {
|
impl NamespaceError {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue