Skip to main content

laminar_core/xdp/
config.rs

1//! XDP configuration.
2
3use std::path::PathBuf;
4
5use super::XdpAttachMode;
6
7/// Configuration for XDP network optimization.
8#[derive(Debug, Clone)]
9pub struct XdpConfig {
10    /// Enable XDP (requires root or `CAP_NET_ADMIN`).
11    pub enabled: bool,
12
13    /// Path to compiled BPF object file.
14    pub bpf_object_path: PathBuf,
15
16    /// Network interface to attach to (e.g., "eth0").
17    pub interface: String,
18
19    /// UDP port for filtering.
20    pub port: u16,
21
22    /// XDP attach mode.
23    pub attach_mode: XdpAttachMode,
24
25    /// Queue size per CPU for XDP redirect.
26    pub cpu_queue_size: u32,
27
28    /// Enable XDP statistics collection.
29    pub collect_stats: bool,
30
31    /// Fallback gracefully if XDP unavailable.
32    pub fallback_on_error: bool,
33}
34
35impl Default for XdpConfig {
36    fn default() -> Self {
37        Self {
38            enabled: false,
39            bpf_object_path: PathBuf::from("/usr/share/laminardb/laminar_xdp.o"),
40            interface: "eth0".to_string(),
41            port: 9999,
42            attach_mode: XdpAttachMode::Auto,
43            cpu_queue_size: 2048,
44            collect_stats: true,
45            fallback_on_error: true,
46        }
47    }
48}
49
50impl XdpConfig {
51    /// Creates a new configuration builder.
52    #[must_use]
53    pub fn builder() -> XdpConfigBuilder {
54        XdpConfigBuilder::default()
55    }
56
57    /// Create configuration with automatic detection.
58    ///
59    /// Detects system capabilities and generates an optimal configuration:
60    /// - Enables XDP only on Linux 4.8+ with the xdp feature
61    /// - Prefers native mode on Linux 5.3+, falls back to generic mode
62    /// - Sizes CPU queues based on available memory
63    ///
64    /// # Note
65    ///
66    /// XDP requires additional setup (BPF object file, `CAP_NET_ADMIN` capability)
67    /// that cannot be auto-detected. This method sets `enabled: false` by default;
68    /// call `.enabled(true)` on the builder to enable XDP after ensuring
69    /// prerequisites are met.
70    ///
71    /// # Example
72    ///
73    /// ```rust,ignore
74    /// use laminar_core::xdp::XdpConfig;
75    ///
76    /// let config = XdpConfig::auto();
77    /// println!("XDP available: {}", config.xdp_available());
78    /// ```
79    #[must_use]
80    pub fn auto() -> Self {
81        let caps = crate::detect::SystemCapabilities::detect();
82
83        let attach_mode = if caps.xdp.native_supported {
84            super::XdpAttachMode::Native
85        } else if caps.xdp.generic_supported {
86            super::XdpAttachMode::Generic
87        } else {
88            super::XdpAttachMode::Auto
89        };
90
91        // Scale queue size based on memory
92        let cpu_queue_size = if caps.memory.total_memory > 32 * 1024 * 1024 * 1024 {
93            4096
94        } else if caps.memory.total_memory > 8 * 1024 * 1024 * 1024 {
95            2048
96        } else {
97            1024
98        };
99
100        Self {
101            // XDP is disabled by default - requires manual setup
102            enabled: false,
103            bpf_object_path: std::path::PathBuf::from("/usr/share/laminardb/laminar_xdp.o"),
104            interface: "eth0".to_string(),
105            port: 9999,
106            attach_mode,
107            cpu_queue_size,
108            collect_stats: true,
109            fallback_on_error: true,
110        }
111    }
112
113    /// Check if XDP is available on this system.
114    ///
115    /// Returns `true` if the kernel supports XDP and the feature is enabled.
116    #[must_use]
117    pub fn xdp_available() -> bool {
118        let caps = crate::detect::SystemCapabilities::detect();
119        caps.xdp.is_usable()
120    }
121
122    /// Validates the configuration.
123    ///
124    /// # Errors
125    ///
126    /// Returns an error if the configuration is invalid.
127    pub fn validate(&self) -> Result<(), super::XdpError> {
128        if self.enabled {
129            if self.interface.is_empty() {
130                return Err(super::XdpError::InvalidConfig(
131                    "interface cannot be empty".to_string(),
132                ));
133            }
134            if self.port == 0 {
135                return Err(super::XdpError::InvalidConfig(
136                    "port must be non-zero".to_string(),
137                ));
138            }
139            if self.cpu_queue_size == 0 {
140                return Err(super::XdpError::InvalidConfig(
141                    "cpu_queue_size must be non-zero".to_string(),
142                ));
143            }
144        }
145        Ok(())
146    }
147}
148
149/// Builder for [`XdpConfig`].
150#[derive(Debug, Default)]
151pub struct XdpConfigBuilder {
152    enabled: Option<bool>,
153    bpf_object_path: Option<PathBuf>,
154    interface: Option<String>,
155    port: Option<u16>,
156    attach_mode: Option<XdpAttachMode>,
157    cpu_queue_size: Option<u32>,
158    collect_stats: Option<bool>,
159    fallback_on_error: Option<bool>,
160}
161
162impl XdpConfigBuilder {
163    /// Enables or disables XDP.
164    #[must_use]
165    pub fn enabled(mut self, enabled: bool) -> Self {
166        self.enabled = Some(enabled);
167        self
168    }
169
170    /// Sets the path to the BPF object file.
171    #[must_use]
172    pub fn bpf_object_path(mut self, path: impl Into<PathBuf>) -> Self {
173        self.bpf_object_path = Some(path.into());
174        self
175    }
176
177    /// Sets the network interface.
178    #[must_use]
179    pub fn interface(mut self, interface: impl Into<String>) -> Self {
180        self.interface = Some(interface.into());
181        self
182    }
183
184    /// Sets the UDP port for filtering.
185    #[must_use]
186    pub fn port(mut self, port: u16) -> Self {
187        self.port = Some(port);
188        self
189    }
190
191    /// Sets the XDP attach mode.
192    #[must_use]
193    pub fn attach_mode(mut self, mode: XdpAttachMode) -> Self {
194        self.attach_mode = Some(mode);
195        self
196    }
197
198    /// Sets the CPU queue size for XDP redirect.
199    #[must_use]
200    pub fn cpu_queue_size(mut self, size: u32) -> Self {
201        self.cpu_queue_size = Some(size);
202        self
203    }
204
205    /// Enables or disables statistics collection.
206    #[must_use]
207    pub fn collect_stats(mut self, enabled: bool) -> Self {
208        self.collect_stats = Some(enabled);
209        self
210    }
211
212    /// Enables or disables fallback on error.
213    #[must_use]
214    pub fn fallback_on_error(mut self, enabled: bool) -> Self {
215        self.fallback_on_error = Some(enabled);
216        self
217    }
218
219    /// Builds the configuration.
220    ///
221    /// # Errors
222    ///
223    /// Returns an error if the configuration is invalid.
224    pub fn build(self) -> Result<XdpConfig, super::XdpError> {
225        let config = XdpConfig {
226            enabled: self.enabled.unwrap_or(false),
227            bpf_object_path: self
228                .bpf_object_path
229                .unwrap_or_else(|| PathBuf::from("/usr/share/laminardb/laminar_xdp.o")),
230            interface: self.interface.unwrap_or_else(|| "eth0".to_string()),
231            port: self.port.unwrap_or(9999),
232            attach_mode: self.attach_mode.unwrap_or_default(),
233            cpu_queue_size: self.cpu_queue_size.unwrap_or(2048),
234            collect_stats: self.collect_stats.unwrap_or(true),
235            fallback_on_error: self.fallback_on_error.unwrap_or(true),
236        };
237        config.validate()?;
238        Ok(config)
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_default_config() {
248        let config = XdpConfig::default();
249        assert!(!config.enabled);
250        assert_eq!(config.port, 9999);
251        assert_eq!(config.interface, "eth0");
252        assert_eq!(config.attach_mode, XdpAttachMode::Auto);
253    }
254
255    #[test]
256    fn test_builder() {
257        let config = XdpConfig::builder()
258            .enabled(true)
259            .interface("lo")
260            .port(8080)
261            .cpu_queue_size(4096)
262            .build()
263            .unwrap();
264
265        assert!(config.enabled);
266        assert_eq!(config.interface, "lo");
267        assert_eq!(config.port, 8080);
268        assert_eq!(config.cpu_queue_size, 4096);
269    }
270
271    #[test]
272    fn test_validation_empty_interface() {
273        let result = XdpConfig::builder().enabled(true).interface("").build();
274        assert!(result.is_err());
275    }
276
277    #[test]
278    fn test_validation_zero_port() {
279        let result = XdpConfig::builder().enabled(true).port(0).build();
280        assert!(result.is_err());
281    }
282
283    #[test]
284    fn test_disabled_skips_validation() {
285        // Empty interface is OK when disabled
286        let config = XdpConfig::builder()
287            .enabled(false)
288            .interface("")
289            .build()
290            .unwrap();
291        assert!(!config.enabled);
292    }
293
294    #[test]
295    fn test_xdp_config_auto() {
296        let config = XdpConfig::auto();
297
298        // Auto config should have XDP disabled by default
299        // (requires manual setup)
300        assert!(!config.enabled);
301
302        // Should have valid defaults
303        assert_eq!(config.interface, "eth0");
304        assert!(config.fallback_on_error);
305        assert!(config.collect_stats);
306        assert!(config.cpu_queue_size >= 1024);
307
308        // Validation should pass when disabled
309        assert!(config.validate().is_ok());
310    }
311
312    #[test]
313    fn test_xdp_available() {
314        // Should not panic on any platform
315        let available = XdpConfig::xdp_available();
316
317        // On non-Linux or without xdp feature, should be false
318        #[cfg(not(all(target_os = "linux", feature = "xdp")))]
319        {
320            assert!(!available);
321        }
322
323        // Suppress warning when feature is enabled
324        let _ = available;
325    }
326}