anti_traceroute/
lib.rs

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/// Protocol for traceroute probes
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TraceProtocol {
15    /// UDP probes (traditional traceroute)
16    Udp,
17    /// ICMP echo requests
18    Icmp,
19    /// TCP SYN probes
20    Tcp,
21}
22
23/// IP version for traceroute
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub enum IpVersion {
26    V4,
27    V6,
28}
29
30/// Output format for traceroute results
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum OutputFormat {
33    /// Human-readable text
34    Text,
35    /// JSON format
36    Json,
37    /// CSV format
38    Csv,
39    /// XML format
40    Xml,
41}
42
43/// Traceroute mode
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum TraceMode {
46    /// Single run mode
47    Single,
48    /// Continuous monitoring (like mtr)
49    Continuous,
50    /// Report mode (statistics after N cycles)
51    Report,
52}
53
54/// Configuration for traceroute
55#[derive(Debug, Clone)]
56pub struct TracerouteConfig {
57    /// Target address (IPv4 or IPv6)
58    pub target_v4: Option<Ipv4Addr>,
59    pub target_v6: Option<Ipv6Addr>,
60    /// IP version to use
61    pub ip_version: IpVersion,
62    /// Protocol for probes
63    pub protocol: TraceProtocol,
64    /// Maximum hops to probe
65    pub max_hops: u8,
66    /// First hop TTL (useful for skipping local network)
67    pub first_hop: u8,
68    /// Timeout for each hop
69    pub timeout: Duration,
70    /// Base UDP port to use
71    pub base_port: u16,
72    /// TCP port for TCP traceroute
73    pub tcp_port: u16,
74    /// Number of attempts per hop
75    pub attempts: u8,
76    /// Packet size in bytes
77    pub packet_size: usize,
78    /// Interval between packets
79    pub packet_interval: Duration,
80    /// Source address to bind to
81    pub source_address: Option<IpAddr>,
82    /// Source interface name
83    pub source_interface: Option<String>,
84    /// Whether to fall back to ICMP echo probes when UDP fails
85    pub icmp_fallback: bool,
86    /// Perform reverse DNS lookups
87    pub resolve_hostnames: bool,
88    /// Perform AS number lookups
89    pub lookup_as: bool,
90    /// Perform geographic lookups
91    pub lookup_geo: bool,
92    /// Output format
93    pub output_format: OutputFormat,
94    /// Traceroute mode
95    pub mode: TraceMode,
96    /// Number of cycles for report mode
97    pub report_cycles: u32,
98    /// Quiet mode (minimal output)
99    pub quiet: bool,
100    /// Show only summary statistics
101    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/// Statistics for a hop across multiple probes
135#[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>, // For calculating statistics
146}
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            // Calculate jitter (average of differences between consecutive measurements)
203            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/// Network information for a hop
216#[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/// Result for a single hop
240#[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/// Progress events during traceroute
260#[derive(Debug, Clone)]
261pub enum TraceProgress {
262    /// Starting to probe a hop
263    HopStart { ttl: u8 },
264    /// Individual attempt (dot indicator)
265    Attempt { ttl: u8, attempt: u8 },
266    /// Attempt timed out
267    AttemptTimeout { ttl: u8, attempt: u8 },
268    /// Using ICMP fallback
269    FallbackStart { ttl: u8 },
270    /// Final hop result
271    HopComplete(HopResult),
272}
273
274/// Main traceroute type
275pub 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    /// Create a new tracer with the given configuration
286    pub fn new(config: TracerouteConfig) -> PingResult<Self> {
287        let mut rng = rand::thread_rng();
288        let icmp_identifier = rng.gen::<u16>();
289
290        // Determine bind address based on IP version and source settings
291        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        // Setup UDP socket if needed
299        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        // Setup TCP socket if needed
308        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            // TCP traceroute requires raw sockets to craft TCP SYN packets
314            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            // Set IP_HDRINCL to include IP headers in our packets
327            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        // Setup ICMP socket if needed
348        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                    // fall back to raw socket if DGRAM not permitted
359                    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    /// Run the traceroute and return the hop results
402    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            // ICMP fallback
443            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            // Network information lookup
452            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    /// Run the traceroute with a callback for live progress updates
468    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            // ICMP fallback
510            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            // Network information lookup
519            if let Some(addr) = hop.addr() {
520                hop.network_info = self.lookup_network_info(addr);
521            }
522
523            // Call the callback with the current hop result
524            let should_continue = callback(&hop);
525
526            if hop.reached_destination || !should_continue {
527                break;
528            }
529        }
530
531        Ok(())
532    }
533
534    /// Run the traceroute with detailed progress callbacks
535    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            // Notify start of hop
553            if !callback(TraceProgress::HopStart { ttl }) {
554                return Ok(());
555            }
556
557            for attempt in 0..self.config.attempts {
558                // Show attempt progress
559                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            // ICMP fallback
596            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            // Network information lookup
606            if let Some(addr) = hop.addr() {
607                hop.network_info = self.lookup_network_info(addr);
608            }
609
610            // Report final hop result
611            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    /// Run continuous traceroute with statistics (like mtr)
622    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                // Probe this hop
633                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                // Get current statistics and network info for this hop
661                let stats = self.hop_statistics.get(&ttl).cloned().unwrap_or_default();
662                let network_info = HopNetworkInfo::default(); // TODO: Store network info
663
664                // Call callback with current statistics
665                if !callback(ttl, &stats, &network_info) {
666                    return Ok(());
667                }
668
669                if reached_destination {
670                    break;
671                }
672
673                // Sleep between packets if configured
674                if self.config.packet_interval > Duration::from_millis(0) {
675                    std::thread::sleep(self.config.packet_interval);
676                }
677            }
678
679            // Sleep between cycles
680            std::thread::sleep(Duration::from_millis(100));
681        }
682    }
683
684    /// Get target address based on IP version
685    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    /// Set hop address based on IP version
697    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    /// Update statistics for a hop
705    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    /// Send UDP probe
711    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    /// Send single UDP probe
727    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    /// Send a TCP probe
771    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            // Send the raw TCP SYN packet
792            match tcp_socket.send_to(&packet, &target.into()) {
793                Ok(_) => {
794                    // For TCP traceroute, we don't wait for a response here
795                    // The response handling should be done separately (ICMP time exceeded or TCP RST/SYN+ACK)
796                    // For now, we'll just return a successful probe
797                    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                // Build IPv4 + TCP SYN packet
825                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                // IPv4 Header
832                packet[0] = 0x45; // Version (4) + IHL (5)
833                packet[1] = 0x00; // DSCP + ECN
834                packet[2..4].copy_from_slice(&(total_len as u16).to_be_bytes()); // Total length
835                packet[4..6].copy_from_slice(&rand::random::<u16>().to_be_bytes()); // Identification
836                packet[6..8].copy_from_slice(&0x4000u16.to_be_bytes()); // Flags + Fragment offset (Don't fragment)
837                packet[8] = ttl; // TTL
838                packet[9] = 6; // Protocol (TCP)
839                packet[10..12].copy_from_slice(&[0, 0]); // Checksum (will be calculated later)
840                packet[12..16].copy_from_slice(&src.octets()); // Source IP
841                packet[16..20].copy_from_slice(&dst.octets()); // Destination IP
842
843                // Calculate IP checksum
844                let ip_checksum = self.calculate_ip_checksum(&packet[0..20]);
845                packet[10..12].copy_from_slice(&ip_checksum.to_be_bytes());
846
847                // TCP Header
848                let source_port = rand::random::<u16>() | 0x8000; // Use high port
849                packet[20..22].copy_from_slice(&source_port.to_be_bytes()); // Source port
850                packet[22..24].copy_from_slice(&self.config.tcp_port.to_be_bytes()); // Destination port
851                packet[24..28].copy_from_slice(&rand::random::<u32>().to_be_bytes()); // Sequence number
852                packet[28..32].copy_from_slice(&[0, 0, 0, 0]); // Acknowledgment number
853                packet[32] = 0x50; // Data offset (5) + Reserved (0)
854                packet[33] = 0x02; // Flags (SYN)
855                packet[34..36].copy_from_slice(&8192u16.to_be_bytes()); // Window size
856                packet[36..38].copy_from_slice(&[0, 0]); // Checksum (will be calculated)
857                packet[38..40].copy_from_slice(&[0, 0]); // Urgent pointer
858
859                // Calculate TCP checksum
860                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        // Pseudo header
892        // Source IP (4 bytes)
893        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        // Destination IP (4 bytes)
896        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        // Protocol (TCP = 6)
899        sum += 6u32;
900        // TCP length
901        sum += tcp_header.len() as u32;
902
903        // TCP header and data
904        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    /// Send an ICMP echo probe
919    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    /// Perform network information lookups for an address
972    fn lookup_network_info(&self, addr: IpAddr) -> HopNetworkInfo {
973        let mut info = HopNetworkInfo::default();
974
975        // Reverse DNS lookup
976        if self.config.resolve_hostnames {
977            info.hostname = self.reverse_dns_lookup(addr);
978        }
979
980        // AS number lookup
981        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        // Geographic lookup
989        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    /// Perform reverse DNS lookup
1001    fn reverse_dns_lookup(&self, addr: IpAddr) -> Option<String> {
1002        // Perform real reverse DNS lookup with timeout
1003        let addr_clone = addr;
1004        let lookup_result = std::thread::spawn(move || lookup_addr(&addr_clone).ok());
1005
1006        // Give it a reasonable timeout (1 second)
1007        match lookup_result.join() {
1008            Ok(result) => result,
1009            Err(_) => None,
1010        }
1011    }
1012
1013    /// Perform AS number lookup
1014    fn as_lookup(&self, addr: IpAddr) -> Option<(u32, String)> {
1015        // Simulated data for demonstration
1016        // In production, would query WHOIS or BGP databases
1017        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    /// Perform geographic lookup
1036    fn geo_lookup(&self, addr: IpAddr) -> Option<(String, String, String)> {
1037        // Simulated data for demonstration
1038        // In production, would use MaxMind GeoIP or similar
1039        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    /// Format output according to the configured format
1074    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    /// Format as human-readable text
1084    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    /// Format as JSON
1150    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    /// Format as CSV
1170    fn format_csv_output(&self, hops: &[HopResult]) -> String {
1171        let mut output = String::new();
1172
1173        // CSV header
1174        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    /// Format as XML
1215    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
1298/// Parse an ICMP response packet
1299fn 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, // Time Exceeded
1317        (3, 3) => true,   // Port unreachable
1318        _ => 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
1344/// Parse ICMP response for echo probes
1345fn 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            // Echo reply
1368            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}