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