rtnetlink/
ns.rs

1// SPDX-License-Identifier: MIT
2
3use 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// if "only" smol or smol+tokio were enabled, we use smol because
18// it doesn't require an active tokio runtime - just to be sure.
19#[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// only tokio enabled, so use tokio
29#[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// neither smol nor tokio - just run blocking op directly.
44// hopefully not too blocking...
45#[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    /// Add a new network namespace.
62    /// This is equivalent to `ip netns add NS_NAME`.
63    pub async fn add(ns_name: String) -> Result<(), Error> {
64        // Forking process to avoid moving caller into new namespace
65        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    /// Remove a network namespace
82    /// This is equivalent to `ip netns del NS_NAME`.
83    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        // Placeholder function, nothing to do here.
113        Ok(())
114    }
115
116    /// This is the parent process form the fork, it waits for the
117    /// child to exit properly
118    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                // panic should have already been printed by the handler
163                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    /// This is the child process, it will actually create the namespace
175    /// resources. It creates the folder and namespace file.
176    /// Returns the namespace file path
177    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        // flags in mkdir
190        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        // creating namespaces folder if not exists
204        #[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        // Try to mount /run/netns, with MS_REC | MS_SHARED
214        // If it fails, creates the mount with MS_BIND | MS_REC
215        // This is the same strategy used by `ip netns add NS`
216        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        // creating the netns file
262        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    /// This function unshare the calling process and move into
282    /// the given network namespace
283    #[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        // unshare to the new network namespace
293        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        // bind to the netns
320        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}