1use crate::{Args, HopStats, Result};
2use anyhow::anyhow;
3use hickory_resolver::{config::*, TokioAsyncResolver};
4use pnet::packet::{
5 icmp::{IcmpPacket, IcmpType, IcmpTypes},
6 ip::IpNextHeaderProtocols,
7 ipv4::Ipv4Packet,
8 util, MutablePacket, Packet,
9};
10use rand;
11use socket2::{Domain, Protocol, Socket, Type};
12use std::{
13 collections::HashMap,
14 mem::MaybeUninit,
15 net::{IpAddr, Ipv4Addr, SocketAddr},
16 sync::Arc,
17 time::{Duration, Instant},
18};
19use tokio::time;
20use tracing::{debug, info, warn};
21
22const MIN_SEQUENCE: u16 = 33000;
23const MAX_SEQUENCE: u16 = 65535;
24
25pub type UpdateCallback = Arc<dyn Fn() + Send + Sync>;
27
28#[derive(Debug, Clone)]
29pub struct RTTUpdate {
30 pub hop: usize, pub rtt: Duration, pub addr: IpAddr, pub sent_count: usize, }
35
36#[derive(Debug, Clone)]
37pub enum NetworkEvent {
38 RTTUpdate(RTTUpdate),
39 HopTimeout { hop: usize, sent_count: usize },
40 TargetReached { hop: usize },
41 RoundComplete { round: usize },
42}
43
44#[derive(Debug, Clone)]
45pub struct SequenceEntry {
46 pub index: usize, pub transit: bool, pub saved_seq: u32, pub send_time: Instant, }
51
52#[derive(Clone)]
53pub struct MtrSession {
54 pub target: String,
55 pub target_addr: IpAddr,
56 pub hops: Vec<HopStats>,
57 pub args: Args,
58 pub resolver: TokioAsyncResolver,
59 pub packet_id: u16,
60 pub next_sequence: u16,
61 pub sequence_table: HashMap<u16, SequenceEntry>, pub batch_at: usize, pub num_hosts: usize, pub update_callback: Option<UpdateCallback>, }
66
67impl MtrSession {
68 pub async fn new(args: Args) -> Result<Self> {
69 let resolver =
70 TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
71
72 let target_addr = if let Ok(ip) = args.target.parse::<IpAddr>() {
74 ip
75 } else {
76 let response = resolver.lookup_ip(&args.target).await?;
77 response
78 .iter()
79 .next()
80 .ok_or_else(|| anyhow!("Failed to resolve hostname"))?
81 };
82
83 let mut hops: Vec<HopStats> = (1..=args.max_hops).map(HopStats::new).collect();
84
85 for hop in &mut hops {
87 hop.set_ema_alpha(args.ema_alpha);
88 }
89 let packet_id = std::process::id() as u16;
90
91 Ok(Self {
92 target: args.target.clone(),
93 target_addr,
94 hops,
95 args,
96 resolver,
97 packet_id,
98 next_sequence: MIN_SEQUENCE,
99 sequence_table: HashMap::new(),
100 batch_at: 0, num_hosts: 10, update_callback: None,
103 })
104 }
105
106 pub async fn run_trace(&mut self) -> Result<()> {
107 info!("Starting trace to {} ({})", self.target, self.target_addr);
108
109 match self.target_addr {
110 IpAddr::V4(ipv4) => self.run_ipv4_trace(ipv4).await,
111 IpAddr::V6(_) => {
112 warn!("IPv6 not yet implemented, falling back to simulation");
113 self.run_simulated_trace().await
114 }
115 }
116 }
117
118 async fn run_ipv4_trace(&mut self, target: Ipv4Addr) -> Result<()> {
119 match self.create_raw_socket() {
121 Ok((send_socket, recv_socket)) => {
122 info!("Using raw ICMP sockets for real traceroute");
123 self.run_mtr_algorithm(target, send_socket, recv_socket)
124 .await
125 }
126 Err(e) => {
127 warn!("Failed to create raw socket ({}), falling back to simulation. Try running with sudo for real traceroute.", e);
128 self.run_simulated_trace().await
129 }
130 }
131 }
132
133 fn create_raw_socket(&self) -> Result<(Socket, Socket)> {
134 let send_socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?;
136
137 let recv_socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?;
139
140 send_socket.set_nonblocking(true)?;
142 recv_socket.set_nonblocking(true)?;
143
144 recv_socket.set_read_timeout(Some(Duration::from_millis(1)))?;
146
147 Ok((send_socket, recv_socket))
148 }
149
150 async fn run_mtr_algorithm(
152 &mut self,
153 target: Ipv4Addr,
154 send_socket: Socket,
155 recv_socket: Socket,
156 ) -> Result<()> {
157 let mut round = 0;
158
159 loop {
160 if let Some(count) = self.args.count {
161 if round >= count {
162 break;
163 }
164 }
165 let restart = self.net_send_batch(target, &send_socket).await?;
167
168 let collect_duration = Duration::from_millis(self.args.interval);
170 let start_collect = Instant::now();
171
172 while start_collect.elapsed() < collect_duration {
173 self.net_process_return(&recv_socket, target).await;
174 tokio::time::sleep(Duration::from_millis(1)).await;
175 }
176
177 if restart {
178 round += 1;
179 if let Some(count) = self.args.count {
180 debug!("Completed round {}/{}, restarting batch", round, count);
181 } else {
182 debug!("Completed round {} (continuous), restarting batch", round);
183 }
184 }
185 }
186
187 Ok(())
188 }
189
190 async fn net_send_batch(&mut self, target: Ipv4Addr, send_socket: &Socket) -> Result<bool> {
192 let mut n_unknown = 0;
193 let mut restart = false;
194
195 self.net_send_query(target, send_socket, self.batch_at)
197 .await?;
198
199 for i in 0..self.batch_at {
201 if self.hops[i].addr.is_none() {
202 n_unknown += 1;
203 }
204
205 if let Some(IpAddr::V4(addr)) = self.hops[i].addr {
207 if addr == target {
208 restart = true;
209 self.num_hosts = i + 1;
210 break;
211 }
212 }
213 }
214
215 if self.batch_at >= (self.args.max_hops as usize) - 1
217 || n_unknown > 5 || (self.hops.get(self.batch_at).and_then(|h| h.addr).is_some_and(|addr| {
219 matches!(addr, IpAddr::V4(a) if a == target)
220 }))
221 {
222 restart = true;
223 self.num_hosts = self.batch_at + 1;
224 }
225
226 if restart {
227 self.batch_at = 0; } else {
229 self.batch_at += 1;
230 }
231
232 Ok(restart)
233 }
234
235 async fn net_send_query(
237 &mut self,
238 target: Ipv4Addr,
239 send_socket: &Socket,
240 index: usize,
241 ) -> Result<()> {
242 let seq = self.prepare_sequence(index);
243 let time_to_live = (index + 1) as u32;
244
245 debug!(
246 "Sending probe: hop={}, TTL={}, seq={}",
247 index + 1,
248 time_to_live,
249 seq
250 );
251
252 Self::send_icmp_packet_static(send_socket, target, time_to_live, self.packet_id, seq)?;
253
254 self.save_sequence_with_send_time(index, seq, Instant::now());
256
257 Ok(())
258 }
259
260 fn prepare_sequence(&mut self, index: usize) -> u16 {
262 let seq = self.next_sequence;
263
264 self.next_sequence += 1;
266 if self.next_sequence == MAX_SEQUENCE {
267 self.next_sequence = MIN_SEQUENCE;
268 }
269
270 self.hops[index].increment_sent();
272
273 seq
274 }
275
276 fn save_sequence_with_send_time(&mut self, index: usize, seq: u16, send_time: Instant) {
277 self.cleanup_old_sequences();
279
280 let entry = SequenceEntry {
282 index,
283 transit: true,
284 saved_seq: self.hops[index].sent as u32,
285 send_time,
286 };
287
288 self.sequence_table.insert(seq, entry);
289
290 debug!(
291 "Saved sequence: seq={}, hop={}, sent_count={}",
292 seq,
293 index + 1,
294 self.hops[index].sent
295 );
296 }
297
298 fn cleanup_old_sequences(&mut self) {
299 let cutoff_time = Instant::now() - Duration::from_secs(5);
301 let mut timed_out_entries = Vec::new();
302
303 self.sequence_table.retain(|seq, entry| {
304 if entry.send_time < cutoff_time {
305 debug!("Packet timed out: seq={}, hop={}", seq, entry.index + 1);
306 timed_out_entries.push(entry.index);
307 false
308 } else {
309 true
310 }
311 });
312
313 for hop_index in timed_out_entries {
315 if hop_index < self.hops.len() {
316 self.hops[hop_index].add_timeout();
317 }
318 }
319 }
320
321 fn mark_sequence_complete(&mut self, seq: u16) -> Option<(usize, Instant)> {
323 if let Some(entry) = self.sequence_table.remove(&seq) {
324 if entry.transit {
325 let age = entry.send_time.elapsed();
327 if age.as_secs() > 5 {
328 debug!(
329 "Discarding very old response for seq {} (age: {:.1}s)",
330 seq,
331 age.as_secs_f64()
332 );
333 return None;
334 }
335 return Some((entry.index, entry.send_time));
336 }
337 }
338 None
339 }
340
341 async fn net_process_return(&mut self, recv_socket: &Socket, target: Ipv4Addr) {
343 for _ in 0..10 {
345 match Self::receive_icmp_response(recv_socket) {
346 Ok((source_ip, icmp_type, seq, receive_time)) => {
347 self.net_process_ping(seq, source_ip, icmp_type, receive_time, target)
348 .await;
349 }
350 Err(_) => break, }
352 }
353 }
354
355 async fn net_process_ping(
357 &mut self,
358 seq: u16,
359 addr: Ipv4Addr,
360 icmp_type: IcmpType,
361 receive_time: Instant,
362 target: Ipv4Addr,
363 ) {
364 let (index, send_time) = match self.mark_sequence_complete(seq) {
365 Some((idx, send_time)) => (idx, send_time),
366 None => {
367 debug!("Received response for unknown sequence: {}", seq);
368 return;
369 }
370 };
371
372 let rtt = receive_time.duration_since(send_time);
374
375 debug!(
376 "Hop {}: Got {} from {} in {:.1}ms",
377 index + 1,
378 icmp_type_name(icmp_type),
379 addr,
380 rtt.as_secs_f64() * 1000.0
381 );
382
383 self.hops[index].add_rtt_from_addr(IpAddr::V4(addr), rtt);
385
386 if !self.args.numeric {
388 if let Ok(names) = self.resolver.reverse_lookup(IpAddr::V4(addr)).await {
389 if let Some(name) = names.iter().next() {
390 self.hops[index].set_hostname_for_addr(
391 IpAddr::V4(addr),
392 name.to_string().trim_end_matches('.').to_string(),
393 );
394 } else {
395 self.hops[index].set_hostname_for_addr(IpAddr::V4(addr), addr.to_string());
396 }
397 } else {
398 self.hops[index].set_hostname_for_addr(IpAddr::V4(addr), addr.to_string());
399 }
400 }
401
402 if addr == target && matches!(icmp_type, IcmpTypes::EchoReply) {
404 info!("Reached target {} at hop {}", target, index + 1);
405 }
406
407 if let Some(callback) = &self.update_callback {
409 callback();
410 }
411 }
412
413 fn receive_icmp_response(socket: &Socket) -> Result<(Ipv4Addr, IcmpType, u16, Instant)> {
414 let mut buffer = [MaybeUninit::uninit(); 1500];
415 let receive_time = Instant::now();
416
417 match socket.recv_from(&mut buffer) {
418 Ok((size, _addr)) => {
419 let initialized_buffer: Vec<u8> = buffer[..size]
421 .iter()
422 .map(|b| unsafe { b.assume_init() })
423 .collect();
424
425 if let Some(ip_packet) = Ipv4Packet::new(&initialized_buffer) {
427 let source_ip = ip_packet.get_source();
428
429 if ip_packet.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
431 let icmp_start = (ip_packet.get_header_length() * 4) as usize;
432 if let Some(icmp_packet) =
433 IcmpPacket::new(&initialized_buffer[icmp_start..])
434 {
435 let icmp_type = icmp_packet.get_icmp_type();
436
437 match icmp_type {
438 IcmpTypes::TimeExceeded => {
439 if let Some((_orig_id, orig_seq)) =
441 Self::extract_original_packet_info(icmp_packet.payload())
442 {
443 return Ok((source_ip, icmp_type, orig_seq, receive_time));
444 }
445 }
446 IcmpTypes::EchoReply => {
447 if let Some(echo_reply) =
449 pnet::packet::icmp::echo_reply::EchoReplyPacket::new(
450 icmp_packet.payload(),
451 )
452 {
453 return Ok((
454 source_ip,
455 icmp_type,
456 echo_reply.get_sequence_number(),
457 receive_time,
458 ));
459 }
460 }
461 IcmpTypes::DestinationUnreachable => {
462 if let Some((_orig_id, orig_seq)) =
464 Self::extract_original_packet_info(icmp_packet.payload())
465 {
466 return Ok((source_ip, icmp_type, orig_seq, receive_time));
467 }
468 }
469 _ => {
470 debug!(
471 "Received unhandled ICMP type: {:?} from {}",
472 icmp_type, source_ip
473 );
474 }
475 }
476 }
477 }
478 }
479 }
480 Err(e) => {
481 if e.kind() != std::io::ErrorKind::WouldBlock
482 && e.kind() != std::io::ErrorKind::TimedOut
483 {
484 debug!("Socket recv error: {}", e);
485 }
486 }
487 }
488
489 Err(anyhow!("No valid response received"))
490 }
491
492 fn send_icmp_packet_static(
493 socket: &Socket,
494 target: Ipv4Addr,
495 ttl: u32,
496 id: u16,
497 sequence: u16,
498 ) -> Result<()> {
499 let mut icmp_buffer = [0u8; 64];
501 let mut icmp_packet =
502 pnet::packet::icmp::echo_request::MutableEchoRequestPacket::new(&mut icmp_buffer)
503 .ok_or_else(|| anyhow!("Failed to create ICMP packet"))?;
504
505 icmp_packet.set_icmp_type(IcmpTypes::EchoRequest);
506 icmp_packet.set_icmp_code(pnet::packet::icmp::IcmpCode::new(0));
507 icmp_packet.set_identifier(id);
508 icmp_packet.set_sequence_number(sequence);
509
510 let payload = format!("mtr-{}-{}", id, sequence);
512 let payload_bytes = payload.as_bytes();
513 if payload_bytes.len() <= icmp_packet.payload().len() {
514 icmp_packet.payload_mut()[..payload_bytes.len()].copy_from_slice(payload_bytes);
515 }
516
517 let checksum = util::checksum(icmp_packet.packet(), 1);
519 icmp_packet.set_checksum(checksum);
520
521 socket.set_ttl(ttl)?;
523
524 let target_addr = SocketAddr::new(IpAddr::V4(target), 0);
526 socket.send_to(icmp_packet.packet(), &target_addr.into())?;
527
528 Ok(())
529 }
530
531 fn extract_original_packet_info(payload: &[u8]) -> Option<(u16, u16)> {
532 if payload.len() >= 28 {
534 if let Some(orig_ip_packet) = Ipv4Packet::new(&payload[4..]) {
537 if orig_ip_packet.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
538 let orig_icmp_start = 4 + (orig_ip_packet.get_header_length() * 4) as usize;
539 if orig_icmp_start < payload.len() && orig_icmp_start + 8 <= payload.len() {
540 if let Some(orig_icmp) =
541 pnet::packet::icmp::echo_request::EchoRequestPacket::new(
542 &payload[orig_icmp_start..],
543 )
544 {
545 return Some((
546 orig_icmp.get_identifier(),
547 orig_icmp.get_sequence_number(),
548 ));
549 }
550 }
551 }
552 }
553 }
554 None
555 }
556
557 async fn run_simulated_trace(&mut self) -> Result<()> {
558 info!("Running simulated traceroute (use sudo for real network tracing)");
559
560 for round in 0..self.args.count.unwrap_or(10) {
561 debug!("Simulation Round {}", round + 1);
562
563 for hop in &mut self.hops {
564 hop.increment_sent();
565
566 let base_latency = hop.hop as u64 * 10 + 20; let jitter = rand::random::<u64>() % 50; let packet_loss_chance = (hop.hop as f64 * 0.05).min(0.25); if rand::random::<f64>() > packet_loss_chance {
572 let rtt = Duration::from_millis(base_latency + jitter);
573 hop.add_rtt(rtt);
574
575 if hop.addr.is_none() {
577 match hop.hop {
579 1 => {
580 hop.addr = Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
581 hop.hostname = if !self.args.numeric {
582 Some("gateway.local".to_string())
583 } else {
584 None
585 };
586 }
587 2..=3 => {
588 hop.addr = Some(IpAddr::V4(Ipv4Addr::new(10, 0, hop.hop, 1)));
589 hop.hostname = if !self.args.numeric {
590 Some(format!("core-{}.isp.net", hop.hop))
591 } else {
592 None
593 };
594 }
595 _ => {
596 let final_octet = if hop.hop >= 8 { 8 } else { hop.hop };
597 hop.addr = Some(IpAddr::V4(Ipv4Addr::new(8, 8, 8, final_octet)));
598 hop.hostname = if !self.args.numeric {
599 Some("dns.google".to_string())
600 } else {
601 None
602 };
603 }
604 }
605 }
606
607 if hop.hop >= 8 {
609 break;
610 }
611 } else {
612 hop.add_timeout();
613 }
614 }
615
616 time::sleep(Duration::from_millis(self.args.interval)).await;
617 }
618
619 Ok(())
620 }
621
622 pub fn set_update_callback(&mut self, callback: UpdateCallback) {
623 self.update_callback = Some(callback);
624 }
625
626 pub async fn run_trace_with_realtime_updates(
631 session_arc: std::sync::Arc<std::sync::Mutex<Self>>,
632 ) -> Result<()> {
633 let (target_addr, args) = {
635 let session = session_arc.lock().unwrap();
636 (session.target_addr, session.args.clone())
637 };
638
639 info!("Starting real-time trace to {}", target_addr);
640
641 match target_addr {
642 IpAddr::V4(ipv4) => Self::run_ipv4_trace_realtime(session_arc, ipv4, args).await,
643 IpAddr::V6(_) => {
644 warn!("IPv6 not yet implemented, falling back to simulation");
645 Self::run_simulated_trace_realtime(session_arc, args).await
646 }
647 }
648 }
649
650 async fn run_ipv4_trace_realtime(
651 session_arc: std::sync::Arc<std::sync::Mutex<Self>>,
652 target: Ipv4Addr,
653 args: Args,
654 ) -> Result<()> {
655 let socket_result = {
657 let session = session_arc.lock().unwrap();
658 session.create_raw_socket()
659 };
660
661 match socket_result {
662 Ok((send_socket, recv_socket)) => {
663 info!("Using raw ICMP sockets for real traceroute");
664 Self::run_mtr_algorithm_realtime(
665 session_arc,
666 target,
667 send_socket,
668 recv_socket,
669 args,
670 )
671 .await
672 }
673 Err(e) => {
674 warn!("Failed to create raw socket ({}), falling back to simulation. Try running with sudo for real traceroute.", e);
675 Self::run_simulated_trace_realtime(session_arc, args).await
676 }
677 }
678 }
679
680 async fn run_mtr_algorithm_realtime(
681 session_arc: std::sync::Arc<std::sync::Mutex<Self>>,
682 target: Ipv4Addr,
683 send_socket: Socket,
684 recv_socket: Socket,
685 args: Args,
686 ) -> Result<()> {
687 let mut round = 0;
688 loop {
689 if let Some(count) = args.count {
690 if round >= count {
691 break;
692 }
693 debug!("MTR Round {}/{}", round + 1, count);
694 } else {
695 debug!("MTR Round {} (continuous)", round + 1);
696 }
697
698 let round_start = Instant::now();
699 let round_duration = Duration::from_millis(args.interval);
700
701 tokio::select! {
703 _ = Self::net_send_batch_realtime(&session_arc, target, &send_socket) => {
704 debug!("Batch sending completed");
705 }
706 _ = async {
707 while round_start.elapsed() < round_duration {
708 Self::net_process_return_realtime(&session_arc, &recv_socket, target).await;
709 tokio::time::sleep(Duration::from_millis(1)).await;
710 }
711 } => {
712 debug!("Round duration completed");
713 }
714 }
715
716 while round_start.elapsed() < round_duration {
718 Self::net_process_return_realtime(&session_arc, &recv_socket, target).await;
719 tokio::time::sleep(Duration::from_millis(1)).await;
720 }
721
722 if let Some(count) = args.count {
723 debug!("Completed round {}/{}", round + 1, count);
724 } else {
725 debug!("Completed round {} (continuous)", round + 1);
726 }
727 round += 1;
728 }
729
730 Ok(())
731 }
732
733 async fn net_send_batch_realtime(
734 session_arc: &std::sync::Arc<std::sync::Mutex<Self>>,
735 target: Ipv4Addr,
736 send_socket: &Socket,
737 ) -> Result<bool> {
738 let (max_hops, packet_id, num_hosts);
739
740 {
742 let session = session_arc.lock().unwrap();
743 max_hops = session.args.max_hops;
744 packet_id = session.packet_id;
745 num_hosts = session.num_hosts;
746 }
747
748 let max_hop_to_send = if num_hosts > 0 {
750 (num_hosts + 2).min(max_hops as usize) } else {
752 8.min(max_hops as usize) };
754
755 for hop_index in 0..max_hop_to_send {
756 let mut next_sequence;
758
759 {
761 let session = session_arc.lock().unwrap();
762 next_sequence = session.next_sequence;
763 }
764
765 let seq = next_sequence;
767 next_sequence += 1;
768 if next_sequence == MAX_SEQUENCE {
769 next_sequence = MIN_SEQUENCE;
770 }
771
772 {
774 let mut session = session_arc.lock().unwrap();
775 session.next_sequence = next_sequence;
776 session.hops[hop_index].increment_sent();
777 }
778
779 let time_to_live = (hop_index + 1) as u32;
781 debug!(
782 "Sending batch probe: hop={}, TTL={}, seq={}",
783 hop_index + 1,
784 time_to_live,
785 seq
786 );
787 Self::send_icmp_packet_static(send_socket, target, time_to_live, packet_id, seq)?;
788
789 let actual_send_time = Instant::now();
791 {
792 let mut session = session_arc.lock().unwrap();
793
794 session.cleanup_old_sequences();
796
797 let entry = SequenceEntry {
798 index: hop_index,
799 transit: true,
800 saved_seq: session.hops[hop_index].sent as u32,
801 send_time: actual_send_time, };
803 session.sequence_table.insert(seq, entry);
804 }
805
806 tokio::time::sleep(Duration::from_millis(50)).await;
808 }
809
810 let mut _restart = false;
812 let mut _n_unknown = 0;
813 {
814 let mut session = session_arc.lock().unwrap();
815
816 for i in 0..max_hop_to_send {
818 if session.hops[i].addr.is_none() {
819 _n_unknown += 1;
820 } else if let Some(IpAddr::V4(addr)) = session.hops[i].addr {
821 if addr == target {
822 _restart = true;
823 session.num_hosts = i + 1;
824 debug!("Target reached at hop {}", i + 1);
825 break;
826 }
827 }
828 }
829
830 if session.num_hosts == 0 && max_hop_to_send >= 8 {
832 session.num_hosts = max_hop_to_send;
833 }
834 }
835
836 Ok(true)
838 }
839
840 async fn net_process_return_realtime(
841 session_arc: &std::sync::Arc<std::sync::Mutex<Self>>,
842 recv_socket: &Socket,
843 target: Ipv4Addr,
844 ) {
845 for _ in 0..10 {
847 match Self::receive_icmp_response(recv_socket) {
848 Ok((source_ip, icmp_type, seq, receive_time)) => {
849 Self::net_process_ping_realtime(
850 session_arc,
851 seq,
852 source_ip,
853 icmp_type,
854 receive_time,
855 target,
856 )
857 .await;
858 }
859 Err(_) => break, }
861 }
862 }
863
864 async fn net_process_ping_realtime(
865 session_arc: &std::sync::Arc<std::sync::Mutex<Self>>,
866 seq: u16,
867 addr: Ipv4Addr,
868 icmp_type: IcmpType,
869 receive_time: Instant,
870 target: Ipv4Addr,
871 ) {
872 let (callback, hop_index) = {
873 let mut session = session_arc.lock().unwrap();
874
875 let (index, send_time) = match session.mark_sequence_complete(seq) {
876 Some((idx, send_time)) => (idx, send_time),
877 None => {
878 debug!("Received response for unknown sequence: {}", seq);
879 return;
880 }
881 };
882
883 let rtt = receive_time.duration_since(send_time);
885
886 debug!(
887 "Hop {}: Got {} from {} in {:.1}ms",
888 index + 1,
889 icmp_type_name(icmp_type),
890 addr,
891 rtt.as_secs_f64() * 1000.0
892 );
893
894 session.hops[index].add_rtt(rtt);
896
897 if session.hops[index].addr.is_none() {
899 session.hops[index].addr = Some(IpAddr::V4(addr));
900
901 if !session.args.numeric {
902 session.hops[index].hostname = Some(addr.to_string());
904 }
905 }
906
907 if addr == target && matches!(icmp_type, IcmpTypes::EchoReply) {
909 info!("Reached target {} at hop {}", target, index + 1);
910 }
911
912 (session.update_callback.clone(), index)
913 };
914
915 let session_arc_clone = Arc::clone(session_arc);
917 let addr_for_dns = addr;
918 tokio::spawn(async move {
919 let (do_resolve, resolver_clone) = {
920 let session = session_arc_clone.lock().unwrap();
921 if !session.args.numeric {
922 if let Some(hostname) = &session.hops[hop_index].hostname {
923 if hostname == &addr_for_dns.to_string() {
925 (true, session.resolver.clone())
926 } else {
927 (false, session.resolver.clone())
928 }
929 } else {
930 (false, session.resolver.clone())
931 }
932 } else {
933 (false, session.resolver.clone())
934 }
935 };
936
937 if do_resolve {
938 if let Ok(names) = resolver_clone
939 .reverse_lookup(IpAddr::V4(addr_for_dns))
940 .await
941 {
942 if let Some(name) = names.iter().next() {
943 let mut session = session_arc_clone.lock().unwrap();
944 session.hops[hop_index].hostname =
945 Some(name.to_string().trim_end_matches('.').to_string());
946
947 if let Some(callback) = &session.update_callback {
949 callback();
950 }
951 }
952 }
953 }
954 });
955
956 if let Some(callback) = callback {
958 callback();
959 }
960 }
961
962 async fn run_simulated_trace_realtime(
963 session_arc: std::sync::Arc<std::sync::Mutex<Self>>,
964 args: Args,
965 ) -> Result<()> {
966 info!("Running simulated traceroute (use sudo for real network tracing)");
967
968 for round in 0..args.count.unwrap_or(10) {
969 debug!("Simulation Round {}", round + 1);
970
971 let (callback, numeric) = {
973 let session = session_arc.lock().unwrap();
974 (session.update_callback.clone(), session.args.numeric)
975 };
976
977 for hop_num in 1..=8 {
979 {
980 let mut session = session_arc.lock().unwrap();
981 if let Some(hop) = session.hops.get_mut(hop_num - 1) {
982 hop.increment_sent();
983
984 let base_latency = hop.hop as u64 * 10 + 20; let jitter = rand::random::<u64>() % 50; let packet_loss_chance = (hop.hop as f64 * 0.05).min(0.25); if rand::random::<f64>() > packet_loss_chance {
990 let rtt = Duration::from_millis(base_latency + jitter);
991
992 let addr = match hop_num {
994 3 => {
995 let rand_choice = rand::random::<f32>();
997 if rand_choice < 0.6 {
998 Ipv4Addr::new(10, 0, 3, 1) } else if rand_choice < 0.8 {
1000 Ipv4Addr::new(10, 0, 3, 2) } else {
1002 Ipv4Addr::new(10, 0, 3, 3) }
1004 }
1005 5 => {
1006 if rand::random::<bool>() {
1008 Ipv4Addr::new(8, 8, 8, 8) } else {
1010 Ipv4Addr::new(8, 8, 4, 4) }
1012 }
1013 _ => {
1014 match hop_num {
1016 1 => Ipv4Addr::new(192, 168, 1, 1),
1017 2 => Ipv4Addr::new(10, 0, 2, 1),
1018 4 => Ipv4Addr::new(172, 16, 4, 1),
1019 6 => Ipv4Addr::new(8, 8, 8, 8),
1020 7 => Ipv4Addr::new(8, 8, 8, 8),
1021 8 => Ipv4Addr::new(8, 8, 8, 8),
1022 _ => Ipv4Addr::new(10, 0, hop_num as u8, 1),
1023 }
1024 }
1025 };
1026
1027 hop.add_rtt_from_addr(IpAddr::V4(addr), rtt);
1028
1029 if !numeric {
1031 let hostname = match addr {
1032 a if a == Ipv4Addr::new(192, 168, 1, 1) => {
1033 "gateway.local".to_string()
1034 }
1035 a if a == Ipv4Addr::new(10, 0, 2, 1) => {
1036 "core-2.isp.net".to_string()
1037 }
1038 a if a == Ipv4Addr::new(10, 0, 3, 1) => {
1039 "core-3.isp.net".to_string()
1040 }
1041 a if a == Ipv4Addr::new(10, 0, 3, 2) => {
1042 "alt-router-3-2.isp.net".to_string()
1043 }
1044 a if a == Ipv4Addr::new(10, 0, 3, 3) => {
1045 "backup-router-3-3.isp.net".to_string()
1046 }
1047 a if a == Ipv4Addr::new(172, 16, 4, 1) => {
1048 "border-4.isp.net".to_string()
1049 }
1050 a if a == Ipv4Addr::new(8, 8, 8, 8) => "dns.google".to_string(),
1051 a if a == Ipv4Addr::new(8, 8, 4, 4) => {
1052 "dns-alt.google".to_string()
1053 }
1054 _ => addr.to_string(),
1055 };
1056 hop.set_hostname_for_addr(IpAddr::V4(addr), hostname);
1057 }
1058
1059 if hop.addr.is_none() {
1061 let hop_number = hop.hop;
1062
1063 match hop_number {
1065 1 => {
1066 hop.addr = Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
1067 hop.hostname = if !numeric {
1068 Some("gateway.local".to_string())
1069 } else {
1070 None
1071 };
1072 }
1073 2..=3 => {
1074 hop.addr =
1075 Some(IpAddr::V4(Ipv4Addr::new(10, 0, hop_number, 1)));
1076 hop.hostname = if !numeric {
1077 Some(format!("core-{}.isp.net", hop_number))
1078 } else {
1079 None
1080 };
1081 }
1082 _ => {
1083 let final_octet =
1084 if hop_number >= 8 { 8 } else { hop_number };
1085 hop.addr =
1086 Some(IpAddr::V4(Ipv4Addr::new(8, 8, 8, final_octet)));
1087 hop.hostname = if !numeric {
1088 Some("dns.google".to_string())
1089 } else {
1090 None
1091 };
1092 }
1093 }
1094 }
1095 } else {
1096 hop.add_timeout();
1097 }
1098 }
1099 }
1100
1101 if let Some(callback) = &callback {
1103 callback();
1104 }
1105
1106 tokio::time::sleep(Duration::from_millis(10)).await;
1108 }
1109
1110 time::sleep(Duration::from_millis(args.interval)).await;
1112 }
1113
1114 Ok(())
1115 }
1116}
1117
1118fn icmp_type_name(icmp_type: IcmpType) -> &'static str {
1119 match icmp_type {
1120 IcmpTypes::TimeExceeded => "TimeExceeded",
1121 IcmpTypes::EchoReply => "EchoReply",
1122 IcmpTypes::DestinationUnreachable => "DestUnreach",
1123 _ => "Other",
1124 }
1125}
1126
1127#[cfg(test)]
1128mod tests {
1129 use super::*;
1130
1131 #[tokio::test]
1132 async fn test_mtr_session_new_with_ip() {
1133 let args = Args {
1134 target: "192.168.1.1".to_string(),
1135 count: Some(5),
1136 interval: 500,
1137 max_hops: 20,
1138 report: false,
1139 numeric: true,
1140 sparkline_scale: crate::SparklineScale::Logarithmic,
1141 ema_alpha: 0.1,
1142 fields: None,
1143 sixel: false,
1144 show_all: false,
1145 };
1146
1147 let session = MtrSession::new(args).await;
1148 assert!(session.is_ok());
1149
1150 let session = session.unwrap();
1151 assert_eq!(session.target, "192.168.1.1");
1152 assert_eq!(session.target_addr.to_string(), "192.168.1.1");
1153 assert_eq!(session.hops.len(), 20);
1154 assert_eq!(session.args.count, Some(5));
1155 assert_eq!(session.args.interval, 500);
1156 }
1157
1158 #[tokio::test]
1159 async fn test_mtr_session_new_with_localhost() {
1160 let args = Args {
1161 target: "localhost".to_string(),
1162 count: Some(3),
1163 interval: 1000,
1164 max_hops: 15,
1165 report: true,
1166 numeric: false,
1167 sparkline_scale: crate::SparklineScale::Logarithmic,
1168 ema_alpha: 0.1,
1169 fields: None,
1170 sixel: false,
1171 show_all: false,
1172 };
1173
1174 let session = MtrSession::new(args).await;
1175 assert!(session.is_ok());
1176
1177 let session = session.unwrap();
1178 assert_eq!(session.target, "localhost");
1179 assert_eq!(session.hops.len(), 15);
1180 assert!(session.args.report);
1181 assert!(!session.args.numeric);
1182 }
1183
1184 #[test]
1185 fn test_mtr_session_clone() {
1186 let args = Args {
1187 target: "example.com".to_string(),
1188 count: Some(10),
1189 interval: 1000,
1190 max_hops: 30,
1191 report: false,
1192 numeric: false,
1193 sparkline_scale: crate::SparklineScale::Logarithmic,
1194 ema_alpha: 0.1,
1195 fields: None,
1196 sixel: false,
1197 show_all: false,
1198 };
1199
1200 let args_clone = args.clone();
1204 assert_eq!(args.target, args_clone.target);
1205 assert_eq!(args.count, args_clone.count);
1206 }
1207}