use std::{ collections::HashMap, net::Ipv4Addr, time::Instant, }; /// Standalone demo that shows the traffic filtering logic without eBPF dependencies /// This runs on any platform and demonstrates how the IP filtering would work // CIDR range structure #[derive(Debug, Clone)] struct CidrRange { network: Ipv4Addr, prefix_len: u8, } impl CidrRange { fn new(cidr_str: &str) -> Result { let parts: Vec<&str> = cidr_str.split('/').collect(); if parts.len() != 2 { return Err(format!("Invalid CIDR format: {}", cidr_str)); } let ip: Ipv4Addr = parts[0].parse() .map_err(|_| format!("Invalid IP address: {}", parts[0]))?; let prefix_len: u8 = parts[1].parse() .map_err(|_| format!("Invalid prefix length: {}", parts[1]))?; if prefix_len > 32 { return Err(format!("Prefix length must be 0-32, got: {}", prefix_len)); } // Calculate the network address let ip_u32 = u32::from(ip); let mask = if prefix_len == 0 { 0 } else { !((1u32 << (32 - prefix_len)) - 1) }; let network_u32 = ip_u32 & mask; let network_ip = Ipv4Addr::from(network_u32); Ok(CidrRange { network: network_ip, prefix_len, }) } fn contains(&self, ip: Ipv4Addr) -> bool { if self.prefix_len == 0 { return true; // 0.0.0.0/0 matches everything } if self.prefix_len > 32 { return false; } let ip_u32 = u32::from(ip); let network_u32 = u32::from(self.network); let mask = if self.prefix_len == 32 { 0xFFFFFFFF } else { !((1u32 << (32 - self.prefix_len)) - 1) }; (ip_u32 & mask) == (network_u32 & mask) } } // Traffic event structure (mirrors eBPF version) #[derive(Debug, Clone)] struct TrafficEvent { src_ip: Ipv4Addr, dst_ip: Ipv4Addr, src_port: u16, dst_port: u16, protocol: &'static str, packet_size: u16, action: &'static str, } // Traffic monitor configuration struct TrafficMonitor { permitted_cidrs: Vec, drop_packets: bool, stats: HashMap, // (packet_count, total_bytes) } impl TrafficMonitor { fn new(cidr_strings: Vec<&str>, drop_packets: bool) -> Result { let mut permitted_cidrs = Vec::new(); for cidr_str in cidr_strings { permitted_cidrs.push(CidrRange::new(cidr_str)?); } Ok(TrafficMonitor { permitted_cidrs, drop_packets, stats: HashMap::new(), }) } fn is_permitted(&self, ip: Ipv4Addr) -> bool { for cidr in &self.permitted_cidrs { if cidr.contains(ip) { return true; } } false } fn process_packet(&mut self, event: TrafficEvent) -> &'static str { if self.is_permitted(event.src_ip) { "ALLOWED" } else { // Update statistics let entry = self.stats.entry(event.src_ip).or_insert((0, 0)); entry.0 += 1; // packet count entry.1 += event.packet_size as u64; // total bytes let action = if self.drop_packets { "DROPPED" } else { "LOGGED" }; println!( "[{}] Non-permitted traffic: {}:{} -> {}:{} (proto: {}, size: {} bytes)", action, event.src_ip, event.src_port, event.dst_ip, event.dst_port, event.protocol, event.packet_size ); action } } fn print_stats(&self) { if self.stats.is_empty() { println!("šŸ“Š No non-permitted traffic detected"); return; } println!("\nšŸ“Š Non-permitted Traffic Statistics:"); println!("===================================="); let mut sorted: Vec<_> = self.stats.iter().collect(); sorted.sort_by(|a, b| b.1.0.cmp(&a.1.0)); // Sort by packet count for (ip, (packets, bytes)) in sorted { println!(" {} - {} packets, {} bytes", ip, packets, bytes); } let total_packets: u64 = self.stats.values().map(|(p, _)| p).sum(); let total_bytes: u64 = self.stats.values().map(|(_, b)| b).sum(); println!(" Total: {} packets, {} bytes from {} unique IPs", total_packets, total_bytes, self.stats.len()); } } fn main() -> Result<(), String> { println!("šŸš€ Traffic Monitor - Standalone Demo"); println!("=====================================\n"); // Configuration - default private networks let permitted_cidrs = vec![ "127.0.0.0/8", // Localhost "10.0.0.0/8", // Private Class A "172.16.0.0/12", // Private Class B "192.168.0.0/16", // Private Class C ]; println!("šŸ“‹ Configured permitted CIDR ranges:"); for (i, cidr) in permitted_cidrs.iter().enumerate() { println!(" {}. {}", i + 1, cidr); } println!(); // Create traffic monitor let mut monitor = TrafficMonitor::new(permitted_cidrs, false)?; // Simulate various traffic scenarios let test_packets = vec![ TrafficEvent { src_ip: "127.0.0.1".parse().unwrap(), dst_ip: "192.168.1.242".parse().unwrap(), src_port: 12345, dst_port: 80, protocol: "TCP", packet_size: 1500, action: "TEST", }, TrafficEvent { src_ip: "192.168.1.100".parse().unwrap(), dst_ip: "192.168.1.242".parse().unwrap(), src_port: 22, dst_port: 54321, protocol: "TCP", packet_size: 64, action: "TEST", }, TrafficEvent { src_ip: "10.0.0.50".parse().unwrap(), dst_ip: "192.168.1.242".parse().unwrap(), src_port: 443, dst_port: 12345, protocol: "TCP", packet_size: 1200, action: "TEST", }, TrafficEvent { src_ip: "8.8.8.8".parse().unwrap(), // Google DNS - NOT permitted dst_ip: "192.168.1.242".parse().unwrap(), src_port: 53, dst_port: 32768, protocol: "UDP", packet_size: 128, action: "TEST", }, TrafficEvent { src_ip: "1.1.1.1".parse().unwrap(), // Cloudflare DNS - NOT permitted dst_ip: "192.168.1.242".parse().unwrap(), src_port: 53, dst_port: 32769, protocol: "UDP", packet_size: 256, action: "TEST", }, TrafficEvent { src_ip: "52.85.83.228".parse().unwrap(), // Amazon AWS - NOT permitted dst_ip: "192.168.1.242".parse().unwrap(), src_port: 443, dst_port: 12346, protocol: "TCP", packet_size: 1500, action: "TEST", }, TrafficEvent { src_ip: "140.82.113.4".parse().unwrap(), // GitHub - NOT permitted dst_ip: "192.168.1.242".parse().unwrap(), src_port: 22, dst_port: 12347, protocol: "TCP", packet_size: 800, action: "TEST", }, TrafficEvent { src_ip: "172.16.0.10".parse().unwrap(), // Private Class B - permitted dst_ip: "192.168.1.242".parse().unwrap(), src_port: 8080, dst_port: 12348, protocol: "TCP", packet_size: 500, action: "TEST", }, ]; println!("šŸ” Processing simulated traffic packets:\n"); for (i, packet) in test_packets.iter().enumerate() { let action = monitor.process_packet(packet.clone()); if action == "ALLOWED" { println!("[ALLOWED] Permitted traffic: {}:{} -> {}:{} (proto: {}, size: {} bytes)", packet.src_ip, packet.src_port, packet.dst_ip, packet.dst_port, packet.protocol, packet.packet_size); } // Add some variety - simulate multiple packets from same IPs if i == 3 || i == 4 { // Repeat Google DNS and Cloudflare packets for _ in 0..3 { let mut repeat_packet = packet.clone(); repeat_packet.packet_size += 50; // Vary packet size monitor.process_packet(repeat_packet); } } } monitor.print_stats(); println!("\nšŸ–„ļø On a Linux system with this traffic monitor:"); println!("1. The eBPF program would run at the XDP layer on your WiFi interface"); println!("2. It would process packets at line speed (millions of packets per second)"); println!("3. Only non-permitted traffic would be sent to userspace for logging"); println!("4. Permitted traffic would pass through with minimal overhead"); println!("5. With --drop-packets, non-permitted traffic would be dropped in the kernel"); println!("\n🐧 To run the full version on Linux:"); println!("sudo ./target/release/traffic-monitor -i en0 -c configs/default.json"); println!("\n✨ Demo completed successfully!"); Ok(()) }