1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! 0.21 I.4: DGA (Domain Generation Algorithm) detection via
//! flowscope's `DgaScorer` + netring's `pattern_detector!` macro.
//!
//! Scores every DNS query name's second-level domain against the
//! bundled English-baseline bigram table. Below a log-likelihood
//! threshold = highly DGA-like = emit anomaly.
//!
//! Run:
//!
//! ```sh
//! cargo run --example monitor_dga_query \
//! --features "tokio,flow,dns" -- eth0
//! ```
use std::time::Duration;
use flowscope::detect::patterns::{DgaScore, DgaScorer};
use flowscope::dns::DnsMessage;
use netring::prelude::*;
/// Wraps the scorer + the most recent score so the macro's
/// `verdict:` body can pick up what `feed:` computed.
struct Dga {
scorer: DgaScorer,
last_query: Option<String>,
last_score: Option<DgaScore>,
}
impl Dga {
fn new() -> Self {
Self {
scorer: DgaScorer::new(),
last_query: None,
last_score: None,
}
}
}
/// Extract the second-level domain (e.g. "kj3h4kj.com" → "kj3h4kj").
fn extract_sld(qname: &str) -> Option<&str> {
let trimmed = qname.trim_end_matches('.');
let mut parts = trimmed.rsplit('.');
let _tld = parts.next()?;
parts.next()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let iface = std::env::args().nth(1).unwrap_or_else(|| "lo".into());
let dga = netring::pattern_detector! {
name: "DgaScorer",
event: Dns,
detector: Dga::new(),
feed: |msg, w| {
if let DnsMessage::Query(q) = msg
&& let Some(question) = q.questions.first()
&& let Some(sld) = extract_sld(&question.name) {
w.last_query = Some(sld.to_string());
w.last_score = Some(w.scorer.score(sld));
}
},
verdict: |_msg, w| {
// log_likelihood threshold: more negative = more
// DGA-like. -8.0 is a moderately aggressive cut.
w.last_score.as_ref().and_then(|s| {
if s.log_likelihood < -8.0 {
Some(*s)
} else {
None
}
})
},
};
Monitor::builder()
.interface(&iface)
.name("dga-watch")
.protocol::<Dns>()
.detect(dga)
.sink(StdoutSink::default())
.build()?
.run_for(Duration::from_secs(300))
.await?;
Ok(())
}