feat: protocol-aware min_packets thresholds (T15)

Replace global min_packets filter with protocol/port-aware thresholds:
- ICMP: 1 packet (ping sweeps, SYN scans)
- UDP/53 (DNS): 1 packet (amplification, DGA, tunneling)
- UDP/123 (NTP): 2 packets (amplification)
- TCP/53 (DNS over TCP): 2 packets
- TCP C2 ports (4444, 8443, 8080, 1337, 31337): 2 packets
- Stratum mining ports (3333, 45700): 2 packets (TCP+UDP)
- All other traffic: global configured threshold (default 5)

Closes the evasion vector where 1-2 packet flows bypass ML entirely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
DaLaw2 2026-04-03 17:24:01 +08:00
parent 8d020c27bd
commit f972ab69be

View File

@ -89,6 +89,36 @@ impl Engine {
self.traffic_logger.is_some()
}
/// Protocol/port-aware min_packets: some traffic patterns are meaningful
/// at very low packet counts and would be invisible to ML at the global threshold.
fn effective_min_packets(flow_key: &crate::model::detection::ml_detection::FlowKey, global: usize) -> usize {
match flow_key.protocol {
// ICMP: single-packet SYN scans, ping sweeps
1 => 1,
// UDP
17 => match flow_key.dst_port {
// DNS: amplification, DGA, tunneling can be 1-2 packets
53 => 1,
// NTP amplification
123 => 2,
// Known mining pool ports
3333 | 45700 => 2,
_ => global,
},
// TCP
6 => match flow_key.dst_port {
// DNS over TCP
53 => 2,
// Common C2 ports: Metasploit, Cobalt Strike, reverse shells
4444 | 8443 | 8080 | 1337 | 31337 => 2,
// Stratum mining
3333 | 45700 => 2,
_ => global,
},
_ => global,
}
}
pub async fn run(self: Arc<Self>) -> oneshot::Sender<()> {
let (shutdown_tx, shutdown_rx) = oneshot::channel();
tokio::spawn(async move {
@ -136,7 +166,7 @@ impl Engine {
all_flows.extend(
t.get_uninferred_flows()
.into_iter()
.filter(|flow| flow.packet_count() >= self.min_packets),
.filter(|flow| flow.packet_count() >= Self::effective_min_packets(&flow.flow_key, self.min_packets)),
);
// lock released here
}