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 #[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 #[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 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 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 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 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 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}