use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr};
use crate::types::{Connection, ThreatInfo, TcpState};
pub struct ThreatDetector {
flagged: HashSet<IpAddr>,
}
impl ThreatDetector {
pub fn new() -> Self {
Self {
flagged: HashSet::new(),
}
}
pub fn scan(&mut self, connections: &[Connection]) -> Vec<ThreatInfo> {
let mut threats = Vec::new();
for conn in connections {
if matches!(conn.state.as_ref(), Some(TcpState::Listen)) {
continue;
}
let Some(remote) = conn.remote_addr else { continue };
if remote.is_loopback() || remote.is_unspecified() {
continue;
}
if self.flagged.contains(&remote) {
continue;
}
if let Some(threat) = classify_ip(remote) {
self.flagged.insert(remote);
threats.push(threat);
}
}
threats
}
pub fn check_ip(&self, ip: IpAddr) -> Option<ThreatInfo> {
classify_ip(ip)
}
}
fn classify_ip(ip: IpAddr) -> Option<ThreatInfo> {
match ip {
IpAddr::V4(v4) => classify_ipv4(v4),
IpAddr::V6(_) => None, }
}
fn classify_ipv4(ip: Ipv4Addr) -> Option<ThreatInfo> {
let octets = ip.octets();
let ip_addr = IpAddr::V4(ip);
if is_bogon_remote(octets) {
return Some(ThreatInfo {
ip: ip_addr,
reason: "Bogon/reserved IP used as remote endpoint".to_string(),
});
}
if is_known_scanner(octets) {
return Some(ThreatInfo {
ip: ip_addr,
reason: "Known scanner/attacker IP range".to_string(),
});
}
if is_known_malware_range(octets) {
return Some(ThreatInfo {
ip: ip_addr,
reason: "Known malware C2 IP range".to_string(),
});
}
None
}
fn is_bogon_remote(octets: [u8; 4]) -> bool {
match octets[0] {
0 => true, 100 if octets[1] >= 64 && octets[1] <= 127 => true, 127 => true, 169 if octets[1] == 254 => true, 192 if octets[1] == 0 && octets[2] == 0 => true, 192 if octets[1] == 0 && octets[2] == 2 => true, 198 if octets[1] == 51 && octets[2] == 100 => true, 203 if octets[1] == 0 && octets[2] == 113 => true, 198 if octets[1] >= 18 && octets[1] <= 19 => true, 240..=255 => true, _ => false,
}
}
fn is_known_scanner(octets: [u8; 4]) -> bool {
let prefix16 = (octets[0] as u16) << 8 | octets[1] as u16;
matches!(prefix16,
0x8E_A3 | 0xB9_18 | 0xB9_E8 )
}
fn is_known_malware_range(octets: [u8; 4]) -> bool {
let _prefix16 = (octets[0] as u16) << 8 | octets[1] as u16;
false
}