Add: dns leak detection

This commit is contained in:
DaZuo0122
2026-01-17 18:45:24 +08:00
parent ccd4a31d21
commit cfa96bde08
30 changed files with 3973 additions and 16 deletions

View File

@@ -2,6 +2,7 @@ use async_trait::async_trait;
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig};
use regex::Regex;
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use sha2::Digest;
use x509_parser::oid_registry::{
OID_KEY_TYPE_DSA, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_KEY_TYPE_GOST_R3410_2012_256,
@@ -10,7 +11,8 @@ use x509_parser::oid_registry::{
use std::sync::Arc;
use wtfnet_core::ErrorCode;
use wtfnet_platform::{
CertProvider, ConnSocket, DnsConfigSnapshot, ListenSocket, NeighborEntry, NeighProvider,
CertProvider, ConnSocket, DnsConfigSnapshot, FlowOwner, FlowOwnerConfidence, FlowOwnerProvider,
FlowOwnerResult, FlowProtocol, FlowTuple, ListenSocket, NeighborEntry, NeighProvider,
NetInterface, Platform, PlatformError, PortsProvider, RootCert, RouteEntry, SysProvider,
};
@@ -20,6 +22,7 @@ pub fn platform() -> Platform {
ports: Arc::new(WindowsPortsProvider),
cert: Arc::new(WindowsCertProvider),
neigh: Arc::new(WindowsNeighProvider),
flow_owner: Arc::new(WindowsFlowOwnerProvider),
}
}
@@ -27,6 +30,7 @@ struct WindowsSysProvider;
struct WindowsPortsProvider;
struct WindowsCertProvider;
struct WindowsNeighProvider;
struct WindowsFlowOwnerProvider;
#[async_trait]
impl SysProvider for WindowsSysProvider {
@@ -579,6 +583,155 @@ fn parse_csv_line(line: &str) -> Vec<String> {
out
}
#[derive(Clone)]
struct FlowEntry {
proto: FlowProtocol,
local: SocketAddr,
remote: Option<SocketAddr>,
pid: u32,
}
fn parse_netstat_flow_entries() -> Result<Vec<FlowEntry>, PlatformError> {
let output = std::process::Command::new("netstat")
.arg("-ano")
.output()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
if !output.status.success() {
return Err(PlatformError::new(ErrorCode::IoError, "netstat -ano failed"));
}
let text = String::from_utf8_lossy(&output.stdout);
let mut entries = Vec::new();
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("TCP") {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 5 {
continue;
}
let state = parts[3];
if state == "LISTENING" {
continue;
}
let local = match parse_netstat_addr(parts[1]) {
Some(addr) => addr,
None => continue,
};
let remote = match parse_netstat_addr(parts[2]) {
Some(addr) => addr,
None => continue,
};
let pid = match parts[4].parse::<u32>() {
Ok(pid) => pid,
Err(_) => continue,
};
entries.push(FlowEntry {
proto: FlowProtocol::Tcp,
local,
remote: Some(remote),
pid,
});
} else if trimmed.starts_with("UDP") {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 4 {
continue;
}
let local = match parse_netstat_addr(parts[1]) {
Some(addr) => addr,
None => continue,
};
let pid = match parts[3].parse::<u32>() {
Ok(pid) => pid,
Err(_) => continue,
};
entries.push(FlowEntry {
proto: FlowProtocol::Udp,
local,
remote: None,
pid,
});
}
}
Ok(entries)
}
fn parse_netstat_addr(value: &str) -> Option<SocketAddr> {
let value = value.trim();
if value == "*:*" {
return None;
}
if let Some(rest) = value.strip_prefix('[') {
let end = rest.find(']')?;
let host = &rest[..end];
let port = rest[end + 2..].parse::<u16>().ok()?;
let host = host.split('%').next().unwrap_or(host);
let ip: IpAddr = host.parse().ok()?;
return Some(SocketAddr::new(ip, port));
}
let pos = value.rfind(':')?;
let host = &value[..pos];
let port = value[pos + 1..].parse::<u16>().ok()?;
let ip: IpAddr = host.parse().ok()?;
Some(SocketAddr::new(ip, port))
}
fn resolve_flow_owner(flow: &FlowTuple) -> Result<FlowOwnerResult, PlatformError> {
let entries = parse_netstat_flow_entries()?;
let proc_map = load_windows_process_map();
let mut matched: Option<(u32, FlowOwnerConfidence)> = None;
for entry in entries {
if entry.proto != flow.proto {
continue;
}
let local_match = entry.local.ip() == flow.src_ip && entry.local.port() == flow.src_port;
if !local_match {
continue;
}
match flow.proto {
FlowProtocol::Tcp => {
if let Some(remote) = entry.remote {
if remote.ip() == flow.dst_ip && remote.port() == flow.dst_port {
matched = Some((entry.pid, FlowOwnerConfidence::High));
break;
}
}
}
FlowProtocol::Udp => {
matched = Some((entry.pid, FlowOwnerConfidence::Medium));
break;
}
}
}
let (pid, confidence) = match matched {
Some(value) => value,
None => {
return Ok(FlowOwnerResult {
owner: None,
confidence: FlowOwnerConfidence::None,
failure_reason: Some("no socket match".to_string()),
})
}
};
let info = proc_map.get(&pid);
let owner = Some(FlowOwner {
pid: Some(pid),
ppid: None,
process_name: info.and_then(|value| value.name.clone()),
process_path: info.and_then(|value| value.path.clone()),
});
Ok(FlowOwnerResult {
owner,
confidence,
failure_reason: None,
})
}
fn load_native_roots(store: &str) -> Result<Vec<RootCert>, PlatformError> {
let certs = rustls_native_certs::load_native_certs()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
@@ -696,3 +849,10 @@ impl NeighProvider for WindowsNeighProvider {
Ok(parse_arp_output(&text))
}
}
#[async_trait]
impl FlowOwnerProvider for WindowsFlowOwnerProvider {
async fn owner_of(&self, flow: FlowTuple) -> Result<FlowOwnerResult, PlatformError> {
resolve_flow_owner(&flow)
}
}