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, .. }) => NetworkNamespace::parent_process(child),
67 Ok(ForkResult::Child) => {
68 NetworkNamespace::child_process(ns_name);
69 }
70 Err(e) => {
71 let err_msg = format!("Fork failed: {}", e);
72 Err(Error::NamespaceError(err_msg))
73 }
74 }
75 }
76
77 pub async fn del(ns_name: String) -> Result<(), Error> {
80 try_spawn_blocking(move || {
81 let mut netns_path = String::new();
82 netns_path.push_str(NETNS_PATH);
83 netns_path.push_str(&ns_name);
84 let ns_path = Path::new(&netns_path);
85
86 if nix::mount::umount2(ns_path, nix::mount::MntFlags::MNT_DETACH).is_err() {
87 let err_msg = String::from("Namespace unmount failed (are you running as root?)");
88 return Err(Error::NamespaceError(err_msg));
89 }
90
91 if nix::unistd::unlink(ns_path).is_err() {
92 let err_msg =
93 String::from("Namespace file remove failed (are you running as root?)");
94 return Err(Error::NamespaceError(err_msg));
95 }
96
97 Ok(())
98 })
99 .await
100 }
101
102 pub fn prep_for_fork() -> Result<(), Error> {
103 Ok(())
105 }
106
107 pub fn parent_process(child: nix::unistd::Pid) -> Result<(), Error> {
110 log::trace!("parent_process child PID: {}", child);
111 log::trace!("Waiting for child to finish...");
112 match waitpid(child, None) {
113 Ok(wait_status) => match wait_status {
114 WaitStatus::Exited(_, res) => {
115 log::trace!("Child exited with: {}", res);
116 if res == 0 {
117 return Ok(());
118 }
119 log::error!("Error child result: {}", res);
120 let err_msg = format!("Error child result: {}", res);
121 Err(Error::NamespaceError(err_msg))
122 }
123 WaitStatus::Signaled(_, signal, has_dump) => {
124 log::error!("Error child killed by signal: {}", signal);
125 let err_msg = format!(
126 "Error child process was killed by signal: {} with core dump {}",
127 signal, has_dump
128 );
129 Err(Error::NamespaceError(err_msg))
130 }
131 _ => {
132 log::error!("Unknown child process status");
133 let err_msg = String::from("Unknown child process status");
134 Err(Error::NamespaceError(err_msg))
135 }
136 },
137 Err(e) => {
138 log::error!("wait error: {}", e);
139 let err_msg = format!("wait error: {}", e);
140 Err(Error::NamespaceError(err_msg))
141 }
142 }
143 }
144
145 fn child_process(ns_name: String) -> ! {
146 let res = std::panic::catch_unwind(|| -> Result<(), Error> {
147 let netns_path = NetworkNamespace::child_process_create_ns(ns_name)?;
148 NetworkNamespace::unshare_processing(netns_path)?;
149 Ok(())
150 });
151 match res {
152 Err(_panic) => {
153 log::error!("child process crashed");
155 std::process::abort()
156 }
157 Ok(Err(fail)) => {
158 log::error!("child process failed: {}", fail);
159 exit(1)
160 }
161 Ok(Ok(())) => exit(0),
162 }
163 }
164
165 pub fn child_process_create_ns(ns_name: String) -> Result<String, Error> {
169 log::trace!("child_process will create the namespace");
170
171 let mut netns_path = String::new();
172
173 let dir_path = Path::new(NETNS_PATH);
174 let mut mkdir_mode = Mode::empty();
175 let mut open_flags = OFlag::empty();
176 let mut mount_flags = nix::mount::MsFlags::empty();
177 let none_fs = Path::new(&NONE_FS);
178 let none_p4: Option<&Path> = None;
179
180 mkdir_mode.insert(Mode::S_IRWXU);
182 mkdir_mode.insert(Mode::S_IRGRP);
183 mkdir_mode.insert(Mode::S_IXGRP);
184 mkdir_mode.insert(Mode::S_IROTH);
185 mkdir_mode.insert(Mode::S_IXOTH);
186
187 open_flags.insert(OFlag::O_RDONLY);
188 open_flags.insert(OFlag::O_CREAT);
189 open_flags.insert(OFlag::O_EXCL);
190
191 netns_path.push_str(NETNS_PATH);
192 netns_path.push_str(&ns_name);
193
194 #[allow(clippy::collapsible_if)]
196 if nix::sys::stat::stat(dir_path).is_err() {
197 if let Err(e) = nix::unistd::mkdir(dir_path, mkdir_mode) {
198 log::error!("mkdir error: {}", e);
199 let err_msg = format!("mkdir error: {}", e);
200 return Err(Error::NamespaceError(err_msg));
201 }
202 }
203
204 mount_flags.insert(nix::mount::MsFlags::MS_REC);
208 mount_flags.insert(nix::mount::MsFlags::MS_SHARED);
209 if nix::mount::mount(
210 Some(Path::new("")),
211 dir_path,
212 Some(none_fs),
213 mount_flags,
214 none_p4,
215 )
216 .is_err()
217 {
218 mount_flags = nix::mount::MsFlags::empty();
219 mount_flags.insert(nix::mount::MsFlags::MS_BIND);
220 mount_flags.insert(nix::mount::MsFlags::MS_REC);
221
222 if let Err(e) = nix::mount::mount(
223 Some(Path::new(dir_path)),
224 dir_path,
225 Some(none_fs),
226 mount_flags,
227 none_p4,
228 ) {
229 log::error!("mount error: {}", e);
230 let err_msg = format!("mount error: {}", e);
231 return Err(Error::NamespaceError(err_msg));
232 }
233 }
234
235 mount_flags = nix::mount::MsFlags::empty();
236 mount_flags.insert(nix::mount::MsFlags::MS_REC);
237 mount_flags.insert(nix::mount::MsFlags::MS_SHARED);
238 if let Err(e) = nix::mount::mount(
239 Some(Path::new("")),
240 dir_path,
241 Some(none_fs),
242 mount_flags,
243 none_p4,
244 ) {
245 log::error!("mount error: {}", e);
246 let err_msg = format!("mount error: {}", e);
247 return Err(Error::NamespaceError(err_msg));
248 }
249
250 let ns_path = Path::new(&netns_path);
251
252 let fd = match nix::fcntl::open(ns_path, open_flags, Mode::empty()) {
254 Ok(raw_fd) => raw_fd,
255 Err(e) => {
256 log::error!("open error: {}", e);
257 let err_msg = format!("open error: {}", e);
258 return Err(Error::NamespaceError(err_msg));
259 }
260 };
261
262 if let Err(e) = nix::unistd::close(fd) {
263 log::error!("close error: {}", e);
264 let err_msg = format!("close error: {}", e);
265 let _ = nix::unistd::unlink(ns_path);
266 return Err(Error::NamespaceError(err_msg));
267 }
268
269 Ok(netns_path)
270 }
271
272 #[allow(unused)]
275 pub fn unshare_processing(netns_path: String) -> Result<(), Error> {
276 let mut setns_flags = CloneFlags::empty();
277 let mut open_flags = OFlag::empty();
278 let ns_path = Path::new(&netns_path);
279
280 let none_fs = Path::new(&NONE_FS);
281 let none_p4: Option<&Path> = None;
282
283 if let Err(e) = nix::sched::unshare(CloneFlags::CLONE_NEWNET) {
285 log::error!("unshare error: {}", e);
286 let err_msg = format!("unshare error: {}", e);
287 let _ = nix::unistd::unlink(ns_path);
288 return Err(Error::NamespaceError(err_msg));
289 }
290
291 open_flags = OFlag::empty();
292 open_flags.insert(OFlag::O_RDONLY);
293 open_flags.insert(OFlag::O_CLOEXEC);
294
295 let fd = match nix::fcntl::open(Path::new(&SELF_NS_PATH), open_flags, Mode::empty()) {
296 Ok(raw_fd) => raw_fd,
297 Err(e) => {
298 log::error!("open error: {}", e);
299 let err_msg = format!("open error: {}", e);
300 return Err(Error::NamespaceError(err_msg));
301 }
302 };
303
304 let self_path = Path::new(&SELF_NS_PATH);
305
306 if let Err(e) = nix::mount::mount(
308 Some(self_path),
309 ns_path,
310 Some(none_fs),
311 nix::mount::MsFlags::MS_BIND,
312 none_p4,
313 ) {
314 log::error!("mount error: {}", e);
315 let err_msg = format!("mount error: {}", e);
316 let _ = nix::unistd::unlink(ns_path);
317 return Err(Error::NamespaceError(err_msg));
318 }
319
320 setns_flags.insert(CloneFlags::CLONE_NEWNET);
321 if let Err(e) = nix::sched::setns(fd, setns_flags) {
322 log::error!("setns error: {}", e);
323 let err_msg = format!("setns error: {}", e);
324 let _ = nix::unistd::unlink(ns_path);
325 return Err(Error::NamespaceError(err_msg));
326 }
327
328 Ok(())
329 }
330}