1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/// Tests for network segment classification logic in v0.6.0
///
/// These tests verify the correct classification of hops into segments:
/// - LAN: Private/internal IP addresses
/// - ISP: Internet Service Provider network (including CGNAT)
/// - TRANSIT: Networks between ISP and destination (including IXPs without ASN)
/// - DESTINATION: Hops within the destination's ASN
/// - UNKNOWN: Unclassifiable segments
#[cfg(test)]
mod tests {
use crate::traceroute::{AsnInfo, SegmentType};
use std::net::Ipv4Addr;
#[test]
fn test_transit_classification_no_asn_public_ip() {
// Test that public IPs without ASN info are classified as TRANSIT
// This covers the case like Equinix peering points (206.223.116.16)
// Simulate the classification logic for a public IP without ASN
let ipv4 = Ipv4Addr::new(206, 223, 116, 16);
let is_internal = crate::traceroute::is_internal_ip(&ipv4);
let is_cgnat = crate::traceroute::is_cgnat(&ipv4);
assert!(!is_internal, "206.223.116.16 should not be internal");
assert!(!is_cgnat, "206.223.116.16 should not be CGNAT");
// When a public IP has no ASN and we're past ISP boundary,
// it should be classified as TRANSIT
let in_isp_segment = true; // Assume we've passed ISP hops
let asn_info: Option<AsnInfo> = None; // No ASN data available
let segment = if !is_internal && !is_cgnat && asn_info.is_none() && in_isp_segment {
SegmentType::Transit
} else {
SegmentType::Unknown
};
assert_eq!(
segment,
SegmentType::Transit,
"Public IP without ASN after ISP should be TRANSIT"
);
}
#[test]
fn test_destination_segment_with_matching_asn() {
// Test that hops with ASN matching destination are marked as DESTINATION
let dest_asn = Some(15169u32); // Google's ASN
let hop_asn_info = Some(AsnInfo {
asn: 15169,
prefix: "142.250.0.0/15".to_string(),
country_code: "US".to_string(),
registry: "arin".to_string(),
name: "GOOGLE, US".to_string(),
});
// Check if hop ASN matches destination ASN
let segment = if let Some(asn_info) = &hop_asn_info {
if let Some(dest) = dest_asn {
if asn_info.asn == dest {
SegmentType::Destination
} else {
SegmentType::Transit
}
} else {
SegmentType::Unknown
}
} else {
SegmentType::Unknown
};
assert_eq!(
segment,
SegmentType::Destination,
"Hop with ASN matching destination should be DESTINATION"
);
}
#[test]
fn test_transit_segment_different_asn() {
// Test that hops with different ASN than ISP and destination are TRANSIT
let isp_asn = Some(46375u32); // Sonic ISP
let dest_asn = Some(15169u32); // Google
let _in_isp_segment = false; // We've passed ISP boundary (unused but kept for clarity)
let hop_asn_info = Some(AsnInfo {
asn: 10310, // Yahoo transit network
prefix: "209.191.64.0/20".to_string(),
country_code: "US".to_string(),
registry: "arin".to_string(),
name: "YAHOO-1, US".to_string(),
});
// Classification logic
let segment = if let Some(asn_info) = &hop_asn_info {
if let Some(isp) = isp_asn {
if asn_info.asn == isp {
SegmentType::Isp
} else if let Some(dest) = dest_asn {
if asn_info.asn == dest {
SegmentType::Destination
} else {
SegmentType::Transit
}
} else {
SegmentType::Transit
}
} else {
SegmentType::Unknown
}
} else {
SegmentType::Unknown
};
assert_eq!(
segment,
SegmentType::Transit,
"Hop with ASN different from ISP and destination should be TRANSIT"
);
}
#[test]
fn test_isp_segment_classification() {
// Test ISP segment classification including CGNAT ranges
// Test CGNAT IP (100.64.0.0/10)
let cgnat_ip = Ipv4Addr::new(100, 65, 0, 1);
assert!(
crate::traceroute::is_cgnat(&cgnat_ip),
"100.65.0.1 should be in CGNAT range"
);
// Test ISP ASN matching
let isp_asn = Some(46375u32);
let hop_asn = AsnInfo {
asn: 46375,
prefix: "75.101.32.0/19".to_string(),
country_code: "US".to_string(),
registry: "arin".to_string(),
name: "AS-SONICTELECOM, US".to_string(),
};
let segment = if Some(hop_asn.asn) == isp_asn {
SegmentType::Isp
} else {
SegmentType::Unknown
};
assert_eq!(
segment,
SegmentType::Isp,
"Hop with ASN matching ISP should be ISP segment"
);
}
#[test]
fn test_lan_segment_classification() {
// Test LAN/private IP classification
let private_ips = vec![
Ipv4Addr::new(192, 168, 1, 1), // 192.168.0.0/16
Ipv4Addr::new(10, 0, 0, 1), // 10.0.0.0/8
Ipv4Addr::new(172, 16, 0, 1), // 172.16.0.0/12
Ipv4Addr::new(127, 0, 0, 1), // Loopback
Ipv4Addr::new(169, 254, 0, 1), // Link-local
];
for ip in private_ips {
assert!(
crate::traceroute::is_internal_ip(&ip),
"{} should be classified as internal/LAN",
ip
);
}
// Test non-private IPs
let public_ips = vec![
Ipv4Addr::new(8, 8, 8, 8),
Ipv4Addr::new(1, 1, 1, 1),
Ipv4Addr::new(206, 223, 116, 16),
];
for ip in public_ips {
assert!(
!crate::traceroute::is_internal_ip(&ip),
"{} should NOT be classified as internal/LAN",
ip
);
}
}
#[test]
fn test_segment_priority_in_classification() {
// Test that ISP classification takes priority over destination
// when we haven't left ISP boundary yet
let isp_asn = Some(46375u32);
let dest_asn = Some(46375u32); // Same as ISP (hypothetical)
let in_isp_segment = true;
let hop_asn = AsnInfo {
asn: 46375,
prefix: "75.101.32.0/19".to_string(),
country_code: "US".to_string(),
registry: "arin".to_string(),
name: "AS-SONICTELECOM, US".to_string(),
};
// When ASN matches both ISP and destination, and we're still in ISP segment,
// it should be classified as ISP
let segment = if in_isp_segment && Some(hop_asn.asn) == isp_asn {
SegmentType::Isp
} else if Some(hop_asn.asn) == dest_asn {
SegmentType::Destination
} else {
SegmentType::Transit
};
assert_eq!(
segment,
SegmentType::Isp,
"When in ISP segment, ISP classification should take priority"
);
}
}