Skip to main content

qualia_core_db/net/
ebpf_firewall.rs

1//! Allocation Firewall (eBPF) Implementation
2//!
3//! This module provides kernel-level socket bypassing and packet filtering using eBPF programs.
4//! Designed for high-performance networking with zero-copy operations and advanced security.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// eBPF Firewall Manager
10pub struct EbpfFirewall {
11    programs: HashMap<String, EbpfProgram>,
12    sockets: HashMap<i32, SocketInfo>,
13    firewall_rules: Vec<FirewallRule>,
14    performance_monitor: PerformanceMonitor,
15}
16
17/// eBPF program with metadata
18#[derive(Debug, Clone)]
19pub struct EbpfProgram {
20    pub name: String,
21    pub program_type: ProgramType,
22    pub bytecode: Vec<u8>,
23    pub program_id: u32,
24    pub attached_sockets: Vec<i32>,
25    pub performance_stats: ProgramStats,
26}
27
28/// eBPF program types
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub enum ProgramType {
31    /// Socket filter program
32    SocketFilter,
33    /// XDP program for high-performance packet processing
34    Xdp,
35    /// TC program for traffic control
36    TrafficControl,
37    /// Tracepoint program for monitoring
38    Tracepoint,
39    /// Kprobe program for kernel function tracing
40    Kprobe,
41}
42
43/// Socket information
44#[derive(Debug, Clone)]
45pub struct SocketInfo {
46    pub fd: i32,
47    pub socket_type: SocketType,
48    pub protocol: Protocol,
49    pub local_address: SocketAddress,
50    pub remote_address: Option<SocketAddress>,
51    pub attached_program: Option<String>,
52    pub bypass_enabled: bool,
53    pub performance_stats: SocketStats,
54}
55
56/// Socket types
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub enum SocketType {
59    Stream,    // TCP
60    Datagram,  // UDP
61    Raw,       // Raw socket
62    SeqPacket, // SCTP
63}
64
65/// Network protocols
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub enum Protocol {
68    Tcp,
69    Udp,
70    Icmp,
71    Ipv6,
72    Raw,
73}
74
75/// Socket address representation
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct SocketAddress {
78    pub ip: String,
79    pub port: u16,
80    pub family: AddressFamily,
81}
82
83/// Address families
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub enum AddressFamily {
86    IPv4,
87    IPv6,
88    Unix,
89}
90
91/// Firewall rule for packet filtering
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct FirewallRule {
94    pub rule_id: u32,
95    pub name: String,
96    pub action: RuleAction,
97    pub conditions: Vec<RuleCondition>,
98    pub priority: u8,
99    pub enabled: bool,
100    pub hit_count: u64,
101}
102
103/// Rule actions
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub enum RuleAction {
106    Allow,
107    Deny,
108    Redirect(String),
109    Modify(PacketModification),
110    Log,
111}
112
113/// Packet modification actions
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct PacketModification {
116    pub field: String,
117    pub operation: ModificationOperation,
118    pub value: Vec<u8>,
119}
120
121/// Modification operations
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum ModificationOperation {
124    Set,
125    Add,
126    Subtract,
127    Xor,
128}
129
130/// Rule conditions
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct RuleCondition {
133    pub field: String,
134    pub operator: ConditionOperator,
135    pub value: Vec<u8>,
136}
137
138/// Condition operators
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub enum ConditionOperator {
141    Equals,
142    NotEquals,
143    GreaterThan,
144    LessThan,
145    Contains,
146    StartsWith,
147    EndsWith,
148}
149
150/// Performance monitor for eBPF programs
151pub struct PerformanceMonitor {
152    program_metrics: HashMap<u32, ProgramMetrics>,
153    socket_metrics: HashMap<i32, SocketMetrics>,
154    global_metrics: GlobalMetrics,
155}
156
157/// Program performance metrics
158#[derive(Debug, Clone)]
159pub struct ProgramMetrics {
160    pub program_id: u32,
161    pub execution_count: u64,
162    pub total_execution_time: u64,
163    pub average_execution_time: f64,
164    pub max_execution_time: u64,
165    pub min_execution_time: u64,
166    pub memory_usage: u64,
167    pub packet_count: u64,
168    pub byte_count: u64,
169}
170
171/// Socket performance metrics
172#[derive(Debug, Clone)]
173pub struct SocketMetrics {
174    pub fd: i32,
175    pub packets_sent: u64,
176    pub packets_received: u64,
177    pub bytes_sent: u64,
178    pub bytes_received: u64,
179    pub connection_time: u64,
180    pub last_activity: u64,
181    pub error_count: u64,
182}
183
184/// Global performance metrics
185#[derive(Debug, Clone)]
186pub struct GlobalMetrics {
187    pub total_packets_processed: u64,
188    pub total_bytes_processed: u64,
189    pub average_processing_time: f64,
190    pub cpu_usage: f64,
191    pub memory_usage: u64,
192    pub active_connections: u64,
193    pub dropped_packets: u64,
194}
195
196/// Program statistics
197#[derive(Debug, Clone)]
198pub struct ProgramStats {
199    pub execution_count: u64,
200    pub total_execution_time: u64,
201    pub memory_usage: u64,
202    pub packet_count: u64,
203}
204
205/// Socket statistics
206#[derive(Debug, Clone)]
207pub struct SocketStats {
208    pub packets_sent: u64,
209    pub packets_received: u64,
210    pub bytes_sent: u64,
211    pub bytes_received: u64,
212    pub connection_time: u64,
213    pub error_count: u64,
214}
215
216/// Zero-copy buffer for direct memory access
217pub struct ZeroCopyBuffer {
218    pub ptr: *mut u8,
219    pub size: usize,
220    pub capacity: usize,
221    pub fd: i32,
222}
223
224impl EbpfFirewall {
225    /// Create new eBPF firewall
226    pub fn new() -> Result<Self, EbpfError> {
227        Ok(Self {
228            programs: HashMap::new(),
229            sockets: HashMap::new(),
230            firewall_rules: Vec::new(),
231            performance_monitor: PerformanceMonitor::new(),
232        })
233    }
234
235    /// Load eBPF program
236    pub fn load_program(
237        &mut self,
238        name: String,
239        program_type: ProgramType,
240        bytecode: Vec<u8>,
241    ) -> Result<u32, EbpfError> {
242        // Validate program bytecode
243        Self::validate_bytecode(&bytecode)?;
244
245        // Generate program ID
246        let program_id = self.generate_program_id();
247
248        // Create program
249        let program = EbpfProgram {
250            name: name.clone(),
251            program_type,
252            bytecode: bytecode.clone(),
253            program_id,
254            attached_sockets: Vec::new(),
255            performance_stats: ProgramStats {
256                execution_count: 0,
257                total_execution_time: 0,
258                memory_usage: 0,
259                packet_count: 0,
260            },
261        };
262
263        // Load program into kernel
264        self.load_program_into_kernel(&program)?;
265
266        // Store program
267        self.programs.insert(name, program);
268
269        Ok(program_id)
270    }
271
272    /// Attach program to socket
273    pub fn attach_socket(&mut self, fd: i32, program_name: &str) -> Result<(), EbpfError> {
274        // Pre-compute all &self operations before any mutable borrows
275        let socket_type = self.detect_socket_type(fd)?;
276        let protocol = self.detect_protocol(fd)?;
277        let local_address = self.get_local_address(fd)?;
278        let remote_address = self.get_remote_address(fd)?;
279
280        // Extract program fields via immutable borrow (dropped before mutable ops)
281        let (program_id, program_type) = {
282            let program = self
283                .programs
284                .get(program_name)
285                .ok_or_else(|| EbpfError::ProgramNotFound(program_name.to_string()))?;
286            (program.program_id, program.program_type.clone())
287        };
288
289        // Attach program to socket (no live &mut program borrow)
290        self.attach_program_to_socket(fd, program_id, program_type)?;
291
292        // Update program attached sockets
293        self.programs
294            .get_mut(program_name)
295            .ok_or_else(|| EbpfError::ProgramNotFound(program_name.to_string()))?
296            .attached_sockets
297            .push(fd);
298
299        // Create and store socket info
300        let socket_info = SocketInfo {
301            fd,
302            socket_type,
303            protocol,
304            local_address,
305            remote_address,
306            attached_program: Some(program_name.to_string()),
307            bypass_enabled: true,
308            performance_stats: SocketStats {
309                packets_sent: 0,
310                packets_received: 0,
311                bytes_sent: 0,
312                bytes_received: 0,
313                connection_time: std::time::SystemTime::now()
314                    .duration_since(std::time::UNIX_EPOCH)
315                    .unwrap()
316                    .as_secs(),
317                error_count: 0,
318            },
319        };
320
321        self.sockets.insert(fd, socket_info);
322
323        Ok(())
324    }
325
326    /// Enable socket bypassing
327    pub fn bypass_socket(&mut self, fd: i32) -> Result<(), EbpfError> {
328        let socket_info = self
329            .sockets
330            .get_mut(&fd)
331            .ok_or_else(|| EbpfError::SocketNotFound(fd))?;
332
333        // Enable bypass
334        socket_info.bypass_enabled = true;
335
336        // Configure kernel for bypass
337        self.configure_socket_bypass(fd)?;
338
339        Ok(())
340    }
341
342    /// Disable socket bypassing
343    pub fn unbypass_socket(&mut self, fd: i32) -> Result<(), EbpfError> {
344        let socket_info = self
345            .sockets
346            .get_mut(&fd)
347            .ok_or_else(|| EbpfError::SocketNotFound(fd))?;
348
349        // Disable bypass
350        socket_info.bypass_enabled = false;
351
352        // Configure kernel for normal processing
353        self.configure_socket_normal(fd)?;
354
355        Ok(())
356    }
357
358    /// Add firewall rule
359    pub fn add_rule(&mut self, rule: FirewallRule) -> Result<(), EbpfError> {
360        // Validate rule
361        self.validate_rule(&rule)?;
362
363        // Add to rules list
364        self.firewall_rules.push(rule.clone());
365
366        // Update eBPF programs with new rule
367        self.update_firewall_programs()?;
368
369        Ok(())
370    }
371
372    /// Remove firewall rule
373    pub fn remove_rule(&mut self, rule_id: u32) -> Result<(), EbpfError> {
374        // Find and remove rule
375        self.firewall_rules.retain(|rule| rule.rule_id != rule_id);
376
377        // Update eBPF programs
378        self.update_firewall_programs()?;
379
380        Ok(())
381    }
382
383    /// Get zero-copy buffer for socket
384    pub fn get_zero_copy_buffer(&self, fd: i32, size: usize) -> Result<ZeroCopyBuffer, EbpfError> {
385        let socket_info = self
386            .sockets
387            .get(&fd)
388            .ok_or_else(|| EbpfError::SocketNotFound(fd))?;
389
390        if !socket_info.bypass_enabled {
391            return Err(EbpfError::BypassNotEnabled(fd));
392        }
393
394        // Create zero-copy buffer
395        let buffer = ZeroCopyBuffer {
396            ptr: std::ptr::null_mut(), // Would be actual memory mapping
397            size,
398            capacity: size,
399            fd,
400        };
401
402        Ok(buffer)
403    }
404
405    /// Process packet through firewall
406    pub fn process_packet(
407        &mut self,
408        packet: &[u8],
409        socket_fd: i32,
410    ) -> Result<PacketAction, EbpfError> {
411        let start_time = std::time::Instant::now();
412
413        // Get socket info
414        let socket_info = self
415            .sockets
416            .get(&socket_fd)
417            .ok_or_else(|| EbpfError::SocketNotFound(socket_fd))?;
418
419        // Check attached program
420        let program_name = socket_info
421            .attached_program
422            .as_ref()
423            .ok_or_else(|| EbpfError::NoProgramAttached(socket_fd))?;
424
425        let program = self
426            .programs
427            .get(program_name)
428            .ok_or_else(|| EbpfError::ProgramNotFound(program_name.clone()))?;
429
430        // Execute eBPF program
431        let action = self.execute_ebpf_program(&program, packet)?;
432
433        // Update performance metrics
434        let execution_time = start_time.elapsed().as_nanos() as u64;
435        self.performance_monitor.update_program_metrics(
436            program.program_id,
437            execution_time,
438            packet.len(),
439        );
440        self.performance_monitor
441            .update_socket_metrics(socket_fd, packet.len());
442
443        Ok(action)
444    }
445
446    /// Get performance statistics
447    pub fn get_performance_stats(&self) -> PerformanceStats {
448        self.performance_monitor.get_global_stats()
449    }
450
451    /// Get socket statistics
452    pub fn get_socket_stats(&self, fd: i32) -> Option<SocketStats> {
453        self.sockets
454            .get(&fd)
455            .map(|info| info.performance_stats.clone())
456    }
457
458    /// Get program statistics
459    pub fn get_program_stats(&self, program_id: u32) -> Option<ProgramStats> {
460        self.programs
461            .values()
462            .find(|p| p.program_id == program_id)
463            .map(|p| p.performance_stats.clone())
464    }
465
466    /// List all sockets
467    pub fn list_sockets(&self) -> Vec<i32> {
468        self.sockets.keys().cloned().collect()
469    }
470
471    /// List all programs
472    pub fn list_programs(&self) -> Vec<String> {
473        self.programs.keys().cloned().collect()
474    }
475
476    /// List all firewall rules
477    pub fn list_rules(&self) -> Vec<FirewallRule> {
478        self.firewall_rules.clone()
479    }
480
481    // Internal methods
482
483    /// Validate eBPF bytecode
484    fn validate_bytecode(bytecode: &[u8]) -> Result<(), EbpfError> {
485        // Check bytecode size
486        if bytecode.len() > 4096 {
487            return Err(EbpfError::InvalidBytecode("Bytecode too large".to_string()));
488        }
489
490        // Check bytecode alignment
491        if bytecode.len() % 8 != 0 {
492            return Err(EbpfError::InvalidBytecode(
493                "Bytecode not aligned".to_string(),
494            ));
495        }
496
497        // Validate eBPF instructions
498        Self::validate_instructions(bytecode)?;
499
500        Ok(())
501    }
502
503    /// Validate eBPF instructions
504    fn validate_instructions(bytecode: &[u8]) -> Result<(), EbpfError> {
505        // Simple validation - in real implementation would parse eBPF instructions
506        for chunk in bytecode.chunks(8) {
507            if chunk.len() != 8 {
508                return Err(EbpfError::InvalidBytecode(
509                    "Invalid instruction size".to_string(),
510                ));
511            }
512        }
513
514        Ok(())
515    }
516
517    /// Load program into kernel
518    ///
519    /// Requires the `aya` feature (Linux-only eBPF runtime loader).  Returns an
520    /// explicit `LoadError` instead of silently succeeding so callers know the
521    /// program was never actually loaded into the kernel.
522    fn load_program_into_kernel(&self, program: &EbpfProgram) -> Result<(), EbpfError> {
523        #[cfg(target_os = "linux")]
524        {
525            return Err(EbpfError::LoadError(format!(
526                "eBPF program '{}' cannot be loaded: aya feature not enabled in this build. \
527                     Recompile with `--features aya` on Linux to enable kernel eBPF loading.",
528                program.name
529            )));
530        }
531        #[cfg(not(target_os = "linux"))]
532        {
533            return Err(EbpfError::LoadError(format!(
534                "eBPF program '{}' cannot be loaded: eBPF is Linux-only \
535                     (current OS does not support BPF syscalls).",
536                program.name
537            )));
538        }
539    }
540
541    /// Attach program to socket
542    ///
543    /// Requires a loaded eBPF program file descriptor from the kernel.  Returns
544    /// `AttachError` instead of silently succeeding when the runtime is absent.
545    fn attach_program_to_socket(
546        &self,
547        fd: i32,
548        program_id: u32,
549        program_type: ProgramType,
550    ) -> Result<(), EbpfError> {
551        #[cfg(target_os = "linux")]
552        {
553            return Err(EbpfError::AttachError(format!(
554                "Cannot attach program {} (type {:?}) to socket fd {}: \
555                     eBPF runtime not available — compile with `--features aya`.",
556                program_id, program_type, fd
557            )));
558        }
559        #[cfg(not(target_os = "linux"))]
560        {
561            let _ = program_type;
562            return Err(EbpfError::AttachError(format!(
563                "Cannot attach program {} to socket fd {}: eBPF is Linux-only.",
564                program_id, fd
565            )));
566        }
567    }
568
569    /// Configure socket bypass (zero-copy)
570    ///
571    /// Returns `ConfigurationError` instead of silently succeeding when the
572    /// kernel eBPF bypass path is not available.
573    fn configure_socket_bypass(&self, fd: i32) -> Result<(), EbpfError> {
574        #[cfg(target_os = "linux")]
575        {
576            return Err(EbpfError::ConfigurationError(format!(
577                "Cannot enable zero-copy bypass for socket fd {}: \
578                     eBPF runtime not available — compile with `--features aya`.",
579                fd
580            )));
581        }
582        #[cfg(not(target_os = "linux"))]
583        {
584            return Err(EbpfError::ConfigurationError(format!(
585                "Cannot enable zero-copy bypass for socket fd {}: eBPF is Linux-only.",
586                fd
587            )));
588        }
589    }
590
591    /// Restore normal socket processing
592    ///
593    /// Returns `ConfigurationError` instead of silently succeeding when the
594    /// eBPF detach path is not available.
595    fn configure_socket_normal(&self, fd: i32) -> Result<(), EbpfError> {
596        #[cfg(target_os = "linux")]
597        {
598            return Err(EbpfError::ConfigurationError(format!(
599                "Cannot restore normal processing for socket fd {}: \
600                     eBPF runtime not available — compile with `--features aya`.",
601                fd
602            )));
603        }
604        #[cfg(not(target_os = "linux"))]
605        {
606            return Err(EbpfError::ConfigurationError(format!(
607                "Cannot restore normal processing for socket fd {}: eBPF is Linux-only.",
608                fd
609            )));
610        }
611    }
612
613    /// Detect socket type
614    fn detect_socket_type(&self, _fd: i32) -> Result<SocketType, EbpfError> {
615        // In real implementation, would use getsockopt() to detect type
616        // For now, return default
617        Ok(SocketType::Stream)
618    }
619
620    /// Detect protocol
621    fn detect_protocol(&self, _fd: i32) -> Result<Protocol, EbpfError> {
622        // In real implementation, would use getsockopt() to detect protocol
623        // For now, return default
624        Ok(Protocol::Tcp)
625    }
626
627    /// Get local address
628    fn get_local_address(&self, _fd: i32) -> Result<SocketAddress, EbpfError> {
629        // In real implementation, would use getsockname()
630        // For now, return default
631        Ok(SocketAddress {
632            ip: "127.0.0.1".to_string(),
633            port: 8080,
634            family: AddressFamily::IPv4,
635        })
636    }
637
638    /// Get remote address
639    fn get_remote_address(&self, _fd: i32) -> Result<Option<SocketAddress>, EbpfError> {
640        // In real implementation, would use getpeername()
641        // For now, return None
642        Ok(None)
643    }
644
645    /// Validate firewall rule
646    fn validate_rule(&self, rule: &FirewallRule) -> Result<(), EbpfError> {
647        // Check rule conditions
648        if rule.conditions.is_empty() {
649            return Err(EbpfError::InvalidRule(
650                "Rule must have at least one condition".to_string(),
651            ));
652        }
653
654        // Validate condition fields
655        for condition in &rule.conditions {
656            if condition.field.is_empty() {
657                return Err(EbpfError::InvalidRule(
658                    "Condition field cannot be empty".to_string(),
659                ));
660            }
661        }
662
663        Ok(())
664    }
665
666    /// Update firewall programs
667    ///
668    /// Re-compiling eBPF programs with new ruleset requires the `aya` kernel
669    /// loader.  Returns `LoadError` rather than silently no-oping.
670    fn update_firewall_programs(&mut self) -> Result<(), EbpfError> {
671        #[cfg(target_os = "linux")]
672        {
673            return Err(EbpfError::LoadError(
674                "Cannot update eBPF firewall programs: aya feature not enabled in this build. \
675                 Recompile with `--features aya` on Linux."
676                    .to_string(),
677            ));
678        }
679        #[cfg(not(target_os = "linux"))]
680        {
681            return Err(EbpfError::LoadError(
682                "Cannot update eBPF firewall programs: eBPF is Linux-only.".to_string(),
683            ));
684        }
685    }
686
687    /// Execute eBPF program
688    ///
689    /// In-kernel eBPF execution requires the `aya` loader.  Returns
690    /// `AttachError` (program was never actually loaded) rather than silently
691    /// allowing all packets.
692    fn execute_ebpf_program(
693        &self,
694        program: &EbpfProgram,
695        _packet: &[u8],
696    ) -> Result<PacketAction, EbpfError> {
697        #[cfg(target_os = "linux")]
698        {
699            return Err(EbpfError::AttachError(format!(
700                "eBPF program '{}' (id {}) is not loaded into the kernel: \
701                     compile with `--features aya` to enable in-kernel execution.",
702                program.name, program.program_id
703            )));
704        }
705        #[cfg(not(target_os = "linux"))]
706        {
707            return Err(EbpfError::AttachError(format!(
708                "eBPF program '{}' cannot be executed: eBPF is Linux-only.",
709                program.name
710            )));
711        }
712    }
713
714    /// Generate unique program ID
715    fn generate_program_id(&self) -> u32 {
716        use std::sync::atomic::{AtomicU32, Ordering};
717        static COUNTER: AtomicU32 = AtomicU32::new(1);
718        COUNTER.fetch_add(1, Ordering::SeqCst)
719    }
720}
721
722impl PerformanceMonitor {
723    /// Create new performance monitor
724    pub fn new() -> Self {
725        Self {
726            program_metrics: HashMap::new(),
727            socket_metrics: HashMap::new(),
728            global_metrics: GlobalMetrics {
729                total_packets_processed: 0,
730                total_bytes_processed: 0,
731                average_processing_time: 0.0,
732                cpu_usage: 0.0,
733                memory_usage: 0,
734                active_connections: 0,
735                dropped_packets: 0,
736            },
737        }
738    }
739
740    /// Update program metrics
741    pub fn update_program_metrics(
742        &mut self,
743        program_id: u32,
744        execution_time: u64,
745        packet_size: usize,
746    ) {
747        let metrics = self
748            .program_metrics
749            .entry(program_id)
750            .or_insert(ProgramMetrics {
751                program_id,
752                execution_count: 0,
753                total_execution_time: 0,
754                average_execution_time: 0.0,
755                max_execution_time: 0,
756                min_execution_time: u64::MAX,
757                memory_usage: 0,
758                packet_count: 0,
759                byte_count: 0,
760            });
761
762        metrics.execution_count += 1;
763        metrics.total_execution_time += execution_time;
764        metrics.average_execution_time =
765            metrics.total_execution_time as f64 / metrics.execution_count as f64;
766        metrics.max_execution_time = metrics.max_execution_time.max(execution_time);
767        metrics.min_execution_time = metrics.min_execution_time.min(execution_time);
768        metrics.packet_count += 1;
769        metrics.byte_count += packet_size as u64;
770
771        // Update global metrics
772        self.global_metrics.total_packets_processed += 1;
773        self.global_metrics.total_bytes_processed += packet_size as u64;
774    }
775
776    /// Update socket metrics
777    pub fn update_socket_metrics(&mut self, fd: i32, packet_size: usize) {
778        let metrics = self.socket_metrics.entry(fd).or_insert(SocketMetrics {
779            fd,
780            packets_sent: 0,
781            packets_received: 0,
782            bytes_sent: 0,
783            bytes_received: 0,
784            connection_time: std::time::SystemTime::now()
785                .duration_since(std::time::UNIX_EPOCH)
786                .unwrap()
787                .as_secs(),
788            last_activity: std::time::SystemTime::now()
789                .duration_since(std::time::UNIX_EPOCH)
790                .unwrap()
791                .as_secs(),
792            error_count: 0,
793        });
794
795        metrics.packets_received += 1;
796        metrics.bytes_received += packet_size as u64;
797        metrics.last_activity = std::time::SystemTime::now()
798            .duration_since(std::time::UNIX_EPOCH)
799            .unwrap()
800            .as_secs();
801    }
802
803    /// Get global statistics
804    pub fn get_global_stats(&self) -> PerformanceStats {
805        PerformanceStats {
806            total_packets_processed: self.global_metrics.total_packets_processed,
807            total_bytes_processed: self.global_metrics.total_bytes_processed,
808            average_processing_time: self.global_metrics.average_processing_time,
809            cpu_usage: self.global_metrics.cpu_usage,
810            memory_usage: self.global_metrics.memory_usage,
811            active_connections: self.global_metrics.active_connections,
812            dropped_packets: self.global_metrics.dropped_packets,
813        }
814    }
815}
816
817/// Packet action result
818#[derive(Debug, Clone, PartialEq)]
819pub enum PacketAction {
820    Allow,
821    Deny,
822    Redirect(String),
823    Modify(PacketModification),
824    Log,
825}
826
827/// Performance statistics
828#[derive(Debug, Clone)]
829pub struct PerformanceStats {
830    pub total_packets_processed: u64,
831    pub total_bytes_processed: u64,
832    pub average_processing_time: f64,
833    pub cpu_usage: f64,
834    pub memory_usage: u64,
835    pub active_connections: u64,
836    pub dropped_packets: u64,
837}
838
839/// eBPF error types
840#[derive(Debug, Clone)]
841pub enum EbpfError {
842    ProgramNotFound(String),
843    SocketNotFound(i32),
844    NoProgramAttached(i32),
845    BypassNotEnabled(i32),
846    InvalidBytecode(String),
847    InvalidRule(String),
848    LoadError(String),
849    AttachError(String),
850    ConfigurationError(String),
851}
852
853impl std::fmt::Display for EbpfError {
854    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
855        match self {
856            EbpfError::ProgramNotFound(msg) => write!(f, "Program not found: {}", msg),
857            EbpfError::SocketNotFound(fd) => write!(f, "Socket not found: {}", fd),
858            EbpfError::NoProgramAttached(fd) => write!(f, "No program attached to socket: {}", fd),
859            EbpfError::BypassNotEnabled(fd) => write!(f, "Bypass not enabled for socket: {}", fd),
860            EbpfError::InvalidBytecode(msg) => write!(f, "Invalid bytecode: {}", msg),
861            EbpfError::InvalidRule(msg) => write!(f, "Invalid rule: {}", msg),
862            EbpfError::LoadError(msg) => write!(f, "Load error: {}", msg),
863            EbpfError::AttachError(msg) => write!(f, "Attach error: {}", msg),
864            EbpfError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
865        }
866    }
867}
868
869impl std::error::Error for EbpfError {}
870
871/// Safety: ZeroCopyBuffer must be handled carefully
872unsafe impl Send for ZeroCopyBuffer {}
873unsafe impl Sync for ZeroCopyBuffer {}
874
875#[cfg(test)]
876mod tests {
877    use super::*;
878
879    #[test]
880    fn test_firewall_creation() {
881        let firewall = EbpfFirewall::new().unwrap();
882        assert_eq!(firewall.list_programs().len(), 0);
883        assert_eq!(firewall.list_sockets().len(), 0);
884        assert_eq!(firewall.list_rules().len(), 0);
885    }
886
887    #[test]
888    fn test_program_loading() {
889        let mut firewall = EbpfFirewall::new().unwrap();
890
891        // Create dummy bytecode
892        let bytecode = vec![0u8; 64];
893
894        // eBPF kernel loading requires the `aya` feature (Linux-only).
895        // On non-Linux builds and without the feature flag, load_program
896        // correctly returns an error rather than silently succeeding.
897        let result = firewall.load_program(
898            "test_program".to_string(),
899            ProgramType::SocketFilter,
900            bytecode,
901        );
902
903        #[cfg(target_os = "linux")]
904        {
905            // Even on Linux, the aya feature is not enabled — expect LoadError.
906            assert!(
907                matches!(result, Err(EbpfError::LoadError(_))),
908                "Expected LoadError when aya feature is absent: {:?}",
909                result
910            );
911        }
912        #[cfg(not(target_os = "linux"))]
913        {
914            // On non-Linux hosts, expect LoadError with a "Linux-only" message.
915            assert!(
916                matches!(result, Err(EbpfError::LoadError(_))),
917                "Expected LoadError on non-Linux: {:?}",
918                result
919            );
920        }
921    }
922
923    #[test]
924    fn test_firewall_rules() {
925        let mut firewall = EbpfFirewall::new().unwrap();
926
927        let rule = FirewallRule {
928            rule_id: 1,
929            name: "test_rule".to_string(),
930            action: RuleAction::Allow,
931            conditions: vec![RuleCondition {
932                field: "source_ip".to_string(),
933                operator: ConditionOperator::Equals,
934                value: vec![192, 168, 1, 1],
935            }],
936            priority: 1,
937            enabled: true,
938            hit_count: 0,
939        };
940
941        // add_rule calls update_firewall_programs which requires aya — expect an error.
942        let add_result = firewall.add_rule(rule);
943        assert!(
944            matches!(add_result, Err(EbpfError::LoadError(_))),
945            "Expected LoadError when aya feature is absent: {:?}",
946            add_result
947        );
948
949        // remove_rule also calls update_firewall_programs — same expectation.
950        let remove_result = firewall.remove_rule(1);
951        assert!(
952            matches!(remove_result, Err(EbpfError::LoadError(_))),
953            "Expected LoadError when aya feature is absent: {:?}",
954            remove_result
955        );
956    }
957
958    #[test]
959    fn test_performance_monitor() {
960        let mut monitor = PerformanceMonitor::new();
961
962        monitor.update_program_metrics(1, 1000, 1024);
963        monitor.update_socket_metrics(1, 1024);
964
965        let stats = monitor.get_global_stats();
966        assert_eq!(stats.total_packets_processed, 1);
967        assert_eq!(stats.total_bytes_processed, 1024);
968    }
969}