1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
2
3use ipnet::{Ipv4Net, Ipv6Net};
4use reqwest::dns::{Addrs, Name, Resolve, Resolving};
5use url::Url;
6
7use crate::errors::{HostError, LocalAddress};
8
9const LOOPBACK_V4: Ipv4Net = Ipv4Net::new_assert(Ipv4Addr::new(127, 0, 0, 0), 8);
10
11const LOCAL_NETWORK_V4: [Ipv4Net; 14] = [
13 Ipv4Net::new_assert(Ipv4Addr::UNSPECIFIED, 8),
14 Ipv4Net::new_assert(Ipv4Addr::new(10, 0, 0, 0), 8),
15 Ipv4Net::new_assert(Ipv4Addr::new(100, 64, 0, 0), 10),
16 Ipv4Net::new_assert(Ipv4Addr::new(169, 254, 0, 0), 16),
17 Ipv4Net::new_assert(Ipv4Addr::new(172, 16, 0, 0), 12),
18 Ipv4Net::new_assert(Ipv4Addr::new(192, 0, 0, 0), 24),
19 Ipv4Net::new_assert(Ipv4Addr::new(192, 0, 0, 170), 31),
20 Ipv4Net::new_assert(Ipv4Addr::new(192, 0, 2, 0), 24),
21 Ipv4Net::new_assert(Ipv4Addr::new(192, 168, 0, 0), 16),
22 Ipv4Net::new_assert(Ipv4Addr::new(198, 18, 0, 0), 15),
23 Ipv4Net::new_assert(Ipv4Addr::new(198, 51, 100, 0), 24),
24 Ipv4Net::new_assert(Ipv4Addr::new(203, 0, 113, 0), 24),
25 Ipv4Net::new_assert(Ipv4Addr::new(240, 0, 0, 0), 4),
26 Ipv4Net::new_assert(Ipv4Addr::BROADCAST, 32),
27];
28
29const GLOBAL_V4: [Ipv4Addr; 2] = [Ipv4Addr::new(192, 0, 0, 9), Ipv4Addr::new(192, 0, 0, 10)];
30
31const LOCAL_NETWORK_V6: [Ipv6Net; 9] = [
32 Ipv6Net::new_assert(Ipv6Addr::UNSPECIFIED, 128),
33 Ipv6Net::new_assert(Ipv6Addr::new(0x64, 0xff9b, 1, 0, 0, 0, 0, 0), 48),
34 Ipv6Net::new_assert(Ipv6Addr::new(0x100, 0, 0, 0, 0, 0, 0, 0), 64),
35 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 0, 0, 0, 0, 0, 0, 0), 23),
36 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32),
37 Ipv6Net::new_assert(Ipv6Addr::new(0x2002, 0, 0, 0, 0, 0, 0, 0), 16),
38 Ipv6Net::new_assert(Ipv6Addr::new(0x3fff, 0, 0, 0, 0, 0, 0, 0), 20),
39 Ipv6Net::new_assert(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0), 7),
40 Ipv6Net::new_assert(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10),
41];
42
43const GLOBAL_V6: [Ipv6Net; 6] = [
44 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 1, 0, 0, 0, 0, 0, 1), 128),
45 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 1, 0, 0, 0, 0, 0, 2), 128),
46 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 3, 0, 0, 0, 0, 0, 0), 32),
47 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 4, 0x112, 0, 0, 0, 0, 0), 48),
48 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 0x20, 0, 0, 0, 0, 0, 0), 28),
49 Ipv6Net::new_assert(Ipv6Addr::new(0x2001, 0x30, 0, 0, 0, 0, 0, 0), 28),
50];
51
52#[derive(Debug, Clone, Copy, Default)]
53pub struct AllowedLocal {
54 pub localhost: bool,
55 pub local_network: bool,
56}
57
58impl AllowedLocal {
59 const fn lets_through(self, local: LocalAddress) -> bool {
60 match local {
61 LocalAddress::Loopback => self.localhost,
62 LocalAddress::LocalNetwork => self.local_network,
63 }
64 }
65}
66
67pub enum Host<'a> {
68 Ip(IpAddr),
69 Name(&'a str),
70}
71
72pub(crate) fn read_host(rest: &str) -> Option<(Host<'_>, &str)> {
74 if let Some(bracketed) = rest.strip_prefix('[') {
75 let (ip, after) = bracketed.split_once(']')?;
76 return Some((Host::Ip(IpAddr::V6(ip.parse::<Ipv6Addr>().ok()?)), after));
77 }
78 let end = rest.find([':', '/']).unwrap_or(rest.len());
79 let (host, rest) = rest.split_at(end);
80 let host = match host.parse::<Ipv4Addr>() {
81 Ok(ip) => Host::Ip(IpAddr::V4(ip)),
82 Err(_) => Host::Name(is_name(host).then_some(host)?),
83 };
84 Some((host, rest))
85}
86
87fn is_name(host: &str) -> bool {
89 let name = host.strip_suffix('.').unwrap_or(host);
90 name.len() <= 253
91 && name.split('.').all(|label| {
92 (1..=63).contains(&label.len())
93 && label
96 .chars()
97 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_'))
98 })
99}
100
101pub(crate) struct ProtectedResolver(pub(crate) AllowedLocal);
103
104impl Resolve for ProtectedResolver {
105 fn resolve(&self, name: Name) -> Resolving {
106 let allowed = self.0;
107 Box::pin(async move {
108 let ips = outside_addresses(&Host::Name(name.as_str()), allowed).await?;
109 let addrs: Addrs = Box::new(ips.into_iter().map(|ip| SocketAddr::new(ip, 0)));
110 Ok(addrs)
111 })
112 }
113}
114
115#[must_use]
117pub fn local_address(ip: IpAddr) -> Option<LocalAddress> {
118 match ip {
119 IpAddr::V4(ip) => local_v4(ip),
120 IpAddr::V6(ip) => ip.to_ipv4_mapped().map_or_else(|| local_v6(ip), local_v4),
121 }
122}
123
124pub async fn outside_addresses(
125 host: &Host<'_>,
126 allowed: AllowedLocal,
127) -> Result<Vec<IpAddr>, HostError> {
128 let ips = match host {
129 Host::Ip(ip) => vec![*ip],
130 Host::Name(name) => tokio::net::lookup_host((*name, 0))
131 .await
132 .map_err(HostError::Unresolved)?
133 .map(|address| address.ip())
134 .collect(),
135 };
136 if ips.is_empty() {
137 return Err(HostError::NoAddresses);
138 }
139 let refused = ips
140 .iter()
141 .filter_map(|ip| local_address(*ip))
142 .find(|local| !allowed.lets_through(*local));
143 refused.map_or(Ok(ips), |local| Err(HostError::Local(local)))
144}
145
146pub(crate) fn refuse_local_ip(url: &Url, allowed: AllowedLocal) -> Result<(), LocalAddress> {
148 let ip = match url.host() {
149 Some(url::Host::Ipv4(ip)) => IpAddr::V4(ip),
150 Some(url::Host::Ipv6(ip)) => IpAddr::V6(ip),
151 _ => return Ok(()),
152 };
153 local_address(ip)
154 .filter(|local| !allowed.lets_through(*local))
155 .map_or(Ok(()), Err)
156}
157
158fn local_v4(ip: Ipv4Addr) -> Option<LocalAddress> {
159 if LOOPBACK_V4.contains(&ip) {
160 Some(LocalAddress::Loopback)
161 } else if LOCAL_NETWORK_V4.iter().any(|net| net.contains(&ip)) && !GLOBAL_V4.contains(&ip) {
162 Some(LocalAddress::LocalNetwork)
163 } else {
164 None
165 }
166}
167
168fn local_v6(ip: Ipv6Addr) -> Option<LocalAddress> {
169 if ip.is_loopback() {
170 Some(LocalAddress::Loopback)
171 } else if LOCAL_NETWORK_V6.iter().any(|net| net.contains(&ip))
172 && !GLOBAL_V6.iter().any(|net| net.contains(&ip))
173 {
174 Some(LocalAddress::LocalNetwork)
175 } else {
176 None
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use std::net::IpAddr;
183
184 use reqwest::dns::Resolve;
185
186 use super::{local_address, AllowedLocal, ProtectedResolver};
187 use crate::errors::{HostError, LocalAddress};
188
189 fn kind(ip: &str) -> Option<LocalAddress> {
190 local_address(ip.parse::<IpAddr>().unwrap())
191 }
192
193 #[test]
196 fn a_loopback_address_is_loopback() {
197 for ip in ["127.0.0.1", "127.255.0.1", "::1", "::ffff:127.0.0.1"] {
198 assert_eq!(kind(ip), Some(LocalAddress::Loopback), "{ip}");
199 }
200 }
201
202 #[test]
203 fn a_special_purpose_address_is_local_network() {
204 for ip in [
205 "0.0.0.0",
206 "10.0.0.1",
207 "100.64.0.1",
208 "169.254.1.1",
209 "172.16.0.1",
210 "192.0.0.1",
211 "192.0.0.171",
212 "192.0.2.1",
213 "192.168.0.1",
214 "198.19.0.1",
215 "198.51.100.1",
216 "203.0.113.1",
217 "240.0.0.1",
218 "255.255.255.255",
219 "::",
220 "::ffff:10.0.0.1",
221 "64:ff9b:1::1",
222 "100::1",
223 "2001:10::1",
224 "2001:db8::1",
225 "2002::1",
226 "3fff::1",
227 "fc00::1",
228 "fe80::1",
229 ] {
230 assert_eq!(kind(ip), Some(LocalAddress::LocalNetwork), "{ip}");
231 }
232 }
233
234 #[test]
236 fn a_mapped_ipv4_is_read_as_its_ipv4() {
237 assert_eq!(kind("::ffff:100.64.0.1"), Some(LocalAddress::LocalNetwork));
238 }
239
240 #[test]
241 fn a_global_address_is_not_local() {
242 for ip in [
243 "8.8.8.8",
244 "192.0.0.9",
245 "192.0.0.10",
246 "224.0.0.1",
247 "::ffff:8.8.8.8",
248 "::127.0.0.1",
249 "64:ff9b::a00:1",
250 "2001:1::1",
251 "2001:3::1",
252 "2001:4:112::1",
253 "2001:20::1",
254 "2001:30::1",
255 "2606:4700::1111",
256 ] {
257 assert_eq!(kind(ip), None, "{ip}");
258 }
259 }
260
261 #[tokio::test]
262 async fn the_resolver_refuses_a_name_that_resolves_to_a_local_address() {
263 let resolved = ProtectedResolver(AllowedLocal::default())
264 .resolve("localhost".parse().unwrap())
265 .await;
266
267 let error = resolved.err().unwrap();
268 assert!(
269 matches!(
270 error.downcast_ref::<HostError>(),
271 Some(HostError::Local(LocalAddress::Loopback))
272 ),
273 "{error:?}"
274 );
275 }
276}