You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aya/traffic-monitor/examples/standalone-demo.rs

290 lines
9.1 KiB
Rust

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

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<Self, String> {
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<CidrRange>,
drop_packets: bool,
stats: HashMap<Ipv4Addr, (u64, u64)>, // (packet_count, total_bytes)
}
impl TrafficMonitor {
fn new(cidr_strings: Vec<&str>, drop_packets: bool) -> Result<Self, String> {
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(())
}