pub mod api;
pub mod config;
pub mod engine;
pub mod error;
pub mod isp_from_path;
pub mod result;
pub mod types;
#[cfg(test)]
mod caching_test;
use ip_network::Ipv4Network;
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
pub use api::{trace_async as trace, trace_with_config_async as trace_with_config, Traceroute};
pub use config::{TimingConfig, TracerouteConfig, TracerouteConfigBuilder};
pub use error::TracerouteError;
pub use result::{TracerouteProgress, TracerouteResult};
pub use types::{ClassifiedHopInfo, IspInfo, RawHopInfo};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SegmentType {
Lan,
Isp,
Transit,
Destination,
Unknown,
}
impl std::fmt::Display for SegmentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SegmentType::Lan => write!(f, "LAN "),
SegmentType::Isp => write!(f, "ISP "),
SegmentType::Transit => write!(f, "TRANSIT"),
SegmentType::Destination => write!(f, "DESTINATION"),
SegmentType::Unknown => write!(f, "UNKNOWN"),
}
}
}
pub fn is_internal_ip(ip: &Ipv4Addr) -> bool {
ip.is_private() || ip.is_loopback() || ip.is_link_local()
}
pub fn is_cgnat(ip: &Ipv4Addr) -> bool {
let octets = ip.octets();
octets[0] == 100 && (64..=127).contains(&octets[1])
}
pub fn parse_asn(asn_str: &str) -> Option<(String, String, String)> {
let parts: Vec<&str> = asn_str.split(" | ").collect();
if parts.len() >= 5 {
Some((
parts[0].to_string(),
parts[1].to_string(),
parts[4].to_string(),
))
} else {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AsnInfo {
pub asn: u32,
pub prefix: String,
pub country_code: String,
pub registry: String,
pub name: String,
}
impl AsnInfo {
pub fn display_asn(&self) -> String {
if self.asn != 0 {
format!("AS{}", self.asn)
} else {
"N/A".to_string()
}
}
}
pub fn parse_cidr(cidr: &str) -> Option<Ipv4Network> {
cidr.parse().ok()
}
#[cfg(test)]
#[path = "traceroute/segment_classification_test.rs"]
mod segment_classification_test;
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_segment_type_display() {
assert_eq!(SegmentType::Lan.to_string(), "LAN ");
assert_eq!(SegmentType::Isp.to_string(), "ISP ");
assert_eq!(SegmentType::Transit.to_string(), "TRANSIT");
assert_eq!(SegmentType::Destination.to_string(), "DESTINATION");
assert_eq!(SegmentType::Unknown.to_string(), "UNKNOWN");
}
#[test]
fn test_is_internal_ip() {
assert!(is_internal_ip(&"192.168.1.1".parse().unwrap()));
assert!(is_internal_ip(&"10.0.0.1".parse().unwrap()));
assert!(is_internal_ip(&"172.16.0.1".parse().unwrap()));
assert!(is_internal_ip(&"172.31.255.255".parse().unwrap()));
assert!(is_internal_ip(&"127.0.0.1".parse().unwrap()));
assert!(is_internal_ip(&"127.255.255.255".parse().unwrap()));
assert!(is_internal_ip(&"169.254.1.1".parse().unwrap()));
assert!(!is_internal_ip(&"8.8.8.8".parse().unwrap()));
assert!(!is_internal_ip(&"1.1.1.1".parse().unwrap()));
assert!(!is_internal_ip(&"172.32.0.1".parse().unwrap())); }
#[test]
fn test_is_cgnat() {
assert!(is_cgnat(&"100.64.0.0".parse().unwrap()));
assert!(is_cgnat(&"100.64.0.1".parse().unwrap()));
assert!(is_cgnat(&"100.127.255.255".parse().unwrap()));
assert!(is_cgnat(&"100.100.100.100".parse().unwrap()));
assert!(!is_cgnat(&"100.63.255.255".parse().unwrap()));
assert!(!is_cgnat(&"100.128.0.0".parse().unwrap()));
assert!(!is_cgnat(&"99.64.0.0".parse().unwrap()));
assert!(!is_cgnat(&"101.64.0.0".parse().unwrap()));
assert!(!is_cgnat(&"8.8.8.8".parse().unwrap()));
assert!(!is_cgnat(&"192.168.1.1".parse().unwrap()));
}
#[test]
fn test_parse_asn() {
let asn_str = "AS13335 | 104.16.0.0/12 | US | ARIN | CLOUDFLARENET";
let result = parse_asn(asn_str);
assert_eq!(
result,
Some((
"AS13335".to_string(),
"104.16.0.0/12".to_string(),
"CLOUDFLARENET".to_string()
))
);
assert_eq!(parse_asn("invalid"), None);
assert_eq!(parse_asn("AS123 | incomplete"), None);
}
#[test]
fn test_parse_cidr() {
assert!(parse_cidr("192.168.0.0/16").is_some());
assert!(parse_cidr("10.0.0.0/8").is_some());
assert!(parse_cidr("172.16.0.0/12").is_some());
assert!(parse_cidr("invalid").is_none());
assert!(parse_cidr("192.168.0.0/33").is_none()); assert!(parse_cidr("256.0.0.0/8").is_none()); }
#[test]
fn test_asn_info() {
let asn_info = AsnInfo {
asn: 13335,
prefix: "104.16.0.0/12".to_string(),
country_code: "US".to_string(),
registry: "ARIN".to_string(),
name: "CLOUDFLARENET".to_string(),
};
assert_eq!(asn_info.asn, 13335);
assert_eq!(asn_info.country_code, "US");
assert_eq!(asn_info.display_asn(), "AS13335");
let cloned = asn_info.clone();
assert_eq!(cloned, asn_info);
}
#[test]
fn test_asn_info_display() {
let asn_info = AsnInfo {
asn: 12345,
prefix: "10.0.0.0/8".to_string(),
country_code: "US".to_string(),
registry: "ARIN".to_string(),
name: "EXAMPLE".to_string(),
};
assert_eq!(asn_info.display_asn(), "AS12345");
let private_asn = AsnInfo {
asn: 0,
prefix: "192.168.0.0/16".to_string(),
country_code: "".to_string(),
registry: "".to_string(),
name: "Private Use".to_string(),
};
assert_eq!(private_asn.display_asn(), "N/A");
}
}