mirror of
https://codeberg.org/icewind/netnsd.git
synced 2026-06-03 09:04:07 +02:00
add support for moving devices into the namespace
This commit is contained in:
parent
6ecb4b384d
commit
3fa69dc434
11 changed files with 411 additions and 88 deletions
76
src/namespace/handle.rs
Normal file
76
src/namespace/handle.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use crate::namespace::NamespaceEnterError;
|
||||
use nix::errno::Errno;
|
||||
use nix::sched::{setns, CloneFlags};
|
||||
use std::fs::File;
|
||||
use std::io::Error as IoError;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread::scope;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct NamespaceHandle {
|
||||
path: PathBuf,
|
||||
fd: OwnedFd,
|
||||
}
|
||||
|
||||
impl NamespaceHandle {
|
||||
/// Open the namespace handle for a path
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, NamespaceHandleError> {
|
||||
let path = path.as_ref();
|
||||
let file = File::open(path).map_err(|error| NamespaceHandleError::Open {
|
||||
error,
|
||||
path: path.into(),
|
||||
})?;
|
||||
Ok(NamespaceHandle {
|
||||
path: path.into(),
|
||||
fd: file.into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Open the namespace handle for the namespace the current process is in
|
||||
pub fn parent() -> Result<Self, NamespaceHandleError> {
|
||||
Self::open("/proc/self/ns/net")
|
||||
}
|
||||
|
||||
pub fn run_in<T: Send, F: FnOnce() -> T + Send>(&self, f: F) -> Result<T, NamespaceEnterError> {
|
||||
scope(|scope| {
|
||||
scope
|
||||
.spawn(|| {
|
||||
setns(&self.fd, CloneFlags::CLONE_NEWNET)?;
|
||||
Ok(f())
|
||||
})
|
||||
.join()
|
||||
.expect("namespace thread panicked")
|
||||
})
|
||||
.map_err(|error| NamespaceEnterError {
|
||||
namespace: self.path.clone(),
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for NamespaceHandle {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.fd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for NamespaceHandle {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for &NamespaceHandle {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NamespaceHandleError {
|
||||
#[error("Failed to open namespace handle {}: {error:#}", path.display())]
|
||||
Open { path: PathBuf, error: IoError },
|
||||
#[error("Failed to enter namespace: {0:#}")]
|
||||
Enter(Errno),
|
||||
}
|
||||
|
|
@ -1,27 +1,32 @@
|
|||
mod handle;
|
||||
mod raw;
|
||||
|
||||
use crate::config::NamespaceName;
|
||||
use crate::link::{LinkError, link_up_ns};
|
||||
use crate::namespace::raw::{NamespaceSetupError, create_network_namespace};
|
||||
use crate::config::{DeviceName, NamespaceName};
|
||||
use crate::link::{link_up, move_all_links_out, move_link_into, LinkError};
|
||||
pub use crate::namespace::handle::{NamespaceHandle, NamespaceHandleError};
|
||||
use crate::namespace::raw::{create_network_namespace, NamespaceSetupError};
|
||||
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 nix::mount::{mount, umount2, MntFlags, MsFlags};
|
||||
use std::fs::{create_dir, read_dir, remove_file, 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};
|
||||
use tracing::{debug, info};
|
||||
|
||||
pub struct NetNs {
|
||||
name: NamespaceName,
|
||||
path: PathBuf,
|
||||
nsd_path: PathBuf,
|
||||
handle: NamespaceHandle,
|
||||
}
|
||||
|
||||
impl NetNs {
|
||||
pub fn existing(include_broken: bool) -> Result<impl Iterator<Item = NamespaceName>, NamespaceError> {
|
||||
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 => {
|
||||
|
|
@ -71,10 +76,12 @@ impl NetNs {
|
|||
})?;
|
||||
}
|
||||
|
||||
let handle = NamespaceHandle::open(&path)?;
|
||||
return Ok(NetNs {
|
||||
name,
|
||||
nsd_path,
|
||||
path,
|
||||
handle,
|
||||
});
|
||||
}
|
||||
Err(e) => return Err(NamespaceError::from_create(path.clone(), e)),
|
||||
|
|
@ -89,10 +96,12 @@ impl NetNs {
|
|||
path: nsd_path.clone(),
|
||||
})?;
|
||||
}
|
||||
let handle = NamespaceHandle::open(&path)?;
|
||||
Result::<_, NamespaceError>::Ok(NetNs {
|
||||
name,
|
||||
path,
|
||||
nsd_path,
|
||||
handle,
|
||||
})
|
||||
})?;
|
||||
|
||||
|
|
@ -145,11 +154,15 @@ impl NetNs {
|
|||
}
|
||||
|
||||
fn setup_interfaces(&self) -> Result<(), NamespaceError> {
|
||||
link_up_ns(&self.path, "lo")?;
|
||||
self.handle
|
||||
.run_in(move || link_up("lo").map_err(NamespaceError::from))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete(self) -> Result<(), NamespaceError> {
|
||||
let parent_namespace = NamespaceHandle::parent()?;
|
||||
|
||||
move_all_links_out(self.handle(), &parent_namespace)?;
|
||||
let name = self.path.file_name().unwrap().to_str().unwrap();
|
||||
info!(name, "deleting network namespace");
|
||||
match umount2(&self.path, MntFlags::MNT_DETACH) {
|
||||
|
|
@ -167,6 +180,16 @@ impl NetNs {
|
|||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Move a device into this namespace
|
||||
pub fn move_device(&self, device: &DeviceName) -> Result<(), LinkError> {
|
||||
move_link_into(device.as_ref(), self.handle())
|
||||
}
|
||||
|
||||
/// Get the namespace handle
|
||||
pub fn handle(&self) -> &NamespaceHandle {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -187,10 +210,14 @@ pub enum NamespaceError {
|
|||
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 },
|
||||
#[error(transparent)]
|
||||
Handle(#[from] NamespaceHandleError),
|
||||
#[error(transparent)]
|
||||
Enter(#[from] NamespaceEnterError),
|
||||
#[error(transparent)]
|
||||
Link(#[from] LinkError),
|
||||
}
|
||||
|
||||
impl NamespaceError {
|
||||
|
|
@ -199,6 +226,13 @@ impl NamespaceError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Error while entering namespace {}: {0:#}", namespace.display())]
|
||||
pub struct NamespaceEnterError {
|
||||
namespace: PathBuf,
|
||||
error: Errno,
|
||||
}
|
||||
|
||||
/// `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) {
|
||||
|
|
@ -207,7 +241,6 @@ fn remove_file_if_exists<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// `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) {
|
||||
|
|
@ -215,4 +248,4 @@ fn remove_non_mount<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
|
|||
Err(err) if err.kind() == ErrorKind::ResourceBusy => Ok(()),
|
||||
rest => rest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue