1use crate::Error;
4use nix::{
5 fcntl::OFlag,
6 sched::CloneFlags,
7 sys::{
8 stat::Mode,
9 wait::{waitpid, WaitStatus},
10 },
11 unistd::{fork, ForkResult},
12};
13use std::{option::Option, path::Path, process::exit};
14
15#[cfg(feature = "smol_socket")]
18async fn try_spawn_blocking<F, R>(fut: F) -> R
19where
20 F: FnOnce() -> R + Send + 'static,
21 R: Send + 'static,
22{
23 async_global_executor::spawn_blocking(fut).await
24}
25
26#[cfg(all(not(feature = "smol_socket"), feature = "tokio_socket"))]
28async fn try_spawn_blocking<F, R>(fut: F) -> R
29where
30 F: FnOnce() -> R + Send + 'static,
31 R: Send + 'static,
32{
33 match tokio::task::spawn_blocking(fut).await {
34 Ok(v) => v,
35 Err(err) => {
36 std::panic::resume_unwind(err.into_panic());
37 }
38 }
39}
40
41#[cfg(all(not(feature = "smol_socket"), not(feature = "tokio_socket")))]
44async fn try_spawn_blocking<F, R>(fut: F) -> R
45where
46 F: FnOnce() -> R + Send + 'static,
47 R: Send + 'static,
48{
49 fut()
50}
51
52pub const NETNS_PATH: &str = "/run/netns/";
53pub const SELF_NS_PATH: &str = "/proc/self/ns/net";
54pub const NONE_FS: &str = "none";
55
56pub struct NetworkNamespace();
57
58impl NetworkNamespace {
59 pub async fn add(ns_name: String) -> Result<(), Error> {
62 NetworkNamespace::prep_for_fork()?;
64 log::trace!("Forking...");
65 match unsafe { fork() } {
66 Ok(ForkResult::Parent { child, .. }) => {
67 NetworkNamespace::parent_process(child)
68 }
69 Ok(ForkResult::Child) => {
70 NetworkNamespace::child_process(ns_name);
71 }
72 Err(e) => {
73 let err_msg = format!("Fork failed: {e}");
74 Err(Error::NamespaceError(err_msg))
75 }
76 }
77 }
78
79 pub async fn del(ns_name: String) -> Result<(), Error> {
82 try_spawn_blocking(move || {
83 let mut netns_path = String::new();
84 netns_path.push_str(NETNS_PATH);
85 netns_path.push_str(&ns_name);
86 let ns_path = Path::new(&netns_path);
87
88 if nix::mount::umount2(ns_path, nix::mount::MntFlags::MNT_DETACH)
89 .is_err()
90 {
91 let err_msg = String::from(
92 "Namespace unmount failed (are you running as root?)",
93 );
94 return Err(Error::NamespaceError(err_msg));
95 }
96
97 if nix::unistd::unlink(ns_path).is_err() {
98 let err_msg = String::from(
99 "Namespace file remove failed (are you running as root?)",
100 );
101 return Err(Error::NamespaceError(err_msg));
102 }
103
104 Ok(())
105 })
106 .await
107 }
108
109 pub fn prep_for_fork() -> Result<(), Error> {
110 Ok(())
112 }
113
114 pub fn parent_process(child: nix::unistd::Pid) -> Result<(), Error> {
117 log::trace!("parent_process child PID: {}", child);
118 log::trace!("Waiting for child to finish...");
119 match waitpid(child, None) {
120 Ok(wait_status) => match wait_status {
121 WaitStatus::Exited(_, res) => {
122 log::trace!("Child exited with: {}", res);
123 if res == 0 {
124 return Ok(());
125 }
126 log::error!("Error child result: {}", res);
127 let err_msg = format!("Error child result: {res}");
128 Err(Error::NamespaceError(err_msg))
129 }
130 WaitStatus::Signaled(_, signal, has_dump) => {
131 log::error!("Error child killed by signal: {}", signal);
132 let err_msg = format!(
133 "Error child process was killed by signal: {signal} with core dump {has_dump}"
134 );
135 Err(Error::NamespaceError(err_msg))
136 }
137 _ => {
138 log::error!("Unknown child process status");
139 let err_msg = String::from("Unknown child process status");
140 Err(Error::NamespaceError(err_msg))
141 }
142 },
143 Err(e) => {
144 log::error!("wait error: {}", e);
145 let err_msg = format!("wait error: {e}");
146 Err(Error::NamespaceError(err_msg))
147 }
148 }
149 }
150
151 fn child_process(ns_name: String) -> ! {
152 let res = std::panic::catch_unwind(|| -> Result<(), Error> {
153 let netns_path =
154 NetworkNamespace::child_process_create_ns(ns_name)?;
155 NetworkNamespace::unshare_processing(netns_path)?;
156 Ok(())
157 });
158 match res {
159 Err(_panic) => {
160 log::error!("child process crashed");
162 std::process::abort()
163 }
164 Ok(Err(fail)) => {
165 log::error!("child process failed: {}", fail);
166 exit(1)
167 }
168 Ok(Ok(())) => exit(0),
169 }
170 }
171
172 pub fn child_process_create_ns(ns_name: String) -> Result<String, Error> {
176 log::trace!("child_process will create the namespace");
177
178 let mut netns_path = String::new();
179
180 let dir_path = Path::new(NETNS_PATH);
181 let mut mkdir_mode = Mode::empty();
182 let mut open_flags = OFlag::empty();
183 let mut mount_flags = nix::mount::MsFlags::empty();
184 let none_fs = Path::new(&NONE_FS);
185 let none_p4: Option<&Path> = None;
186
187 mkdir_mode.insert(Mode::S_IRWXU);
189 mkdir_mode.insert(Mode::S_IRGRP);
190 mkdir_mode.insert(Mode::S_IXGRP);
191 mkdir_mode.insert(Mode::S_IROTH);
192 mkdir_mode.insert(Mode::S_IXOTH);
193
194 open_flags.insert(OFlag::O_RDONLY);
195 open_flags.insert(OFlag::O_CREAT);
196 open_flags.insert(OFlag::O_EXCL);
197
198 netns_path.push_str(NETNS_PATH);
199 netns_path.push_str(&ns_name);
200
201 #[allow(clippy::collapsible_if)]
203 if nix::sys::stat::stat(dir_path).is_err() {
204 if let Err(e) = nix::unistd::mkdir(dir_path, mkdir_mode) {
205 log::error!("mkdir error: {}", e);
206 let err_msg = format!("mkdir error: {e}");
207 return Err(Error::NamespaceError(err_msg));
208 }
209 }
210
211 mount_flags.insert(nix::mount::MsFlags::MS_REC);
215 mount_flags.insert(nix::mount::MsFlags::MS_SHARED);
216 if nix::mount::mount(
217 Some(Path::new("")),
218 dir_path,
219 Some(none_fs),
220 mount_flags,
221 none_p4,
222 )
223 .is_err()
224 {
225 mount_flags = nix::mount::MsFlags::empty();
226 mount_flags.insert(nix::mount::MsFlags::MS_BIND);
227 mount_flags.insert(nix::mount::MsFlags::MS_REC);
228
229 if let Err(e) = nix::mount::mount(
230 Some(Path::new(dir_path)),
231 dir_path,
232 Some(none_fs),
233 mount_flags,
234 none_p4,
235 ) {
236 log::error!("mount error: {}", e);
237 let err_msg = format!("mount error: {e}");
238 return Err(Error::NamespaceError(err_msg));
239 }
240 }
241
242 mount_flags = nix::mount::MsFlags::empty();
243 mount_flags.insert(nix::mount::MsFlags::MS_REC);
244 mount_flags.insert(nix::mount::MsFlags::MS_SHARED);
245 if let Err(e) = nix::mount::mount(
246 Some(Path::new("")),
247 dir_path,
248 Some(none_fs),
249 mount_flags,
250 none_p4,
251 ) {
252 log::error!("mount error: {}", e);
253 let err_msg = format!("mount error: {e}");
254 return Err(Error::NamespaceError(err_msg));
255 }
256
257 let ns_path = Path::new(&netns_path);
258
259 let fd = match nix::fcntl::open(ns_path, open_flags, Mode::empty()) {
261 Ok(raw_fd) => raw_fd,
262 Err(e) => {
263 log::error!("open error: {}", e);
264 let err_msg = format!("open error: {e}");
265 return Err(Error::NamespaceError(err_msg));
266 }
267 };
268
269 if let Err(e) = nix::unistd::close(fd) {
270 log::error!("close error: {}", e);
271 let err_msg = format!("close error: {e}");
272 let _ = nix::unistd::unlink(ns_path);
273 return Err(Error::NamespaceError(err_msg));
274 }
275
276 Ok(netns_path)
277 }
278
279 #[allow(unused)]
282 pub fn unshare_processing(netns_path: String) -> Result<(), Error> {
283 let mut setns_flags = CloneFlags::empty();
284 let mut open_flags = OFlag::empty();
285 let ns_path = Path::new(&netns_path);
286
287 let none_fs = Path::new(&NONE_FS);
288 let none_p4: Option<&Path> = None;
289
290 if let Err(e) = nix::sched::unshare(CloneFlags::CLONE_NEWNET) {
292 log::error!("unshare error: {}", e);
293 let err_msg = format!("unshare error: {e}");
294 let _ = nix::unistd::unlink(ns_path);
295 return Err(Error::NamespaceError(err_msg));
296 }
297
298 open_flags = OFlag::empty();
299 open_flags.insert(OFlag::O_RDONLY);
300 open_flags.insert(OFlag::O_CLOEXEC);
301
302 let fd = match nix::fcntl::open(
303 Path::new(&SELF_NS_PATH),
304 open_flags,
305 Mode::empty(),
306 ) {
307 Ok(raw_fd) => raw_fd,
308 Err(e) => {
309 log::error!("open error: {}", e);
310 let err_msg = format!("open error: {e}");
311 return Err(Error::NamespaceError(err_msg));
312 }
313 };
314
315 let self_path = Path::new(&SELF_NS_PATH);
316
317 if let Err(e) = nix::mount::mount(
319 Some(self_path),
320 ns_path,
321 Some(none_fs),
322 nix::mount::MsFlags::MS_BIND,
323 none_p4,
324 ) {
325 log::error!("mount error: {}", e);
326 let err_msg = format!("mount error: {e}");
327 let _ = nix::unistd::unlink(ns_path);
328 return Err(Error::NamespaceError(err_msg));
329 }
330
331 setns_flags.insert(CloneFlags::CLONE_NEWNET);
332 if let Err(e) = nix::sched::setns(fd, setns_flags) {
333 log::error!("setns error: {}", e);
334 let err_msg = format!("setns error: {e}");
335 let _ = nix::unistd::unlink(ns_path);
336 return Err(Error::NamespaceError(err_msg));
337 }
338
339 Ok(())
340 }
341}