Add base subcrates

This commit is contained in:
DaZuo0122
2026-01-16 00:38:03 +08:00
commit 240107e00f
17 changed files with 5081 additions and 0 deletions

View File

@@ -0,0 +1,529 @@
use async_trait::async_trait;
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig};
use regex::Regex;
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,
OID_KEY_TYPE_GOST_R3410_2012_512, OID_PKCS1_RSAENCRYPTION,
};
use std::sync::Arc;
use wtfnet_core::ErrorCode;
use wtfnet_platform::{
CertProvider, DnsConfigSnapshot, ListenSocket, NeighborEntry, NeighProvider, NetInterface,
Platform, PlatformError, PortsProvider, RootCert, RouteEntry, SysProvider,
};
pub fn platform() -> Platform {
Platform {
sys: Arc::new(WindowsSysProvider),
ports: Arc::new(WindowsPortsProvider),
cert: Arc::new(WindowsCertProvider),
neigh: Arc::new(WindowsNeighProvider),
}
}
struct WindowsSysProvider;
struct WindowsPortsProvider;
struct WindowsCertProvider;
struct WindowsNeighProvider;
#[async_trait]
impl SysProvider for WindowsSysProvider {
async fn interfaces(&self) -> Result<Vec<NetInterface>, PlatformError> {
let interfaces = NetworkInterface::show()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
Ok(interfaces.into_iter().map(map_interface).collect())
}
async fn routes(&self) -> Result<Vec<RouteEntry>, PlatformError> {
let interfaces = NetworkInterface::show()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
parse_windows_routes(&interfaces)
}
async fn dns_config(&self) -> Result<DnsConfigSnapshot, PlatformError> {
let output = std::process::Command::new("ipconfig")
.arg("/all")
.output()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
if !output.status.success() {
return Err(PlatformError::new(
ErrorCode::IoError,
"ipconfig /all failed",
));
}
let text = String::from_utf8_lossy(&output.stdout);
Ok(parse_ipconfig_dns(&text))
}
}
fn map_interface(iface: NetworkInterface) -> NetInterface {
let addresses = iface
.addr
.into_iter()
.map(|addr| match addr {
Addr::V4(v4) => wtfnet_platform::NetAddress {
ip: v4.ip.to_string(),
prefix_len: prefix_from_v4_netmask(v4.netmask),
scope: None,
},
Addr::V6(v6) => wtfnet_platform::NetAddress {
ip: v6.ip.to_string(),
prefix_len: prefix_from_v6_netmask(v6.netmask),
scope: None,
},
})
.collect();
NetInterface {
name: iface.name,
index: Some(iface.index),
is_up: None,
mtu: None,
mac: iface.mac_addr,
addresses,
}
}
fn prefix_from_v4_netmask(netmask: Option<std::net::Ipv4Addr>) -> Option<u8> {
netmask.map(|mask| u32::from_be_bytes(mask.octets()).count_ones() as u8)
}
fn prefix_from_v6_netmask(netmask: Option<std::net::Ipv6Addr>) -> Option<u8> {
netmask.map(|mask| u128::from_be_bytes(mask.octets()).count_ones() as u8)
}
fn parse_windows_routes(
interfaces: &[NetworkInterface],
) -> Result<Vec<RouteEntry>, PlatformError> {
let output = std::process::Command::new("route")
.arg("print")
.output()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
if !output.status.success() {
return Err(PlatformError::new(
ErrorCode::IoError,
"route print failed",
));
}
let text = String::from_utf8_lossy(&output.stdout);
let mut routes = Vec::new();
routes.extend(parse_windows_ipv4_routes(&text, interfaces));
routes.extend(parse_windows_ipv6_routes(&text, interfaces));
Ok(routes)
}
fn parse_windows_ipv4_routes(
text: &str,
interfaces: &[NetworkInterface],
) -> Vec<RouteEntry> {
let mut routes = Vec::new();
let mut in_ipv4 = false;
let mut in_active = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("IPv4 Route Table") {
in_ipv4 = true;
in_active = false;
continue;
}
if trimmed.starts_with("IPv6 Route Table") {
in_ipv4 = false;
in_active = false;
continue;
}
if !in_ipv4 {
continue;
}
if trimmed.starts_with("Active Routes:") {
in_active = true;
continue;
}
if !in_active {
continue;
}
if trimmed.starts_with("====") || trimmed.is_empty() || trimmed.starts_with("Network") {
continue;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 5 {
continue;
}
let destination = parts[0];
let netmask = parts[1];
let gateway = parts[2];
let interface_addr = parts[3];
let metric = parts[4].parse::<u32>().ok();
let prefix = parse_ipv4_prefix(netmask);
let destination = if let Some(prefix) = prefix {
format!("{}/{}", destination, prefix)
} else {
destination.to_string()
};
let iface = interface_name_from_ip(interfaces, interface_addr);
routes.push(RouteEntry {
destination,
gateway: Some(gateway.to_string()).filter(|g| g != "0.0.0.0"),
interface: iface,
metric,
});
}
routes
}
fn parse_windows_ipv6_routes(
text: &str,
interfaces: &[NetworkInterface],
) -> Vec<RouteEntry> {
let mut routes = Vec::new();
let mut in_ipv6 = false;
let mut in_active = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("IPv6 Route Table") {
in_ipv6 = true;
in_active = false;
continue;
}
if trimmed.starts_with("====") && in_ipv6 && in_active {
break;
}
if !in_ipv6 {
continue;
}
if trimmed.starts_with("Active Routes:") {
in_active = true;
continue;
}
if !in_active {
continue;
}
if trimmed.is_empty() || trimmed.starts_with("If") {
continue;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 4 {
continue;
}
let iface_index = parts[0];
let metric = parts[1].parse::<u32>().ok();
let destination = parts[2].to_string();
let gateway = parts[3].to_string();
let iface = interface_name_from_index(interfaces, iface_index);
routes.push(RouteEntry {
destination,
gateway: Some(gateway).filter(|g| g != "On-link"),
interface: iface,
metric,
});
}
routes
}
fn parse_ipv4_prefix(netmask: &str) -> Option<u32> {
let mask: std::net::Ipv4Addr = netmask.parse().ok()?;
Some(u32::from_be_bytes(mask.octets()).count_ones())
}
fn interface_name_from_ip(
interfaces: &[NetworkInterface],
addr: &str,
) -> Option<String> {
let parsed: std::net::IpAddr = addr.parse().ok()?;
for iface in interfaces {
if iface.addr.iter().any(|entry| entry.ip() == parsed) {
return Some(iface.name.clone());
}
}
None
}
fn interface_name_from_index(
interfaces: &[NetworkInterface],
index: &str,
) -> Option<String> {
let index = index.parse::<u32>().ok()?;
interfaces
.iter()
.find(|iface| iface.index == index)
.map(|iface| iface.name.clone())
}
fn parse_ipconfig_dns(text: &str) -> DnsConfigSnapshot {
let mut servers = Vec::new();
let mut search_domains = Vec::new();
let dns_server_re = Regex::new(r"^DNS Servers?\s*[:.]\s*(.+)$").unwrap();
let dns_suffix_re = Regex::new(r"^DNS Suffix Search List\.?\s*[:.]\s*(.+)$").unwrap();
let mut in_dns_servers = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
in_dns_servers = false;
continue;
}
if let Some(caps) = dns_server_re.captures(trimmed) {
if let Some(value) = caps.get(1) {
servers.push(value.as_str().to_string());
}
in_dns_servers = true;
continue;
}
if in_dns_servers && !trimmed.contains(':') {
servers.push(trimmed.to_string());
continue;
}
if let Some(caps) = dns_suffix_re.captures(trimmed) {
if let Some(value) = caps.get(1) {
let list = value.as_str();
for entry in list.split_whitespace() {
search_domains.push(entry.to_string());
}
}
continue;
}
}
DnsConfigSnapshot {
servers,
search_domains,
}
}
fn parse_windows_listeners() -> Result<Vec<ListenSocket>, 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 sockets = Vec::new();
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("TCP") {
if let Some(socket) = parse_netstat_tcp_line(trimmed) {
sockets.push(socket);
}
} else if trimmed.starts_with("UDP") {
if let Some(socket) = parse_netstat_udp_line(trimmed) {
sockets.push(socket);
}
}
}
Ok(sockets)
}
fn parse_netstat_tcp_line(line: &str) -> Option<ListenSocket> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 5 {
return None;
}
let local = parts[1];
let state = parts[3];
let pid = parts[4].parse::<u32>().ok();
if state != "LISTENING" {
return None;
}
Some(ListenSocket {
proto: "tcp".to_string(),
local_addr: local.to_string(),
state: Some(state.to_string()),
pid,
ppid: None,
process_name: None,
process_path: None,
owner: None,
})
}
fn parse_netstat_udp_line(line: &str) -> Option<ListenSocket> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 4 {
return None;
}
let local = parts[1];
let pid = parts[3].parse::<u32>().ok();
Some(ListenSocket {
proto: "udp".to_string(),
local_addr: local.to_string(),
state: None,
pid,
ppid: None,
process_name: None,
process_path: None,
owner: None,
})
}
fn parse_arp_output(text: &str) -> Vec<NeighborEntry> {
let mut neighbors = Vec::new();
let mut current_iface = None;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("Interface:") {
current_iface = trimmed
.split_whitespace()
.nth(1)
.map(|value| value.to_string());
continue;
}
if trimmed.starts_with("Internet Address") || trimmed.is_empty() {
continue;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() < 3 {
continue;
}
neighbors.push(NeighborEntry {
ip: parts[0].to_string(),
mac: Some(parts[1].to_string()),
interface: current_iface.clone(),
state: Some(parts[2].to_string()),
});
}
neighbors
}
fn extract_port(value: &str) -> Option<u16> {
if let Some(pos) = value.rfind(':') {
return value[pos + 1..].parse::<u16>().ok();
}
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()))?;
let mut roots = Vec::new();
for cert in certs {
let der = cert.as_ref();
let parsed = match x509_parser::parse_x509_certificate(der) {
Ok((_, cert)) => cert,
Err(_) => continue,
};
let subject = parsed.subject().to_string();
let issuer = parsed.issuer().to_string();
let not_before = parsed.validity().not_before.to_string();
let not_after = parsed.validity().not_after.to_string();
let serial = parsed.tbs_certificate.raw_serial_as_string();
let sha1 = format_fingerprint(sha1::Sha1::digest(der).as_slice());
let sha256 = format_fingerprint(sha2::Sha256::digest(der).as_slice());
let (key_algorithm, key_size) = key_info(&parsed);
roots.push(RootCert {
subject,
issuer,
not_before,
not_after,
serial_number: serial,
sha1,
sha256,
key_algorithm,
key_size,
store: Some(store.to_string()),
});
}
Ok(roots)
}
fn key_info(cert: &x509_parser::certificate::X509Certificate<'_>) -> (String, Option<u32>) {
let algorithm = &cert.subject_pki.algorithm.algorithm;
let name = if algorithm == &OID_PKCS1_RSAENCRYPTION {
"RSA"
} else if algorithm == &OID_KEY_TYPE_EC_PUBLIC_KEY {
"EC"
} else if algorithm == &OID_KEY_TYPE_DSA {
"DSA"
} else if algorithm == &OID_KEY_TYPE_GOST_R3410_2012_256 {
"GOST2012-256"
} else if algorithm == &OID_KEY_TYPE_GOST_R3410_2012_512 {
"GOST2012-512"
} else {
"Unknown"
};
let key_size = cert
.subject_pki
.parsed()
.ok()
.map(|key| key.key_size() as u32)
.filter(|size| *size > 0);
(name.to_string(), key_size)
}
fn format_fingerprint(bytes: &[u8]) -> String {
let mut out = String::new();
for (idx, byte) in bytes.iter().enumerate() {
if idx > 0 {
out.push(':');
}
use std::fmt::Write;
let _ = write!(out, "{:02x}", byte);
}
out
}
#[async_trait]
impl PortsProvider for WindowsPortsProvider {
async fn listening(&self) -> Result<Vec<ListenSocket>, PlatformError> {
let sockets = parse_windows_listeners()?;
Ok(sockets)
}
async fn who_owns(&self, port: u16) -> Result<Vec<ListenSocket>, PlatformError> {
let sockets = parse_windows_listeners()?;
Ok(sockets
.into_iter()
.filter(|socket| extract_port(&socket.local_addr) == Some(port))
.collect())
}
}
#[async_trait]
impl CertProvider for WindowsCertProvider {
async fn trusted_roots(&self) -> Result<Vec<RootCert>, PlatformError> {
load_native_roots("windows")
}
}
#[async_trait]
impl NeighProvider for WindowsNeighProvider {
async fn neighbors(&self) -> Result<Vec<NeighborEntry>, PlatformError> {
let output = std::process::Command::new("arp")
.arg("-a")
.output()
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
if !output.status.success() {
return Err(PlatformError::new(ErrorCode::IoError, "arp -a failed"));
}
let text = String::from_utf8_lossy(&output.stdout);
Ok(parse_arp_output(&text))
}
}