1use std::net::{IpAddr, Ipv6Addr, SocketAddr};
2
3use tokio::net::TcpStream;
4
5use crate::{BoxStream, ConnectError, TargetAddr, TargetHost};
6
7pub fn is_reserved_or_private_ip(ip: &IpAddr) -> bool {
28 match ip {
29 IpAddr::V4(v4) => {
30 v4.is_loopback()
31 || v4.is_link_local()
32 || v4.is_private()
33 || v4.is_unspecified()
34 || v4.is_multicast()
35 || v4.is_broadcast()
36 || is_v4_documentation(v4)
37 || is_v4_benchmarking(v4)
38 || is_v4_reserved(v4)
39 || is_v4_this_network(v4)
40 }
41 IpAddr::V6(v6) => {
42 if let Some(v4) = v6.to_ipv4_mapped() {
46 return is_reserved_or_private_ip(&IpAddr::V4(v4));
47 }
48 v6.is_loopback()
49 || v6.is_unspecified()
50 || v6.is_multicast()
51 || is_v6_documentation(v6)
52 || is_unicast_link_local_v6(v6)
53 || is_unique_local_v6(v6)
54 || is_v6_discard_prefix(v6)
55 }
56 }
57}
58
59fn is_unique_local_v6(ip: &Ipv6Addr) -> bool {
61 let octets = ip.octets();
62 (octets[0] & 0xfe) == 0xfc
63}
64
65fn is_unicast_link_local_v6(ip: &Ipv6Addr) -> bool {
67 let octets = ip.octets();
68 octets[0] == 0xfe && (octets[1] & 0xc0) == 0x80
69}
70
71fn is_v6_discard_prefix(ip: &Ipv6Addr) -> bool {
73 let octets = ip.octets();
74 octets[0] == 0x01 && octets[1..8].iter().all(|b| *b == 0)
75}
76
77fn is_v4_this_network(ip: &std::net::Ipv4Addr) -> bool {
79 ip.octets()[0] == 0
80}
81
82fn is_v4_documentation(ip: &std::net::Ipv4Addr) -> bool {
86 let octets = ip.octets();
87 matches!(
88 octets,
89 [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _] | [192, 88, 99, _]
90 )
91}
92
93fn is_v4_benchmarking(ip: &std::net::Ipv4Addr) -> bool {
95 let octets = ip.octets();
96 octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)
97}
98
99fn is_v4_reserved(ip: &std::net::Ipv4Addr) -> bool {
103 ip.octets()[0] >= 240
104}
105
106fn is_v6_documentation(ip: &Ipv6Addr) -> bool {
108 let octets = ip.octets();
109 octets[0] == 0x20 && octets[1] == 0x01 && octets[2] == 0x0d && octets[3] == 0xb8
110}
111
112pub fn is_dns_rebinding_risk(ip: &IpAddr) -> bool {
114 is_reserved_or_private_ip(ip)
115}
116
117#[trait_variant::make(Connector: Send)]
119pub trait LocalConnector {
120 async fn connect(&self, target: &TargetAddr) -> Result<BoxStream, ConnectError>;
121}
122
123pub struct DirectConnector;
125
126#[derive(Debug, Clone)]
128pub struct ConnectOptions {
129 pub local_bind: Option<SocketAddr>,
130 pub enforce_dns_rebinding_check: bool,
133 pub enforce_literal_ip_check: bool,
136}
137
138impl Default for ConnectOptions {
139 fn default() -> Self {
140 Self {
141 local_bind: None,
142 enforce_dns_rebinding_check: true,
143 enforce_literal_ip_check: false,
144 }
145 }
146}
147
148impl DirectConnector {
149 pub async fn connect_with_options(
150 &self,
151 target: &TargetAddr,
152 options: &ConnectOptions,
153 ) -> Result<BoxStream, ConnectError> {
154 let addrs = resolve_target(
155 target,
156 options.enforce_dns_rebinding_check,
157 options.enforce_literal_ip_check,
158 )
159 .await?;
160 connect_to_addrs(&addrs, options.local_bind).await
161 }
162}
163
164async fn connect_to_addrs(
165 addrs: &[SocketAddr],
166 local_bind: Option<SocketAddr>,
167) -> Result<BoxStream, ConnectError> {
168 let mut last_error = None;
169 for &addr in addrs {
170 let result = if let Some(local) = local_bind {
171 let local = match local {
172 SocketAddr::V6(local) => local
173 .ip()
174 .to_ipv4_mapped()
175 .map(|ip| SocketAddr::new(ip.into(), local.port()))
176 .unwrap_or(local.into()),
177 local => local,
178 };
179 let socket = if local.is_ipv4() {
180 tokio::net::TcpSocket::new_v4()
181 } else {
182 tokio::net::TcpSocket::new_v6()
183 }
184 .map_err(ConnectError::Io)?;
185 socket.bind(local).map_err(ConnectError::Io)?;
186 socket.connect(addr).await.map_err(ConnectError::Io)
187 } else {
188 TcpStream::connect(addr).await.map_err(ConnectError::Io)
189 };
190 match result {
191 Ok(stream) => return Ok(Box::new(stream)),
192 Err(error) => last_error = Some(error),
193 }
194 }
195 Err(last_error.unwrap_or_else(|| ConnectError::DnsResolution("no addresses found".to_string())))
196}
197
198async fn resolve_target(
199 target: &TargetAddr,
200 enforce_dns_rebinding_check: bool,
201 enforce_literal_ip_check: bool,
202) -> Result<Vec<SocketAddr>, ConnectError> {
203 match &target.host {
204 TargetHost::Ip(ip) => {
205 if enforce_literal_ip_check && is_dns_rebinding_risk(ip) {
206 return Err(ConnectError::ReservedTarget(*ip));
207 }
208 Ok(vec![SocketAddr::new(*ip, target.port)])
209 }
210 TargetHost::Domain(domain) => {
211 let lookup = format!("{}:{}", domain, target.port);
212 let addrs: Vec<_> = tokio::net::lookup_host(&lookup)
213 .await
214 .map_err(|e| ConnectError::DnsResolution(e.to_string()))?
215 .collect();
216 if addrs.is_empty() {
217 return Err(ConnectError::DnsResolution(
218 "no addresses found".to_string(),
219 ));
220 }
221 if enforce_dns_rebinding_check {
222 if let Some(reserved) = addrs.iter().find(|addr| is_dns_rebinding_risk(&addr.ip()))
223 {
224 return Err(ConnectError::ReservedTarget(reserved.ip()));
225 }
226 }
227 Ok(addrs)
228 }
229 }
230}
231
232impl Connector for DirectConnector {
233 async fn connect(&self, target: &TargetAddr) -> Result<BoxStream, ConnectError> {
234 self.connect_with_options(target, &ConnectOptions::default())
235 .await
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::net::Ipv4Addr;
243 use tokio::io::{AsyncReadExt, AsyncWriteExt};
244
245 #[tokio::test]
246 async fn test_direct_connect_echo() {
247 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
248 let addr = listener.local_addr().unwrap();
249
250 let jh = tokio::spawn(async move {
251 let (mut stream, _) = listener.accept().await.unwrap();
252 let mut buf = [0u8; 1024];
253 let n = stream.read(&mut buf).await.unwrap();
254 stream.write_all(&buf[..n]).await.unwrap();
255 });
256
257 let target = TargetAddr {
258 host: TargetHost::Ip(addr.ip()),
259 port: addr.port(),
260 };
261
262 let connector = DirectConnector;
263 let mut stream = Connector::connect(&connector, &target).await.unwrap();
264
265 stream.write_all(b"ping").await.unwrap();
266 let mut buf = [0u8; 4];
267 stream.read_exact(&mut buf).await.unwrap();
268 assert_eq!(&buf, b"ping");
269
270 jh.await.unwrap();
271 }
272
273 #[tokio::test]
274 async fn dns_rebinding_policy_applies_consistently_to_domains() {
275 let target = TargetAddr {
276 host: TargetHost::Domain("localhost".to_string()),
277 port: 80,
278 };
279
280 assert!(resolve_target(&target, false, false).await.is_ok());
281 assert!(ConnectOptions::default().enforce_dns_rebinding_check);
282 assert!(matches!(
283 resolve_target(
284 &target,
285 ConnectOptions::default().enforce_dns_rebinding_check,
286 ConnectOptions::default().enforce_literal_ip_check,
287 )
288 .await,
289 Err(ConnectError::ReservedTarget(_))
290 ));
291 }
292
293 #[test]
294 fn reserved_ipv4_loopback() {
295 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
296 127, 0, 0, 1
297 ))));
298 }
299
300 #[test]
301 fn reserved_ipv4_private_10() {
302 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
303 10, 0, 0, 1
304 ))));
305 }
306
307 #[test]
308 fn reserved_ipv4_private_172() {
309 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
310 172, 16, 0, 1
311 ))));
312 }
313
314 #[test]
315 fn reserved_ipv4_private_192() {
316 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
317 192, 168, 1, 1
318 ))));
319 }
320
321 #[test]
322 fn reserved_ipv4_link_local() {
323 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
324 169, 254, 1, 1
325 ))));
326 }
327
328 #[test]
329 fn reserved_ipv4_unspecified() {
330 assert!(is_reserved_or_private_ip(&IpAddr::V4(
331 Ipv4Addr::UNSPECIFIED
332 )));
333 }
334
335 #[test]
336 fn not_reserved_ipv4_public() {
337 assert!(!is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
338 8, 8, 8, 8
339 ))));
340 }
341
342 #[test]
343 fn reserved_ipv6_loopback() {
344 assert!(is_reserved_or_private_ip(&IpAddr::V6(Ipv6Addr::LOCALHOST)));
345 }
346
347 #[test]
348 fn reserved_ipv6_link_local() {
349 let ip = "fe80::1".parse::<Ipv6Addr>().unwrap();
350 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
351 }
352
353 #[test]
354 fn reserved_ipv4_mapped_ipv6() {
355 let ip = "::ffff:127.0.0.1".parse::<Ipv6Addr>().unwrap();
356 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
357 }
358
359 #[test]
360 fn reserved_ipv6_unique_local() {
361 let ip = "fd00::1".parse::<Ipv6Addr>().unwrap();
362 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
363 }
364
365 #[test]
366 fn reserved_ipv6_unspecified() {
367 assert!(is_reserved_or_private_ip(&IpAddr::V6(
368 Ipv6Addr::UNSPECIFIED
369 )));
370 }
371
372 #[test]
373 fn not_reserved_ipv6_public() {
374 let ip = "2606:4700:4700::1111".parse::<Ipv6Addr>().unwrap();
375 assert!(!is_reserved_or_private_ip(&IpAddr::V6(ip)));
376 }
377
378 #[test]
379 fn reserved_ipv4_multicast() {
380 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
381 224, 0, 0, 1
382 ))));
383 }
384
385 #[test]
386 fn reserved_ipv4_broadcast() {
387 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::BROADCAST)));
388 }
389
390 #[test]
391 fn reserved_ipv4_documentation() {
392 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
393 192, 0, 2, 1
394 ))));
395 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
396 198, 51, 100, 1
397 ))));
398 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
399 203, 0, 113, 1
400 ))));
401 }
402
403 #[test]
404 fn reserved_ipv4_benchmarking() {
405 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
406 198, 18, 0, 1
407 ))));
408 }
409
410 #[test]
411 fn reserved_ipv4_reserved_future() {
412 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
413 240, 0, 0, 1
414 ))));
415 }
416
417 #[test]
418 fn reserved_ipv4_this_network() {
419 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
420 0, 1, 2, 3
421 ))));
422 }
423
424 #[test]
425 fn reserved_ipv6_multicast() {
426 let ip = "ff02::1".parse::<Ipv6Addr>().unwrap();
427 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
428 }
429
430 #[test]
431 fn reserved_ipv6_documentation() {
432 let ip = "2001:db8::1".parse::<Ipv6Addr>().unwrap();
433 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
434 }
435
436 #[test]
437 fn reserved_ipv6_discard_prefix() {
438 let ip = "0100::1".parse::<Ipv6Addr>().unwrap();
439 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
440 }
441
442 #[tokio::test]
443 async fn reject_domain_resolving_to_loopback() {
444 let connector = DirectConnector;
445 let target = TargetAddr {
446 host: TargetHost::Domain("localhost".to_string()),
447 port: 1,
448 };
449 let result = connector
450 .connect_with_options(
451 &target,
452 &ConnectOptions {
453 enforce_dns_rebinding_check: true,
454 ..Default::default()
455 },
456 )
457 .await;
458 assert!(matches!(result, Err(ConnectError::ReservedTarget(_))));
459 }
460
461 #[tokio::test]
462 async fn direct_connect_falls_back_to_next_resolved_address() {
463 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
464 let good_addr = listener.local_addr().unwrap();
465 let bad_addr = SocketAddr::new(good_addr.ip(), good_addr.port() + 1);
466
467 let accept = tokio::spawn(async move { listener.accept().await.unwrap() });
468 let stream = connect_to_addrs(&[bad_addr, good_addr], None)
469 .await
470 .expect("second resolved address should be attempted");
471 drop(stream);
472 accept.await.unwrap();
473 }
474
475 #[tokio::test]
476 async fn mapped_ipv6_local_bind_uses_ipv4_socket() {
477 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
478 let addr = listener.local_addr().unwrap();
479 let accept = tokio::spawn(async move { listener.accept().await.unwrap() });
480
481 let mapped = SocketAddr::new("::ffff:127.0.0.1".parse().unwrap(), 0);
482 let stream = connect_to_addrs(&[addr], Some(mapped))
483 .await
484 .expect("mapped IPv6 local bind should connect to IPv4");
485 drop(stream);
486 accept.await.unwrap();
487 }
488}