rust_cni/libcni/
api.rs

1// Copyright (c) 2024 https://github.com/divinerapier/cni-rs
2use log::{debug, error, info, trace, warn};
3use serde::{Deserialize, Serialize};
4
5use super::CNIError;
6
7use super::exec::{Exec, ExecArgs, RawExec};
8use crate::libcni::result::result100;
9use crate::libcni::result::{APIResult, ResultCNI};
10use crate::libcni::types::NetworkConfig;
11use std::collections::HashMap;
12use std::fs;
13use std::io::Write;
14use std::path::Path;
15
16pub trait CNI {
17    fn add_network_list(
18        &self,
19        net: NetworkConfigList,
20        rt: RuntimeConf,
21    ) -> ResultCNI<Box<dyn APIResult>>;
22
23    fn check_network_list(&self, net: NetworkConfigList, rt: RuntimeConf) -> ResultCNI<()>;
24
25    fn delete_network_list(&self, net: NetworkConfigList, rt: RuntimeConf) -> ResultCNI<()>;
26
27    fn get_network_list_cached_result(
28        &self,
29        net: NetworkConfigList,
30        rt: RuntimeConf,
31    ) -> ResultCNI<Box<dyn APIResult>>;
32
33    fn add_network(
34        &self,
35        name: String,
36        cni_version: String,
37        net: NetworkConfig,
38        prev_result: Option<Box<dyn APIResult>>,
39        rt: RuntimeConf,
40    ) -> ResultCNI<Box<dyn APIResult>>;
41
42    fn check_network(
43        &self,
44        name: String,
45        cni_version: String,
46        prev_result: Option<Box<dyn APIResult>>,
47        net: NetworkConfig,
48        rt: RuntimeConf,
49    ) -> ResultCNI<()>;
50
51    fn delete_network(
52        &self,
53        name: String,
54        cni_version: String,
55        net: NetworkConfig,
56        rt: RuntimeConf,
57    ) -> ResultCNI<()>;
58
59    fn get_network_cached_result(
60        &self,
61        net: NetworkConfig,
62        rt: RuntimeConf,
63    ) -> ResultCNI<Box<dyn APIResult>>;
64
65    fn get_network_cached_config(
66        &self,
67        net: NetworkConfig,
68        rt: RuntimeConf,
69    ) -> ResultCNI<(Vec<u8>, RuntimeConf)>;
70
71    fn validate_network_list(&self, net: NetworkConfigList) -> ResultCNI<Vec<String>>;
72
73    fn validate_network(&self, net: NetworkConfig) -> ResultCNI<Vec<String>>;
74}
75
76#[derive(Default, Clone, Serialize, Deserialize)]
77pub struct NetworkConfigList {
78    pub name: String,
79    pub cni_version: String,
80    pub disable_check: bool,
81    pub plugins: Vec<NetworkConfig>,
82    pub bytes: Vec<u8>,
83}
84
85impl NetworkConfigList {
86    pub fn validate(&self) -> Result<(), String> {
87        if self.name.is_empty() {
88            return Err("Network name cannot be empty".to_string());
89        }
90        if self.cni_version.is_empty() {
91            return Err("CNI version cannot be empty".to_string());
92        }
93        if self.plugins.is_empty() {
94            return Err("At least one plugin is required".to_string());
95        }
96        Ok(())
97    }
98}
99
100#[derive(Clone, Default, Serialize, Deserialize)]
101pub struct RuntimeConf {
102    pub container_id: String,
103    pub net_ns: String,
104    pub if_name: String,
105    pub args: Vec<[String; 2]>,
106    pub capability_args: HashMap<String, String>,
107    pub cache_dir: String,
108}
109
110impl RuntimeConf {
111    pub fn get_cache_key(&self) -> String {
112        let id_part = if self.container_id.len() > 12 {
113            &self.container_id[..12]
114        } else {
115            &self.container_id
116        };
117
118        format!("{}-{}", id_part, self.if_name)
119    }
120}
121
122#[derive(Default)]
123pub struct CNIConfig {
124    pub path: Vec<String>,
125    pub exec: RawExec,
126    pub cache_dir: String,
127}
128
129impl CNIConfig {
130    fn get_cache_dir(&self, netname: &str) -> std::path::PathBuf {
131        let cache_dir = if self.cache_dir.is_empty() {
132            "/var/lib/cni/cache".to_string()
133        } else {
134            self.cache_dir.clone()
135        };
136
137        let path = Path::new(&cache_dir).join(netname);
138        if !path.exists() {
139            if let Err(e) = std::fs::create_dir_all(&path) {
140                warn!("Failed to create cache directory {}: {}", path.display(), e);
141            }
142        }
143        path
144    }
145
146    fn cache_network_config(
147        &self,
148        network_name: &str,
149        rt: &RuntimeConf,
150        config_bytes: &[u8],
151    ) -> ResultCNI<()> {
152        let cache_dir = self.get_cache_dir(network_name);
153        let key = rt.get_cache_key();
154        let config_path = cache_dir.join(format!("{}.config", key));
155
156        trace!("Caching network config to {}", config_path.display());
157
158        let mut file =
159            fs::File::create(config_path).map_err(|e| Box::new(CNIError::Io(Box::new(e))))?;
160
161        file.write_all(config_bytes)
162            .map_err(|e| Box::new(CNIError::Io(Box::new(e))))?;
163
164        Ok(())
165    }
166
167    fn cache_network_result(
168        &self,
169        network_name: &str,
170        rt: &RuntimeConf,
171        result: &dyn APIResult,
172    ) -> ResultCNI<()> {
173        let cache_dir = self.get_cache_dir(network_name);
174        let key = rt.get_cache_key();
175        let result_path = cache_dir.join(format!("{}.result", key));
176
177        trace!("Caching network result to {}", result_path.display());
178
179        let result_json = result.get_json();
180        let result_bytes = result_json.dump().as_bytes().to_vec();
181
182        let mut file =
183            fs::File::create(result_path).map_err(|e| Box::new(CNIError::Io(Box::new(e))))?;
184
185        file.write_all(&result_bytes)
186            .map_err(|e| Box::new(CNIError::Io(Box::new(e))))?;
187
188        Ok(())
189    }
190
191    #[allow(clippy::type_complexity)]
192    fn read_cached_network(
193        &self,
194        netname: &str,
195        rt: &RuntimeConf,
196    ) -> Result<(Box<dyn APIResult>, Vec<u8>, RuntimeConf), String> {
197        trace!("Reading cached network {} config", netname);
198        let cache_dir = self.get_cache_dir(netname);
199        let key = rt.get_cache_key();
200        let result_path = cache_dir.join(format!("{}.result", key));
201        let config_path = cache_dir.join(format!("{}.config", key));
202
203        if !result_path.exists() || !config_path.exists() {
204            return Err("Cache files do not exist".to_string());
205        }
206
207        // Read files
208        let result_bytes = fs::read(&result_path).map_err(|e| e.to_string())?;
209        let config_bytes = fs::read(&config_path).map_err(|e| e.to_string())?;
210
211        // Parse result
212        let _: serde_json::Value = serde_json::from_slice(&result_bytes)
213            .map_err(|e| format!("Failed to parse result cache: {}", e))?;
214
215        // Create result object
216        let result = result100::Result {
217            cni_version: Some(
218                rt.args
219                    .iter()
220                    .find(|arg| arg[0] == "cniVersion")
221                    .map(|arg| arg[1].clone())
222                    .unwrap_or_else(|| "0.3.1".to_string()),
223            ),
224            ..Default::default()
225        };
226
227        let result = Box::new(result) as Box<dyn APIResult>;
228
229        Ok((result, config_bytes, rt.clone()))
230    }
231
232    fn build_new_config(
233        &self,
234        name: String,
235        cni_version: String,
236        orig: &NetworkConfig,
237        prev_result: Option<Box<dyn APIResult>>,
238        _rt: &RuntimeConf,
239    ) -> Result<NetworkConfig, String> {
240        debug!("Building new network config for {}", name);
241
242        let mut json_object = match json::parse(String::from_utf8_lossy(&orig.bytes).as_ref()) {
243            Ok(obj) => obj,
244            Err(e) => return Err(format!("Failed to parse network config: {}", e)),
245        };
246
247        // Insert required fields
248        if let Err(e) = json_object.insert("name", name) {
249            return Err(format!("Failed to insert name: {}", e));
250        }
251
252        if let Err(e) = json_object.insert("cniVersion", cni_version) {
253            return Err(format!("Failed to insert cniVersion: {}", e));
254        }
255
256        // Insert previous result (if provided)
257        if let Some(prev_result) = prev_result {
258            let prev_json = prev_result.get_json();
259            debug!("Adding prevResult to config: {}", prev_json.dump());
260            if let Err(e) = json_object.insert("prevResult", prev_json) {
261                return Err(format!("Failed to insert prevResult: {}", e));
262            }
263        }
264
265        let new_bytes = json_object.dump().as_bytes().to_vec();
266        trace!("Built new config: {}", String::from_utf8_lossy(&new_bytes));
267
268        // Create new config with updated bytes
269        let mut new_conf = orig.clone();
270        new_conf.bytes = new_bytes;
271
272        Ok(new_conf)
273    }
274}
275
276impl CNI for CNIConfig {
277    fn add_network_list(
278        &self,
279        net: NetworkConfigList,
280        rt: RuntimeConf,
281    ) -> ResultCNI<Box<dyn APIResult>> {
282        info!("Adding network list: {}", net.name);
283
284        // Validate the plugin chain
285        self.validate_network_list(net.clone())?;
286
287        let mut prev_result: Option<Box<dyn APIResult>> = None;
288
289        // Apply each plugin in the chain
290        for (i, plugin) in net.plugins.iter().enumerate() {
291            debug!(
292                "Executing plugin {}/{}: {}",
293                i + 1,
294                net.plugins.len(),
295                plugin.network._type
296            );
297
298            // Add network with current plugin
299            let result = self.add_network(
300                net.name.clone(),
301                net.cni_version.clone(),
302                plugin.clone(),
303                prev_result,
304                rt.clone(),
305            )?;
306
307            // Update previous result for next plugin
308            prev_result = Some(result);
309        }
310
311        // Cache the final result
312        if let Some(result) = &prev_result {
313            if let Err(e) = self.cache_network_result(&net.name, &rt, result.as_ref()) {
314                warn!("Failed to cache network result: {}", e);
315            }
316        }
317
318        info!("Successfully added network list: {}", net.name);
319
320        // Return the final result
321        Ok(prev_result.unwrap_or_else(|| Box::<result100::Result>::default()))
322    }
323
324    fn check_network_list(&self, net: NetworkConfigList, rt: RuntimeConf) -> ResultCNI<()> {
325        info!("Checking network list: {}", net.name);
326
327        // Skip check if disabled
328        if net.disable_check {
329            debug!("Network check is disabled for {}", net.name);
330            return Ok(());
331        }
332
333        // Get cached result from previous add operation
334        let (prev_result, _, _) = match self.read_cached_network(&net.name, &rt) {
335            Ok(data) => data,
336            Err(e) => {
337                warn!("No cached result found for network {}: {}", net.name, e);
338                (
339                    Box::<result100::Result>::default() as Box<dyn APIResult>,
340                    Vec::new(),
341                    rt.clone(),
342                )
343            }
344        };
345
346        // Check each plugin in the chain
347        for (i, plugin) in net.plugins.iter().enumerate() {
348            debug!(
349                "Checking plugin {}/{}: {}",
350                i + 1,
351                net.plugins.len(),
352                plugin.network._type
353            );
354
355            // Check network with current plugin
356            self.check_network(
357                net.name.clone(),
358                net.cni_version.clone(),
359                Some(prev_result.clone_box()),
360                plugin.clone(),
361                rt.clone(),
362            )?;
363        }
364
365        info!("Network list check passed: {}", net.name);
366        Ok(())
367    }
368
369    fn delete_network_list(&self, net: NetworkConfigList, rt: RuntimeConf) -> ResultCNI<()> {
370        info!("Deleting network list: {}", net.name);
371
372        // Delete in reverse order
373        for (i, plugin) in net.plugins.iter().enumerate().rev() {
374            debug!(
375                "Deleting plugin {}/{}: {}",
376                net.plugins.len() - i,
377                net.plugins.len(),
378                plugin.network._type
379            );
380
381            // Delete network with current plugin
382            if let Err(e) = self.delete_network(
383                net.name.clone(),
384                net.cni_version.clone(),
385                plugin.clone(),
386                rt.clone(),
387            ) {
388                warn!("Error deleting plugin {}: {}", plugin.network._type, e);
389                // Continue with next plugin even if one fails
390            }
391        }
392
393        // Clean up cached data
394        let cache_dir = self.get_cache_dir(&net.name);
395        let key = rt.get_cache_key();
396        let result_path = cache_dir.join(format!("{}.result", key));
397        let config_path = cache_dir.join(format!("{}.config", key));
398
399        if result_path.exists() {
400            if let Err(e) = fs::remove_file(&result_path) {
401                warn!("Failed to remove cached result: {}", e);
402            }
403        }
404
405        if config_path.exists() {
406            if let Err(e) = fs::remove_file(&config_path) {
407                warn!("Failed to remove cached config: {}", e);
408            }
409        }
410
411        info!("Successfully deleted network list: {}", net.name);
412        Ok(())
413    }
414
415    fn get_network_list_cached_result(
416        &self,
417        net: NetworkConfigList,
418        rt: RuntimeConf,
419    ) -> ResultCNI<Box<dyn APIResult>> {
420        debug!("Getting cached result for network list: {}", net.name);
421
422        match self.read_cached_network(&net.name, &rt) {
423            Ok((result, _, _)) => {
424                debug!("Found cached result for network {}", net.name);
425                Ok(result)
426            }
427            Err(e) => {
428                let err_msg = format!("No cached result for network {}: {}", net.name, e);
429                error!("{}", err_msg);
430                Err(Box::new(CNIError::NotFound(net.name, err_msg)))
431            }
432        }
433    }
434
435    fn add_network(
436        &self,
437        name: String,
438        cni_version: String,
439        net: NetworkConfig,
440        prev_result: Option<Box<dyn APIResult>>,
441        rt: RuntimeConf,
442    ) -> ResultCNI<Box<dyn APIResult>> {
443        debug!("Adding network {} with plugin {}", name, net.network._type);
444
445        // Find plugin path
446        let plugin_path = self
447            .exec
448            .find_in_path(net.network._type.clone(), self.path.clone())?;
449
450        // Setup environment
451        let environ = ExecArgs {
452            command: "ADD".to_string(),
453            containerd_id: rt.container_id.clone(),
454            netns: rt.net_ns.clone(),
455            plugin_args: rt.args.clone(),
456            plugin_args_str: rt
457                .args
458                .iter()
459                .map(|arg| format!("{}={}", arg[0], arg[1]))
460                .collect::<Vec<_>>()
461                .join(";"),
462            ifname: rt.if_name.clone(),
463            path: self.path[0].clone(),
464        };
465
466        // Build new config with name, version and prevResult
467        let new_conf = match self.build_new_config(
468            name.clone(),
469            cni_version.clone(),
470            &net,
471            prev_result,
472            &rt,
473        ) {
474            Ok(conf) => conf,
475            Err(e) => return Err(Box::new(CNIError::Config(e))),
476        };
477
478        // Cache network config
479        if let Err(e) = self.cache_network_config(&name, &rt, &new_conf.bytes) {
480            warn!("Failed to cache network config: {}", e);
481        }
482
483        // Execute plugin
484        let result_bytes =
485            self.exec
486                .exec_plugins(plugin_path, &new_conf.bytes, environ.to_env())?;
487
488        // Directly deserialize the result JSON into the result structure
489        let mut result: result100::Result = match serde_json::from_slice(&result_bytes) {
490            Ok(r) => r,
491            Err(e) => {
492                // If direct deserialization fails, create a default result with minimal information
493                debug!(
494                    "Failed to directly deserialize result: {}, creating minimal result",
495                    e
496                );
497                result100::Result {
498                    cni_version: Some(cni_version.clone()),
499                    ..Default::default()
500                }
501            }
502        };
503
504        // Ensure CNI version is set
505        if result.cni_version.is_none() {
506            result.cni_version = Some(cni_version);
507        }
508
509        debug!("Successfully added network {}", name);
510        Ok(Box::new(result))
511    }
512
513    fn check_network(
514        &self,
515        name: String,
516        cni_version: String,
517        prev_result: Option<Box<dyn APIResult>>,
518        net: NetworkConfig,
519        rt: RuntimeConf,
520    ) -> ResultCNI<()> {
521        debug!(
522            "Checking network {} with plugin {}",
523            name, net.network._type
524        );
525
526        // Find plugin in path
527        let plugin_path = self
528            .exec
529            .find_in_path(net.network._type.clone(), self.path.clone())?;
530
531        // Set up environment
532        let environ = ExecArgs {
533            command: "CHECK".to_string(),
534            containerd_id: rt.container_id.clone(),
535            netns: rt.net_ns.clone(),
536            plugin_args: rt.args.clone(),
537            plugin_args_str: rt
538                .args
539                .iter()
540                .map(|arg| format!("{}={}", arg[0], arg[1]))
541                .collect::<Vec<_>>()
542                .join(";"),
543            ifname: rt.if_name.clone(),
544            path: self.path[0].clone(),
545        };
546
547        // Build new config with name, version and prevResult
548        let new_conf =
549            match self.build_new_config(name.clone(), cni_version, &net, prev_result, &rt) {
550                Ok(conf) => conf,
551                Err(e) => return Err(Box::new(CNIError::Config(e))),
552            };
553
554        // Execute plugin
555        self.exec
556            .exec_plugins(plugin_path, &new_conf.bytes, environ.to_env())?;
557
558        debug!("Network check passed for {}", name);
559        Ok(())
560    }
561
562    fn delete_network(
563        &self,
564        name: String,
565        cni_version: String,
566        net: NetworkConfig,
567        rt: RuntimeConf,
568    ) -> ResultCNI<()> {
569        debug!(
570            "Deleting network {} with plugin {}",
571            name, net.network._type
572        );
573
574        // Find plugin in path
575        let plugin_path = self
576            .exec
577            .find_in_path(net.network._type.clone(), self.path.clone())?;
578
579        // Set up environment
580        let environ = ExecArgs {
581            command: "DEL".to_string(),
582            containerd_id: rt.container_id.clone(),
583            netns: rt.net_ns.clone(),
584            plugin_args: rt.args.clone(),
585            plugin_args_str: rt
586                .args
587                .iter()
588                .map(|arg| format!("{}={}", arg[0], arg[1]))
589                .collect::<Vec<_>>()
590                .join(";"),
591            ifname: rt.if_name.clone(),
592            path: self.path[0].clone(),
593        };
594
595        // Build new config with name and version
596        let new_conf = match self.build_new_config(name.clone(), cni_version, &net, None, &rt) {
597            Ok(conf) => conf,
598            Err(e) => return Err(Box::new(CNIError::Config(e))),
599        };
600
601        // Execute plugin
602        self.exec
603            .exec_plugins(plugin_path, &new_conf.bytes, environ.to_env())?;
604
605        debug!("Successfully deleted network {}", name);
606        Ok(())
607    }
608
609    fn get_network_cached_result(
610        &self,
611        net: NetworkConfig,
612        rt: RuntimeConf,
613    ) -> ResultCNI<Box<dyn APIResult>> {
614        debug!("Getting cached result for network {}", net.network.name);
615
616        match self.read_cached_network(&net.network.name, &rt) {
617            Ok((result, _, _)) => {
618                debug!("Found cached result for network {}", net.network.name);
619                Ok(result)
620            }
621            Err(e) => {
622                let err_msg = format!("No cached result for network {}: {}", net.network.name, e);
623                error!("{}", err_msg);
624                Err(Box::new(CNIError::NotFound(net.network.name, err_msg)))
625            }
626        }
627    }
628
629    fn get_network_cached_config(
630        &self,
631        net: NetworkConfig,
632        rt: RuntimeConf,
633    ) -> ResultCNI<(Vec<u8>, RuntimeConf)> {
634        debug!("Getting cached config for network {}", net.network.name);
635
636        match self.read_cached_network(&net.network.name, &rt) {
637            Ok((_, config_bytes, cached_rt)) => {
638                debug!("Found cached config for network {}", net.network.name);
639                Ok((config_bytes, cached_rt))
640            }
641            Err(e) => {
642                let err_msg = format!("No cached config for network {}: {}", net.network.name, e);
643                error!("{}", err_msg);
644                Err(Box::new(CNIError::NotFound(net.network.name, err_msg)))
645            }
646        }
647    }
648
649    fn validate_network_list(&self, net: NetworkConfigList) -> ResultCNI<Vec<String>> {
650        debug!("Validating network list: {}", net.name);
651
652        // Check basic requirements
653        if let Err(e) = net.validate() {
654            return Err(Box::new(CNIError::Config(e)));
655        }
656
657        // Validate each plugin
658        let mut plugin_types = Vec::new();
659        for plugin in &net.plugins {
660            let types = self.validate_network(plugin.clone())?;
661            plugin_types.extend(types);
662        }
663
664        debug!("Network list validation passed for {}", net.name);
665        Ok(plugin_types)
666    }
667
668    fn validate_network(&self, net: NetworkConfig) -> ResultCNI<Vec<String>> {
669        debug!("Validating network: {}", net.network.name);
670
671        // Check basic requirements
672        if net.network._type.is_empty() {
673            return Err(Box::new(CNIError::Config(
674                "Plugin type cannot be empty".to_string(),
675            )));
676        }
677
678        // Find plugin in path
679        let plugin_path = self
680            .exec
681            .find_in_path(net.network._type.clone(), self.path.clone())?;
682
683        // Set up environment for VERSION command
684        let environ = ExecArgs {
685            command: "VERSION".to_string(),
686            containerd_id: "".to_string(),
687            netns: "".to_string(),
688            plugin_args: Vec::new(),
689            plugin_args_str: "".to_string(),
690            ifname: "".to_string(),
691            path: self.path[0].clone(),
692        };
693
694        // Execute plugin with VERSION command
695        match self.exec.exec_plugins(plugin_path, &[], environ.to_env()) {
696            Ok(version_bytes) => {
697                // Parse version info
698                match serde_json::from_slice::<serde_json::Value>(&version_bytes) {
699                    Ok(version_info) => {
700                        if let Some(supported_versions) = version_info.get("supportedVersions") {
701                            if let Some(versions_array) = supported_versions.as_array() {
702                                let versions: Vec<String> = versions_array
703                                    .iter()
704                                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
705                                    .collect();
706
707                                debug!(
708                                    "Plugin {} supports versions: {:?}",
709                                    net.network._type, versions
710                                );
711                                return Ok(versions);
712                            }
713                        }
714
715                        warn!(
716                            "Plugin {} did not return supported versions",
717                            net.network._type
718                        );
719                        Ok(vec![])
720                    }
721                    Err(e) => {
722                        warn!(
723                            "Failed to parse version info from plugin {}: {}",
724                            net.network._type, e
725                        );
726                        Ok(vec![])
727                    }
728                }
729            }
730            Err(e) => {
731                warn!(
732                    "Failed to get version info from plugin {}: {}",
733                    net.network._type, e
734                );
735                Ok(vec![])
736            }
737        }
738    }
739}