1use crate::{
4 config::ProbeType,
5 error::NetPulseError,
6 probers::{icmp::IcmpProber, udp::UdpProber, Prober},
7 state::{new_app_state, TargetState},
8};
9use anyhow::Result;
10use std::sync::{
11 atomic::{AtomicBool, Ordering},
12 Arc,
13};
14use tokio::task::JoinHandle;
15use tokio::time::{self, Duration};
16
17pub async fn run_trace_mode(
18 target: String,
19 resolved_ip: String,
20 probe_type: ProbeType,
21 timeout_ms: u64,
22 interval_ms: u64,
23 window: usize,
24) -> Result<()> {
25 let prober: Arc<dyn Prober> = match probe_type {
26 ProbeType::Icmp => Arc::new(IcmpProber::new(timeout_ms)),
27 ProbeType::Udp => Arc::new(UdpProber::new(timeout_ms)),
28 ProbeType::Tcp => unreachable!("TCP tracing prevented by CLI"),
29 };
30
31 println!(
32 "Tracing route to {} ({}), {} probes...",
33 target,
34 resolved_ip,
35 prober.name()
36 );
37
38 let state = new_app_state();
39 let running = Arc::new(AtomicBool::new(true));
40
41 let running_clone = Arc::clone(&running);
43 tokio::spawn(async move {
44 let _ = tokio::signal::ctrl_c().await;
45 running_clone.store(false, Ordering::SeqCst);
46 });
47
48 let mut discovered_hops = Vec::new();
51 let mut max_ttl = 30;
52
53 for ttl in 1..=30 {
54 if !running.load(Ordering::Relaxed) {
55 return Ok(());
56 }
57
58 {
60 let mut guard = state.lock().unwrap();
61 let mut ts = TargetState::new_hop(window);
62 ts.last_ip = Some(String::from("???"));
64 guard.insert(ttl.to_string(), ts);
65 }
66
67 let seq = (ttl as u64) << 8;
68 let result = prober.probe(&resolved_ip, seq, Some(ttl as u32)).await;
69
70 match result {
71 Ok(res) => {
72 let ip = res
73 .responder_ip
74 .clone()
75 .unwrap_or_else(|| String::from("???"));
76
77 {
78 let mut guard = state.lock().unwrap();
79 if let Some(ts) = guard.get_mut(&ttl.to_string()) {
80 ts.buffer.push(res.clone());
81 ts.last_rtt_us = res.rtt_us;
82 ts.last_ip = Some(ip.clone());
83 ts.seq += 1;
84 }
85 }
86
87 discovered_hops.push((ttl, ip.clone()));
88
89 if ip == resolved_ip {
94 max_ttl = ttl;
95 break;
96 }
97 }
98 Err(e) => {
99 if matches!(e, NetPulseError::InsufficientPrivileges) {
100 eprintln!("Fatal error: {}", e);
101 std::process::exit(1);
102 }
103 discovered_hops.push((ttl, String::from("???")));
106 {
107 let mut guard = state.lock().unwrap();
108 if let Some(ts) = guard.get_mut(&ttl.to_string()) {
109 ts.seq += 1;
110 }
111 }
112 }
113 }
114 }
115
116 let mut tasks: Vec<JoinHandle<()>> = Vec::new();
119 for ttl in 1..=max_ttl {
120 let prober_clone = Arc::clone(&prober);
121 let target_clone = resolved_ip.clone();
122 let state_clone = Arc::clone(&state);
123 let running_task = Arc::clone(&running);
124
125 let task = tokio::spawn(async move {
126 let mut ticker = time::interval(Duration::from_millis(interval_ms));
127 let mut local_seq = 1; while running_task.load(Ordering::Relaxed) {
130 ticker.tick().await;
131
132 let seq_val = ((ttl as u64) << 8) | (local_seq % 256);
133 let probe_res = match prober_clone
134 .probe(&target_clone, seq_val, Some(ttl as u32))
135 .await
136 {
137 Ok(p) => p,
138 Err(_) => crate::probers::ProbeResult::loss(&target_clone, seq_val),
139 };
140
141 {
142 let mut guard = state_clone.lock().unwrap();
143 if let Some(ts) = guard.get_mut(&ttl.to_string()) {
144 ts.buffer.push(probe_res.clone());
145 ts.last_rtt_us = probe_res.rtt_us;
146 ts.seq += 1;
147 if let Some(ip) = probe_res.responder_ip {
148 ts.last_ip = Some(ip);
149 }
150 }
151 }
152 local_seq += 1;
153 }
154 });
155 tasks.push(task);
156 }
157
158 crate::tui::trace_tui::run(
160 state.clone(),
161 max_ttl,
162 &target,
163 &resolved_ip,
164 prober.name(),
165 interval_ms,
166 )
167 .await?;
168
169 running.store(false, Ordering::SeqCst);
171 for task in tasks {
172 let _ = task.await;
173 }
174
175 Ok(())
176}