Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/traits/src/app_components/host_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ pub fn is_public_ipv6(addr: Ipv6Addr) -> bool {
if first == 0x2001 && second == 0x0db8 {
return false;
}
// Documentation 3fff::/20 (RFC 9637). It falls inside global-unicast
// Documentation 3fff::/20 (RFC 9637): first hextet 0x3fff with the top
// nibble of the second hextet zero. It falls inside global-unicast
// 2000::/3, so the terminal rule below would otherwise accept it.
if (first & 0xfff0) == 0x3ff0 {
if first == 0x3fff && (second & 0xf000) == 0 {
return false;
}
// Only global unicast 2000::/3 is routable today; reject anything else not
Expand Down
2 changes: 1 addition & 1 deletion crates/traits/src/app_components/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ fn group_avatar_url_rejects_documentation_ipv6_ranges() {
"https://[2001:db8::1]/a.png",
"https://[2001:db8:abcd:12::1]/a.png",
"https://[3fff::1]/a.png",
"https://[3fff:ffff::1]/a.png",
"https://[3fff:0fff::1]/a.png",
] {
assert!(
validate_and_normalize_group_avatar_url(raw).is_err(),
Expand Down
12 changes: 11 additions & 1 deletion crates/traits/tests/host_safety_public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ fn shared_host_safety_classifies_canonical_ipv6_ranges() {
assert!(is_public_ipv6("::ffff:93.184.216.34".parse().unwrap()));
assert!(is_public_ip(IpAddr::V6("2606:4700::1".parse().unwrap())));

// Only the actual RFC 9637 block 3fff:0000::/20 is documentation space.
// Neighbouring global-unicast hextets (3ff0:: to 3ffe::) and 3fff:: past the
// /20 boundary (second hextet >= 0x1000) are ordinary routable space.
for raw in ["3ff0::1", "3ffe::1", "3fff:1000::1", "3fff:ffff::1"] {
let addr: Ipv6Addr = raw.parse().expect("test IPv6 literal parses");
assert!(is_public_ipv6(addr), "{raw} should be accepted");
assert!(is_public_ip(IpAddr::V6(addr)), "{raw} should be accepted");
}

for raw in [
"::1", // loopback
"::", // unspecified
Expand All @@ -91,7 +100,8 @@ fn shared_host_safety_classifies_canonical_ipv6_ranges() {
"2002::1", // 6to4 transition prefix
"2001::1", // Teredo 2001:0000::/32
"2001:db8::1", // documentation
"3fff::1", // documentation 3fff::/20
"3fff::1", // documentation 3fff::/20 (lower edge)
"3fff:0fff::1", // documentation 3fff::/20 (upper edge)
"4000::1", // outside global-unicast 2000::/3
] {
let addr: Ipv6Addr = raw.parse().expect("test IPv6 literal parses");
Expand Down