libcontainer/
namespaces.rs1use std::collections;
11
12use nix::sched::CloneFlags;
13use nix::sys::stat;
14use nix::{fcntl, unistd};
15use oci_spec::runtime::{LinuxNamespace, LinuxNamespaceType};
16
17use crate::syscall::Syscall;
18use crate::syscall::syscall::create_syscall;
19
20type Result<T> = std::result::Result<T, NamespaceError>;
21
22#[derive(Debug, thiserror::Error)]
23pub enum NamespaceError {
24 #[error(transparent)]
25 Nix(#[from] nix::Error),
26 #[error(transparent)]
27 IO(#[from] std::io::Error),
28 #[error(transparent)]
29 Syscall(#[from] crate::syscall::SyscallError),
30 #[error("Namespace type not supported: {0}")]
31 NotSupported(String),
32}
33
34static ORDERED_NAMESPACES: &[CloneFlags] = &[
35 CloneFlags::CLONE_NEWUSER,
36 CloneFlags::CLONE_NEWPID,
37 CloneFlags::CLONE_NEWUTS,
38 CloneFlags::CLONE_NEWIPC,
39 CloneFlags::CLONE_NEWNET,
40 CloneFlags::CLONE_NEWCGROUP,
41 CloneFlags::CLONE_NEWNS,
42];
43
44pub struct Namespaces {
46 command: Box<dyn Syscall>,
47 namespace_map: collections::HashMap<CloneFlags, LinuxNamespace>,
48}
49
50fn get_clone_flag(namespace_type: LinuxNamespaceType) -> Result<CloneFlags> {
51 let flag = match namespace_type {
52 LinuxNamespaceType::User => CloneFlags::CLONE_NEWUSER,
53 LinuxNamespaceType::Pid => CloneFlags::CLONE_NEWPID,
54 LinuxNamespaceType::Uts => CloneFlags::CLONE_NEWUTS,
55 LinuxNamespaceType::Ipc => CloneFlags::CLONE_NEWIPC,
56 LinuxNamespaceType::Network => CloneFlags::CLONE_NEWNET,
57 LinuxNamespaceType::Cgroup => CloneFlags::CLONE_NEWCGROUP,
58 LinuxNamespaceType::Mount => CloneFlags::CLONE_NEWNS,
59 LinuxNamespaceType::Time => return Err(NamespaceError::NotSupported("time".to_string())),
60 };
61
62 Ok(flag)
63}
64
65impl TryFrom<Option<&Vec<LinuxNamespace>>> for Namespaces {
66 type Error = NamespaceError;
67
68 fn try_from(namespaces: Option<&Vec<LinuxNamespace>>) -> Result<Self> {
69 let command: Box<dyn Syscall> = create_syscall();
70 let namespace_map: collections::HashMap<CloneFlags, LinuxNamespace> = namespaces
71 .unwrap_or(&vec![])
72 .iter()
73 .map(|ns| match get_clone_flag(ns.typ()) {
74 Ok(flag) => Ok((flag, ns.clone())),
75 Err(err) => Err(err),
76 })
77 .collect::<Result<Vec<(CloneFlags, LinuxNamespace)>>>()?
78 .into_iter()
79 .collect();
80
81 Ok(Namespaces {
82 command,
83 namespace_map,
84 })
85 }
86}
87
88impl Namespaces {
89 pub fn apply_namespaces<F: Fn(CloneFlags) -> bool>(&self, filter: F) -> Result<()> {
90 let to_enter: Vec<(&CloneFlags, &LinuxNamespace)> = ORDERED_NAMESPACES
91 .iter()
92 .filter(|c| filter(**c))
93 .filter_map(|c| self.namespace_map.get_key_value(c))
94 .collect();
95
96 for (_, ns) in to_enter {
97 self.unshare_or_setns(ns)?;
98 }
99 Ok(())
100 }
101
102 pub fn unshare_or_setns(&self, namespace: &LinuxNamespace) -> Result<()> {
103 tracing::debug!("unshare or setns: {:?}", namespace);
104 match namespace.path() {
105 Some(path) => {
106 let fd = fcntl::open(path, fcntl::OFlag::empty(), stat::Mode::empty())
107 .inspect_err(|err| {
108 tracing::error!(?err, ?namespace, "failed to open namespace file");
109 })?;
110 self.command
111 .set_ns(fd, get_clone_flag(namespace.typ())?)
112 .map_err(|err| {
113 tracing::error!(?err, ?namespace, "failed to set namespace");
114 err
115 })?;
116 unistd::close(fd).inspect_err(|err| {
117 tracing::error!(?err, ?namespace, "failed to close namespace file");
118 })?;
119 }
120 None => {
121 self.command
122 .unshare(get_clone_flag(namespace.typ())?)
123 .map_err(|err| {
124 tracing::error!(?err, ?namespace, "failed to unshare namespace");
125 err
126 })?;
127 }
128 }
129
130 Ok(())
131 }
132
133 pub fn get(&self, k: LinuxNamespaceType) -> Result<Option<&LinuxNamespace>> {
134 Ok(self.namespace_map.get(&get_clone_flag(k)?))
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use oci_spec::runtime::{LinuxNamespaceBuilder, LinuxNamespaceType};
141 use serial_test::serial;
142
143 use super::*;
144 use crate::syscall::test::TestHelperSyscall;
145
146 fn gen_sample_linux_namespaces() -> Vec<LinuxNamespace> {
147 vec![
148 LinuxNamespaceBuilder::default()
149 .typ(LinuxNamespaceType::Mount)
150 .path("/dev/null")
151 .build()
152 .unwrap(),
153 LinuxNamespaceBuilder::default()
154 .typ(LinuxNamespaceType::Network)
155 .path("/dev/null")
156 .build()
157 .unwrap(),
158 LinuxNamespaceBuilder::default()
159 .typ(LinuxNamespaceType::Pid)
160 .build()
161 .unwrap(),
162 LinuxNamespaceBuilder::default()
163 .typ(LinuxNamespaceType::User)
164 .build()
165 .unwrap(),
166 LinuxNamespaceBuilder::default()
167 .typ(LinuxNamespaceType::Ipc)
168 .build()
169 .unwrap(),
170 ]
171 }
172
173 #[test]
174 #[serial]
175 fn test_apply_namespaces() {
176 let sample_linux_namespaces = gen_sample_linux_namespaces();
177 let namespaces = Namespaces::try_from(Some(&sample_linux_namespaces))
178 .expect("create namespace struct should be good");
179 let test_command: &TestHelperSyscall = namespaces.command.as_any().downcast_ref().unwrap();
180 assert!(
181 namespaces
182 .apply_namespaces(|ns_type| { ns_type != CloneFlags::CLONE_NEWIPC })
183 .is_ok()
184 );
185
186 let mut setns_args: Vec<_> = test_command
187 .get_setns_args()
188 .into_iter()
189 .map(|(_fd, cf)| cf)
190 .collect();
191 setns_args.sort();
192 let mut expect = vec![CloneFlags::CLONE_NEWNS, CloneFlags::CLONE_NEWNET];
193 expect.sort();
194 assert_eq!(setns_args, expect);
195
196 let mut unshare_args = test_command.get_unshare_args();
197 unshare_args.sort();
198 let mut expect = vec![CloneFlags::CLONE_NEWUSER, CloneFlags::CLONE_NEWPID];
199 expect.sort();
200 assert_eq!(unshare_args, expect)
201 }
202}