1use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub enum AddrScope {
18 Global,
20 Private,
22 UniqueLocal,
24 Other,
26 LinkLocal,
28 Loopback,
30 Unspecified,
32}
33
34impl AddrScope {
35 pub fn of(ip: IpAddr) -> Self {
37 match ip {
38 IpAddr::V4(v4) => v4_scope(v4),
39 IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
40 Some(v4) => {
41 if v4.is_loopback() {
42 Self::Loopback
43 } else if v4.is_unspecified() {
44 Self::Unspecified
45 } else if v4.is_link_local() {
46 Self::LinkLocal
47 } else if v4_is_global(v4) {
48 Self::Global
49 } else {
50 Self::Other
51 }
52 }
53 None => {
54 if v6.is_loopback() {
55 Self::Loopback
56 } else if v6.is_unspecified() {
57 Self::Unspecified
58 } else if in_v6(v6, 0xfe80 << 112, 10) {
59 Self::LinkLocal
60 } else if in_v6(v6, 0xfc00 << 112, 7) {
61 Self::UniqueLocal
62 } else if v6_is_private(v6) {
63 Self::Other
64 } else {
65 Self::Global
66 }
67 }
68 },
69 }
70 }
71
72 pub const fn preference(self) -> u8 {
75 match self {
76 Self::Global => 0,
77 Self::Private | Self::UniqueLocal => 1,
78 Self::Other => 2,
79 Self::LinkLocal => 3,
80 Self::Loopback => 4,
81 Self::Unspecified => 5,
82 }
83 }
84
85 pub const fn as_str(self) -> &'static str {
89 match self {
90 Self::Global => "global",
91 Self::Private => "private",
92 Self::UniqueLocal => "unique-local",
93 Self::Other => "other",
94 Self::LinkLocal => "link-local",
95 Self::Loopback => "loopback",
96 Self::Unspecified => "unspecified",
97 }
98 }
99}
100
101fn v4_scope(v4: Ipv4Addr) -> AddrScope {
102 if v4.is_loopback() {
103 AddrScope::Loopback
104 } else if v4.is_unspecified() {
105 AddrScope::Unspecified
106 } else if v4.is_link_local() {
107 AddrScope::LinkLocal
108 } else if v4_is_private(v4) {
109 AddrScope::Private
110 } else if v4_is_global(v4) {
111 AddrScope::Global
112 } else {
113 AddrScope::Other
114 }
115}
116
117fn in_v4(ip: Ipv4Addr, net: u32, prefix: u32) -> bool {
118 let shift = 32 - prefix;
119 prefix == 0 || (u32::from(ip) >> shift) == (net >> shift)
120}
121
122fn in_v6(ip: Ipv6Addr, net: u128, prefix: u32) -> bool {
123 let shift = 128 - prefix;
124 prefix == 0 || (u128::from(ip) >> shift) == (net >> shift)
125}
126
127const fn v4(a: u8, b: u8, c: u8, d: u8) -> u32 {
128 u32::from_be_bytes([a, b, c, d])
129}
130
131fn v4_is_private(ip: Ipv4Addr) -> bool {
133 const NETS: [(u32, u32); 14] = [
134 (v4(0, 0, 0, 0), 8),
135 (v4(10, 0, 0, 0), 8),
136 (v4(127, 0, 0, 0), 8),
137 (v4(169, 254, 0, 0), 16),
138 (v4(172, 16, 0, 0), 12),
139 (v4(192, 0, 0, 0), 24),
140 (v4(192, 0, 0, 170), 31),
141 (v4(192, 0, 2, 0), 24),
142 (v4(192, 168, 0, 0), 16),
143 (v4(198, 18, 0, 0), 15),
144 (v4(198, 51, 100, 0), 24),
145 (v4(203, 0, 113, 0), 24),
146 (v4(240, 0, 0, 0), 4),
147 (v4(255, 255, 255, 255), 32),
148 ];
149 const EXCEPT: [(u32, u32); 2] = [(v4(192, 0, 0, 9), 32), (v4(192, 0, 0, 10), 32)];
150 NETS.iter().any(|&(n, p)| in_v4(ip, n, p)) && !EXCEPT.iter().any(|&(n, p)| in_v4(ip, n, p))
151}
152
153fn v4_is_global(ip: Ipv4Addr) -> bool {
155 !in_v4(ip, v4(100, 64, 0, 0), 10) && !v4_is_private(ip)
156}
157
158fn v6_is_private(ip: Ipv6Addr) -> bool {
160 const NETS: [(u128, u32); 11] = [
161 (1, 128),
162 (0, 128),
163 (0xffff_0000_0000, 96),
164 (0x0064_ff9b_0001 << 80, 48),
165 (0x0100 << 112, 64),
166 (0x2001 << 112, 23),
167 (0x2001_0db8 << 96, 32),
168 (0x2002 << 112, 16),
169 (0x3fff << 112, 20),
170 (0xfc00 << 112, 7),
171 (0xfe80 << 112, 10),
172 ];
173 const EXCEPT: [(u128, u32); 6] = [
174 ((0x2001_0001 << 96) | 1, 128),
175 ((0x2001_0001 << 96) | 2, 128),
176 (0x2001_0003 << 96, 32),
177 (0x2001_0004_0112 << 80, 48),
178 (0x2001_0020 << 96, 28),
179 (0x2001_0030 << 96, 28),
180 ];
181 NETS.iter().any(|&(n, p)| in_v6(ip, n, p)) && !EXCEPT.iter().any(|&(n, p)| in_v6(ip, n, p))
182}
183
184#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
189pub struct ScopedAddr {
190 pub ip: IpAddr,
192 pub ifindex: Option<u32>,
194}
195
196impl ScopedAddr {
197 pub const fn new(ip: IpAddr) -> Self {
199 Self { ip, ifindex: None }
200 }
201
202 pub fn scope(&self) -> AddrScope {
204 AddrScope::of(self.ip)
205 }
206
207 pub fn is_candidate(&self) -> bool {
210 match self.scope() {
211 AddrScope::Loopback | AddrScope::Unspecified => false,
212 AddrScope::LinkLocal => self.ip.is_ipv4() || self.ifindex.is_some(),
213 _ => true,
214 }
215 }
216}
217
218impl From<IpAddr> for ScopedAddr {
219 fn from(ip: IpAddr) -> Self {
220 Self::new(ip)
221 }
222}
223
224#[cfg(feature = "alloc")]
228pub fn candidate_addresses(addrs: &[ScopedAddr]) -> Vec<ScopedAddr> {
229 let mut out: Vec<ScopedAddr> = addrs
230 .iter()
231 .copied()
232 .filter(ScopedAddr::is_candidate)
233 .collect();
234 out.sort_by_key(|a| (a.scope().preference(), *a));
235 out.dedup();
236 out
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 fn scope(s: &str) -> AddrScope {
244 AddrScope::of(s.parse().unwrap())
245 }
246
247 #[test]
248 fn scopes() {
249 assert_eq!(scope("8.8.8.8"), AddrScope::Global);
250 assert_eq!(scope("192.168.1.2"), AddrScope::Private);
251 assert_eq!(scope("10.0.0.1"), AddrScope::Private);
252 assert_eq!(scope("192.0.0.9"), AddrScope::Global);
253 assert_eq!(scope("100.64.0.1"), AddrScope::Other);
254 assert_eq!(scope("169.254.1.1"), AddrScope::LinkLocal);
255 assert_eq!(scope("127.0.0.1"), AddrScope::Loopback);
256 assert_eq!(scope("0.0.0.0"), AddrScope::Unspecified);
257 assert_eq!(scope("0.1.2.3"), AddrScope::Private);
258 assert_eq!(scope("2606:4700::1"), AddrScope::Global);
259 assert_eq!(scope("fd00::1"), AddrScope::UniqueLocal);
260 assert_eq!(scope("fe80::1"), AddrScope::LinkLocal);
261 assert_eq!(scope("::1"), AddrScope::Loopback);
262 assert_eq!(scope("::"), AddrScope::Unspecified);
263 assert_eq!(scope("2001:db8::1"), AddrScope::Other);
264 assert_eq!(scope("2001:20::1"), AddrScope::Global);
265 assert_eq!(scope("::ffff:10.0.0.1"), AddrScope::Other);
266 assert_eq!(scope("::ffff:8.8.8.8"), AddrScope::Global);
267 assert_eq!(scope("::ffff:127.0.0.1"), AddrScope::Loopback);
268 }
269
270 #[cfg(feature = "alloc")]
271 #[test]
272 fn candidates() {
273 let a = |s: &str, i: Option<u32>| ScopedAddr {
274 ip: s.parse().unwrap(),
275 ifindex: i,
276 };
277 let got = candidate_addresses(&[
278 a("fe80::1", None),
279 a("fe80::2", Some(3)),
280 a("169.254.0.5", None),
281 a("127.0.0.1", None),
282 a("fd00::1", None),
283 a("192.168.1.9", None),
284 a("192.168.1.2", None),
285 a("8.8.8.8", None),
286 a("100.64.0.1", None),
287 a("192.168.1.2", None),
288 ]);
289 assert_eq!(
290 got,
291 [
292 a("8.8.8.8", None),
293 a("192.168.1.2", None),
294 a("192.168.1.9", None),
295 a("fd00::1", None),
296 a("100.64.0.1", None),
297 a("169.254.0.5", None),
298 a("fe80::2", Some(3)),
299 ]
300 );
301 }
302}