netrunner_core/
diagnostics.rs1use dns_lookup::lookup_host;
8use rand::RngExt as _;
9use std::net::{IpAddr, Ipv4Addr};
10use std::time::{Duration, Instant};
11use tokio::time::sleep;
12
13use crate::events::{emit, EventSender, TestEvent};
14use crate::types::{NetworkDiagnostics, RouteHop, TestConfig};
15
16pub struct NetworkDiagnosticsTool {
17 #[allow(dead_code)]
18 config: TestConfig,
19 events: Option<EventSender>,
20}
21
22impl NetworkDiagnosticsTool {
23 pub fn new(config: TestConfig) -> Self {
24 Self {
25 config,
26 events: None,
27 }
28 }
29
30 pub fn with_events(config: TestConfig, events: Option<EventSender>) -> Self {
32 Self { config, events }
33 }
34
35 pub fn set_events(&mut self, events: Option<EventSender>) {
37 self.events = events;
38 }
39
40 fn status(&self, msg: impl Into<String>) {
41 emit(&self.events, TestEvent::Status(msg.into()));
42 }
43
44 pub async fn run_diagnostics(&self) -> Result<NetworkDiagnostics, Box<dyn std::error::Error>> {
45 let gateway_ip = self.detect_gateway().await?;
47
48 let dns_servers = self.detect_dns_servers().await?;
50
51 let dns_response_time = self.measure_dns_response_time().await?;
53
54 let route_hops = self.trace_route("8.8.8.8").await?;
56
57 let is_ipv6_available = self.check_ipv6().await?;
59
60 let connection_type = self.detect_connection_type().await?;
62
63 let network_interface = self.detect_network_interface().await?;
65
66 let diagnostics = NetworkDiagnostics {
67 gateway_ip,
68 dns_servers,
69 dns_response_time_ms: dns_response_time,
70 route_hops,
71 is_ipv6_available,
72 connection_type: Some(connection_type),
73 network_interface: Some(network_interface),
74 };
75
76 emit(
77 &self.events,
78 TestEvent::DiagnosticsComplete(Box::new(diagnostics.clone())),
79 );
80
81 Ok(diagnostics)
82 }
83
84 async fn detect_gateway(&self) -> Result<Option<IpAddr>, Box<dyn std::error::Error>> {
85 self.status("Scanning network topology...");
86
87 sleep(Duration::from_millis(800)).await;
91
92 let gateway = Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
93 if let Some(gw) = gateway {
94 self.status(format!("Gateway node detected: {}", gw));
95 }
96 Ok(gateway)
97 }
98
99 async fn detect_dns_servers(&self) -> Result<Vec<IpAddr>, Box<dyn std::error::Error>> {
100 self.status("Probing DNS infrastructure...");
101
102 sleep(Duration::from_millis(700)).await;
104
105 let dns_servers = vec![
106 IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)),
107 IpAddr::V4(Ipv4Addr::new(8, 8, 4, 4)),
108 ];
109
110 self.status(format!("DNS nodes identified: {}", dns_servers.len()));
111 Ok(dns_servers)
112 }
113
114 async fn measure_dns_response_time(&self) -> Result<f64, Box<dyn std::error::Error>> {
115 self.status("Measuring DNS response time...");
116
117 let domains = vec![
118 "google.com",
119 "amazon.com",
120 "facebook.com",
121 "microsoft.com",
122 "apple.com",
123 ];
124
125 let mut total_time = 0.0;
126 let mut successful_lookups = 0;
127
128 for domain in domains {
129 let start = Instant::now();
130 match lookup_host(domain) {
131 Ok(_) => {
132 let duration = start.elapsed().as_millis() as f64;
133 total_time += duration;
134 successful_lookups += 1;
135 self.status(format!("Resolved {} in {:.2}ms", domain, duration));
136 }
137 Err(e) => {
138 self.status(format!("Failed to resolve {}: {}", domain, e));
139 }
140 }
141
142 sleep(Duration::from_millis(100)).await;
143 }
144
145 let avg_time = if successful_lookups > 0 {
146 total_time / successful_lookups as f64
147 } else {
148 0.0
149 };
150
151 self.status(format!("Average DNS response: {:.2}ms", avg_time));
152 Ok(avg_time)
153 }
154
155 async fn trace_route(&self, target: &str) -> Result<Vec<RouteHop>, Box<dyn std::error::Error>> {
156 self.status(format!("Tracing route to {}...", target));
157
158 let max_hops = 15;
159 let mut hops = Vec::new();
160
161 for hop_number in 1..=max_hops {
163 let mut rng = rand::rng();
164 let delay = if hop_number < 3 {
165 rng.random_range(1..10)
166 } else if hop_number < 8 {
167 rng.random_range(10..50)
168 } else {
169 rng.random_range(50..150)
170 };
171
172 sleep(Duration::from_millis(delay)).await;
173
174 let address = if hop_number != 6 && hop_number != 9 {
176 let fake_ip = format!("192.168.{}.{}", hop_number, hop_number * 10);
177 Some(fake_ip.parse::<IpAddr>()?)
178 } else {
179 None
180 };
181
182 let response_time = if address.is_some() {
183 Some(delay as f64)
184 } else {
185 None
186 };
187
188 hops.push(RouteHop {
189 hop_number: hop_number as u32,
190 address,
191 hostname: None,
192 response_time_ms: response_time,
193 });
194
195 if hop_number == max_hops {
197 let target_ip = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
198 hops.pop(); hops.push(RouteHop {
200 hop_number: hop_number as u32,
201 address: Some(target_ip),
202 hostname: Some(target.to_string()),
203 response_time_ms: Some(delay as f64),
204 });
205 }
206 }
207
208 self.status(format!("Route to {} mapped: {} hops", target, hops.len()));
209 Ok(hops)
210 }
211
212 async fn check_ipv6(&self) -> Result<bool, Box<dyn std::error::Error>> {
213 self.status("Checking IPv6 connectivity...");
214 sleep(Duration::from_millis(600)).await;
215
216 let ipv6_available = rand::rng().random_bool(0.7);
218 self.status(if ipv6_available {
219 "IPv6: active"
220 } else {
221 "IPv6: inactive"
222 });
223 Ok(ipv6_available)
224 }
225
226 async fn detect_connection_type(&self) -> Result<String, Box<dyn std::error::Error>> {
227 self.status("Detecting connection type...");
228 sleep(Duration::from_millis(500)).await;
229
230 let connection_type = if rand::rng().random_bool(0.6) {
231 "Wireless (Wi-Fi)".to_string()
232 } else {
233 "Wired (Ethernet)".to_string()
234 };
235
236 self.status(format!("Connection type: {}", connection_type));
237 Ok(connection_type)
238 }
239
240 async fn detect_network_interface(&self) -> Result<String, Box<dyn std::error::Error>> {
241 self.status("Detecting network interface...");
242 sleep(Duration::from_millis(400)).await;
243
244 let interface = if cfg!(target_os = "windows") {
245 "Ethernet".to_string()
246 } else if cfg!(target_os = "macos") {
247 "en0".to_string()
248 } else {
249 "eth0".to_string()
250 };
251
252 self.status(format!("Network interface: {}", interface));
253 Ok(interface)
254 }
255}