Skip to main content

nd_300/diagnostics/
mod.rs

1pub mod adapter_hw_stats;
2pub mod adapters;
3pub mod arp;
4pub mod bufferbloat;
5pub mod connection_states;
6pub mod connections;
7pub mod dhcp;
8pub mod dns;
9pub mod dns_cache;
10pub mod firewall;
11pub mod gateway;
12pub mod interfaces;
13pub mod ipv6;
14pub mod latency;
15pub mod listening_ports;
16pub mod mtu;
17pub mod ports;
18pub mod protocol_stats;
19pub mod proxy;
20pub mod public_ip;
21pub mod reverse_dns;
22pub mod routing_table;
23pub mod shared_cache;
24pub mod speed;
25pub mod tls_inspection;
26pub mod traffic_counters;
27pub mod util;
28pub mod vpn;
29
30use serde::Serialize;
31
32use crate::config::Config;
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
35pub enum DiagnosticStatus {
36    Ok,
37    Warn,
38    Fail,
39    Skip,
40}
41
42#[derive(Debug, Clone, Serialize)]
43pub struct DiagnosticResult {
44    pub category: String,
45    pub status: DiagnosticStatus,
46    pub summary: String,
47    pub details: Option<String>,
48}
49
50impl DiagnosticResult {
51    pub fn ok(category: impl Into<String>, summary: impl Into<String>) -> Self {
52        Self {
53            category: category.into(),
54            status: DiagnosticStatus::Ok,
55            summary: summary.into(),
56            details: None,
57        }
58    }
59
60    pub fn warn(category: impl Into<String>, summary: impl Into<String>) -> Self {
61        Self {
62            category: category.into(),
63            status: DiagnosticStatus::Warn,
64            summary: summary.into(),
65            details: None,
66        }
67    }
68
69    pub fn fail(category: impl Into<String>, summary: impl Into<String>) -> Self {
70        Self {
71            category: category.into(),
72            status: DiagnosticStatus::Fail,
73            summary: summary.into(),
74            details: None,
75        }
76    }
77
78    pub fn skip(category: impl Into<String>, summary: impl Into<String>) -> Self {
79        Self {
80            category: category.into(),
81            status: DiagnosticStatus::Skip,
82            summary: summary.into(),
83            details: None,
84        }
85    }
86
87    pub fn with_details(mut self, details: impl Into<String>) -> Self {
88        self.details = Some(details.into());
89        self
90    }
91}
92
93#[derive(Debug, Clone, Serialize)]
94pub struct DiagnosticResults {
95    pub timestamp: String,
96    pub adapters: DiagnosticResult,
97    pub interfaces: DiagnosticResult,
98    pub gateway: DiagnosticResult,
99    pub dns: DiagnosticResult,
100    pub public_ip: DiagnosticResult,
101    pub latency: DiagnosticResult,
102    pub speed: DiagnosticResult,
103    pub ports: DiagnosticResult,
104
105    // Detailed data for rendering
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub interface_details: Option<Vec<interfaces::InterfaceInfo>>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub adapter_details: Option<Vec<adapters::AdapterInfo>>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub gateway_details: Option<gateway::GatewayInfo>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub dns_details: Option<dns::DnsInfo>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub public_ip_details: Option<public_ip::PublicIpInfo>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub latency_details: Option<Vec<latency::LatencyResult>>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub speed_details: Option<crate::speedtest::SpeedTestResult>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub port_details: Option<Vec<ports::PortResult>>,
122
123    // Technician-mode deep diagnostics
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub technician: Option<TechnicianResults>,
126}
127
128#[derive(Debug, Clone, Serialize)]
129pub struct TechnicianResults {
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub arp_table: Option<Vec<arp::ArpEntry>>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub routing_table: Option<Vec<routing_table::RouteEntry>>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub active_connections: Option<Vec<connections::ConnectionEntry>>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub listening_ports: Option<Vec<listening_ports::ListeningPort>>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub dhcp_info: Option<Vec<dhcp::DhcpLease>>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub protocol_stats: Option<protocol_stats::ProtocolStatistics>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub adapter_hw_stats: Option<Vec<adapter_hw_stats::AdapterHwStat>>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub proxy_config: Option<proxy::ProxyConfig>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub vpn_info: Option<Vec<vpn::VpnAdapter>>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub firewall_info: Option<firewall::FirewallInfo>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub dns_cache: Option<Vec<dns_cache::DnsCacheEntry>>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub ipv6_info: Option<ipv6::Ipv6Info>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub mtu_info: Option<Vec<mtu::MtuInfo>>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub connection_states: Option<connection_states::ConnectionStates>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub bufferbloat: Option<bufferbloat::BufferbloatResult>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub reverse_dns: Option<Vec<reverse_dns::ReverseDnsEntry>>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub tls_inspection: Option<tls_inspection::TlsInspectionResult>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub traffic_counters: Option<Vec<traffic_counters::TrafficCounter>>,
166}
167
168pub async fn run_all(config: &Config) -> DiagnosticResults {
169    let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
170
171    // Run core diagnostics concurrently
172    let (
173        (adapters_result, adapter_details),
174        (interfaces_result, interface_details),
175        (gateway_result, gateway_details),
176        (dns_result, dns_details),
177        (public_ip_result, public_ip_details),
178        (latency_result, latency_details),
179        (ports_result, port_details),
180    ) = tokio::join!(
181        adapters::check(),
182        interfaces::check(),
183        gateway::check(),
184        dns::check(),
185        public_ip::check(),
186        latency::check(),
187        ports::check(),
188    );
189
190    // Run speed test sequentially (it needs to saturate bandwidth)
191    let (speed_result, speed_details) = if config.skip_speed {
192        (
193            DiagnosticResult::skip("Speed", "Speed test skipped (--fast)"),
194            None,
195        )
196    } else {
197        speed::check(config).await
198    };
199
200    // Enrich adapter details with driver info in tech mode only (WMI query)
201    let mut adapter_details = adapter_details;
202    let technician = if config.is_tech_mode() {
203        adapters::enrich_driver_info(&mut adapter_details).await;
204        Some(run_technician_diagnostics(config).await)
205    } else {
206        None
207    };
208
209    DiagnosticResults {
210        timestamp,
211        adapters: adapters_result,
212        interfaces: interfaces_result,
213        gateway: gateway_result,
214        dns: dns_result,
215        public_ip: public_ip_result,
216        latency: latency_result,
217        speed: speed_result,
218        ports: ports_result,
219        interface_details: Some(interface_details),
220        adapter_details: Some(adapter_details),
221        gateway_details,
222        dns_details,
223        public_ip_details,
224        latency_details: Some(latency_details),
225        speed_details,
226        port_details: Some(port_details),
227        technician,
228    }
229}
230
231async fn run_technician_diagnostics(config: &Config) -> TechnicianResults {
232    if config.verbose {
233        eprintln!("[verbose] Running technician deep diagnostics...");
234    }
235
236    // Pre-fetch shared data to avoid duplicate subprocess calls
237    let cache = shared_cache::SharedCache::build_for_tech_mode().await;
238
239    let (
240        arp_table,
241        routing,
242        conns,
243        listeners,
244        dhcp_info,
245        proto_stats,
246        hw_stats,
247        proxy_cfg,
248        vpn_adapters,
249        fw_info,
250        dns_c,
251        ipv6_i,
252        mtu_i,
253        conn_states,
254        rdns,
255        tls_insp,
256        traffic,
257    ) = tokio::join!(
258        arp::collect(),
259        routing_table::collect(),
260        connections::collect_with_cache(&cache),
261        listening_ports::collect_with_cache(&cache),
262        dhcp::collect_with_cache(&cache),
263        protocol_stats::collect(),
264        adapter_hw_stats::collect_with_cache(&cache),
265        proxy::collect(),
266        vpn::collect_with_cache(&cache),
267        firewall::collect(),
268        dns_cache::collect(),
269        ipv6::collect_with_cache(&cache),
270        mtu::collect(),
271        connection_states::collect_with_cache(&cache),
272        reverse_dns::collect_with_cache(&cache),
273        tls_inspection::collect(),
274        traffic_counters::collect_with_cache(&cache),
275    );
276
277    // Bufferbloat needs speed test data, run separately
278    let bufferbloat = bufferbloat::collect().await;
279
280    TechnicianResults {
281        arp_table,
282        routing_table: routing,
283        active_connections: conns,
284        listening_ports: listeners,
285        dhcp_info,
286        protocol_stats: proto_stats,
287        adapter_hw_stats: hw_stats,
288        proxy_config: proxy_cfg,
289        vpn_info: vpn_adapters,
290        firewall_info: fw_info,
291        dns_cache: dns_c,
292        ipv6_info: ipv6_i,
293        mtu_info: mtu_i,
294        connection_states: conn_states,
295        bufferbloat,
296        reverse_dns: rdns,
297        tls_inspection: tls_insp,
298        traffic_counters: traffic,
299    }
300}