eggress_core/
connector.rs1use 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 {
25 match ip {
26 IpAddr::V4(v4) => {
27 v4.is_loopback()
28 || v4.is_link_local()
29 || v4.is_private()
30 || v4.is_unspecified()
31 || v4.is_multicast()
32 || v4.is_broadcast()
33 || is_v4_documentation(v4)
34 || is_v4_benchmarking(v4)
35 || is_v4_reserved(v4)
36 || is_v4_this_network(v4)
37 }
38 IpAddr::V6(v6) => {
39 if let Some(v4) = v6.to_ipv4_mapped() {
43 return is_reserved_or_private_ip(&IpAddr::V4(v4));
44 }
45 v6.is_loopback()
46 || v6.is_unspecified()
47 || v6.is_multicast()
48 || is_v6_documentation(v6)
49 || is_unicast_link_local_v6(v6)
50 || is_unique_local_v6(v6)
51 || is_v6_discard_prefix(v6)
52 }
53 }
54}
55
56fn is_unique_local_v6(ip: &Ipv6Addr) -> bool {
58 let octets = ip.octets();
59 (octets[0] & 0xfe) == 0xfc
60}
61
62fn is_unicast_link_local_v6(ip: &Ipv6Addr) -> bool {
64 let octets = ip.octets();
65 octets[0] == 0xfe && (octets[1] & 0xc0) == 0x80
66}
67
68fn is_v6_discard_prefix(ip: &Ipv6Addr) -> bool {
70 let octets = ip.octets();
71 octets[0] == 0x01 && octets[1..8].iter().all(|b| *b == 0)
72}
73
74fn is_v4_this_network(ip: &std::net::Ipv4Addr) -> bool {
76 ip.octets()[0] == 0
77}
78
79fn is_v4_documentation(ip: &std::net::Ipv4Addr) -> bool {
83 let octets = ip.octets();
84 matches!(
85 octets,
86 [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _] | [192, 88, 99, _]
87 )
88}
89
90fn is_v4_benchmarking(ip: &std::net::Ipv4Addr) -> bool {
92 let octets = ip.octets();
93 octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)
94}
95
96fn is_v4_reserved(ip: &std::net::Ipv4Addr) -> bool {
99 let octets = ip.octets();
100 octets[0] >= 240 && octets[0] < 255
101}
102
103fn is_v6_documentation(ip: &Ipv6Addr) -> bool {
105 let octets = ip.octets();
106 octets[0] == 0x20 && octets[1] == 0x01 && octets[2] == 0x0d && octets[3] == 0xb8
107}
108
109pub fn is_dns_rebinding_risk(ip: &IpAddr) -> bool {
115 is_reserved_or_private_ip(ip)
116}
117
118#[trait_variant::make(Connector: Send)]
120pub trait LocalConnector {
121 async fn connect(&self, target: &TargetAddr) -> Result<BoxStream, ConnectError>;
122}
123
124pub struct DirectConnector;
126
127#[derive(Debug, Clone, Default)]
129pub struct ConnectOptions {
130 pub local_bind: Option<SocketAddr>,
131}
132
133impl DirectConnector {
134 pub async fn connect_with_options(
135 &self,
136 target: &TargetAddr,
137 options: &ConnectOptions,
138 ) -> Result<BoxStream, ConnectError> {
139 let addr = resolve_target(target).await?;
140 let stream = if let Some(local) = options.local_bind {
141 let socket = if local.is_ipv4() {
142 tokio::net::TcpSocket::new_v4()
143 } else {
144 tokio::net::TcpSocket::new_v6()
145 }
146 .map_err(ConnectError::Io)?;
147 socket.bind(local).map_err(ConnectError::Io)?;
148 socket.connect(addr).await.map_err(ConnectError::Io)?
149 } else {
150 TcpStream::connect(addr).await?
151 };
152 Ok(Box::new(stream))
153 }
154}
155
156async fn resolve_target(target: &TargetAddr) -> Result<SocketAddr, ConnectError> {
157 match &target.host {
158 TargetHost::Ip(ip) => Ok(SocketAddr::new(*ip, target.port)),
159 TargetHost::Domain(domain) => {
160 let lookup = format!("{}:{}", domain, target.port);
161 let mut addrs = tokio::net::lookup_host(&lookup)
162 .await
163 .map_err(|e| ConnectError::DnsResolution(e.to_string()))?;
164 let resolved = addrs
165 .next()
166 .ok_or_else(|| ConnectError::DnsResolution("no addresses found".to_string()))?;
167 if is_dns_rebinding_risk(&resolved.ip()) {
168 return Err(ConnectError::ReservedTarget(resolved.ip()));
169 }
170 Ok(resolved)
171 }
172 }
173}
174
175impl Connector for DirectConnector {
176 async fn connect(&self, target: &TargetAddr) -> Result<BoxStream, ConnectError> {
177 self.connect_with_options(target, &ConnectOptions::default())
178 .await
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use std::net::Ipv4Addr;
186 use tokio::io::{AsyncReadExt, AsyncWriteExt};
187
188 #[tokio::test]
189 async fn test_direct_connect_echo() {
190 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
191 let addr = listener.local_addr().unwrap();
192
193 let jh = tokio::spawn(async move {
194 let (mut stream, _) = listener.accept().await.unwrap();
195 let mut buf = [0u8; 1024];
196 let n = stream.read(&mut buf).await.unwrap();
197 stream.write_all(&buf[..n]).await.unwrap();
198 });
199
200 let target = TargetAddr {
201 host: TargetHost::Ip(addr.ip()),
202 port: addr.port(),
203 };
204
205 let connector = DirectConnector;
206 let mut stream = Connector::connect(&connector, &target).await.unwrap();
207
208 stream.write_all(b"ping").await.unwrap();
209 let mut buf = [0u8; 4];
210 stream.read_exact(&mut buf).await.unwrap();
211 assert_eq!(&buf, b"ping");
212
213 jh.await.unwrap();
214 }
215
216 #[test]
217 fn reserved_ipv4_loopback() {
218 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
219 127, 0, 0, 1
220 ))));
221 }
222
223 #[test]
224 fn reserved_ipv4_private_10() {
225 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
226 10, 0, 0, 1
227 ))));
228 }
229
230 #[test]
231 fn reserved_ipv4_private_172() {
232 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
233 172, 16, 0, 1
234 ))));
235 }
236
237 #[test]
238 fn reserved_ipv4_private_192() {
239 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
240 192, 168, 1, 1
241 ))));
242 }
243
244 #[test]
245 fn reserved_ipv4_link_local() {
246 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
247 169, 254, 1, 1
248 ))));
249 }
250
251 #[test]
252 fn reserved_ipv4_unspecified() {
253 assert!(is_reserved_or_private_ip(&IpAddr::V4(
254 Ipv4Addr::UNSPECIFIED
255 )));
256 }
257
258 #[test]
259 fn not_reserved_ipv4_public() {
260 assert!(!is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
261 8, 8, 8, 8
262 ))));
263 }
264
265 #[test]
266 fn reserved_ipv6_loopback() {
267 assert!(is_reserved_or_private_ip(&IpAddr::V6(Ipv6Addr::LOCALHOST)));
268 }
269
270 #[test]
271 fn reserved_ipv6_link_local() {
272 let ip = "fe80::1".parse::<Ipv6Addr>().unwrap();
273 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
274 }
275
276 #[test]
277 fn reserved_ipv4_mapped_ipv6() {
278 let ip = "::ffff:127.0.0.1".parse::<Ipv6Addr>().unwrap();
279 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
280 }
281
282 #[test]
283 fn reserved_ipv6_unique_local() {
284 let ip = "fd00::1".parse::<Ipv6Addr>().unwrap();
285 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
286 }
287
288 #[test]
289 fn reserved_ipv6_unspecified() {
290 assert!(is_reserved_or_private_ip(&IpAddr::V6(
291 Ipv6Addr::UNSPECIFIED
292 )));
293 }
294
295 #[test]
296 fn not_reserved_ipv6_public() {
297 let ip = "2606:4700:4700::1111".parse::<Ipv6Addr>().unwrap();
298 assert!(!is_reserved_or_private_ip(&IpAddr::V6(ip)));
299 }
300
301 #[test]
302 fn reserved_ipv4_multicast() {
303 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
304 224, 0, 0, 1
305 ))));
306 }
307
308 #[test]
309 fn reserved_ipv4_broadcast() {
310 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::BROADCAST)));
311 }
312
313 #[test]
314 fn reserved_ipv4_documentation() {
315 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
316 192, 0, 2, 1
317 ))));
318 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
319 198, 51, 100, 1
320 ))));
321 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
322 203, 0, 113, 1
323 ))));
324 }
325
326 #[test]
327 fn reserved_ipv4_benchmarking() {
328 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
329 198, 18, 0, 1
330 ))));
331 }
332
333 #[test]
334 fn reserved_ipv4_reserved_future() {
335 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
336 240, 0, 0, 1
337 ))));
338 }
339
340 #[test]
341 fn reserved_ipv4_this_network() {
342 assert!(is_reserved_or_private_ip(&IpAddr::V4(Ipv4Addr::new(
343 0, 1, 2, 3
344 ))));
345 }
346
347 #[test]
348 fn reserved_ipv6_multicast() {
349 let ip = "ff02::1".parse::<Ipv6Addr>().unwrap();
350 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
351 }
352
353 #[test]
354 fn reserved_ipv6_documentation() {
355 let ip = "2001:db8::1".parse::<Ipv6Addr>().unwrap();
356 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
357 }
358
359 #[test]
360 fn reserved_ipv6_discard_prefix() {
361 let ip = "0100::1".parse::<Ipv6Addr>().unwrap();
362 assert!(is_reserved_or_private_ip(&IpAddr::V6(ip)));
363 }
364
365 #[tokio::test]
366 async fn reject_domain_resolving_to_loopback() {
367 let connector = DirectConnector;
368 let target = TargetAddr {
369 host: TargetHost::Domain("localhost".to_string()),
370 port: 1,
371 };
372 let result = Connector::connect(&connector, &target).await;
373 assert!(matches!(result, Err(ConnectError::ReservedTarget(_))));
374 }
375}