Add multiple features

This commit is contained in:
DaZuo0122
2026-01-16 23:16:58 +08:00
parent c367ca29e4
commit cb022127c0
18 changed files with 1883 additions and 4 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "wtfnet-diag"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
wtfnet-platform = { path = "../wtfnet-platform" }
wtfnet-dns = { path = "../wtfnet-dns" }
zip = "0.6"

View File

@@ -0,0 +1,142 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use thiserror::Error;
use wtfnet_dns::{DnsDetectResult, DnsTransport};
use wtfnet_platform::{DnsConfigSnapshot, ListenSocket, NetInterface, NeighborEntry, RouteEntry};
use wtfnet_platform::{Platform, PlatformError};
use zip::write::FileOptions;
#[derive(Debug, Error)]
pub enum DiagError {
#[error("platform error: {0}")]
Platform(String),
#[error("dns error: {0}")]
Dns(String),
#[error("io error: {0}")]
Io(String),
#[error("zip error: {0}")]
Zip(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagOptions {
pub dns_detect_domain: Option<String>,
pub dns_detect_timeout_ms: u64,
pub dns_detect_repeat: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagReport {
pub interfaces: Option<Vec<NetInterface>>,
pub routes: Option<Vec<RouteEntry>>,
pub dns_config: Option<DnsConfigSnapshot>,
pub ports_listen: Option<Vec<ListenSocket>>,
pub neighbors: Option<Vec<NeighborEntry>>,
pub dns_detect: Option<DnsDetectResult>,
pub warnings: Vec<String>,
}
pub async fn run(platform: &Platform, options: DiagOptions) -> Result<DiagReport, DiagError> {
let mut warnings = Vec::new();
let interfaces = match platform.sys.interfaces().await {
Ok(value) => Some(value),
Err(err) => {
warnings.push(format_platform_error("interfaces", err));
None
}
};
let routes = match platform.sys.routes().await {
Ok(value) => Some(value),
Err(err) => {
warnings.push(format_platform_error("routes", err));
None
}
};
let dns_config = match platform.sys.dns_config().await {
Ok(value) => Some(value),
Err(err) => {
warnings.push(format_platform_error("dns_config", err));
None
}
};
let ports_listen = match platform.ports.listening().await {
Ok(value) => Some(value),
Err(err) => {
warnings.push(format_platform_error("ports_listen", err));
None
}
};
let neighbors = match platform.neigh.neighbors().await {
Ok(value) => Some(value),
Err(err) => {
warnings.push(format_platform_error("neighbors", err));
None
}
};
let dns_detect = if let Some(domain) = options.dns_detect_domain.as_ref() {
match wtfnet_dns::detect(
domain,
&wtfnet_dns::default_detect_servers(DnsTransport::Udp),
DnsTransport::Udp,
None,
options.dns_detect_repeat,
options.dns_detect_timeout_ms,
)
.await
{
Ok(value) => Some(value),
Err(err) => {
warnings.push(format!("dns_detect: {err}"));
None
}
}
} else {
None
};
Ok(DiagReport {
interfaces,
routes,
dns_config,
ports_listen,
neighbors,
dns_detect,
warnings,
})
}
pub fn write_bundle(
path: &Path,
meta_json: &Value,
report_json: &Value,
) -> Result<(), DiagError> {
let file = File::create(path).map_err(|err| DiagError::Io(err.to_string()))?;
let mut zip = zip::ZipWriter::new(file);
let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
zip.start_file("meta.json", options)
.map_err(|err| DiagError::Zip(err.to_string()))?;
let meta_bytes = serde_json::to_vec_pretty(meta_json)
.map_err(|err| DiagError::Io(err.to_string()))?;
zip.write_all(&meta_bytes)
.map_err(|err| DiagError::Io(err.to_string()))?;
zip.start_file("report.json", options)
.map_err(|err| DiagError::Zip(err.to_string()))?;
let report_bytes = serde_json::to_vec_pretty(report_json)
.map_err(|err| DiagError::Io(err.to_string()))?;
zip.write_all(&report_bytes)
.map_err(|err| DiagError::Io(err.to_string()))?;
zip.finish()
.map_err(|err| DiagError::Zip(err.to_string()))?;
Ok(())
}
fn format_platform_error(section: &str, err: PlatformError) -> String {
format!("{section}: {} ({:?})", err.message, err.code)
}