699 lines
20 KiB
Rust
699 lines
20 KiB
Rust
use async_trait::async_trait;
|
|
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig};
|
|
use regex::Regex;
|
|
use std::collections::HashMap;
|
|
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, ConnSocket, 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 proc_map = load_windows_process_map();
|
|
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(mut socket) = parse_netstat_tcp_line(trimmed) {
|
|
enrich_socket(&mut socket, &proc_map);
|
|
sockets.push(socket);
|
|
}
|
|
} else if trimmed.starts_with("UDP") {
|
|
if let Some(mut socket) = parse_netstat_udp_line(trimmed) {
|
|
enrich_socket(&mut socket, &proc_map);
|
|
sockets.push(socket);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(sockets)
|
|
}
|
|
|
|
fn parse_windows_connections() -> Result<Vec<ConnSocket>, PlatformError> {
|
|
let proc_map = load_windows_process_map();
|
|
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") {
|
|
continue;
|
|
}
|
|
if let Some(mut socket) = parse_netstat_tcp_conn_line(trimmed) {
|
|
enrich_conn_socket(&mut socket, &proc_map);
|
|
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_tcp_conn_line(line: &str) -> Option<ConnSocket> {
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() < 5 {
|
|
return None;
|
|
}
|
|
let local = parts[1];
|
|
let remote = parts[2];
|
|
let state = parts[3];
|
|
let pid = parts[4].parse::<u32>().ok();
|
|
|
|
if state == "LISTENING" {
|
|
return None;
|
|
}
|
|
|
|
Some(ConnSocket {
|
|
proto: "tcp".to_string(),
|
|
local_addr: local.to_string(),
|
|
remote_addr: remote.to_string(),
|
|
state: Some(state.to_string()),
|
|
pid,
|
|
ppid: None,
|
|
process_name: None,
|
|
process_path: 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 enrich_socket(socket: &mut ListenSocket, map: &HashMap<u32, ProcInfo>) {
|
|
let pid = match socket.pid {
|
|
Some(pid) => pid,
|
|
None => return,
|
|
};
|
|
if let Some(info) = map.get(&pid) {
|
|
socket.process_name = info.name.clone();
|
|
socket.process_path = info.path.clone();
|
|
}
|
|
}
|
|
|
|
fn enrich_conn_socket(socket: &mut ConnSocket, map: &HashMap<u32, ProcInfo>) {
|
|
let pid = match socket.pid {
|
|
Some(pid) => pid,
|
|
None => return,
|
|
};
|
|
if let Some(info) = map.get(&pid) {
|
|
socket.process_name = info.name.clone();
|
|
socket.process_path = info.path.clone();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct ProcInfo {
|
|
name: Option<String>,
|
|
path: Option<String>,
|
|
}
|
|
|
|
fn load_windows_process_map() -> HashMap<u32, ProcInfo> {
|
|
let mut map = HashMap::new();
|
|
let mut name_map = HashMap::new();
|
|
let tasklist = std::process::Command::new("tasklist")
|
|
.args(["/fo", "csv", "/nh"])
|
|
.output();
|
|
if let Ok(output) = tasklist {
|
|
if output.status.success() {
|
|
let text = String::from_utf8_lossy(&output.stdout);
|
|
for line in text.lines() {
|
|
let parts = parse_csv_line(line);
|
|
if parts.len() < 2 {
|
|
continue;
|
|
}
|
|
if let Ok(pid) = parts[1].parse::<u32>() {
|
|
name_map.insert(pid, parts[0].to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let wmic = std::process::Command::new("wmic")
|
|
.args(["process", "get", "ProcessId,ExecutablePath", "/FORMAT:CSV"])
|
|
.output();
|
|
if let Ok(output) = wmic {
|
|
if output.status.success() {
|
|
let text = String::from_utf8_lossy(&output.stdout);
|
|
for line in text.lines() {
|
|
let parts = parse_csv_line(line);
|
|
if parts.len() < 3 {
|
|
continue;
|
|
}
|
|
let path = parts[1].trim();
|
|
let pid = parts[2].trim().parse::<u32>().ok();
|
|
if let Some(pid) = pid {
|
|
let name = name_map.get(&pid).cloned();
|
|
let path = if path.is_empty() {
|
|
None
|
|
} else {
|
|
Some(path.to_string())
|
|
};
|
|
map.insert(pid, ProcInfo { name, path });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (pid, name) in name_map {
|
|
map.entry(pid)
|
|
.or_insert_with(|| ProcInfo {
|
|
name: Some(name),
|
|
path: None,
|
|
});
|
|
}
|
|
|
|
map
|
|
}
|
|
|
|
fn parse_csv_line(line: &str) -> Vec<String> {
|
|
let mut out = Vec::new();
|
|
let mut current = String::new();
|
|
let mut in_quotes = false;
|
|
for ch in line.chars() {
|
|
match ch {
|
|
'"' => {
|
|
in_quotes = !in_quotes;
|
|
}
|
|
',' if !in_quotes => {
|
|
out.push(current.trim_matches('"').to_string());
|
|
current.clear();
|
|
}
|
|
_ => current.push(ch),
|
|
}
|
|
}
|
|
if !current.is_empty() {
|
|
out.push(current.trim_matches('"').to_string());
|
|
}
|
|
out
|
|
}
|
|
|
|
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 fn connections(&self) -> Result<Vec<ConnSocket>, PlatformError> {
|
|
parse_windows_connections()
|
|
}
|
|
}
|
|
|
|
#[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))
|
|
}
|
|
}
|