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,16 @@
[package]
name = "wtfnet-platform-linux"
version = "0.1.0"
edition = "2024"
[dependencies]
async-trait = "0.1"
network-interface = "1"
resolv-conf = "0.7"
rustls-native-certs = "0.7"
sha1 = "0.10"
sha2 = "0.10"
time = { version = "0.3", features = ["formatting"] }
x509-parser = "0.16"
wtfnet-platform = { path = "../wtfnet-platform" }
wtfnet-core = { path = "../wtfnet-core" }

View File

@@ -0,0 +1,411 @@
use async_trait::async_trait;
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig};
use sha2::Digest;
use std::sync::Arc;
use wtfnet_core::ErrorCode;
use wtfnet_platform::{
CertProvider, DnsConfigSnapshot, ListenSocket, NeighborEntry, NeighProvider, NetInterface,
Platform, PlatformError, PortsProvider, RootCert, RouteEntry, SysProvider,
};
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,
};
pub fn platform() -> Platform {
Platform {
sys: Arc::new(LinuxSysProvider),
ports: Arc::new(LinuxPortsProvider),
cert: Arc::new(LinuxCertProvider),
neigh: Arc::new(LinuxNeighProvider),
}
}
struct LinuxSysProvider;
struct LinuxPortsProvider;
struct LinuxCertProvider;
struct LinuxNeighProvider;
#[async_trait]
impl SysProvider for LinuxSysProvider {
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 mut routes = Vec::new();
routes.extend(parse_ipv4_routes()?);
routes.extend(parse_ipv6_routes()?);
Ok(routes)
}
async fn dns_config(&self) -> Result<DnsConfigSnapshot, PlatformError> {
let contents = std::fs::read_to_string("/etc/resolv.conf")
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let cfg = resolv_conf::Config::parse(&contents)
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let servers = cfg
.nameservers
.iter()
.map(|ns| ns.to_string())
.collect();
let search_domains = cfg
.get_last_search_or_domain()
.map(|domain| domain.to_string())
.collect();
Ok(DnsConfigSnapshot {
servers,
search_domains,
})
}
}
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_ipv4_routes() -> Result<Vec<RouteEntry>, PlatformError> {
let contents = std::fs::read_to_string("/proc/net/route")
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let mut routes = Vec::new();
for (idx, line) in contents.lines().enumerate() {
if idx == 0 {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 8 {
continue;
}
let iface = parts[0].to_string();
let dest = parse_ipv4_hex(parts[1]);
let gateway = parse_ipv4_hex(parts[2]);
let mask = parse_ipv4_hex(parts[7]);
let metric = parts[6].parse::<u32>().ok();
let destination = match (dest, mask) {
(Some(dest), Some(mask)) => {
let prefix = u32::from(mask).count_ones();
format!("{}/{}", dest, prefix)
}
(Some(dest), None) => dest.to_string(),
_ => continue,
};
routes.push(RouteEntry {
destination,
gateway: gateway.map(|ip| ip.to_string()).filter(|ip| ip != "0.0.0.0"),
interface: Some(iface),
metric,
});
}
Ok(routes)
}
fn parse_ipv6_routes() -> Result<Vec<RouteEntry>, PlatformError> {
let contents = std::fs::read_to_string("/proc/net/ipv6_route")
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let mut routes = Vec::new();
for line in contents.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 10 {
continue;
}
let dest = parse_ipv6_hex(parts[0]);
let dest_prefix = u32::from_str_radix(parts[1], 16).ok();
let gateway = parse_ipv6_hex(parts[4]);
let metric = u32::from_str_radix(parts[5], 16).ok();
let iface = parts[9].to_string();
let destination = match (dest, dest_prefix) {
(Some(dest), Some(prefix)) => format!("{}/{}", dest, prefix),
(Some(dest), None) => dest.to_string(),
_ => continue,
};
routes.push(RouteEntry {
destination,
gateway: gateway.map(|ip| ip.to_string()).filter(|ip| ip != "::"),
interface: Some(iface),
metric,
});
}
Ok(routes)
}
fn parse_ipv4_hex(value: &str) -> Option<std::net::Ipv4Addr> {
if value.len() != 8 {
return None;
}
let raw = u32::from_str_radix(value, 16).ok()?;
let bytes = raw.to_le_bytes();
Some(std::net::Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]))
}
fn parse_ipv6_hex(value: &str) -> Option<std::net::Ipv6Addr> {
if value.len() != 32 {
return None;
}
let mut bytes = [0u8; 16];
for i in 0..16 {
let start = i * 2;
let chunk = &value[start..start + 2];
bytes[i] = u8::from_str_radix(chunk, 16).ok()?;
}
Some(std::net::Ipv6Addr::from(bytes))
}
fn parse_linux_tcp(path: &str, is_v6: bool) -> Result<Vec<ListenSocket>, PlatformError> {
let contents = std::fs::read_to_string(path)
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let mut sockets = Vec::new();
for (idx, line) in contents.lines().enumerate() {
if idx == 0 {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 4 {
continue;
}
let local = parts[1];
let state = parts[3];
if state != "0A" {
continue;
}
if let Some(local_addr) = parse_proc_socket_addr(local, is_v6) {
sockets.push(ListenSocket {
proto: "tcp".to_string(),
local_addr,
state: Some("LISTEN".to_string()),
pid: None,
ppid: None,
process_name: None,
process_path: None,
owner: None,
});
}
}
Ok(sockets)
}
fn parse_linux_udp(path: &str, is_v6: bool) -> Result<Vec<ListenSocket>, PlatformError> {
let contents = std::fs::read_to_string(path)
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
let mut sockets = Vec::new();
for (idx, line) in contents.lines().enumerate() {
if idx == 0 {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
continue;
}
let local = parts[1];
if let Some(local_addr) = parse_proc_socket_addr(local, is_v6) {
sockets.push(ListenSocket {
proto: "udp".to_string(),
local_addr,
state: None,
pid: None,
ppid: None,
process_name: None,
process_path: None,
owner: None,
});
}
}
Ok(sockets)
}
fn parse_proc_socket_addr(value: &str, is_v6: bool) -> Option<String> {
let mut parts = value.split(':');
let addr_hex = parts.next()?;
let port_hex = parts.next()?;
let port = u16::from_str_radix(port_hex, 16).ok()?;
if is_v6 {
let addr = parse_ipv6_hex(addr_hex)?;
Some(format!("[{}]:{}", addr, port))
} else {
let addr = parse_ipv4_hex(addr_hex)?;
Some(format!("{}:{}", addr, port))
}
}
fn parse_linux_arp(contents: &str) -> Vec<NeighborEntry> {
let mut neighbors = Vec::new();
for (idx, line) in contents.lines().enumerate() {
if idx == 0 {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 6 {
continue;
}
let flags = parts[2];
let state = match flags {
"0x2" => Some("reachable".to_string()),
_ => Some("stale".to_string()),
};
neighbors.push(NeighborEntry {
ip: parts[0].to_string(),
mac: Some(parts[3].to_string()).filter(|mac| mac != "00:00:00:00:00:00"),
interface: Some(parts[5].to_string()),
state,
});
}
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 LinuxPortsProvider {
async fn listening(&self) -> Result<Vec<ListenSocket>, PlatformError> {
let mut sockets = Vec::new();
sockets.extend(parse_linux_tcp("/proc/net/tcp", false)?);
sockets.extend(parse_linux_tcp("/proc/net/tcp6", true)?);
sockets.extend(parse_linux_udp("/proc/net/udp", false)?);
sockets.extend(parse_linux_udp("/proc/net/udp6", true)?);
Ok(sockets)
}
async fn who_owns(&self, port: u16) -> Result<Vec<ListenSocket>, PlatformError> {
let sockets = self.listening().await?;
Ok(sockets
.into_iter()
.filter(|socket| extract_port(&socket.local_addr) == Some(port))
.collect())
}
}
#[async_trait]
impl CertProvider for LinuxCertProvider {
async fn trusted_roots(&self) -> Result<Vec<RootCert>, PlatformError> {
load_native_roots("linux")
}
}
#[async_trait]
impl NeighProvider for LinuxNeighProvider {
async fn neighbors(&self) -> Result<Vec<NeighborEntry>, PlatformError> {
let contents = std::fs::read_to_string("/proc/net/arp")
.map_err(|err| PlatformError::new(ErrorCode::IoError, err.to_string()))?;
Ok(parse_linux_arp(&contents))
}
}