Add: dns leak detection
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user