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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
use super::*;
use alloc::format;
fn literal(content: &str) -> LiteralScope {
match classify(&format!("[{content}]")) {
DomainScope::Literal(scope) => scope,
other => panic!("expected a literal for '[{content}]', got {other:?}"),
}
}
// ── Names ──
#[test]
fn a_registrable_name_is_global() {
assert_eq!(classify("example.com"), DomainScope::Global);
assert_eq!(classify("mail.example.co.uk"), DomainScope::Global);
}
#[test]
fn a_single_label_is_local() {
// The name a resolver on this network answers for, and nothing outside it.
assert_eq!(classify("printer"), DomainScope::Local);
assert_eq!(classify("localhost"), DomainScope::Local);
}
#[test]
fn the_reserved_names_are_local() {
for domain in [
"host.localhost",
"host.test",
"host.invalid",
"host.example",
"files.local",
"home.arpa",
"printer.home.arpa",
"db.internal",
] {
assert_eq!(
classify(domain),
DomainScope::Local,
"{domain} is reserved for use inside a network"
);
}
}
#[test]
fn reserved_names_match_whole_labels_only() {
// The direction that a substring match would get wrong: each of these ends
// with the letters of a reserved name without being under it.
for domain in [
"notlocal.com",
"localhost.com",
"example.com",
"test.com",
"internal.com",
"nothome.arpa",
] {
assert_eq!(
classify(domain),
DomainScope::Global,
"{domain} is not under a reserved name"
);
}
}
#[test]
fn a_name_under_no_delegated_suffix_is_local() {
// With the PSL compiled in this is a name the root zone does not delegate;
// without it, one whose final label is not TLD-like. Both readings agree
// that it means something only to the resolver being asked.
assert_eq!(classify("printer.lan.4"), DomainScope::Local);
}
#[cfg(feature = "psl")]
#[test]
fn an_unknown_tld_is_local_when_the_suffix_list_is_compiled_in() {
// TLD-like by shape, absent from the root zone. Only the PSL reading can
// tell the difference, so this case is what the feature buys.
assert_eq!(classify("host.nonexistenttld"), DomainScope::Local);
assert_eq!(classify("host.com"), DomainScope::Global);
}
#[test]
fn names_the_dns_never_resolves_are_not_global() {
// Special-use names whose resolution is defined to happen outside the DNS.
// `Global` claims the global DNS can resolve the name, and for these it
// cannot — by the document that reserved them, not by circumstance.
//
// `.onion` fails in both feature configurations, not just without the
// suffix list: the PSL lists `onion`, so asking it returns a known suffix
// and reports the name as registrable.
assert_eq!(classify("site.onion"), DomainScope::Local);
assert_eq!(classify("service.alt"), DomainScope::Local);
}
#[test]
fn is_global_folds_the_name_cases() {
assert!(classify("example.com").is_global());
assert!(!classify("files.local").is_global());
assert!(!classify("printer").is_global());
}
// ── Literals ──
#[test]
fn a_public_ipv4_literal_is_global() {
assert_eq!(literal("192.0.2.1"), LiteralScope::Ipv4(IpScope::Global));
assert_eq!(literal("8.8.8.8"), LiteralScope::Ipv4(IpScope::Global));
}
#[test]
fn the_reserved_ipv4_ranges_are_local() {
for content in [
"10.0.0.1", // RFC 1918
"172.16.0.1", // RFC 1918, low edge
"172.31.255.254", // RFC 1918, high edge
"192.168.1.5", // RFC 1918
"100.64.0.1", // RFC 6598, low edge
"100.127.255.254", // RFC 6598, high edge
"169.254.1.1", // RFC 3927
"127.0.0.1", // RFC 1122 loopback
"0.0.0.0", // RFC 1122 "this network"
"198.18.0.1", // RFC 2544 benchmarking, low edge
"198.19.255.254", // RFC 2544 benchmarking, high edge
] {
assert_eq!(
literal(content),
LiteralScope::Ipv4(IpScope::Local),
"{content} is reserved for use inside a network"
);
}
}
#[test]
fn the_ipv4_addresses_that_never_leave_the_network_are_local() {
for content in [
"255.255.255.255", // RFC 919 §7 limited broadcast: never forwarded
"224.0.0.1", // RFC 5771 §4 local network control block
"224.0.0.251", // the same block, where mDNS lives
"239.1.2.3", // RFC 2365 §4 administratively scoped multicast
] {
assert_eq!(
literal(content),
LiteralScope::Ipv4(IpScope::Local),
"{content} does not leave the network it is sent on"
);
}
}
#[test]
fn the_ipv4_multicast_that_does_leave_the_network_stays_global() {
// The direction that a whole-224/4 rule would get wrong: multicast is not
// local by being multicast, only by its assigned scope.
for content in [
"224.0.1.1", // internetwork control, forwarded
"233.1.2.3", // RFC 3180 GLOP, globally assigned
] {
assert_eq!(
literal(content),
LiteralScope::Ipv4(IpScope::Global),
"{content} is routable multicast"
);
}
}
#[test]
fn ipv6_multicast_is_local_below_global_scope() {
// RFC 4291 §2.7: the fourth nibble is the scope, and only 0xE is global.
for content in [
"IPv6:ff01::1", // interface-local
"IPv6:ff02::1", // link-local, the all-nodes address
"IPv6:ff05::1", // site-local
"IPv6:ff08::1", // organization-local
] {
assert_eq!(
literal(content),
LiteralScope::Ipv6(IpScope::Local),
"{content} is confined to its scope"
);
}
assert_eq!(
literal("IPv6:ff0e::1"),
LiteralScope::Ipv6(IpScope::Global),
"scope 0xE is global multicast"
);
}
#[test]
fn the_ranges_neighbouring_the_reserved_ipv4_ones_are_global() {
// The off-by-one direction: each of these sits immediately outside a
// reserved range, and a mask written one bit wide would swallow it.
for content in [
"172.15.255.255", // just below 172.16/12
"172.32.0.1", // just above 172.16/12
"100.63.255.255", // just below 100.64/10
"100.128.0.1", // just above 100.64/10
"169.253.0.1", // just below 169.254/16
"169.255.0.1", // just above 169.254/16
"11.0.0.1", // just above 10/8
"192.169.0.1", // just above 192.168/16
"126.0.0.1", // just below 127/8
"128.0.0.1", // just above 127/8
"1.0.0.1", // just above 0/8
"198.17.255.255", // just below 198.18/15
"198.20.0.1", // just above 198.18/15
] {
assert_eq!(
literal(content),
LiteralScope::Ipv4(IpScope::Global),
"{content} is outside every reserved range"
);
}
}
#[test]
fn a_padded_ipv4_literal_keeps_its_value() {
// `Snum` allows the padding the URI grammar does not, so this is the same
// address as 10.0.0.1 and must not read as some other one.
assert_eq!(
literal("010.000.000.001"),
LiteralScope::Ipv4(IpScope::Local)
);
}
#[test]
fn a_public_ipv6_literal_is_global() {
assert_eq!(
literal("IPv6:2001:db8::1"),
LiteralScope::Ipv6(IpScope::Global)
);
}
#[test]
fn the_reserved_ipv6_ranges_are_local() {
for content in [
"IPv6:fd00::1", // RFC 4193, the fd00::/8 half in general use
"IPv6:fc00::1", // RFC 4193, low edge of fc00::/7
"IPv6:fdff::1", // RFC 4193, high edge
"IPv6:fe80::1", // RFC 4291 link-local, low edge
"IPv6:febf::1", // RFC 4291 link-local, high edge
"IPv6:fec0::1", // RFC 3879 site-local, deprecated but site-scoped
"IPv6:feff::1", // the high edge of that block
"IPv6:::1", // RFC 4291 loopback
"IPv6:::", // RFC 4291 unspecified
] {
assert_eq!(
literal(content),
LiteralScope::Ipv6(IpScope::Local),
"{content} is reserved for use inside a network"
);
}
}
#[test]
fn the_ranges_neighbouring_the_reserved_ipv6_ones_are_global() {
for content in [
"IPv6:fbff::1", // just below fc00::/7
"IPv6:fe00::1", // above fc00::/7, below fe80::/10, unassigned
"IPv6:fe7f::1", // the high edge of that unassigned gap
] {
assert_eq!(
literal(content),
LiteralScope::Ipv6(IpScope::Global),
"{content} is outside every reserved range"
);
}
}
#[test]
fn an_ipv4_mapped_literal_reports_the_reach_of_the_address_it_holds() {
// Two spellings of one host: classifying them differently would make the
// answer depend on how the address was written rather than where it goes.
assert_eq!(
literal("IPv6:::ffff:192.168.1.5"),
LiteralScope::Ipv6(IpScope::Local)
);
assert_eq!(
literal("IPv6:::ffff:192.0.2.1"),
LiteralScope::Ipv6(IpScope::Global)
);
}
#[test]
fn an_ipv4_compatible_literal_reports_the_reach_of_the_address_it_holds() {
// The deprecated `::a.b.c.d` form (RFC 4291 §2.5.5.1) holds an IPv4 address
// just as the mapped form does, and `Ipv6Addr` parses it, so the parser
// accepts the literal. Reading only the mapped form would classify one
// spelling of a private endpoint as globally reachable.
assert_eq!(
literal("IPv6:::192.168.1.5"),
LiteralScope::Ipv6(IpScope::Local)
);
assert_eq!(
literal("IPv6:::192.0.2.1"),
LiteralScope::Ipv6(IpScope::Global)
);
}
#[test]
fn the_addresses_that_look_like_embedded_ipv4_but_are_not() {
// `::1` and `::` sit inside `::/96` and would decode as `0.0.0.1` and
// `0.0.0.0` if the embedded-IPv4 reading were applied blindly. They are
// loopback and unspecified in their own right, and answering through their
// own rule is what keeps the answer true rather than accidentally right.
assert_eq!(literal("IPv6:::1"), LiteralScope::Ipv6(IpScope::Local));
assert_eq!(literal("IPv6:::"), LiteralScope::Ipv6(IpScope::Local));
}
#[test]
fn an_ipv4_mapped_literal_with_a_padded_tail_is_read_the_same_way() {
// The depadding path, which `Ipv6Addr` alone refuses.
assert_eq!(
literal("IPv6:::ffff:192.168.001.005"),
LiteralScope::Ipv6(IpScope::Local)
);
}
#[test]
fn a_general_address_literal_is_neither_family_and_never_global() {
assert_eq!(literal("AS400:QSYS"), LiteralScope::General);
assert!(!classify("[AS400:QSYS]").is_global());
}
#[test]
fn is_global_folds_the_literal_cases() {
assert!(classify("[192.0.2.1]").is_global());
assert!(!classify("[192.168.1.5]").is_global());
assert!(classify("[IPv6:2001:db8::1]").is_global());
assert!(!classify("[IPv6:fd00::1]").is_global());
}