use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::time::{Duration, Instant};
use crate::{Packet, ThreatType, Severity, Anomaly, ThreatIndicator, ThreatLevel};
pub struct ThreatConfig {
pub port_scan_window: Duration,
pub port_scan_threshold: usize,
pub ddos_window: Duration,
pub ddos_packet_rate_threshold: f64,
pub syn_flood_threshold: usize,
pub connection_window: Duration,
}
impl Default for ThreatConfig {
fn default() -> Self {
Self {
port_scan_window: Duration::from_secs(60),
port_scan_threshold: 20,
ddos_window: Duration::from_secs(10),
ddos_packet_rate_threshold: 999.0, syn_flood_threshold: 100,
connection_window: Duration::from_secs(300),
}
}
}
#[derive(Debug, Clone)]
struct ConnectionRecord {
#[allow(dead_code)]
source_ip: IpAddr,
port: u16,
timestamp: Instant,
}
#[derive(Debug)]
struct PacketStats {
packet_count: usize,
syn_count: usize,
window_start: Instant,
}
pub struct ThreatDetector {
config: ThreatConfig,
connections: HashMap<IpAddr, Vec<ConnectionRecord>>,
packet_stats: PacketStats,
current_threat_type: ThreatType,
threat_indicators: Vec<ThreatIndicator>,
}
impl ThreatDetector {
pub fn new() -> Self {
Self {
config: ThreatConfig::default(),
connections: HashMap::new(),
packet_stats: PacketStats {
packet_count: 0,
syn_count: 0,
window_start: Instant::now(),
},
current_threat_type: ThreatType::Unknown,
threat_indicators: Vec::new(),
}
}
pub fn add_connection(&mut self, ip: IpAddr, port: u16) {
let record = ConnectionRecord {
source_ip: ip,
port,
timestamp: Instant::now(),
};
self.connections
.entry(ip)
.or_insert_with(Vec::new)
.push(record);
self.clean_old_connections();
}
pub fn is_port_scan(&self, ip: IpAddr) -> bool {
if let Some(connections) = self.connections.get(&ip) {
let now = Instant::now();
let recent_ports: HashSet<u16> = connections
.iter()
.filter(|conn| now.duration_since(conn.timestamp) <= self.config.port_scan_window)
.map(|conn| conn.port)
.collect();
recent_ports.len() >= self.config.port_scan_threshold
} else {
false
}
}
pub fn analyze_packet(&mut self, packet: &Packet) {
let now = Instant::now();
if now.duration_since(self.packet_stats.window_start) > self.config.ddos_window {
self.packet_stats = PacketStats {
packet_count: 0,
syn_count: 0,
window_start: now,
};
}
self.packet_stats.packet_count += 1;
if is_syn_packet(packet) {
self.packet_stats.syn_count += 1;
}
if self.packet_stats.syn_count >= self.config.syn_flood_threshold {
self.current_threat_type = ThreatType::SynFlood;
}
}
pub fn is_ddos_active(&self) -> bool {
let elapsed = Instant::now().duration_since(self.packet_stats.window_start);
let elapsed_secs = elapsed.as_secs_f64().max(1.0);
let packet_rate = self.packet_stats.packet_count as f64 / elapsed_secs;
packet_rate > self.config.ddos_packet_rate_threshold
}
pub fn get_threat_type(&self) -> ThreatType {
self.current_threat_type.clone()
}
pub fn detect_anomaly(&mut self, packet: &Packet) -> Option<Anomaly> {
if packet.data.len() < 20 {
return Some(Anomaly {
severity: Severity::High,
});
}
if let Some(port) = extract_destination_port(packet) {
if port == 31337 {
return Some(Anomaly {
severity: Severity::Medium,
});
}
}
if packet.data.iter().all(|&b| b == 0xFF) {
return Some(Anomaly {
severity: Severity::High,
});
}
None
}
pub fn add_threat_indicator(&mut self, indicator: ThreatIndicator) {
self.threat_indicators.push(indicator);
}
pub fn get_threat_level(&self) -> ThreatLevel {
match self.threat_indicators.len() {
0 => ThreatLevel::Low,
1 => ThreatLevel::Medium,
2 => ThreatLevel::High,
_ => ThreatLevel::Critical,
}
}
fn clean_old_connections(&mut self) {
let now = Instant::now();
for connections in self.connections.values_mut() {
connections.retain(|conn| {
now.duration_since(conn.timestamp) <= self.config.connection_window
});
}
self.connections.retain(|_, conns| !conns.is_empty());
}
}
fn is_syn_packet(packet: &Packet) -> bool {
packet.data.len() >= 20 && packet.data[9] == 0x06 }
fn extract_destination_port(packet: &Packet) -> Option<u16> {
if packet.data.len() >= 24 {
let port_bytes = &packet.data[22..24];
Some(u16::from_be_bytes([port_bytes[0], port_bytes[1]]))
} else {
None
}
}