1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
11
12pub const NAT64_DISCOVERY_NAME: &str = "ipv4only.arpa";
14
15fn prefix_matches(address: IpAddr, prefix: IpAddr, bits: u32) -> bool {
16 match (address, prefix) {
17 (IpAddr::V4(address), IpAddr::V4(prefix)) => {
18 let mask = u32::MAX.checked_shl(32 - bits).unwrap_or(0);
19 u32::from(address) & mask == u32::from(prefix) & mask
20 }
21 (IpAddr::V6(address), IpAddr::V6(prefix)) => {
22 let mask = u128::MAX.checked_shl(128 - bits).unwrap_or(0);
23 u128::from(address) & mask == u128::from(prefix) & mask
24 }
25 _ => false,
26 }
27}
28
29fn parse_row(line: &str) -> Option<(IpAddr, u32, bool)> {
30 let mut fields = line.split_whitespace();
31 let block = fields.next()?;
32 let value = fields.next()?;
33 if fields.next().is_some() {
34 return None;
35 }
36 let (prefix, bits) = block.split_once('/')?;
37 let prefix: IpAddr = prefix.parse().ok()?;
38 let bits: u32 = bits.parse().ok()?;
39 let aligned = match prefix {
40 IpAddr::V4(ip) if bits <= 32 => {
41 let mask = u32::MAX.checked_shl(32 - bits).unwrap_or(0);
42 u32::from(ip) & mask == u32::from(ip)
43 }
44 IpAddr::V6(ip) if bits <= 128 => {
45 let mask = u128::MAX.checked_shl(128 - bits).unwrap_or(0);
46 u128::from(ip) & mask == u128::from(ip)
47 }
48 _ => return None,
49 };
50 let reachable = match value {
51 "true" => true,
52 "false" => false,
53 _ => return None,
54 };
55 aligned.then_some((prefix, bits, reachable))
56}
57
58pub fn is_private(address: IpAddr) -> bool {
60 match address {
61 IpAddr::V4(ip) if ip.is_multicast() => return true,
62 IpAddr::V6(ip) if ip.is_multicast() => return true,
63 IpAddr::V6(ip) if embedded_ipv4(ip).is_some_and(|v4| is_private(IpAddr::V4(v4))) => {
64 return true;
65 }
66 _ => {}
67 }
68
69 let mut longest = 0;
70 let mut reachable = true;
71 for line in include_str!("address-registry.txt").lines() {
72 let Some((prefix, bits, row_reachable)) = parse_row(line) else {
73 continue;
74 };
75 if bits >= longest && prefix_matches(address, prefix, bits) {
76 longest = bits;
77 reachable = row_reachable;
78 }
79 }
80 !reachable
81}
82
83fn embedded_at(ip: Ipv6Addr, bits: u8) -> Ipv4Addr {
85 let octets = ip.octets();
86 let mut v4 = (usize::from(bits / 8)..16)
87 .filter(|&i| i != 8)
88 .map(|i| octets[i]);
89 Ipv4Addr::from(std::array::from_fn(|_| v4.next().unwrap_or(0)))
90}
91
92fn nat64_prefixes(answer: impl IntoIterator<Item = IpAddr>) -> Vec<(Ipv6Addr, u8)> {
94 let mut prefixes = vec![];
95 for ip in answer {
96 let IpAddr::V6(ip) = ip else { continue };
97 for bits in [96, 64, 56, 48, 40, 32] {
98 if matches!(embedded_at(ip, bits).octets(), [192, 0, 0, 170 | 171]) {
99 let mask = u128::MAX << (128 - u32::from(bits));
100 prefixes.push((Ipv6Addr::from(u128::from(ip) & mask), bits));
101 }
102 }
103 }
104 prefixes
105}
106
107pub async fn discover_nat64<A, E>(
110 lookup: Option<impl Future<Output = Result<A, E>>>,
111) -> Vec<(Ipv6Addr, u8)>
112where
113 A: IntoIterator<Item = SocketAddr>,
114{
115 let Some(lookup) = lookup else {
116 return vec![];
117 };
118 match crate::time::timeout(std::time::Duration::from_secs(5), lookup).await {
119 Ok(Ok(answer)) => nat64_prefixes(answer.into_iter().map(|address| address.ip())),
120 _ => vec![],
121 }
122}
123
124pub fn is_private_behind(address: IpAddr, prefixes: &[(Ipv6Addr, u8)]) -> bool {
126 is_private(address)
127 || matches!(address, IpAddr::V6(ip) if prefixes.iter().any(|&(prefix, bits)| {
128 prefix_matches(address, prefix.into(), bits.into())
129 && is_private(embedded_at(ip, bits).into())
130 }))
131}
132
133fn embedded_ipv4(ip: Ipv6Addr) -> Option<Ipv4Addr> {
134 if let Some(v4) = ip.to_ipv4_mapped() {
135 return Some(v4);
136 }
137 let value = u128::from(ip);
138 let well_known = u128::from("64:ff9b::".parse::<Ipv6Addr>().unwrap());
139 let local_use = u128::from("64:ff9b:1::".parse::<Ipv6Addr>().unwrap());
140 let in_well_known = value >> 32 == well_known >> 32;
141 let in_local_use = value >> 80 == local_use >> 80;
142 (in_well_known || in_local_use).then(|| Ipv4Addr::from(value as u32))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[xmtp_common::test(unwrap_try = true)]
150 fn registry_rows_are_well_formed() {
151 for (index, line) in include_str!("address-registry.txt").lines().enumerate() {
152 let line = line.trim();
153 if line.is_empty() || line.starts_with('#') {
154 continue;
155 }
156 assert!(parse_row(line).is_some(), "line {}: {line}", index + 1);
157 }
158 }
159
160 #[xmtp_common::test(unwrap_try = true)]
161 fn parse_row_accepts_only_well_formed_rows() {
162 for (row, expected) in [
163 ("10.0.0.0/8\tfalse", Some(("10.0.0.0".parse()?, 8, false))),
164 ("10.0.0.0/8", None),
165 ("10.0.0.0/8 true extra", None),
166 ("bad/8 true", None),
167 ("10.0.0.0 true", None),
168 ("10.0.0.0/33 true", None),
169 ("2001:db8::/129 true", None),
170 ("10.1.0.0/8 true", None),
171 ("2001:db8::1/32 true", None),
172 ("10.0.0.0/8 TRUE", None),
173 ] {
174 assert_eq!(parse_row(row), expected, "{row}");
175 }
176 }
177
178 #[xmtp_common::test(unwrap_try = true)]
179 fn registry_and_embedded_addresses() {
180 for address in [
181 "10.0.0.1",
182 "::ffff:10.0.0.1",
183 "64:ff9b::a00:1",
184 "64:ff9b:1::a00:1",
185 "::ffff:8.8.8.8",
186 "64:ff9b:1::808:808",
187 "ff02::1",
188 "224.0.0.1",
189 "192.0.2.1",
190 ] {
191 assert!(is_private(address.parse()?), "{address}");
192 }
193 for address in [
194 "8.8.8.8",
195 "2606:4700:4700::1111",
196 "192.0.0.9",
197 "64:ff9b::808:808",
198 ] {
199 assert!(!is_private(address.parse()?), "{address}");
200 }
201 let net: Ipv6Addr = "2a00:1450:4001:64::".parse()?;
202 let (net96, net64) = ((net, 96), (net, 64));
203 for (answer, expected) in [
204 ("2a00:1450:4001:64::c000:aa", vec![net96]),
205 ("2a00:1450:4001:64:c0:0:aa00:0", vec![net64]),
206 ("2001:db8:c000:ab::", vec![("2001:db8::".parse()?, 32)]),
207 ("192.0.0.170", vec![]),
208 ] {
209 assert_eq!(nat64_prefixes([answer.parse()?]), expected, "{answer}");
210 }
211 for (address, prefixes, expected) in [
212 ("2a00:1450:4001:64:a:0:100:0", net64, true),
213 ("2a00:1450:4001:64::808:808", net96, false),
214 ("2606:4700:4700::1111", net96, false),
215 ] {
216 assert_eq!(
217 is_private_behind(address.parse()?, &[prefixes]),
218 expected,
219 "{address}"
220 );
221 }
222 }
223}