1use log::{debug, error, trace, warn};
3use serde::{Deserialize, Serialize};
4
5use crate::libcni::result::ResultCNI;
6use crate::libcni::CNIError;
7use std::path::Path;
8use std::process::{Command, Stdio};
9use std::{collections::HashMap, io::Write};
10
11#[derive(Default, Serialize, Deserialize, Debug)]
12pub struct ExecArgs {
13 pub(crate) command: String,
14 pub(crate) containerd_id: String,
15 pub(crate) netns: String,
16 pub(crate) plugin_args: Vec<[String; 2]>,
17 pub(crate) plugin_args_str: String,
18 pub(crate) ifname: String,
19 pub(crate) path: String,
20}
21
22impl ExecArgs {
23 pub fn to_env(&self) -> Vec<String> {
24 debug!("Preparing environment for CNI execution , args :{:?}", self);
25 let mut result_env = Vec::default();
26
27 std::env::set_var("CNI_COMMAND", self.command.clone());
29 std::env::set_var("CNI_CONTAINERID", self.containerd_id.clone());
30 std::env::set_var("CNI_NETNS", self.netns.clone());
31 std::env::set_var("CNI_ARGS", self.plugin_args_str.clone());
32 std::env::set_var("CNI_IFNAME", self.ifname.clone());
33 std::env::set_var("CNI_PATH", self.path.clone());
34
35 for (k, v) in std::env::vars() {
37 result_env.push(format!("{}={}", k, v));
38 }
39
40 trace!(
41 "CNI environment prepared with {} variables",
42 result_env.len()
43 );
44 result_env
45 }
46}
47
48pub trait Exec {
49 fn exec_plugins(
50 &self,
51 plugin_path: String,
52 stdin_data: &[u8],
53 environ: Vec<String>,
54 ) -> super::ResultCNI<Vec<u8>>;
55
56 fn find_in_path(&self, plugin: String, paths: Vec<String>) -> ResultCNI<String>;
57
58 fn decode(&self, data: &[u8]) -> ResultCNI<()>;
59}
60
61#[derive(Default)]
62pub struct RawExec {}
63
64impl Exec for RawExec {
65 fn exec_plugins(
66 &self,
67 plugin_path: String,
68 stdin_data: &[u8],
69 environ: Vec<String>,
70 ) -> ResultCNI<Vec<u8>> {
71 debug!("Executing CNI plugin: {}", plugin_path);
72 trace!("CNI stdin data: {}", String::from_utf8_lossy(stdin_data));
73
74 let envs: HashMap<String, String> = environ
76 .iter()
77 .filter_map(|env_var| {
78 let parts: Vec<&str> = env_var.splitn(2, '=').collect();
79 if parts.len() == 2 {
80 Some((parts[0].to_string(), parts[1].to_string()))
81 } else {
82 None
83 }
84 })
85 .collect();
86 if !Path::new(&plugin_path).exists() {
89 let err_msg = format!("CNI plugin not found: {}", plugin_path);
90 return Err(Box::new(CNIError::ExecuteError(err_msg)));
91 }
92
93 let mut plugin_cmd = match Command::new(&plugin_path)
95 .stdin(Stdio::piped())
96 .stdout(Stdio::piped())
97 .stderr(Stdio::piped())
98 .envs(envs)
99 .spawn()
100 {
101 Ok(cmd) => cmd,
102 Err(e) => {
103 let err_msg = format!("Failed to start CNI plugin {}: {}", plugin_path, e);
104 return Err(Box::new(CNIError::ExecuteError(err_msg)));
105 }
106 };
107 debug!("cni stdin is: {:?}", String::from_utf8_lossy(stdin_data));
108 if let Some(mut stdin) = plugin_cmd.stdin.take() {
110 if let Err(e) = stdin.write_all(stdin_data) {
111 let err_msg = format!("Failed to write to plugin stdin: {}", e);
112 return Err(Box::new(CNIError::ExecuteError(err_msg)));
113 }
114 drop(stdin);
116 }
117
118 let output = match plugin_cmd.wait_with_output() {
120 Ok(output) => output,
121 Err(e) => {
122 let err_msg = format!("Failed to get plugin output: {}", e);
123 return Err(Box::new(CNIError::ExecuteError(err_msg)));
124 }
125 };
126
127 if !output.stderr.is_empty() {
129 let stderr = String::from_utf8_lossy(&output.stderr);
130 warn!("CNI plugin stderr: {}", stderr);
131 }
132
133 if let Ok(json_value) = serde_json::from_slice::<serde_json::Value>(&output.stdout) {
135 if let Some(error_code) = json_value.get("code") {
136 if error_code.as_u64().is_some() {
137 let msg = String::from_utf8_lossy(&output.stdout).to_string();
138 return Err(Box::new(CNIError::ExecuteError(msg)));
139 }
140 }
141 }
142
143 debug!("CNI plugin execution successful");
144 Ok(output.stdout)
145 }
146
147 fn find_in_path(&self, plugin: String, paths: Vec<String>) -> ResultCNI<String> {
148 trace!("Finding CNI plugin {} in paths", plugin);
149
150 if paths.is_empty() {
151 let err_msg = format!("No plugin paths provided for {}", plugin);
152 error!("{}", err_msg);
153 return Err(Box::new(CNIError::Config(err_msg)));
154 }
155
156 for path in &paths {
157 let full_path = format!("{}/{}", path, plugin);
158 let plugin_path = Path::new(&full_path);
159
160 if plugin_path.exists() {
161 debug!("Found CNI plugin at: {}", full_path);
162 return Ok(full_path);
163 }
164 }
165
166 let err_msg = format!("CNI plugin {} not found in paths {:?}", plugin, paths);
167 error!("{}", err_msg);
168 Err(Box::new(CNIError::NotFound(plugin, paths.join(":"))))
169 }
170
171 fn decode(&self, data: &[u8]) -> ResultCNI<()> {
172 trace!("Decoding CNI data: {} bytes", data.len());
173
174 match serde_json::from_slice::<serde_json::Value>(data) {
176 Ok(_) => {
177 trace!("CNI data successfully decoded");
178 Ok(())
179 }
180 Err(e) => {
181 let err_msg = format!("Failed to decode CNI data: {}", e);
182 error!("{}", err_msg);
183 Err(Box::new(CNIError::VarDecode(
184 "Invalid JSON format".to_string(),
185 )))
186 }
187 }
188 }
189}