use std::collections;
use nix::sched::CloneFlags;
use nix::sys::stat;
use nix::{fcntl, unistd};
use oci_spec::runtime::{LinuxNamespace, LinuxNamespaceType};
use crate::syscall::Syscall;
use crate::syscall::syscall::create_syscall;
type Result<T> = std::result::Result<T, NamespaceError>;
#[derive(Debug, thiserror::Error)]
pub enum NamespaceError {
#[error(transparent)]
Nix(#[from] nix::Error),
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Syscall(#[from] crate::syscall::SyscallError),
#[error("Namespace type not supported: {0}")]
NotSupported(String),
}
static ORDERED_NAMESPACES: &[CloneFlags] = &[
CloneFlags::CLONE_NEWUSER,
CloneFlags::CLONE_NEWPID,
CloneFlags::CLONE_NEWUTS,
CloneFlags::CLONE_NEWIPC,
CloneFlags::CLONE_NEWNET,
CloneFlags::CLONE_NEWCGROUP,
CloneFlags::CLONE_NEWNS,
];
pub struct Namespaces {
command: Box<dyn Syscall>,
namespace_map: collections::HashMap<CloneFlags, LinuxNamespace>,
}
fn get_clone_flag(namespace_type: LinuxNamespaceType) -> Result<CloneFlags> {
let flag = match namespace_type {
LinuxNamespaceType::User => CloneFlags::CLONE_NEWUSER,
LinuxNamespaceType::Pid => CloneFlags::CLONE_NEWPID,
LinuxNamespaceType::Uts => CloneFlags::CLONE_NEWUTS,
LinuxNamespaceType::Ipc => CloneFlags::CLONE_NEWIPC,
LinuxNamespaceType::Network => CloneFlags::CLONE_NEWNET,
LinuxNamespaceType::Cgroup => CloneFlags::CLONE_NEWCGROUP,
LinuxNamespaceType::Mount => CloneFlags::CLONE_NEWNS,
LinuxNamespaceType::Time => return Err(NamespaceError::NotSupported("time".to_string())),
};
Ok(flag)
}
impl TryFrom<Option<&Vec<LinuxNamespace>>> for Namespaces {
type Error = NamespaceError;
fn try_from(namespaces: Option<&Vec<LinuxNamespace>>) -> Result<Self> {
let command: Box<dyn Syscall> = create_syscall();
let namespace_map: collections::HashMap<CloneFlags, LinuxNamespace> = namespaces
.unwrap_or(&vec![])
.iter()
.map(|ns| match get_clone_flag(ns.typ()) {
Ok(flag) => Ok((flag, ns.clone())),
Err(err) => Err(err),
})
.collect::<Result<Vec<(CloneFlags, LinuxNamespace)>>>()?
.into_iter()
.collect();
Ok(Namespaces {
command,
namespace_map,
})
}
}
impl Namespaces {
pub fn apply_namespaces<F: Fn(CloneFlags) -> bool>(&self, filter: F) -> Result<()> {
let to_enter: Vec<(&CloneFlags, &LinuxNamespace)> = ORDERED_NAMESPACES
.iter()
.filter(|c| filter(**c))
.filter_map(|c| self.namespace_map.get_key_value(c))
.collect();
for (_, ns) in to_enter {
self.unshare_or_setns(ns)?;
}
Ok(())
}
pub fn unshare_or_setns(&self, namespace: &LinuxNamespace) -> Result<()> {
tracing::debug!("unshare or setns: {:?}", namespace);
match namespace.path() {
Some(path) => {
let fd = fcntl::open(path, fcntl::OFlag::empty(), stat::Mode::empty())
.inspect_err(|err| {
tracing::error!(?err, ?namespace, "failed to open namespace file");
})?;
self.command
.set_ns(fd, get_clone_flag(namespace.typ())?)
.map_err(|err| {
tracing::error!(?err, ?namespace, "failed to set namespace");
err
})?;
unistd::close(fd).inspect_err(|err| {
tracing::error!(?err, ?namespace, "failed to close namespace file");
})?;
}
None => {
self.command
.unshare(get_clone_flag(namespace.typ())?)
.map_err(|err| {
tracing::error!(?err, ?namespace, "failed to unshare namespace");
err
})?;
}
}
Ok(())
}
pub fn get(&self, k: LinuxNamespaceType) -> Result<Option<&LinuxNamespace>> {
Ok(self.namespace_map.get(&get_clone_flag(k)?))
}
}
#[cfg(test)]
mod tests {
use oci_spec::runtime::{LinuxNamespaceBuilder, LinuxNamespaceType};
use serial_test::serial;
use super::*;
use crate::syscall::test::TestHelperSyscall;
fn gen_sample_linux_namespaces() -> Vec<LinuxNamespace> {
vec![
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::Mount)
.path("/dev/null")
.build()
.unwrap(),
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::Network)
.path("/dev/null")
.build()
.unwrap(),
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::Pid)
.build()
.unwrap(),
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::User)
.build()
.unwrap(),
LinuxNamespaceBuilder::default()
.typ(LinuxNamespaceType::Ipc)
.build()
.unwrap(),
]
}
#[test]
#[serial]
fn test_apply_namespaces() {
let sample_linux_namespaces = gen_sample_linux_namespaces();
let namespaces = Namespaces::try_from(Some(&sample_linux_namespaces))
.expect("create namespace struct should be good");
let test_command: &TestHelperSyscall = namespaces.command.as_any().downcast_ref().unwrap();
assert!(
namespaces
.apply_namespaces(|ns_type| { ns_type != CloneFlags::CLONE_NEWIPC })
.is_ok()
);
let mut setns_args: Vec<_> = test_command
.get_setns_args()
.into_iter()
.map(|(_fd, cf)| cf)
.collect();
setns_args.sort();
let mut expect = vec![CloneFlags::CLONE_NEWNS, CloneFlags::CLONE_NEWNET];
expect.sort();
assert_eq!(setns_args, expect);
let mut unshare_args = test_command.get_unshare_args();
unshare_args.sort();
let mut expect = vec![CloneFlags::CLONE_NEWUSER, CloneFlags::CLONE_NEWPID];
expect.sort();
assert_eq!(unshare_args, expect)
}
}