1pub mod api;
26pub mod config;
27pub mod engine;
28pub mod error;
29pub mod result;
30pub mod types;
31
32#[cfg(test)]
33mod caching_test;
34
35use ip_network::Ipv4Network;
36use serde::{Deserialize, Serialize};
37use std::net::Ipv4Addr;
38
39pub use api::resolve_target_with_family;
41pub use api::{Traceroute, trace_async as trace, trace_with_config_async as trace_with_config};
42pub use config::{PreferredFamily, TimingConfig, TracerouteConfig, TracerouteConfigBuilder};
43pub use error::{ConfigError, TracerouteError};
44pub use result::{TracerouteProgress, TracerouteResult};
45pub use types::{ClassifiedHopInfo, IspInfo, RawHopInfo};
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[non_exhaustive]
65pub enum SegmentType {
66 Lan,
68 Isp,
70 Transit,
72 Destination,
74 Unknown,
76}
77
78impl std::fmt::Display for SegmentType {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 SegmentType::Lan => write!(f, "LAN "),
82 SegmentType::Isp => write!(f, "ISP "),
83 SegmentType::Transit => write!(f, "TRANSIT"),
84 SegmentType::Destination => write!(f, "DESTINATION"),
85 SegmentType::Unknown => write!(f, "UNKNOWN"),
86 }
87 }
88}
89
90pub fn is_internal_ip(ip: &Ipv4Addr) -> bool {
92 ip.is_private() || ip.is_loopback() || ip.is_link_local()
93}
94
95pub fn is_cgnat(ip: &Ipv4Addr) -> bool {
97 let octets = ip.octets();
98 octets[0] == 100 && (64..=127).contains(&octets[1])
99}
100
101pub fn is_internal_ipv6(ip: &std::net::Ipv6Addr) -> bool {
111 let first_segment = ip.segments()[0];
112 ip.is_loopback()
113 || (first_segment & 0xffc0) == 0xfe80
115 || (first_segment & 0xfe00) == 0xfc00
117}
118
119pub fn parse_asn(asn_str: &str) -> Option<(String, String, String)> {
121 let parts: Vec<&str> = asn_str.split(" | ").collect();
123 if parts.len() >= 5 {
124 Some((
125 parts[0].to_string(),
126 parts[1].to_string(),
127 parts[4].to_string(),
128 ))
129 } else {
130 None
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct AsnInfo {
156 pub asn: u32,
161 pub prefix: String,
163 pub country_code: String,
165 pub registry: String,
167 pub name: String,
169}
170
171impl AsnInfo {
172 pub fn display_asn(&self) -> String {
176 if self.asn != 0 {
177 format!("AS{}", self.asn)
178 } else {
179 "N/A".to_string()
180 }
181 }
182}
183
184pub fn parse_cidr(cidr: &str) -> Option<Ipv4Network> {
186 cidr.parse().ok()
187}
188
189#[cfg(test)]
190#[path = "traceroute/segment_classification_test.rs"]
191mod segment_classification_test;
192
193#[cfg(test)]
194#[allow(clippy::unwrap_used)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_segment_type_display() {
200 assert_eq!(SegmentType::Lan.to_string(), "LAN ");
201 assert_eq!(SegmentType::Isp.to_string(), "ISP ");
202 assert_eq!(SegmentType::Transit.to_string(), "TRANSIT");
203 assert_eq!(SegmentType::Destination.to_string(), "DESTINATION");
204 assert_eq!(SegmentType::Unknown.to_string(), "UNKNOWN");
205 }
206
207 #[test]
208 fn test_is_internal_ip() {
209 assert!(is_internal_ip(&"192.168.1.1".parse().unwrap()));
211 assert!(is_internal_ip(&"10.0.0.1".parse().unwrap()));
212 assert!(is_internal_ip(&"172.16.0.1".parse().unwrap()));
213 assert!(is_internal_ip(&"172.31.255.255".parse().unwrap()));
214
215 assert!(is_internal_ip(&"127.0.0.1".parse().unwrap()));
217 assert!(is_internal_ip(&"127.255.255.255".parse().unwrap()));
218
219 assert!(is_internal_ip(&"169.254.1.1".parse().unwrap()));
221
222 assert!(!is_internal_ip(&"8.8.8.8".parse().unwrap()));
224 assert!(!is_internal_ip(&"1.1.1.1".parse().unwrap()));
225 assert!(!is_internal_ip(&"172.32.0.1".parse().unwrap())); }
227
228 #[test]
229 fn test_is_cgnat() {
230 assert!(is_cgnat(&"100.64.0.0".parse().unwrap()));
232 assert!(is_cgnat(&"100.64.0.1".parse().unwrap()));
233 assert!(is_cgnat(&"100.127.255.255".parse().unwrap()));
234 assert!(is_cgnat(&"100.100.100.100".parse().unwrap()));
235
236 assert!(!is_cgnat(&"100.63.255.255".parse().unwrap()));
238 assert!(!is_cgnat(&"100.128.0.0".parse().unwrap()));
239 assert!(!is_cgnat(&"99.64.0.0".parse().unwrap()));
240 assert!(!is_cgnat(&"101.64.0.0".parse().unwrap()));
241
242 assert!(!is_cgnat(&"8.8.8.8".parse().unwrap()));
244 assert!(!is_cgnat(&"192.168.1.1".parse().unwrap()));
245 }
246
247 #[test]
248 fn test_is_internal_ipv6() {
249 use std::net::Ipv6Addr;
250 assert!(is_internal_ipv6(&Ipv6Addr::LOCALHOST));
252 assert!(is_internal_ipv6(&"fe80::1".parse::<Ipv6Addr>().unwrap()));
254 assert!(is_internal_ipv6(
255 &"febf:ffff::1".parse::<Ipv6Addr>().unwrap()
256 ));
257 assert!(!is_internal_ipv6(&"fec0::1".parse::<Ipv6Addr>().unwrap()));
259 assert!(!is_internal_ipv6(&"fe7f::1".parse::<Ipv6Addr>().unwrap()));
260 assert!(is_internal_ipv6(&"fc00::1".parse::<Ipv6Addr>().unwrap()));
262 assert!(is_internal_ipv6(
263 &"fd12:3456:789a::1".parse::<Ipv6Addr>().unwrap()
264 ));
265 assert!(is_internal_ipv6(
266 &"fdff:ffff::ffff".parse::<Ipv6Addr>().unwrap()
267 ));
268 assert!(!is_internal_ipv6(&"fbff::1".parse::<Ipv6Addr>().unwrap()));
270 assert!(!is_internal_ipv6(
272 &"2001:4860:4860::8888".parse::<Ipv6Addr>().unwrap()
273 ));
274 assert!(!is_internal_ipv6(
275 &"2606:4700::1".parse::<Ipv6Addr>().unwrap()
276 ));
277 assert!(!is_internal_ipv6(&Ipv6Addr::UNSPECIFIED));
278 }
279
280 #[test]
281 fn test_parse_asn() {
282 let asn_str = "AS13335 | 104.16.0.0/12 | US | ARIN | CLOUDFLARENET";
283 let result = parse_asn(asn_str);
284 assert_eq!(
285 result,
286 Some((
287 "AS13335".to_string(),
288 "104.16.0.0/12".to_string(),
289 "CLOUDFLARENET".to_string()
290 ))
291 );
292
293 assert_eq!(parse_asn("invalid"), None);
295 assert_eq!(parse_asn("AS123 | incomplete"), None);
296 }
297
298 #[test]
299 fn test_parse_cidr() {
300 assert!(parse_cidr("192.168.0.0/16").is_some());
301 assert!(parse_cidr("10.0.0.0/8").is_some());
302 assert!(parse_cidr("172.16.0.0/12").is_some());
303
304 assert!(parse_cidr("invalid").is_none());
306 assert!(parse_cidr("192.168.0.0/33").is_none()); assert!(parse_cidr("256.0.0.0/8").is_none()); }
309
310 #[test]
311 fn test_asn_info() {
312 let asn_info = AsnInfo {
313 asn: 13335,
314 prefix: "104.16.0.0/12".to_string(),
315 country_code: "US".to_string(),
316 registry: "ARIN".to_string(),
317 name: "CLOUDFLARENET".to_string(),
318 };
319
320 assert_eq!(asn_info.asn, 13335);
321 assert_eq!(asn_info.country_code, "US");
322 assert_eq!(asn_info.display_asn(), "AS13335");
323
324 let cloned = asn_info.clone();
326 assert_eq!(cloned, asn_info);
327 }
328
329 #[test]
330 fn test_asn_info_display() {
331 let asn_info = AsnInfo {
333 asn: 12345,
334 prefix: "10.0.0.0/8".to_string(),
335 country_code: "US".to_string(),
336 registry: "ARIN".to_string(),
337 name: "EXAMPLE".to_string(),
338 };
339 assert_eq!(asn_info.display_asn(), "AS12345");
340
341 let private_asn = AsnInfo {
343 asn: 0,
344 prefix: "192.168.0.0/16".to_string(),
345 country_code: "".to_string(),
346 registry: "".to_string(),
347 name: "Private Use".to_string(),
348 };
349 assert_eq!(private_asn.display_asn(), "N/A");
350 }
351}