⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/virt/
namespace.rs

1//! Linux-compatible namespace isolation for containers
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::{collections::BTreeMap, string::String};
8
9use crate::{error::KernelError, process::ProcessId};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub enum NamespaceType {
13    Pid,
14    Mount,
15    Network,
16    User,
17    Ipc,
18    Uts,
19}
20
21impl core::fmt::Display for NamespaceType {
22    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23        match self {
24            Self::Pid => write!(f, "pid"),
25            Self::Mount => write!(f, "mnt"),
26            Self::Network => write!(f, "net"),
27            Self::User => write!(f, "user"),
28            Self::Ipc => write!(f, "ipc"),
29            Self::Uts => write!(f, "uts"),
30        }
31    }
32}
33
34#[cfg(feature = "alloc")]
35pub struct PidNamespace {
36    container_to_global: BTreeMap<u32, ProcessId>,
37    global_to_container: BTreeMap<u64, u32>,
38    next_container_pid: u32,
39}
40
41#[cfg(feature = "alloc")]
42impl PidNamespace {
43    pub fn new() -> Self {
44        Self {
45            container_to_global: BTreeMap::new(),
46            global_to_container: BTreeMap::new(),
47            next_container_pid: 1,
48        }
49    }
50    pub fn add_process(&mut self, global_pid: ProcessId) -> u32 {
51        let cpid = self.next_container_pid;
52        self.next_container_pid += 1;
53        self.container_to_global.insert(cpid, global_pid);
54        self.global_to_container.insert(global_pid.0, cpid);
55        cpid
56    }
57    pub fn remove_process(&mut self, global_pid: ProcessId) {
58        if let Some(cpid) = self.global_to_container.remove(&global_pid.0) {
59            self.container_to_global.remove(&cpid);
60        }
61    }
62    pub fn translate_pid(&self, container_pid: u32) -> Option<ProcessId> {
63        self.container_to_global.get(&container_pid).copied()
64    }
65    pub fn container_pid(&self, global_pid: ProcessId) -> Option<u32> {
66        self.global_to_container.get(&global_pid.0).copied()
67    }
68    pub fn process_count(&self) -> usize {
69        self.container_to_global.len()
70    }
71    pub fn contains(&self, global_pid: ProcessId) -> bool {
72        self.global_to_container.contains_key(&global_pid.0)
73    }
74}
75
76#[cfg(feature = "alloc")]
77impl Default for PidNamespace {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83#[cfg(feature = "alloc")]
84pub struct MountNamespace {
85    root_path: String,
86    mounts: BTreeMap<String, String>,
87}
88
89#[cfg(feature = "alloc")]
90impl MountNamespace {
91    pub fn new(root: &str) -> Self {
92        Self {
93            root_path: String::from(root),
94            mounts: BTreeMap::new(),
95        }
96    }
97    pub fn set_root(&mut self, path: &str) {
98        self.root_path = String::from(path);
99    }
100    pub fn root(&self) -> &str {
101        &self.root_path
102    }
103    pub fn add_mount(&mut self, mountpoint: &str, source: &str) {
104        self.mounts
105            .insert(String::from(mountpoint), String::from(source));
106    }
107    pub fn remove_mount(&mut self, mountpoint: &str) {
108        self.mounts.remove(mountpoint);
109    }
110    pub fn mount_count(&self) -> usize {
111        self.mounts.len()
112    }
113    pub fn resolve_path(&self, path: &str) -> String {
114        if path.starts_with('/') {
115            alloc::format!("{}{}", self.root_path, path)
116        } else {
117            alloc::format!("{}/{}", self.root_path, path)
118        }
119    }
120}
121
122#[cfg(feature = "alloc")]
123impl Default for MountNamespace {
124    fn default() -> Self {
125        Self::new("/")
126    }
127}
128
129#[cfg(feature = "alloc")]
130#[derive(Debug, Clone)]
131pub struct VethInterface {
132    pub name: String,
133    pub peer_name: String,
134    pub ipv4_addr: Option<u32>,
135    pub is_up: bool,
136}
137
138#[cfg(feature = "alloc")]
139pub struct NetworkNamespace {
140    interfaces: BTreeMap<String, VethInterface>,
141    has_loopback: bool,
142}
143
144#[cfg(feature = "alloc")]
145impl NetworkNamespace {
146    pub fn new() -> Self {
147        Self {
148            interfaces: BTreeMap::new(),
149            has_loopback: true,
150        }
151    }
152    pub fn create_veth(&mut self, name: &str, peer_name: &str) -> Result<(), KernelError> {
153        self.interfaces.insert(
154            String::from(name),
155            VethInterface {
156                name: String::from(name),
157                peer_name: String::from(peer_name),
158                ipv4_addr: None,
159                is_up: false,
160            },
161        );
162        Ok(())
163    }
164    pub fn interface_up(&mut self, name: &str) -> Result<(), KernelError> {
165        match self.interfaces.get_mut(name) {
166            Some(i) => {
167                i.is_up = true;
168                Ok(())
169            }
170            None => Err(KernelError::NotFound {
171                resource: "network interface",
172                id: 0,
173            }),
174        }
175    }
176    pub fn assign_ipv4(&mut self, name: &str, addr: u32) -> Result<(), KernelError> {
177        match self.interfaces.get_mut(name) {
178            Some(i) => {
179                i.ipv4_addr = Some(addr);
180                Ok(())
181            }
182            None => Err(KernelError::NotFound {
183                resource: "network interface",
184                id: 0,
185            }),
186        }
187    }
188    pub fn interface_count(&self) -> usize {
189        self.interfaces.len()
190    }
191    pub fn has_loopback(&self) -> bool {
192        self.has_loopback
193    }
194}
195
196#[cfg(feature = "alloc")]
197impl Default for NetworkNamespace {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203#[cfg(feature = "alloc")]
204pub struct UtsNamespace {
205    hostname: String,
206    domainname: String,
207}
208
209#[cfg(feature = "alloc")]
210impl UtsNamespace {
211    pub fn new(hostname: &str) -> Self {
212        Self {
213            hostname: String::from(hostname),
214            domainname: String::new(),
215        }
216    }
217    pub fn hostname(&self) -> &str {
218        &self.hostname
219    }
220    pub fn set_hostname(&mut self, name: &str) {
221        self.hostname = String::from(name);
222    }
223    pub fn domainname(&self) -> &str {
224        &self.domainname
225    }
226    pub fn set_domainname(&mut self, name: &str) {
227        self.domainname = String::from(name);
228    }
229}
230
231#[cfg(feature = "alloc")]
232impl Default for UtsNamespace {
233    fn default() -> Self {
234        Self::new("localhost")
235    }
236}
237
238#[cfg(feature = "alloc")]
239pub struct NamespaceSet {
240    pub pid: PidNamespace,
241    pub mount: MountNamespace,
242    pub network: NetworkNamespace,
243    pub uts: UtsNamespace,
244}
245
246#[cfg(feature = "alloc")]
247impl NamespaceSet {
248    pub fn new(hostname: &str) -> Self {
249        Self {
250            pid: PidNamespace::new(),
251            mount: MountNamespace::new("/"),
252            network: NetworkNamespace::new(),
253            uts: UtsNamespace::new(hostname),
254        }
255    }
256}
257
258#[cfg(feature = "alloc")]
259impl Default for NamespaceSet {
260    fn default() -> Self {
261        Self::new("container")
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_pid_namespace() {
271        let mut ns = PidNamespace::new();
272        let cpid = ns.add_process(ProcessId(42));
273        assert_eq!(cpid, 1);
274        assert_eq!(ns.translate_pid(1), Some(ProcessId(42)));
275        assert_eq!(ns.container_pid(ProcessId(42)), Some(1));
276        ns.remove_process(ProcessId(42));
277        assert_eq!(ns.process_count(), 0);
278    }
279
280    #[test]
281    fn test_mount_namespace() {
282        let ns = MountNamespace::new("/containers/test");
283        assert_eq!(ns.resolve_path("/bin/sh"), "/containers/test/bin/sh");
284    }
285
286    #[test]
287    fn test_network_namespace() {
288        let mut ns = NetworkNamespace::new();
289        assert!(ns.create_veth("veth0", "veth0-host").is_ok());
290        assert_eq!(ns.interface_count(), 1);
291        assert!(ns.interface_up("veth0").is_ok());
292        assert!(ns.interface_up("nonexistent").is_err());
293    }
294
295    #[test]
296    fn test_uts_namespace() {
297        let mut ns = UtsNamespace::new("test");
298        assert_eq!(ns.hostname(), "test");
299        ns.set_hostname("new");
300        assert_eq!(ns.hostname(), "new");
301    }
302
303    #[test]
304    fn test_namespace_type_display() {
305        assert_eq!(alloc::format!("{}", NamespaceType::Pid), "pid");
306        assert_eq!(alloc::format!("{}", NamespaceType::Mount), "mnt");
307    }
308}