bpfd_api/
config.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright Authors of bpfd
3
4use std::{collections::HashMap, str::FromStr};
5
6use aya::programs::XdpFlags;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::util::directories::*;
11
12#[derive(Debug, Deserialize, Default, Clone)]
13pub struct Config {
14    pub interfaces: Option<HashMap<String, InterfaceConfig>>,
15    #[serde(default)]
16    pub grpc: Grpc,
17    pub signing: Option<SigningConfig>,
18}
19
20#[derive(Debug, Deserialize, Clone)]
21pub struct SigningConfig {
22    pub allow_unsigned: bool,
23}
24
25impl Default for SigningConfig {
26    fn default() -> Self {
27        Self {
28            // Allow unsigned programs by default
29            allow_unsigned: true,
30        }
31    }
32}
33
34#[derive(Debug, Error)]
35pub enum ConfigError {
36    #[error("Error parsing config file: {0}")]
37    ParseError(#[from] toml::de::Error),
38}
39
40impl FromStr for Config {
41    type Err = ConfigError;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        toml::from_str(s).map_err(ConfigError::ParseError)
45    }
46}
47
48#[derive(Debug, Deserialize, Copy, Clone)]
49pub struct InterfaceConfig {
50    pub xdp_mode: XdpMode,
51}
52
53#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
54#[serde(rename_all = "lowercase")]
55pub enum XdpMode {
56    Skb,
57    Drv,
58    Hw,
59}
60
61impl XdpMode {
62    pub fn as_flags(&self) -> XdpFlags {
63        match self {
64            XdpMode::Skb => XdpFlags::SKB_MODE,
65            XdpMode::Drv => XdpFlags::DRV_MODE,
66            XdpMode::Hw => XdpFlags::HW_MODE,
67        }
68    }
69}
70
71impl ToString for XdpMode {
72    fn to_string(&self) -> String {
73        match self {
74            XdpMode::Skb => "skb".to_string(),
75            XdpMode::Drv => "drv".to_string(),
76            XdpMode::Hw => "hw".to_string(),
77        }
78    }
79}
80
81#[derive(Debug, Deserialize, Clone)]
82pub struct Grpc {
83    #[serde(default)]
84    pub endpoints: Vec<Endpoint>,
85}
86
87impl Default for Grpc {
88    fn default() -> Self {
89        Self {
90            endpoints: vec![Endpoint::default()],
91        }
92    }
93}
94
95#[derive(Debug, Deserialize, Clone)]
96#[serde(tag = "type", rename_all = "lowercase")]
97pub enum Endpoint {
98    Unix {
99        #[serde(default = "default_unix")]
100        path: String,
101        #[serde(default = "default_enabled")]
102        enabled: bool,
103    },
104}
105
106impl Default for Endpoint {
107    fn default() -> Self {
108        Endpoint::Unix {
109            path: default_unix(),
110            enabled: default_enabled(),
111        }
112    }
113}
114
115fn default_unix() -> String {
116    RTPATH_BPFD_SOCKET.to_string()
117}
118
119fn default_enabled() -> bool {
120    true
121}
122
123#[cfg(test)]
124mod test {
125    use super::*;
126
127    #[test]
128    fn test_config_from_invalid_string() {
129        assert!(Config::from_str("i am a teapot").is_err());
130    }
131
132    #[test]
133    fn test_config_single_iface() {
134        let input = r#"
135        [interfaces]
136          [interfaces.eth0]
137          xdp_mode = "drv"
138        "#;
139        let config: Config = toml::from_str(input).expect("error parsing toml input");
140        match config.interfaces {
141            Some(i) => {
142                assert!(i.contains_key("eth0"));
143                assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv)
144            }
145            None => panic!("expected interfaces to be present"),
146        }
147    }
148
149    #[test]
150    fn test_config_multiple_iface() {
151        let input = r#"
152        [interfaces]
153          [interfaces.eth0]
154          xdp_mode = "drv"
155          [interfaces.eth1]
156          xdp_mode = "hw"
157          [interfaces.eth2]
158          xdp_mode = "skb"
159        "#;
160        let config: Config = toml::from_str(input).expect("error parsing toml input");
161        match config.interfaces {
162            Some(i) => {
163                assert_eq!(i.len(), 3);
164                assert!(i.contains_key("eth0"));
165                assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv);
166                assert!(i.contains_key("eth1"));
167                assert_eq!(i.get("eth1").unwrap().xdp_mode, XdpMode::Hw);
168                assert!(i.contains_key("eth2"));
169                assert_eq!(i.get("eth2").unwrap().xdp_mode, XdpMode::Skb);
170            }
171            None => panic!("expected interfaces to be present"),
172        }
173    }
174
175    #[test]
176    fn test_config_endpoint_default() {
177        let input = r#"
178        "#;
179
180        let config: Config = toml::from_str(input).expect("error parsing toml input");
181        let endpoints = config.grpc.endpoints;
182        assert_eq!(endpoints.len(), 1);
183
184        match endpoints.get(0).unwrap() {
185            Endpoint::Unix { path, enabled } => {
186                assert_eq!(path, &default_unix());
187                assert_eq!(enabled, &true);
188            }
189        }
190    }
191
192    #[test]
193    fn test_config_endpoint_unix_default() {
194        let input = r#"
195            [[grpc.endpoints]]
196            type = "unix"
197            "#;
198
199        let config: Config = toml::from_str(input).expect("error parsing toml input");
200        let endpoints = config.grpc.endpoints;
201        assert_eq!(endpoints.len(), 1);
202
203        match endpoints.get(0).unwrap() {
204            Endpoint::Unix { path, enabled } => {
205                assert_eq!(path, &default_unix());
206                assert!(enabled);
207            }
208        }
209    }
210
211    #[test]
212    fn test_config_endpoint_unix() {
213        let input = r#"
214            [[grpc.endpoints]]
215            type = "unix"
216            path = "/tmp/socket"
217            "#;
218
219        let config: Config = toml::from_str(input).expect("error parsing toml input");
220        let endpoints = config.grpc.endpoints;
221        assert_eq!(endpoints.len(), 1);
222
223        match endpoints.get(0).unwrap() {
224            Endpoint::Unix { path, enabled } => {
225                assert_eq!(path, "/tmp/socket");
226                assert!(enabled);
227            }
228        }
229    }
230
231    #[test]
232    fn test_config_endpoint() {
233        let input = r#"
234            [[grpc.endpoints]]
235            type = "unix"
236            enabled = true
237            path = "/run/bpfd/bpfd.sock"
238
239            [[grpc.endpoints]]
240            type = "unix"
241            enabled = true
242            path = "/run/bpfd/bpfd2.sock"
243        "#;
244
245        let expected_endpoints: Vec<Endpoint> = vec![
246            Endpoint::Unix {
247                path: String::from("/run/bpfd/bpfd.sock"),
248                enabled: true,
249            },
250            Endpoint::Unix {
251                path: String::from("/run/bpfd/bpfd2.sock"),
252                enabled: true,
253            },
254        ];
255
256        let config: Config = toml::from_str(input).expect("error parsing toml input");
257        let endpoints = config.grpc.endpoints;
258        assert_eq!(endpoints.len(), 2);
259
260        for (i, endpoint) in endpoints.iter().enumerate() {
261            match endpoint {
262                Endpoint::Unix { path, enabled } => {
263                    let Endpoint::Unix {
264                        path: expected_path,
265                        enabled: expected_enabled,
266                    } = expected_endpoints.get(i).unwrap();
267                    assert_eq!(path, expected_path);
268                    assert_eq!(enabled, expected_enabled);
269                }
270            }
271        }
272    }
273}