Add multiple features
This commit is contained in:
12
crates/wtfnet-diag/Cargo.toml
Normal file
12
crates/wtfnet-diag/Cargo.toml
Normal 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"
|
||||
142
crates/wtfnet-diag/src/lib.rs
Normal file
142
crates/wtfnet-diag/src/lib.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user