1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub struct EbpfFirewall {
11 programs: HashMap<String, EbpfProgram>,
12 sockets: HashMap<i32, SocketInfo>,
13 firewall_rules: Vec<FirewallRule>,
14 performance_monitor: PerformanceMonitor,
15}
16
17#[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub enum ProgramType {
31 SocketFilter,
33 Xdp,
35 TrafficControl,
37 Tracepoint,
39 Kprobe,
41}
42
43#[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub enum SocketType {
59 Stream, Datagram, Raw, SeqPacket, }
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub enum Protocol {
68 Tcp,
69 Udp,
70 Icmp,
71 Ipv6,
72 Raw,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct SocketAddress {
78 pub ip: String,
79 pub port: u16,
80 pub family: AddressFamily,
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub enum AddressFamily {
86 IPv4,
87 IPv6,
88 Unix,
89}
90
91#[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub enum RuleAction {
106 Allow,
107 Deny,
108 Redirect(String),
109 Modify(PacketModification),
110 Log,
111}
112
113#[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub enum ModificationOperation {
124 Set,
125 Add,
126 Subtract,
127 Xor,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct RuleCondition {
133 pub field: String,
134 pub operator: ConditionOperator,
135 pub value: Vec<u8>,
136}
137
138#[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
150pub struct PerformanceMonitor {
152 program_metrics: HashMap<u32, ProgramMetrics>,
153 socket_metrics: HashMap<i32, SocketMetrics>,
154 global_metrics: GlobalMetrics,
155}
156
157#[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#[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#[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#[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#[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
216pub struct ZeroCopyBuffer {
218 pub ptr: *mut u8,
219 pub size: usize,
220 pub capacity: usize,
221 pub fd: i32,
222}
223
224impl EbpfFirewall {
225 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 pub fn load_program(
237 &mut self,
238 name: String,
239 program_type: ProgramType,
240 bytecode: Vec<u8>,
241 ) -> Result<u32, EbpfError> {
242 Self::validate_bytecode(&bytecode)?;
244
245 let program_id = self.generate_program_id();
247
248 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 self.load_program_into_kernel(&program)?;
265
266 self.programs.insert(name, program);
268
269 Ok(program_id)
270 }
271
272 pub fn attach_socket(&mut self, fd: i32, program_name: &str) -> Result<(), EbpfError> {
274 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 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 self.attach_program_to_socket(fd, program_id, program_type)?;
291
292 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 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 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 socket_info.bypass_enabled = true;
335
336 self.configure_socket_bypass(fd)?;
338
339 Ok(())
340 }
341
342 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 socket_info.bypass_enabled = false;
351
352 self.configure_socket_normal(fd)?;
354
355 Ok(())
356 }
357
358 pub fn add_rule(&mut self, rule: FirewallRule) -> Result<(), EbpfError> {
360 self.validate_rule(&rule)?;
362
363 self.firewall_rules.push(rule.clone());
365
366 self.update_firewall_programs()?;
368
369 Ok(())
370 }
371
372 pub fn remove_rule(&mut self, rule_id: u32) -> Result<(), EbpfError> {
374 self.firewall_rules.retain(|rule| rule.rule_id != rule_id);
376
377 self.update_firewall_programs()?;
379
380 Ok(())
381 }
382
383 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 let buffer = ZeroCopyBuffer {
396 ptr: std::ptr::null_mut(), size,
398 capacity: size,
399 fd,
400 };
401
402 Ok(buffer)
403 }
404
405 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 let socket_info = self
415 .sockets
416 .get(&socket_fd)
417 .ok_or_else(|| EbpfError::SocketNotFound(socket_fd))?;
418
419 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 let action = self.execute_ebpf_program(&program, packet)?;
432
433 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 pub fn get_performance_stats(&self) -> PerformanceStats {
448 self.performance_monitor.get_global_stats()
449 }
450
451 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 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 pub fn list_sockets(&self) -> Vec<i32> {
468 self.sockets.keys().cloned().collect()
469 }
470
471 pub fn list_programs(&self) -> Vec<String> {
473 self.programs.keys().cloned().collect()
474 }
475
476 pub fn list_rules(&self) -> Vec<FirewallRule> {
478 self.firewall_rules.clone()
479 }
480
481 fn validate_bytecode(bytecode: &[u8]) -> Result<(), EbpfError> {
485 if bytecode.len() > 4096 {
487 return Err(EbpfError::InvalidBytecode("Bytecode too large".to_string()));
488 }
489
490 if bytecode.len() % 8 != 0 {
492 return Err(EbpfError::InvalidBytecode(
493 "Bytecode not aligned".to_string(),
494 ));
495 }
496
497 Self::validate_instructions(bytecode)?;
499
500 Ok(())
501 }
502
503 fn validate_instructions(bytecode: &[u8]) -> Result<(), EbpfError> {
505 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 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 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 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 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 fn detect_socket_type(&self, _fd: i32) -> Result<SocketType, EbpfError> {
615 Ok(SocketType::Stream)
618 }
619
620 fn detect_protocol(&self, _fd: i32) -> Result<Protocol, EbpfError> {
622 Ok(Protocol::Tcp)
625 }
626
627 fn get_local_address(&self, _fd: i32) -> Result<SocketAddress, EbpfError> {
629 Ok(SocketAddress {
632 ip: "127.0.0.1".to_string(),
633 port: 8080,
634 family: AddressFamily::IPv4,
635 })
636 }
637
638 fn get_remote_address(&self, _fd: i32) -> Result<Option<SocketAddress>, EbpfError> {
640 Ok(None)
643 }
644
645 fn validate_rule(&self, rule: &FirewallRule) -> Result<(), EbpfError> {
647 if rule.conditions.is_empty() {
649 return Err(EbpfError::InvalidRule(
650 "Rule must have at least one condition".to_string(),
651 ));
652 }
653
654 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 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 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 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 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 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 self.global_metrics.total_packets_processed += 1;
773 self.global_metrics.total_bytes_processed += packet_size as u64;
774 }
775
776 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 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#[derive(Debug, Clone, PartialEq)]
819pub enum PacketAction {
820 Allow,
821 Deny,
822 Redirect(String),
823 Modify(PacketModification),
824 Log,
825}
826
827#[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#[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
871unsafe 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 let bytecode = vec![0u8; 64];
893
894 let result = firewall.load_program(
898 "test_program".to_string(),
899 ProgramType::SocketFilter,
900 bytecode,
901 );
902
903 #[cfg(target_os = "linux")]
904 {
905 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 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 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 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}