veridian_kernel/services/cni/
plugin.rs1#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9
10#[derive(Debug, Clone)]
16pub struct CniConfig {
17 pub cni_version: String,
19 pub name: String,
21 pub type_name: String,
23 pub bridge: String,
25 pub subnet: String,
27 pub gateway: String,
29 pub options: BTreeMap<String, String>,
31}
32
33impl Default for CniConfig {
34 fn default() -> Self {
35 CniConfig {
36 cni_version: String::from("1.0.0"),
37 name: String::from("veridian-net"),
38 type_name: String::from("bridge"),
39 bridge: String::from("cni0"),
40 subnet: String::from("10.244.0.0/24"),
41 gateway: String::from("10.244.0.1"),
42 options: BTreeMap::new(),
43 }
44 }
45}
46
47impl CniConfig {
48 pub fn from_key_value(input: &str) -> Self {
53 let mut config = CniConfig::default();
54 for line in input.lines() {
55 let line = line.trim();
56 if line.is_empty() || line.starts_with('#') {
57 continue;
58 }
59 if let Some((key, value)) = line.split_once('=') {
60 let key = key.trim();
61 let value = value.trim();
62 match key {
63 "cniVersion" => config.cni_version = String::from(value),
64 "name" => config.name = String::from(value),
65 "type" => config.type_name = String::from(value),
66 "bridge" => config.bridge = String::from(value),
67 "subnet" => config.subnet = String::from(value),
68 "gateway" => config.gateway = String::from(value),
69 _ => {
70 config
71 .options
72 .insert(String::from(key), String::from(value));
73 }
74 }
75 }
76 }
77 config
78 }
79}
80
81#[derive(Debug, Clone)]
87pub struct CniInterface {
88 pub name: String,
90 pub mac: String,
92 pub sandbox: bool,
94}
95
96#[derive(Debug, Clone)]
98pub struct CniIpConfig {
99 pub address: String,
101 pub gateway: String,
103 pub interface_idx: usize,
105}
106
107#[derive(Debug, Clone)]
109pub struct CniRoute {
110 pub dst: String,
112 pub gw: String,
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct CniDns {
119 pub nameservers: Vec<String>,
121 pub search: Vec<String>,
123}
124
125#[derive(Debug, Clone)]
127pub struct CniResult {
128 pub cni_version: String,
130 pub interfaces: Vec<CniInterface>,
132 pub ips: Vec<CniIpConfig>,
134 pub routes: Vec<CniRoute>,
136 pub dns: CniDns,
138}
139
140impl Default for CniResult {
141 fn default() -> Self {
142 CniResult {
143 cni_version: String::from("1.0.0"),
144 interfaces: Vec::new(),
145 ips: Vec::new(),
146 routes: Vec::new(),
147 dns: CniDns::default(),
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum CniError {
159 InvalidConfig(String),
161 SetupFailed(String),
163 TeardownFailed(String),
165 PluginNotFound(String),
167 NoAddressAvailable,
169}
170
171pub trait CniPlugin {
173 fn add(&mut self, container_id: &str, config: &CniConfig) -> Result<CniResult, CniError>;
175
176 fn del(&mut self, container_id: &str, config: &CniConfig) -> Result<(), CniError>;
178
179 fn check(&self, container_id: &str, config: &CniConfig) -> Result<(), CniError>;
181
182 fn version(&self) -> Vec<String>;
184}
185
186#[derive(Debug, Clone)]
192pub struct VethPair {
193 pub host_name: String,
195 pub container_name: String,
197 pub container_id: String,
199 pub ip_address: String,
201 pub mac_address: String,
203}
204
205#[derive(Debug)]
207pub struct BridgePlugin {
208 bridge_name: String,
210 veth_pairs: BTreeMap<String, VethPair>,
212 next_host_part: u32,
214 next_veth_idx: u32,
216}
217
218impl Default for BridgePlugin {
219 fn default() -> Self {
220 Self::new(String::from("cni0"))
221 }
222}
223
224impl BridgePlugin {
225 pub fn new(bridge_name: String) -> Self {
227 BridgePlugin {
228 bridge_name,
229 veth_pairs: BTreeMap::new(),
230 next_host_part: 2, next_veth_idx: 0,
232 }
233 }
234
235 fn setup_veth(&mut self, container_id: &str) -> VethPair {
237 let idx = self.next_veth_idx;
238 self.next_veth_idx += 1;
239
240 let host_name = alloc::format!("veth{:04x}", idx);
241 let container_name = String::from("eth0");
242
243 let mac_address =
245 alloc::format!("02:42:ac:11:{:02x}:{:02x}", (idx >> 8) & 0xFF, idx & 0xFF);
246
247 VethPair {
248 host_name,
249 container_name,
250 container_id: String::from(container_id),
251 ip_address: String::new(), mac_address,
253 }
254 }
255
256 fn attach_to_bridge(&self, _veth: &VethPair) -> Result<(), CniError> {
258 Ok(())
260 }
261
262 fn configure_nat(&self, _subnet: &str) -> Result<(), CniError> {
264 Ok(())
266 }
267
268 fn allocate_ip(&mut self, config: &CniConfig) -> Result<String, CniError> {
270 let subnet = &config.subnet;
272 let slash_pos = subnet.find('/').unwrap_or(subnet.len());
273 let base = &subnet[..slash_pos];
274
275 if let Some(last_dot) = base.rfind('.') {
277 let prefix = &base[..last_dot];
278 let host_part = self.next_host_part;
279 if host_part > 254 {
280 return Err(CniError::NoAddressAvailable);
281 }
282 self.next_host_part += 1;
283 Ok(alloc::format!("{}.{}/24", prefix, host_part))
284 } else {
285 Err(CniError::InvalidConfig(String::from(
286 "invalid subnet format",
287 )))
288 }
289 }
290
291 pub fn active_count(&self) -> usize {
293 self.veth_pairs.len()
294 }
295}
296
297impl CniPlugin for BridgePlugin {
298 fn add(&mut self, container_id: &str, config: &CniConfig) -> Result<CniResult, CniError> {
299 if self.veth_pairs.contains_key(container_id) {
301 return Err(CniError::SetupFailed(String::from(
302 "container already attached",
303 )));
304 }
305
306 let mut veth = self.setup_veth(container_id);
307 let ip_address = self.allocate_ip(config)?;
308 veth.ip_address = ip_address.clone();
309
310 self.attach_to_bridge(&veth)?;
311 self.configure_nat(&config.subnet)?;
312
313 let result = CniResult {
314 cni_version: config.cni_version.clone(),
315 interfaces: alloc::vec![
316 CniInterface {
317 name: veth.host_name.clone(),
318 mac: String::new(),
319 sandbox: false,
320 },
321 CniInterface {
322 name: veth.container_name.clone(),
323 mac: veth.mac_address.clone(),
324 sandbox: true,
325 },
326 ],
327 ips: alloc::vec![CniIpConfig {
328 address: ip_address,
329 gateway: config.gateway.clone(),
330 interface_idx: 1,
331 }],
332 routes: alloc::vec![CniRoute {
333 dst: String::from("0.0.0.0/0"),
334 gw: config.gateway.clone(),
335 }],
336 dns: CniDns::default(),
337 };
338
339 self.veth_pairs.insert(String::from(container_id), veth);
340 Ok(result)
341 }
342
343 fn del(&mut self, container_id: &str, _config: &CniConfig) -> Result<(), CniError> {
344 if self.veth_pairs.remove(container_id).is_none() {
345 return Err(CniError::TeardownFailed(String::from(
346 "container not found",
347 )));
348 }
349 Ok(())
350 }
351
352 fn check(&self, container_id: &str, _config: &CniConfig) -> Result<(), CniError> {
353 if self.veth_pairs.contains_key(container_id) {
354 Ok(())
355 } else {
356 Err(CniError::SetupFailed(String::from(
357 "container not attached",
358 )))
359 }
360 }
361
362 fn version(&self) -> Vec<String> {
363 alloc::vec![
364 String::from("0.3.0"),
365 String::from("0.3.1"),
366 String::from("0.4.0"),
367 String::from("1.0.0"),
368 ]
369 }
370}
371
372#[cfg(test)]
377mod tests {
378 #[allow(unused_imports)]
379 use alloc::string::ToString;
380
381 use super::*;
382
383 fn default_config() -> CniConfig {
384 CniConfig::default()
385 }
386
387 #[test]
388 fn test_cni_config_default() {
389 let config = CniConfig::default();
390 assert_eq!(config.type_name, "bridge");
391 assert_eq!(config.subnet, "10.244.0.0/24");
392 }
393
394 #[test]
395 fn test_cni_config_parse() {
396 let input = "name=my-net\ntype=bridge\nsubnet=10.0.0.0/16\ngateway=10.0.0.1\n";
397 let config = CniConfig::from_key_value(input);
398 assert_eq!(config.name, "my-net");
399 assert_eq!(config.subnet, "10.0.0.0/16");
400 assert_eq!(config.gateway, "10.0.0.1");
401 }
402
403 #[test]
404 fn test_bridge_add() {
405 let mut plugin = BridgePlugin::default();
406 let config = default_config();
407 let result = plugin.add("container1", &config).unwrap();
408 assert_eq!(result.interfaces.len(), 2);
409 assert_eq!(result.ips.len(), 1);
410 assert!(result.ips[0].address.contains("10.244.0."));
411 assert_eq!(result.routes.len(), 1);
412 }
413
414 #[test]
415 fn test_bridge_add_duplicate() {
416 let mut plugin = BridgePlugin::default();
417 let config = default_config();
418 plugin.add("c1", &config).unwrap();
419 let result = plugin.add("c1", &config);
420 assert!(result.is_err());
421 }
422
423 #[test]
424 fn test_bridge_del() {
425 let mut plugin = BridgePlugin::default();
426 let config = default_config();
427 plugin.add("c1", &config).unwrap();
428 plugin.del("c1", &config).unwrap();
429 assert_eq!(plugin.active_count(), 0);
430 }
431
432 #[test]
433 fn test_bridge_del_not_found() {
434 let mut plugin = BridgePlugin::default();
435 let config = default_config();
436 assert!(plugin.del("nonexistent", &config).is_err());
437 }
438
439 #[test]
440 fn test_bridge_check() {
441 let mut plugin = BridgePlugin::default();
442 let config = default_config();
443 plugin.add("c1", &config).unwrap();
444 assert!(plugin.check("c1", &config).is_ok());
445 assert!(plugin.check("c2", &config).is_err());
446 }
447
448 #[test]
449 fn test_bridge_version() {
450 let plugin = BridgePlugin::default();
451 let versions = plugin.version();
452 assert!(versions.contains(&String::from("1.0.0")));
453 }
454
455 #[test]
456 fn test_multiple_containers_get_different_ips() {
457 let mut plugin = BridgePlugin::default();
458 let config = default_config();
459 let r1 = plugin.add("c1", &config).unwrap();
460 let r2 = plugin.add("c2", &config).unwrap();
461 assert_ne!(r1.ips[0].address, r2.ips[0].address);
462 }
463
464 #[test]
465 fn test_config_parse_with_comments() {
466 let input = "# comment\nname=test\n\ntype=bridge\n";
467 let config = CniConfig::from_key_value(input);
468 assert_eq!(config.name, "test");
469 assert_eq!(config.type_name, "bridge");
470 }
471}