1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
13
14pub fn is_global(ip: IpAddr) -> bool {
16 match ip {
17 IpAddr::V4(v4) => is_global_v4(v4),
18 IpAddr::V6(v6) => is_global_v6(v6),
19 }
20}
21
22fn is_global_v4(ip: Ipv4Addr) -> bool {
23 let o = ip.octets();
24 !(ip.is_private()
25 || ip.is_loopback()
26 || ip.is_link_local()
27 || ip.is_broadcast()
28 || ip.is_documentation()
29 || ip.is_multicast()
30 || ip.is_unspecified()
31 || o[0] == 0
32 || (o[0] == 100 && (o[1] & 0xc0) == 64) || (o[0] == 192 && o[1] == 0 && o[2] == 0) || (o[0] == 198 && (o[1] & 0xfe) == 18) || o[0] >= 240) }
37
38fn is_global_v6(ip: Ipv6Addr) -> bool {
39 if let Some(v4) = ip.to_ipv4_mapped() {
40 return is_global_v4(v4);
41 }
42 let s = ip.segments();
43 !(ip.is_loopback()
44 || ip.is_unspecified()
45 || ip.is_multicast()
46 || (s[0] & 0xffc0) == 0xfe80 || (s[0] & 0xfe00) == 0xfc00 || (s[0] == 0x2001 && s[1] == 0x0db8) || (s[0] == 0x0064 && s[1] == 0xff9b) || (s[0] == 0x2001 && s[1] == 0x0002 && s[2] == 0)) }
52
53pub fn format_host(ip: IpAddr) -> String {
55 match ip {
56 IpAddr::V4(v4) => v4.to_string(),
57 IpAddr::V6(v6) => format!("[{v6}]"),
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 fn g(s: &str) -> bool {
66 is_global(s.parse().unwrap())
67 }
68
69 #[test]
70 fn ipv4_classification() {
71 assert!(!g("10.1.2.3"));
72 assert!(!g("172.16.0.1"));
73 assert!(!g("192.168.86.237"));
74 assert!(!g("100.87.8.146"), "CGNAT space must not be global");
75 assert!(!g("100.127.255.255"));
76 assert!(g("100.128.0.1"), "just past the /10");
77 assert!(!g("169.254.1.1"));
78 assert!(!g("127.0.0.1"));
79 assert!(!g("0.0.0.0"));
80 assert!(!g("203.0.113.5"), "documentation space");
81 assert!(!g("198.18.0.1"));
82 assert!(!g("240.0.0.1"));
83 assert!(g("8.8.8.8"));
84 assert!(g("95.216.1.1"));
85 }
86
87 #[test]
88 fn ipv6_classification() {
89 assert!(!g("fc00::1"));
90 assert!(!g("fd12:3456::1"));
91 assert!(!g("fe80::1"));
92 assert!(!g("::1"));
93 assert!(!g("2001:db8::1"));
94 assert!(!g("::ffff:192.168.1.1"), "v4-mapped private");
95 assert!(g("::ffff:8.8.8.8"), "v4-mapped public");
96 assert!(g("2606:4700::1111"));
97 assert!(g("2a00:1450:400f:80d::200e"));
98 }
99
100 #[test]
101 fn host_formatting() {
102 assert_eq!(format_host("1.2.3.4".parse().unwrap()), "1.2.3.4");
103 assert_eq!(
104 format_host("2606:4700::1111".parse().unwrap()),
105 "[2606:4700::1111]"
106 );
107 }
108}