rtnetlink/
ns.rs

1// SPDX-License-Identifier: MIT
2
3use 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// if "only" smol or smol+tokio were enabled, we use smol because
16// it doesn't require an active tokio runtime - just to be sure.
17#[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// only tokio enabled, so use tokio
27#[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// neither smol nor tokio - just run blocking op directly.
42// hopefully not too blocking...
43#[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    /// Add a new network namespace.
60    /// This is equivalent to `ip netns add NS_NAME`.
61    pub async fn add(ns_name: String) -> Result<(), Error> {
62        // Forking process to avoid moving caller into new namespace
63        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    /// Remove a network namespace
78    /// This is equivalent to `ip netns del NS_NAME`.
79    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        // Placeholder function, nothing to do here.
104        Ok(())
105    }
106
107    /// This is the parent process form the fork, it waits for the
108    /// child to exit properly
109    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                // panic should have already been printed by the handler
154                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    /// This is the child process, it will actually create the namespace
166    /// resources. It creates the folder and namespace file.
167    /// Returns the namespace file path
168    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        // flags in mkdir
181        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        // creating namespaces folder if not exists
195        #[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        // Try to mount /run/netns, with MS_REC | MS_SHARED
205        // If it fails, creates the mount with MS_BIND | MS_REC
206        // This is the same strategy used by `ip netns add NS`
207        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        // creating the netns file
253        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    /// This function unshare the calling process and move into
273    /// the given network namespace
274    #[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        // unshare to the new network namespace
284        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        // bind to the netns
307        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}