Add: dns leak detection
This commit is contained in:
102
crates/wtfnet-dnsleak/src/lib.rs
Normal file
102
crates/wtfnet-dnsleak/src/lib.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
mod classify;
|
||||
mod policy;
|
||||
mod privacy;
|
||||
mod report;
|
||||
mod route;
|
||||
mod rules;
|
||||
mod sensor;
|
||||
|
||||
use crate::classify::ClassifiedEvent;
|
||||
use crate::sensor::capture_events;
|
||||
use std::time::Instant;
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
use wtfnet_platform::{FlowOwnerProvider, FlowTuple};
|
||||
|
||||
pub use crate::policy::{LeakPolicy, LeakPolicyProfile, PolicySummary};
|
||||
pub use crate::privacy::{apply_privacy, PrivacyMode};
|
||||
pub use crate::report::{LeakEvent, LeakReport, LeakSummary, LeakTransport, RouteClass, Severity};
|
||||
pub use crate::sensor::{iface_diagnostics, IfaceDiag};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DnsLeakError {
|
||||
#[error("not supported: {0}")]
|
||||
NotSupported(String),
|
||||
#[error("io error: {0}")]
|
||||
Io(String),
|
||||
#[error("policy error: {0}")]
|
||||
Policy(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LeakWatchOptions {
|
||||
pub duration_ms: u64,
|
||||
pub iface: Option<String>,
|
||||
pub policy: LeakPolicy,
|
||||
pub privacy: PrivacyMode,
|
||||
pub include_events: bool,
|
||||
}
|
||||
|
||||
pub async fn watch(
|
||||
options: LeakWatchOptions,
|
||||
flow_owner: Option<&dyn FlowOwnerProvider>,
|
||||
) -> Result<LeakReport, DnsLeakError> {
|
||||
debug!(
|
||||
duration_ms = options.duration_ms,
|
||||
iface = ?options.iface,
|
||||
include_events = options.include_events,
|
||||
"dns leak watch start"
|
||||
);
|
||||
let start = Instant::now();
|
||||
let events = capture_events(&options).await?;
|
||||
let mut leak_events = Vec::new();
|
||||
|
||||
for event in events {
|
||||
let enriched = enrich_event(event, flow_owner).await;
|
||||
if let Some(decision) = rules::evaluate(&enriched, &options.policy) {
|
||||
let mut leak_event = report::LeakEvent::from_decision(enriched, decision);
|
||||
privacy::apply_privacy(&mut leak_event, options.privacy);
|
||||
leak_events.push(leak_event);
|
||||
}
|
||||
}
|
||||
|
||||
let summary = LeakSummary::from_events(&leak_events);
|
||||
let report = LeakReport {
|
||||
duration_ms: start.elapsed().as_millis() as u64,
|
||||
policy: options.policy.summary(),
|
||||
summary,
|
||||
events: if options.include_events {
|
||||
leak_events
|
||||
} else {
|
||||
Vec::new()
|
||||
},
|
||||
};
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
async fn enrich_event(
|
||||
event: ClassifiedEvent,
|
||||
flow_owner: Option<&dyn FlowOwnerProvider>,
|
||||
) -> report::EnrichedEvent {
|
||||
let mut enriched = route::enrich_route(event);
|
||||
if let Some(provider) = flow_owner {
|
||||
let flow = FlowTuple {
|
||||
proto: enriched.proto,
|
||||
src_ip: enriched.src_ip,
|
||||
src_port: enriched.src_port,
|
||||
dst_ip: enriched.dst_ip,
|
||||
dst_port: enriched.dst_port,
|
||||
};
|
||||
match provider.owner_of(flow).await {
|
||||
Ok(result) => {
|
||||
enriched.owner = result.owner;
|
||||
enriched.owner_confidence = result.confidence;
|
||||
enriched.owner_failure = result.failure_reason;
|
||||
}
|
||||
Err(err) => {
|
||||
enriched.owner_failure = Some(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
enriched
|
||||
}
|
||||
Reference in New Issue
Block a user