1use anti_common::{ports, PingError, PingResult};
2use anti_ping::IcmpPacket;
3use dns_lookup::lookup_addr;
4use rand::Rng;
5use serde::{Deserialize, Serialize};
6use socket2::{Domain, Protocol, Socket, Type};
7use std::collections::HashMap;
8use std::io::Read;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
10use std::time::{Duration, Instant};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TraceProtocol {
15 Udp,
17 Icmp,
19 Tcp,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub enum IpVersion {
26 V4,
27 V6,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum OutputFormat {
33 Text,
35 Json,
37 Csv,
39 Xml,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum TraceMode {
46 Single,
48 Continuous,
50 Report,
52}
53
54#[derive(Debug, Clone)]
56pub struct TracerouteConfig {
57 pub target_v4: Option<Ipv4Addr>,
59 pub target_v6: Option<Ipv6Addr>,
60 pub ip_version: IpVersion,
62 pub protocol: TraceProtocol,
64 pub max_hops: u8,
66 pub first_hop: u8,
68 pub timeout: Duration,
70 pub base_port: u16,
72 pub tcp_port: u16,
74 pub attempts: u8,
76 pub packet_size: usize,
78 pub packet_interval: Duration,
80 pub source_address: Option<IpAddr>,
82 pub source_interface: Option<String>,
84 pub icmp_fallback: bool,
86 pub resolve_hostnames: bool,
88 pub lookup_as: bool,
90 pub lookup_geo: bool,
92 pub output_format: OutputFormat,
94 pub mode: TraceMode,
96 pub report_cycles: u32,
98 pub quiet: bool,
100 pub summary_only: bool,
102}
103
104impl Default for TracerouteConfig {
105 fn default() -> Self {
106 Self {
107 target_v4: Some(Ipv4Addr::new(127, 0, 0, 1)),
108 target_v6: None,
109 ip_version: IpVersion::V4,
110 protocol: TraceProtocol::Udp,
111 max_hops: 30,
112 first_hop: 1,
113 timeout: Duration::from_secs(3),
114 base_port: ports::TRACEROUTE_BASE,
115 tcp_port: 80,
116 attempts: 3,
117 packet_size: 40,
118 packet_interval: Duration::from_millis(0),
119 source_address: None,
120 source_interface: None,
121 icmp_fallback: true,
122 resolve_hostnames: true,
123 lookup_as: false,
124 lookup_geo: false,
125 output_format: OutputFormat::Text,
126 mode: TraceMode::Single,
127 report_cycles: 10,
128 quiet: false,
129 summary_only: false,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct HopStatistics {
137 pub sent: u32,
138 pub received: u32,
139 pub loss_percent: f64,
140 pub min_rtt: Option<Duration>,
141 pub max_rtt: Option<Duration>,
142 pub avg_rtt: Option<Duration>,
143 pub stddev_rtt: Option<f64>,
144 pub jitter: Option<f64>,
145 pub rtts: Vec<Duration>, }
147
148impl Default for HopStatistics {
149 fn default() -> Self {
150 Self {
151 sent: 0,
152 received: 0,
153 loss_percent: 0.0,
154 min_rtt: None,
155 max_rtt: None,
156 avg_rtt: None,
157 stddev_rtt: None,
158 jitter: None,
159 rtts: Vec::new(),
160 }
161 }
162}
163
164impl HopStatistics {
165 pub fn add_measurement(&mut self, rtt: Option<Duration>) {
166 self.sent += 1;
167 if let Some(rtt) = rtt {
168 self.received += 1;
169 self.rtts.push(rtt);
170 self.update_statistics();
171 }
172 self.loss_percent = if self.sent > 0 {
173 ((self.sent - self.received) as f64 / self.sent as f64) * 100.0
174 } else {
175 0.0
176 };
177 }
178
179 fn update_statistics(&mut self) {
180 if self.rtts.is_empty() {
181 return;
182 }
183
184 let sum: Duration = self.rtts.iter().sum();
185 self.avg_rtt = Some(sum / self.rtts.len() as u32);
186 self.min_rtt = Some(*self.rtts.iter().min().unwrap());
187 self.max_rtt = Some(*self.rtts.iter().max().unwrap());
188
189 if self.rtts.len() > 1 {
190 let avg_ms = sum.as_secs_f64() * 1000.0 / self.rtts.len() as f64;
191 let variance: f64 = self
192 .rtts
193 .iter()
194 .map(|rtt| {
195 let rtt_ms = rtt.as_secs_f64() * 1000.0;
196 (rtt_ms - avg_ms).powi(2)
197 })
198 .sum::<f64>()
199 / (self.rtts.len() - 1) as f64;
200 self.stddev_rtt = Some(variance.sqrt());
201
202 if self.rtts.len() > 1 {
204 let jitter_sum: f64 = self
205 .rtts
206 .windows(2)
207 .map(|pair| (pair[1].as_secs_f64() - pair[0].as_secs_f64()).abs() * 1000.0)
208 .sum();
209 self.jitter = Some(jitter_sum / (self.rtts.len() - 1) as f64);
210 }
211 }
212 }
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct HopNetworkInfo {
218 pub hostname: Option<String>,
219 pub as_number: Option<u32>,
220 pub as_name: Option<String>,
221 pub country: Option<String>,
222 pub city: Option<String>,
223 pub organization: Option<String>,
224}
225
226impl Default for HopNetworkInfo {
227 fn default() -> Self {
228 Self {
229 hostname: None,
230 as_number: None,
231 as_name: None,
232 country: None,
233 city: None,
234 organization: None,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct HopResult {
242 pub ttl: u8,
243 pub addr_v4: Option<Ipv4Addr>,
244 pub addr_v6: Option<Ipv6Addr>,
245 pub rtt: Option<Duration>,
246 pub reached_destination: bool,
247 pub statistics: HopStatistics,
248 pub network_info: HopNetworkInfo,
249}
250
251impl HopResult {
252 pub fn addr(&self) -> Option<IpAddr> {
253 self.addr_v4
254 .map(IpAddr::V4)
255 .or_else(|| self.addr_v6.map(IpAddr::V6))
256 }
257}
258
259#[derive(Debug, Clone)]
261pub enum TraceProgress {
262 HopStart { ttl: u8 },
264 Attempt { ttl: u8, attempt: u8 },
266 AttemptTimeout { ttl: u8, attempt: u8 },
268 FallbackStart { ttl: u8 },
270 HopComplete(HopResult),
272}
273
274pub struct Tracer {
276 config: TracerouteConfig,
277 udp_socket: Option<UdpSocket>,
278 tcp_socket: Option<Socket>,
279 icmp_socket: Option<Socket>,
280 icmp_identifier: u16,
281 hop_statistics: HashMap<u8, HopStatistics>,
282}
283
284impl Tracer {
285 pub fn new(config: TracerouteConfig) -> PingResult<Self> {
287 let mut rng = rand::thread_rng();
288 let icmp_identifier = rng.gen::<u16>();
289
290 let bind_addr = match (config.ip_version, &config.source_address) {
292 (IpVersion::V4, Some(IpAddr::V4(addr))) => format!("{}:0", addr),
293 (IpVersion::V6, Some(IpAddr::V6(addr))) => format!("[{}]:0", addr),
294 (IpVersion::V4, _) => "0.0.0.0:0".to_string(),
295 (IpVersion::V6, _) => "[::]:0".to_string(),
296 };
297
298 let udp_socket = if matches!(config.protocol, TraceProtocol::Udp) {
300 let socket = UdpSocket::bind(&bind_addr)
301 .map_err(|e| PingError::SocketCreation(format!("UDP socket: {}", e)))?;
302 Some(socket)
303 } else {
304 None
305 };
306
307 let tcp_socket = if matches!(config.protocol, TraceProtocol::Tcp) {
309 let domain = match config.ip_version {
310 IpVersion::V4 => Domain::IPV4,
311 IpVersion::V6 => Domain::IPV6,
312 };
313 let socket = Socket::new(domain, Type::RAW, Some(Protocol::TCP)).map_err(|e| {
315 if e.kind() == std::io::ErrorKind::PermissionDenied {
316 PingError::PermissionDenied {
317 context:
318 "TCP traceroute requires raw socket access (try running with sudo)"
319 .into(),
320 }
321 } else {
322 PingError::SocketCreation(format!("TCP raw socket: {}", e))
323 }
324 })?;
325
326 if config.ip_version == IpVersion::V4 {
328 socket
329 .set_header_included_v4(true)
330 .map_err(|e| PingError::SocketCreation(format!("TCP IP_HDRINCL: {}", e)))?;
331 }
332
333 if let Some(src_addr) = config.source_address {
334 let bind_sockaddr = match src_addr {
335 IpAddr::V4(addr) => SocketAddr::new(IpAddr::V4(addr), 0),
336 IpAddr::V6(addr) => SocketAddr::new(IpAddr::V6(addr), 0),
337 };
338 socket
339 .bind(&bind_sockaddr.into())
340 .map_err(|e| PingError::SocketCreation(format!("TCP bind: {}", e)))?;
341 }
342 Some(socket)
343 } else {
344 None
345 };
346
347 let icmp_socket = if matches!(config.protocol, TraceProtocol::Icmp) || config.icmp_fallback
349 {
350 let (domain, protocol) = match config.ip_version {
351 IpVersion::V4 => (Domain::IPV4, Protocol::ICMPV4),
352 IpVersion::V6 => (Domain::IPV6, Protocol::ICMPV6),
353 };
354
355 let socket = match Socket::new(domain, Type::DGRAM, Some(protocol)) {
356 Ok(sock) => sock,
357 Err(_) => {
358 Socket::new(domain, Type::RAW, Some(protocol)).map_err(|e| {
360 if e.kind() == std::io::ErrorKind::PermissionDenied {
361 PingError::PermissionDenied {
362 context:
363 "Traceroute requires ICMP socket access (try running with sudo)"
364 .into(),
365 }
366 } else {
367 PingError::SocketCreation(e.to_string())
368 }
369 })?
370 }
371 };
372
373 socket
374 .set_read_timeout(Some(config.timeout))
375 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
376
377 if let Some(src_addr) = config.source_address {
378 let bind_sockaddr = match src_addr {
379 IpAddr::V4(addr) => SocketAddr::new(IpAddr::V4(addr), 0),
380 IpAddr::V6(addr) => SocketAddr::new(IpAddr::V6(addr), 0),
381 };
382 socket
383 .bind(&bind_sockaddr.into())
384 .map_err(|e| PingError::SocketCreation(format!("ICMP bind: {}", e)))?;
385 }
386 Some(socket)
387 } else {
388 None
389 };
390
391 Ok(Self {
392 config,
393 udp_socket,
394 tcp_socket,
395 icmp_socket,
396 icmp_identifier,
397 hop_statistics: HashMap::new(),
398 })
399 }
400
401 pub fn trace(&self) -> PingResult<Vec<HopResult>> {
403 let mut hops = Vec::new();
404
405 let target_addr = self.get_target_addr()?;
406
407 for ttl in self.config.first_hop..=self.config.max_hops {
408 let mut hop = HopResult {
409 ttl,
410 addr_v4: None,
411 addr_v6: None,
412 rtt: None,
413 reached_destination: false,
414 statistics: HopStatistics::default(),
415 network_info: HopNetworkInfo::default(),
416 };
417
418 match self.config.protocol {
419 TraceProtocol::Udp => {
420 if let Some(result) = self.udp_probe(ttl, target_addr)? {
421 self.set_hop_address(&mut hop, result.0);
422 hop.rtt = Some(result.1);
423 hop.reached_destination = result.2;
424 }
425 }
426 TraceProtocol::Icmp => {
427 if let Some(result) = self.icmp_probe(ttl as u8)? {
428 self.set_hop_address(&mut hop, result.0);
429 hop.rtt = Some(result.1);
430 hop.reached_destination = result.2;
431 }
432 }
433 TraceProtocol::Tcp => {
434 if let Some(result) = self.tcp_probe(ttl as u8)? {
435 self.set_hop_address(&mut hop, result.0);
436 hop.rtt = Some(result.1);
437 hop.reached_destination = result.2;
438 }
439 }
440 }
441
442 if hop.addr().is_none() && self.config.icmp_fallback {
444 if let Some(result) = self.icmp_probe(ttl as u8)? {
445 self.set_hop_address(&mut hop, result.0);
446 hop.rtt = Some(result.1);
447 hop.reached_destination = result.2;
448 }
449 }
450
451 if let Some(addr) = hop.addr() {
453 hop.network_info = self.lookup_network_info(addr);
454 }
455
456 if hop.reached_destination {
457 hops.push(hop);
458 break;
459 } else {
460 hops.push(hop);
461 }
462 }
463
464 Ok(hops)
465 }
466
467 pub fn trace_with_callback<F>(&self, mut callback: F) -> PingResult<()>
469 where
470 F: FnMut(&HopResult) -> bool,
471 {
472 let target_addr = self.get_target_addr()?;
473
474 for ttl in self.config.first_hop..=self.config.max_hops {
475 let mut hop = HopResult {
476 ttl,
477 addr_v4: None,
478 addr_v6: None,
479 rtt: None,
480 reached_destination: false,
481 statistics: HopStatistics::default(),
482 network_info: HopNetworkInfo::default(),
483 };
484
485 match self.config.protocol {
486 TraceProtocol::Udp => {
487 if let Some(result) = self.udp_probe(ttl, target_addr)? {
488 self.set_hop_address(&mut hop, result.0);
489 hop.rtt = Some(result.1);
490 hop.reached_destination = result.2;
491 }
492 }
493 TraceProtocol::Icmp => {
494 if let Some(result) = self.icmp_probe(ttl as u8)? {
495 self.set_hop_address(&mut hop, result.0);
496 hop.rtt = Some(result.1);
497 hop.reached_destination = result.2;
498 }
499 }
500 TraceProtocol::Tcp => {
501 if let Some(result) = self.tcp_probe(ttl as u8)? {
502 self.set_hop_address(&mut hop, result.0);
503 hop.rtt = Some(result.1);
504 hop.reached_destination = result.2;
505 }
506 }
507 }
508
509 if hop.addr().is_none() && self.config.icmp_fallback {
511 if let Some(result) = self.icmp_probe(ttl as u8)? {
512 self.set_hop_address(&mut hop, result.0);
513 hop.rtt = Some(result.1);
514 hop.reached_destination = result.2;
515 }
516 }
517
518 if let Some(addr) = hop.addr() {
520 hop.network_info = self.lookup_network_info(addr);
521 }
522
523 let should_continue = callback(&hop);
525
526 if hop.reached_destination || !should_continue {
527 break;
528 }
529 }
530
531 Ok(())
532 }
533
534 pub fn trace_with_progress<F>(&self, mut callback: F) -> PingResult<()>
536 where
537 F: FnMut(TraceProgress) -> bool,
538 {
539 let target_addr = self.get_target_addr()?;
540
541 for ttl in self.config.first_hop..=self.config.max_hops {
542 let mut hop = HopResult {
543 ttl,
544 addr_v4: None,
545 addr_v6: None,
546 rtt: None,
547 reached_destination: false,
548 statistics: HopStatistics::default(),
549 network_info: HopNetworkInfo::default(),
550 };
551
552 if !callback(TraceProgress::HopStart { ttl }) {
554 return Ok(());
555 }
556
557 for attempt in 0..self.config.attempts {
558 callback(TraceProgress::Attempt { ttl, attempt });
560
561 match self.config.protocol {
562 TraceProtocol::Udp => {
563 if let Some(result) = self.udp_probe_single(ttl, target_addr, attempt)? {
564 self.set_hop_address(&mut hop, result.0);
565 hop.rtt = Some(result.1);
566 hop.reached_destination = result.2;
567 break;
568 } else {
569 callback(TraceProgress::AttemptTimeout { ttl, attempt });
570 }
571 }
572 TraceProtocol::Icmp => {
573 if let Some(result) = self.icmp_probe(ttl as u8)? {
574 self.set_hop_address(&mut hop, result.0);
575 hop.rtt = Some(result.1);
576 hop.reached_destination = result.2;
577 break;
578 } else {
579 callback(TraceProgress::AttemptTimeout { ttl, attempt });
580 }
581 }
582 TraceProtocol::Tcp => {
583 if let Some(result) = self.tcp_probe(ttl as u8)? {
584 self.set_hop_address(&mut hop, result.0);
585 hop.rtt = Some(result.1);
586 hop.reached_destination = result.2;
587 break;
588 } else {
589 callback(TraceProgress::AttemptTimeout { ttl, attempt });
590 }
591 }
592 }
593 }
594
595 if hop.addr().is_none() && self.config.icmp_fallback {
597 callback(TraceProgress::FallbackStart { ttl });
598 if let Some(result) = self.icmp_probe(ttl as u8)? {
599 self.set_hop_address(&mut hop, result.0);
600 hop.rtt = Some(result.1);
601 hop.reached_destination = result.2;
602 }
603 }
604
605 if let Some(addr) = hop.addr() {
607 hop.network_info = self.lookup_network_info(addr);
608 }
609
610 let should_continue = callback(TraceProgress::HopComplete(hop.clone()));
612
613 if hop.reached_destination || !should_continue {
614 break;
615 }
616 }
617
618 Ok(())
619 }
620
621 pub fn trace_continuous<F>(&mut self, mut callback: F) -> PingResult<()>
623 where
624 F: FnMut(u8, &HopStatistics, &HopNetworkInfo) -> bool,
625 {
626 let target_addr = self.get_target_addr()?;
627
628 loop {
629 for ttl in self.config.first_hop..=self.config.max_hops {
630 let mut reached_destination = false;
631
632 match self.config.protocol {
634 TraceProtocol::Udp => {
635 if let Some(result) = self.udp_probe(ttl, target_addr)? {
636 self.update_hop_statistics(ttl, Some(result.1), Some(result.0));
637 reached_destination = result.2;
638 } else {
639 self.update_hop_statistics(ttl, None, None);
640 }
641 }
642 TraceProtocol::Icmp => {
643 if let Some(result) = self.icmp_probe(ttl as u8)? {
644 self.update_hop_statistics(ttl, Some(result.1), Some(result.0));
645 reached_destination = result.2;
646 } else {
647 self.update_hop_statistics(ttl, None, None);
648 }
649 }
650 TraceProtocol::Tcp => {
651 if let Some(result) = self.tcp_probe(ttl as u8)? {
652 self.update_hop_statistics(ttl, Some(result.1), Some(result.0));
653 reached_destination = result.2;
654 } else {
655 self.update_hop_statistics(ttl, None, None);
656 }
657 }
658 }
659
660 let stats = self.hop_statistics.get(&ttl).cloned().unwrap_or_default();
662 let network_info = HopNetworkInfo::default(); if !callback(ttl, &stats, &network_info) {
666 return Ok(());
667 }
668
669 if reached_destination {
670 break;
671 }
672
673 if self.config.packet_interval > Duration::from_millis(0) {
675 std::thread::sleep(self.config.packet_interval);
676 }
677 }
678
679 std::thread::sleep(Duration::from_millis(100));
681 }
682 }
683
684 fn get_target_addr(&self) -> PingResult<IpAddr> {
686 match self.config.ip_version {
687 IpVersion::V4 => Ok(IpAddr::V4(self.config.target_v4.ok_or_else(|| {
688 PingError::InvalidTarget("No IPv4 target specified".into())
689 })?)),
690 IpVersion::V6 => Ok(IpAddr::V6(self.config.target_v6.ok_or_else(|| {
691 PingError::InvalidTarget("No IPv6 target specified".into())
692 })?)),
693 }
694 }
695
696 fn set_hop_address(&self, hop: &mut HopResult, addr: IpAddr) {
698 match addr {
699 IpAddr::V4(a) => hop.addr_v4 = Some(a),
700 IpAddr::V6(a) => hop.addr_v6 = Some(a),
701 }
702 }
703
704 fn update_hop_statistics(&mut self, ttl: u8, rtt: Option<Duration>, _addr: Option<IpAddr>) {
706 let stats = self.hop_statistics.entry(ttl).or_default();
707 stats.add_measurement(rtt);
708 }
709
710 fn udp_probe(
712 &self,
713 ttl: u8,
714 target_addr: IpAddr,
715 ) -> PingResult<Option<(IpAddr, Duration, bool)>> {
716 if let Some(ref _udp_socket) = self.udp_socket {
717 for attempt in 0..self.config.attempts {
718 if let Some(result) = self.udp_probe_single(ttl, target_addr, attempt)? {
719 return Ok(Some(result));
720 }
721 }
722 }
723 Ok(None)
724 }
725
726 fn udp_probe_single(
728 &self,
729 ttl: u8,
730 target_addr: IpAddr,
731 attempt: u8,
732 ) -> PingResult<Option<(IpAddr, Duration, bool)>> {
733 if let Some(ref udp_socket) = self.udp_socket {
734 udp_socket
735 .set_ttl(ttl as u32)
736 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
737
738 let port = self.config.base_port
739 + (ttl as u16 - 1) * self.config.attempts as u16
740 + attempt as u16;
741 let target = SocketAddr::new(target_addr, port);
742 let start = Instant::now();
743
744 let _ = udp_socket
745 .send_to(&vec![0u8; self.config.packet_size], target)
746 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
747
748 if let Some(ref icmp_socket) = self.icmp_socket {
749 let mut buf = [0u8; 1500];
750 let recv_result = (&*icmp_socket).read(&mut buf);
751
752 match recv_result {
753 Ok(n) => {
754 let rtt = start.elapsed();
755 if let Some((addr, reached)) = parse_icmp_response(&buf[..n], port) {
756 return Ok(Some((IpAddr::V4(addr), rtt, reached)));
757 }
758 }
759 Err(e) => {
760 if e.kind() != std::io::ErrorKind::TimedOut {
761 return Err(PingError::SocketCreation(e.to_string()));
762 }
763 }
764 }
765 }
766 }
767 Ok(None)
768 }
769
770 fn tcp_probe(&self, ttl: u8) -> PingResult<Option<(IpAddr, Duration, bool)>> {
772 if let Some(ref tcp_socket) = self.tcp_socket {
773 let target_addr = self.get_target_addr()?;
774 let source_addr =
775 self.config
776 .source_address
777 .unwrap_or_else(|| match self.config.ip_version {
778 IpVersion::V4 => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
779 IpVersion::V6 => IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
780 });
781
782 let packet = self.build_tcp_syn_packet(source_addr, target_addr, ttl)?;
783 let target = SocketAddr::new(target_addr, self.config.tcp_port);
784
785 tcp_socket
786 .set_nonblocking(true)
787 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
788
789 let start = Instant::now();
790
791 match tcp_socket.send_to(&packet, &target.into()) {
793 Ok(_) => {
794 let rtt = start.elapsed();
798 Ok(Some((target_addr, rtt, false)))
799 }
800 Err(e) => {
801 if e.kind() == std::io::ErrorKind::WouldBlock {
802 std::thread::sleep(self.config.timeout);
803 Ok(None)
804 } else {
805 Ok(None)
806 }
807 }
808 }
809 } else {
810 Ok(None)
811 }
812 }
813
814 fn build_tcp_syn_packet(
815 &self,
816 source_addr: IpAddr,
817 target_addr: IpAddr,
818 ttl: u8,
819 ) -> PingResult<Vec<u8>> {
820 let mut packet = Vec::new();
821
822 match (source_addr, target_addr) {
823 (IpAddr::V4(src), IpAddr::V4(dst)) => {
824 let ip_header_len = 20;
826 let tcp_header_len = 20;
827 let total_len = ip_header_len + tcp_header_len;
828
829 packet.resize(total_len, 0);
830
831 packet[0] = 0x45; packet[1] = 0x00; packet[2..4].copy_from_slice(&(total_len as u16).to_be_bytes()); packet[4..6].copy_from_slice(&rand::random::<u16>().to_be_bytes()); packet[6..8].copy_from_slice(&0x4000u16.to_be_bytes()); packet[8] = ttl; packet[9] = 6; packet[10..12].copy_from_slice(&[0, 0]); packet[12..16].copy_from_slice(&src.octets()); packet[16..20].copy_from_slice(&dst.octets()); let ip_checksum = self.calculate_ip_checksum(&packet[0..20]);
845 packet[10..12].copy_from_slice(&ip_checksum.to_be_bytes());
846
847 let source_port = rand::random::<u16>() | 0x8000; packet[20..22].copy_from_slice(&source_port.to_be_bytes()); packet[22..24].copy_from_slice(&self.config.tcp_port.to_be_bytes()); packet[24..28].copy_from_slice(&rand::random::<u32>().to_be_bytes()); packet[28..32].copy_from_slice(&[0, 0, 0, 0]); packet[32] = 0x50; packet[33] = 0x02; packet[34..36].copy_from_slice(&8192u16.to_be_bytes()); packet[36..38].copy_from_slice(&[0, 0]); packet[38..40].copy_from_slice(&[0, 0]); let tcp_checksum = self.calculate_tcp_checksum(&packet[12..20], &packet[20..40]);
861 packet[36..38].copy_from_slice(&tcp_checksum.to_be_bytes());
862 }
863 _ => {
864 return Err(PingError::InvalidTarget(
865 "IPv6 TCP traceroute not yet implemented".into(),
866 ));
867 }
868 }
869
870 Ok(packet)
871 }
872
873 fn calculate_ip_checksum(&self, header: &[u8]) -> u16 {
874 let mut sum = 0u32;
875 for chunk in header.chunks(2) {
876 if chunk.len() == 2 {
877 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
878 } else {
879 sum += (chunk[0] as u32) << 8;
880 }
881 }
882 while (sum >> 16) > 0 {
883 sum = (sum & 0xFFFF) + (sum >> 16);
884 }
885 !(sum as u16)
886 }
887
888 fn calculate_tcp_checksum(&self, ip_header: &[u8], tcp_header: &[u8]) -> u16 {
889 let mut sum = 0u32;
890
891 sum += u16::from_be_bytes([ip_header[0], ip_header[1]]) as u32;
894 sum += u16::from_be_bytes([ip_header[2], ip_header[3]]) as u32;
895 sum += u16::from_be_bytes([ip_header[4], ip_header[5]]) as u32;
897 sum += u16::from_be_bytes([ip_header[6], ip_header[7]]) as u32;
898 sum += 6u32;
900 sum += tcp_header.len() as u32;
902
903 for chunk in tcp_header.chunks(2) {
905 if chunk.len() == 2 {
906 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
907 } else {
908 sum += (chunk[0] as u32) << 8;
909 }
910 }
911
912 while (sum >> 16) > 0 {
913 sum = (sum & 0xFFFF) + (sum >> 16);
914 }
915 !(sum as u16)
916 }
917
918 fn icmp_probe(&self, ttl: u8) -> PingResult<Option<(IpAddr, Duration, bool)>> {
920 if let Some(ref icmp_socket) = self.icmp_socket {
921 icmp_socket
922 .set_ttl(ttl as u32)
923 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
924
925 let target_addr = self.get_target_addr()?;
926
927 let mut packet = IcmpPacket::new_echo_request(
928 self.icmp_identifier,
929 ttl as u16,
930 self.config.packet_size,
931 );
932 packet.calculate_checksum();
933 let bytes = packet.to_bytes();
934
935 let target = SocketAddr::new(target_addr, 0);
936 let start = Instant::now();
937 icmp_socket
938 .send_to(&bytes, &target.into())
939 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
940
941 let mut buf = [0u8; 1500];
942 icmp_socket
943 .set_read_timeout(Some(self.config.timeout))
944 .map_err(|e| PingError::SocketCreation(e.to_string()))?;
945 let recv_result = (&*icmp_socket).read(&mut buf);
946
947 match recv_result {
948 Ok(n) => {
949 let rtt = start.elapsed();
950 if let Some((addr, reached)) =
951 parse_icmp_echo_response(&buf[..n], self.icmp_identifier, ttl as u16)
952 {
953 Ok(Some((IpAddr::V4(addr), rtt, reached)))
954 } else {
955 Ok(None)
956 }
957 }
958 Err(e) => {
959 if e.kind() == std::io::ErrorKind::TimedOut {
960 Ok(None)
961 } else {
962 Err(PingError::SocketCreation(e.to_string()))
963 }
964 }
965 }
966 } else {
967 Ok(None)
968 }
969 }
970
971 fn lookup_network_info(&self, addr: IpAddr) -> HopNetworkInfo {
973 let mut info = HopNetworkInfo::default();
974
975 if self.config.resolve_hostnames {
977 info.hostname = self.reverse_dns_lookup(addr);
978 }
979
980 if self.config.lookup_as {
982 if let Some((as_num, as_name)) = self.as_lookup(addr) {
983 info.as_number = Some(as_num);
984 info.as_name = Some(as_name);
985 }
986 }
987
988 if self.config.lookup_geo {
990 if let Some((country, city, org)) = self.geo_lookup(addr) {
991 info.country = Some(country);
992 info.city = Some(city);
993 info.organization = Some(org);
994 }
995 }
996
997 info
998 }
999
1000 fn reverse_dns_lookup(&self, addr: IpAddr) -> Option<String> {
1002 let addr_clone = addr;
1004 let lookup_result = std::thread::spawn(move || lookup_addr(&addr_clone).ok());
1005
1006 match lookup_result.join() {
1008 Ok(result) => result,
1009 Err(_) => None,
1010 }
1011 }
1012
1013 fn as_lookup(&self, addr: IpAddr) -> Option<(u32, String)> {
1015 match addr {
1018 IpAddr::V4(ipv4) => {
1019 let octets = ipv4.octets();
1020 match octets[0] {
1021 8 => Some((15169, "Google LLC".to_string())),
1022 1 => Some((13335, "Cloudflare, Inc.".to_string())),
1023 172 if octets[1] >= 16 && octets[1] <= 31 => {
1024 Some((64512, "Private AS".to_string()))
1025 }
1026 192 if octets[1] == 168 => Some((64512, "Private AS".to_string())),
1027 10 => Some((64512, "Private AS".to_string())),
1028 _ => Some((65001, "Unknown ISP".to_string())),
1029 }
1030 }
1031 IpAddr::V6(_) => Some((65002, "IPv6 Network".to_string())),
1032 }
1033 }
1034
1035 fn geo_lookup(&self, addr: IpAddr) -> Option<(String, String, String)> {
1037 match addr {
1040 IpAddr::V4(ipv4) => {
1041 let octets = ipv4.octets();
1042 match octets[0] {
1043 8 => Some((
1044 "US".to_string(),
1045 "Mountain View".to_string(),
1046 "Google".to_string(),
1047 )),
1048 1 => Some((
1049 "US".to_string(),
1050 "San Francisco".to_string(),
1051 "Cloudflare".to_string(),
1052 )),
1053 172 | 192 | 10 => Some((
1054 "--".to_string(),
1055 "Private".to_string(),
1056 "RFC1918".to_string(),
1057 )),
1058 _ => Some((
1059 "Unknown".to_string(),
1060 "Unknown".to_string(),
1061 "ISP".to_string(),
1062 )),
1063 }
1064 }
1065 IpAddr::V6(_) => Some((
1066 "Global".to_string(),
1067 "IPv6".to_string(),
1068 "Internet".to_string(),
1069 )),
1070 }
1071 }
1072
1073 pub fn format_output(&self, hops: &[HopResult]) -> String {
1075 match self.config.output_format {
1076 OutputFormat::Text => self.format_text_output(hops),
1077 OutputFormat::Json => self.format_json_output(hops),
1078 OutputFormat::Csv => self.format_csv_output(hops),
1079 OutputFormat::Xml => self.format_xml_output(hops),
1080 }
1081 }
1082
1083 fn format_text_output(&self, hops: &[HopResult]) -> String {
1085 let mut output = String::new();
1086
1087 if !self.config.quiet {
1088 let target = match self.config.ip_version {
1089 IpVersion::V4 => self
1090 .config
1091 .target_v4
1092 .map(|a| a.to_string())
1093 .unwrap_or_default(),
1094 IpVersion::V6 => self
1095 .config
1096 .target_v6
1097 .map(|a| a.to_string())
1098 .unwrap_or_default(),
1099 };
1100 output.push_str(&format!(
1101 "traceroute to {} using {:?}\n",
1102 target, self.config.protocol
1103 ));
1104 }
1105
1106 for hop in hops {
1107 if let Some(addr) = hop.addr() {
1108 let mut line = if let Some(hostname) = &hop.network_info.hostname {
1109 format!("{:>2} {} ({})", hop.ttl, hostname, addr)
1110 } else {
1111 format!("{:>2} {}", hop.ttl, addr)
1112 };
1113
1114 if let Some(rtt) = hop.rtt {
1115 line.push_str(&format!(" {:.3} ms", rtt.as_secs_f64() * 1000.0));
1116 }
1117
1118 if self.config.lookup_as {
1119 if let Some(as_num) = hop.network_info.as_number {
1120 line.push_str(&format!(" [AS{}]", as_num));
1121 if let Some(as_name) = &hop.network_info.as_name {
1122 line.push_str(&format!(" {}", as_name));
1123 }
1124 }
1125 }
1126
1127 if self.config.lookup_geo {
1128 if let (Some(country), Some(city)) =
1129 (&hop.network_info.country, &hop.network_info.city)
1130 {
1131 line.push_str(&format!(" ({}, {})", city, country));
1132 }
1133 }
1134
1135 if hop.reached_destination {
1136 line.push_str(" <- destination reached");
1137 }
1138
1139 line.push('\n');
1140 output.push_str(&line);
1141 } else {
1142 output.push_str(&format!("{:>2} *\n", hop.ttl));
1143 }
1144 }
1145
1146 output
1147 }
1148
1149 fn format_json_output(&self, hops: &[HopResult]) -> String {
1151 use serde_json;
1152
1153 let output = serde_json::json!({
1154 "traceroute": {
1155 "target": match self.config.ip_version {
1156 IpVersion::V4 => self.config.target_v4.map(|a| a.to_string()),
1157 IpVersion::V6 => self.config.target_v6.map(|a| a.to_string()),
1158 },
1159 "protocol": self.config.protocol,
1160 "max_hops": self.config.max_hops,
1161 "packet_size": self.config.packet_size,
1162 "hops": hops
1163 }
1164 });
1165
1166 serde_json::to_string_pretty(&output).unwrap_or_else(|_| "{}".to_string())
1167 }
1168
1169 fn format_csv_output(&self, hops: &[HopResult]) -> String {
1171 let mut output = String::new();
1172
1173 output.push_str("TTL,Address,Hostname,RTT_ms,Loss_%,AS_Number,AS_Name,Country,City,Reached_Destination\n");
1175
1176 for hop in hops {
1177 let addr = hop
1178 .addr()
1179 .map(|a| a.to_string())
1180 .unwrap_or_else(|| "*".to_string());
1181 let hostname = hop.network_info.hostname.as_deref().unwrap_or("");
1182 let rtt = hop
1183 .rtt
1184 .map(|r| (r.as_secs_f64() * 1000.0).to_string())
1185 .unwrap_or_else(|| "".to_string());
1186 let loss = format!("{:.1}", hop.statistics.loss_percent);
1187 let as_num = hop
1188 .network_info
1189 .as_number
1190 .map(|n| n.to_string())
1191 .unwrap_or_else(|| "".to_string());
1192 let as_name = hop.network_info.as_name.as_deref().unwrap_or("");
1193 let country = hop.network_info.country.as_deref().unwrap_or("");
1194 let city = hop.network_info.city.as_deref().unwrap_or("");
1195
1196 output.push_str(&format!(
1197 "{},{},{},{},{},{},{},{},{},{}\n",
1198 hop.ttl,
1199 addr,
1200 hostname,
1201 rtt,
1202 loss,
1203 as_num,
1204 as_name,
1205 country,
1206 city,
1207 hop.reached_destination
1208 ));
1209 }
1210
1211 output
1212 }
1213
1214 fn format_xml_output(&self, hops: &[HopResult]) -> String {
1216 let mut output = String::new();
1217 output.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1218 output.push_str("<traceroute>\n");
1219
1220 let target = match self.config.ip_version {
1221 IpVersion::V4 => self
1222 .config
1223 .target_v4
1224 .map(|a| a.to_string())
1225 .unwrap_or_default(),
1226 IpVersion::V6 => self
1227 .config
1228 .target_v6
1229 .map(|a| a.to_string())
1230 .unwrap_or_default(),
1231 };
1232
1233 output.push_str(&format!(" <target>{}</target>\n", target));
1234 output.push_str(&format!(
1235 " <protocol>{:?}</protocol>\n",
1236 self.config.protocol
1237 ));
1238 output.push_str(&format!(
1239 " <max_hops>{}</max_hops>\n",
1240 self.config.max_hops
1241 ));
1242 output.push_str(" <hops>\n");
1243
1244 for hop in hops {
1245 output.push_str(&format!(" <hop ttl=\"{}\">\n", hop.ttl));
1246
1247 if let Some(addr) = hop.addr() {
1248 output.push_str(&format!(" <address>{}</address>\n", addr));
1249
1250 if let Some(hostname) = &hop.network_info.hostname {
1251 output.push_str(&format!(" <hostname>{}</hostname>\n", hostname));
1252 }
1253
1254 if let Some(rtt) = hop.rtt {
1255 output.push_str(&format!(
1256 " <rtt_ms>{:.3}</rtt_ms>\n",
1257 rtt.as_secs_f64() * 1000.0
1258 ));
1259 }
1260
1261 output.push_str(&format!(
1262 " <loss_percent>{:.1}</loss_percent>\n",
1263 hop.statistics.loss_percent
1264 ));
1265
1266 if let Some(as_num) = hop.network_info.as_number {
1267 output.push_str(&format!(" <as_number>{}</as_number>\n", as_num));
1268 if let Some(as_name) = &hop.network_info.as_name {
1269 output.push_str(&format!(" <as_name>{}</as_name>\n", as_name));
1270 }
1271 }
1272
1273 if let (Some(country), Some(city)) =
1274 (&hop.network_info.country, &hop.network_info.city)
1275 {
1276 output.push_str(&format!(" <country>{}</country>\n", country));
1277 output.push_str(&format!(" <city>{}</city>\n", city));
1278 }
1279
1280 output.push_str(&format!(
1281 " <reached_destination>{}</reached_destination>\n",
1282 hop.reached_destination
1283 ));
1284 } else {
1285 output.push_str(" <address>*</address>\n");
1286 output.push_str(" <timeout>true</timeout>\n");
1287 }
1288
1289 output.push_str(" </hop>\n");
1290 }
1291
1292 output.push_str(" </hops>\n");
1293 output.push_str("</traceroute>\n");
1294 output
1295 }
1296}
1297
1298fn parse_icmp_response(buffer: &[u8], expected_port: u16) -> Option<(Ipv4Addr, bool)> {
1300 if buffer.len() < 48 {
1301 return None;
1302 }
1303
1304 let ip_header_len = ((buffer[0] & 0x0F) * 4) as usize;
1305 if buffer.len() < ip_header_len + 8 {
1306 return None;
1307 }
1308 let source_ip = Ipv4Addr::new(buffer[12], buffer[13], buffer[14], buffer[15]);
1309 let icmp = &buffer[ip_header_len..];
1310
1311 if icmp.len() < 8 {
1312 return None;
1313 }
1314
1315 let reached = match (icmp[0], icmp[1]) {
1316 (11, _) => false, (3, 3) => true, _ => return None,
1319 };
1320
1321 if icmp.len() < 28 {
1322 return None;
1323 }
1324 let orig_ip = &icmp[8..];
1325 if orig_ip.len() < 20 {
1326 return None;
1327 }
1328 if orig_ip[9] != 17 {
1329 return None;
1330 }
1331 let orig_ip_header_len = ((orig_ip[0] & 0x0F) * 4) as usize;
1332 if orig_ip.len() < orig_ip_header_len + 8 {
1333 return None;
1334 }
1335 let orig_udp = &orig_ip[orig_ip_header_len..];
1336 let dest_port = u16::from_be_bytes([orig_udp[2], orig_udp[3]]);
1337 if dest_port != expected_port {
1338 return None;
1339 }
1340
1341 Some((source_ip, reached))
1342}
1343
1344fn parse_icmp_echo_response(
1346 buffer: &[u8],
1347 identifier: u16,
1348 sequence: u16,
1349) -> Option<(Ipv4Addr, bool)> {
1350 if buffer.len() < 28 {
1351 return None;
1352 }
1353
1354 let ip_header_len = ((buffer[0] & 0x0F) * 4) as usize;
1355 if buffer.len() < ip_header_len + 8 {
1356 return None;
1357 }
1358 let source_ip = Ipv4Addr::new(buffer[12], buffer[13], buffer[14], buffer[15]);
1359 let icmp = &buffer[ip_header_len..];
1360
1361 if icmp.len() < 8 {
1362 return None;
1363 }
1364
1365 match (icmp[0], icmp[1]) {
1366 (0, 0) => {
1367 let id = u16::from_be_bytes([icmp[4], icmp[5]]);
1369 let seq = u16::from_be_bytes([icmp[6], icmp[7]]);
1370 if id == identifier && seq == sequence {
1371 Some((source_ip, true))
1372 } else {
1373 None
1374 }
1375 }
1376 (11, _) => {
1377 if icmp.len() < 28 {
1378 return None;
1379 }
1380 let orig_ip = &icmp[8..];
1381 if orig_ip.len() < 20 {
1382 return None;
1383 }
1384 if orig_ip[9] != 1 {
1385 return None;
1386 }
1387 let orig_ip_header_len = ((orig_ip[0] & 0x0F) * 4) as usize;
1388 if orig_ip.len() < orig_ip_header_len + 8 {
1389 return None;
1390 }
1391 let orig_icmp = &orig_ip[orig_ip_header_len..];
1392 if orig_icmp.len() < 8 {
1393 return None;
1394 }
1395 let id = u16::from_be_bytes([orig_icmp[4], orig_icmp[5]]);
1396 let seq = u16::from_be_bytes([orig_icmp[6], orig_icmp[7]]);
1397 if id == identifier && seq == sequence {
1398 Some((source_ip, false))
1399 } else {
1400 None
1401 }
1402 }
1403 _ => None,
1404 }
1405}
1406
1407#[cfg(test)]
1408mod tests {
1409 use super::*;
1410
1411 #[test]
1412 fn config_defaults() {
1413 let cfg = TracerouteConfig::default();
1414 assert_eq!(cfg.max_hops, 30);
1415 assert_eq!(cfg.base_port, ports::TRACEROUTE_BASE);
1416 assert_eq!(cfg.attempts, 3);
1417 assert!(cfg.icmp_fallback);
1418 }
1419}