1use std::collections::HashMap;
2use std::fmt;
3use std::net::IpAddr;
4use std::pin::Pin;
5use std::sync::{Arc, Mutex};
6use std::task::{Context, Poll};
7use std::time::{Duration, Instant};
8
9use eggress_core::BoxStream;
10use eggress_core::{ClientIdentity, ProtocolId, TargetAddr, TargetHost};
11use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
12
13pub struct AuthReuseCache {
20 timeout: Duration,
21 entries: Mutex<HashMap<IpAddr, AuthReuseEntry>>,
22 max_entries: usize,
23}
24
25struct AuthReuseEntry {
26 identity: ClientIdentity,
27 last_authenticated: Instant,
28}
29
30impl AuthReuseCache {
31 pub const DEFAULT_MAX_ENTRIES: usize = 4096;
32
33 pub fn new(timeout: Duration) -> Self {
34 Self {
35 timeout,
36 entries: Mutex::new(HashMap::new()),
37 max_entries: Self::DEFAULT_MAX_ENTRIES,
38 }
39 }
40
41 pub fn lookup(&self, peer_ip: IpAddr) -> Option<ClientIdentity> {
42 let mut entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
43 let now = Instant::now();
44 entries.retain(|_, entry| now.duration_since(entry.last_authenticated) <= self.timeout);
45 entries.get(&peer_ip).map(|entry| entry.identity.clone())
46 }
47
48 pub fn record(&self, peer_ip: IpAddr, identity: ClientIdentity) {
49 let mut entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
50 let now = Instant::now();
51 entries.retain(|_, entry| now.duration_since(entry.last_authenticated) <= self.timeout);
52 if entries.len() >= self.max_entries && !entries.contains_key(&peer_ip) {
53 if let Some(oldest) = entries
54 .iter()
55 .min_by_key(|(_, entry)| entry.last_authenticated)
56 .map(|(ip, _)| *ip)
57 {
58 entries.remove(&oldest);
59 }
60 }
61 entries.insert(
62 peer_ip,
63 AuthReuseEntry {
64 identity,
65 last_authenticated: now,
66 },
67 );
68 }
69
70 pub fn len(&self) -> usize {
71 self.entries.lock().unwrap_or_else(|e| e.into_inner()).len()
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.len() == 0
76 }
77}
78
79#[derive(Clone)]
80pub enum InboundAuthentication {
81 None,
82 UsernamePassword {
83 username: String,
84 password: String,
85 },
86 UsernamePasswordWithReuse {
87 username: String,
88 password: String,
89 reuse: Arc<AuthReuseCache>,
90 },
91}
92
93impl fmt::Debug for InboundAuthentication {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 InboundAuthentication::None => write!(f, "InboundAuthentication::None"),
97 InboundAuthentication::UsernamePassword { .. } => {
98 write!(f, "InboundAuthentication::UsernamePassword {{ .. }}")
99 }
100 InboundAuthentication::UsernamePasswordWithReuse { .. } => write!(
101 f,
102 "InboundAuthentication::UsernamePasswordWithReuse {{ .. }}"
103 ),
104 }
105 }
106}
107
108impl fmt::Display for InboundAuthentication {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 InboundAuthentication::None => write!(f, "none"),
112 InboundAuthentication::UsernamePassword { .. } => write!(f, "username/password"),
113 InboundAuthentication::UsernamePasswordWithReuse { .. } => {
114 write!(f, "username/password with IP reuse")
115 }
116 }
117 }
118}
119
120pub(crate) fn auth_credentials(
121 auth: &InboundAuthentication,
122) -> Option<(&str, &str, Option<&AuthReuseCache>)> {
123 match auth {
124 InboundAuthentication::None => None,
125 InboundAuthentication::UsernamePassword { username, password } => {
126 Some((username, password, None))
127 }
128 InboundAuthentication::UsernamePasswordWithReuse {
129 username,
130 password,
131 reuse,
132 } => Some((username, password, Some(reuse))),
133 }
134}
135
136pub(crate) fn cached_identity(
137 auth: &InboundAuthentication,
138 peer_ip: Option<IpAddr>,
139) -> Option<ClientIdentity> {
140 let (_, _, reuse) = auth_credentials(auth)?;
141 peer_ip.and_then(|ip| reuse.and_then(|cache| cache.lookup(ip)))
142}
143
144pub(crate) fn record_authenticated(
145 auth: &InboundAuthentication,
146 peer_ip: Option<IpAddr>,
147 identity: &ClientIdentity,
148) {
149 let Some((_, _, Some(cache))) = auth_credentials(auth) else {
150 return;
151 };
152 if let Some(ip) = peer_ip {
153 cache.record(ip, identity.clone());
154 }
155}
156
157#[derive(Debug, thiserror::Error)]
159pub enum AcceptError {
160 #[error("protocol error")]
161 Protocol(#[source] Box<dyn std::error::Error + Send + Sync>),
162 #[error("authentication failed")]
163 AuthenticationFailed,
164}
165
166impl From<Box<dyn std::error::Error + Send + Sync>> for AcceptError {
167 fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
168 AcceptError::Protocol(e)
169 }
170}
171
172pub enum AcceptedSession {
174 Tunnel(PendingTunnel),
175 HttpForward(PendingHttpForward),
176 UdpAssociate(PendingUdpAssociate),
177 Echo(BoxStream),
178}
179
180pub struct PendingTunnel {
183 pub target: TargetAddr,
184 pub client: BoxStream,
185 pub protocol: TunnelProtocol,
186 pub reply_context: ReplyContext,
187 pub identity: ClientIdentity,
188}
189
190pub struct PendingHttpForward {
192 pub target: TargetAddr,
193 pub client: BoxStream,
194 pub request: eggress_protocol_http::forward::ForwardRequest,
195 pub identity: ClientIdentity,
196}
197
198pub struct PendingUdpAssociate {
200 pub client: BoxStream,
201 pub protocol: TunnelProtocol,
202 pub identity: ClientIdentity,
203 pub client_hint: Option<TargetAddr>,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum TunnelProtocol {
209 HttpConnect,
210 Http2,
211 Http3,
212 WebSocket,
213 Socks4,
214 Socks5,
215 Shadowsocks,
216 ShadowsocksR,
217 Trojan,
218 Raw,
219}
220
221pub enum ReplyContext {
223 Http,
224 Http2,
225 Http3,
226 WebSocket,
227 Socks4,
228 Socks5,
229 Shadowsocks,
230 Trojan,
231 Raw,
232}
233
234#[derive(Clone)]
236pub struct InboundShadowsocksConfig {
237 pub method: String,
238 pub password: String,
239 #[cfg(feature = "pproxy-legacy")]
240 pub auth_prefix: Option<Vec<u8>>,
241 #[cfg(feature = "pproxy-legacy")]
242 pub plugins: Vec<String>,
243}
244
245#[derive(Clone)]
247pub struct InboundTrojanConfig {
248 pub password: String,
249 pub fallback: Option<String>,
253}
254
255struct PrefixedStream {
257 prefix: std::io::Cursor<Vec<u8>>,
258 inner: BoxStream,
259}
260
261impl PrefixedStream {
262 fn new(prefix: Vec<u8>, inner: BoxStream) -> Self {
263 Self {
264 prefix: std::io::Cursor::new(prefix),
265 inner,
266 }
267 }
268}
269
270impl AsyncRead for PrefixedStream {
271 fn poll_read(
272 mut self: Pin<&mut Self>,
273 cx: &mut Context<'_>,
274 buf: &mut tokio::io::ReadBuf<'_>,
275 ) -> Poll<std::io::Result<()>> {
276 let pos = self.prefix.position() as usize;
277 let len = self.prefix.get_ref().len();
278 if pos < len {
279 let remaining = &self.prefix.get_ref()[pos..];
280 let to_copy = remaining.len().min(buf.remaining());
281 buf.put_slice(&remaining[..to_copy]);
282 self.prefix.set_position((pos + to_copy) as u64);
283 return Poll::Ready(Ok(()));
284 }
285 Pin::new(&mut self.inner).poll_read(cx, buf)
286 }
287}
288
289impl AsyncWrite for PrefixedStream {
290 fn poll_write(
291 mut self: Pin<&mut Self>,
292 cx: &mut Context<'_>,
293 buf: &[u8],
294 ) -> Poll<std::io::Result<usize>> {
295 Pin::new(&mut self.inner).poll_write(cx, buf)
296 }
297
298 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
299 Pin::new(&mut self.inner).poll_flush(cx)
300 }
301
302 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
303 Pin::new(&mut self.inner).poll_shutdown(cx)
304 }
305}
306
307pub async fn accept(
311 client: BoxStream,
312 protocols: &[ProtocolId],
313 auth: &InboundAuthentication,
314 shadowsocks_config: Option<&InboundShadowsocksConfig>,
315 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
316 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
317 >,
318 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
319 trojan_config: Option<&InboundTrojanConfig>,
320) -> Result<AcceptedSession, AcceptError> {
321 #[cfg(not(feature = "extended"))]
322 let _ = (shadowsocks_config, shadowsocks_metrics, trojan_config);
323 accept_with_fixed_target(
324 client,
325 protocols,
326 auth,
327 shadowsocks_config,
328 shadowsocks_metrics,
329 trojan_config,
330 None,
331 )
332 .await
333}
334
335pub async fn accept_with_fixed_target(
336 client: BoxStream,
337 protocols: &[ProtocolId],
338 auth: &InboundAuthentication,
339 shadowsocks_config: Option<&InboundShadowsocksConfig>,
340 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
341 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
342 >,
343 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
344 trojan_config: Option<&InboundTrojanConfig>,
345 fixed_target: Option<&TargetAddr>,
346) -> Result<AcceptedSession, AcceptError> {
347 accept_with_fixed_target_for_peer(
348 client,
349 protocols,
350 auth,
351 shadowsocks_config,
352 shadowsocks_metrics,
353 trojan_config,
354 fixed_target,
355 None,
356 )
357 .await
358}
359
360#[allow(clippy::too_many_arguments)]
361pub async fn accept_with_fixed_target_for_peer(
362 client: BoxStream,
363 protocols: &[ProtocolId],
364 auth: &InboundAuthentication,
365 shadowsocks_config: Option<&InboundShadowsocksConfig>,
366 #[cfg(feature = "extended")] shadowsocks_metrics: Option<
367 &std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>,
368 >,
369 #[cfg(not(feature = "extended"))] shadowsocks_metrics: Option<&()>,
370 trojan_config: Option<&InboundTrojanConfig>,
371 fixed_target: Option<&TargetAddr>,
372 peer_ip: Option<IpAddr>,
373) -> Result<AcceptedSession, AcceptError> {
374 #[cfg(not(feature = "extended"))]
375 let _ = (shadowsocks_config, shadowsocks_metrics, trojan_config);
376 #[cfg(feature = "extended")]
377 #[inline]
378 fn shadows_metrics(
379 m: Option<&std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>>,
380 ) -> Option<std::sync::Arc<eggress_protocol_shadowsocks::ShadowsocksMetrics>> {
381 m.cloned()
382 }
383 let mut stream = client;
384 if protocols.len() == 1 && protocols.contains(&ProtocolId::Echo) {
385 return Ok(AcceptedSession::Echo(stream));
386 }
387 if protocols.len() == 1 && protocols.contains(&ProtocolId::Raw) {
388 let target = fixed_target
389 .cloned()
390 .ok_or_else(|| AcceptError::Protocol("raw listener requires fixed_target".into()))?;
391 return Ok(AcceptedSession::Tunnel(PendingTunnel {
392 target,
393 client: stream,
394 protocol: TunnelProtocol::Raw,
395 reply_context: ReplyContext::Raw,
396 identity: ClientIdentity::Anonymous,
397 }));
398 }
399 let mut first_byte = [0u8; 1];
400 stream
401 .read_exact(&mut first_byte)
402 .await
403 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
404
405 let has_socks5 = protocols.contains(&ProtocolId::Socks5);
406 let has_socks4 = protocols.contains(&ProtocolId::Socks4);
407 let has_http = protocols.contains(&ProtocolId::Http);
408
409 if first_byte[0] == 0x05 && has_socks5 {
411 tracing::trace!(
412 "detected protocol: socks5 (first_byte={:#04x})",
413 first_byte[0]
414 );
415 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
416 return accept_socks5(stream, auth, peer_ip).await;
417 }
418
419 if first_byte[0] == 0x04 && has_socks4 {
421 tracing::trace!(
422 "detected protocol: socks4 (first_byte={:#04x})",
423 first_byte[0]
424 );
425 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
426 return accept_socks4(stream, auth, peer_ip).await;
427 }
428
429 if has_http {
431 let mut prefix = vec![first_byte[0]];
433 let mut buf = [0u8; 32];
434 let n = stream
435 .read(&mut buf)
436 .await
437 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
438 prefix.extend_from_slice(&buf[..n]);
439
440 match detect_http_method(&prefix) {
441 DetectResult::Match => {
442 tracing::trace!(
443 "detected protocol: http (prefix={:?})",
444 &prefix[..prefix.len().min(16)]
445 );
446 let stream: BoxStream = Box::new(PrefixedStream::new(prefix, stream));
447 return accept_http(stream, auth, peer_ip).await;
448 }
449 DetectResult::NeedMore => {
450 let mut more = [0u8; 32];
452 let n = stream
453 .read(&mut more)
454 .await
455 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
456 prefix.extend_from_slice(&more[..n]);
457 match detect_http_method(&prefix) {
458 DetectResult::Match => {
459 tracing::trace!(
460 "detected protocol: http (prefix={:?})",
461 &prefix[..prefix.len().min(16)]
462 );
463 let stream: BoxStream = Box::new(PrefixedStream::new(prefix, stream));
464 return accept_http(stream, auth, peer_ip).await;
465 }
466 DetectResult::NoMatch => {
467 return Err(AcceptError::Protocol(
468 "no matching protocol for listener".into(),
469 ));
470 }
471 DetectResult::NeedMore => {
472 return Err(AcceptError::Protocol(
473 "no matching protocol for listener".into(),
474 ));
475 }
476 }
477 }
478 DetectResult::NoMatch => {
479 return Err(AcceptError::Protocol(
480 "no matching protocol for listener".into(),
481 ));
482 }
483 }
484 }
485
486 #[cfg(feature = "extended")]
488 if protocols.len() == 1 && protocols.contains(&ProtocolId::Shadowsocks) {
489 if let Some(ss_config) = shadowsocks_config {
490 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
491 match eggress_protocol_shadowsocks::CipherMethod::parse_method(&ss_config.method) {
492 Ok(method) => {
493 let (ss_stream, target_addr) =
494 eggress_protocol_shadowsocks::tcp::shadowsocks_accept(
495 stream,
496 &ss_config.password,
497 method,
498 shadows_metrics(shadowsocks_metrics),
499 )
500 .await
501 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
502
503 return Ok(AcceptedSession::Tunnel(PendingTunnel {
504 target: target_addr,
505 client: ss_stream,
506 protocol: TunnelProtocol::Shadowsocks,
507 reply_context: ReplyContext::Shadowsocks,
508 identity: ClientIdentity::Anonymous,
509 }));
510 }
511 Err(modern_error) => {
512 #[cfg(feature = "legacy-crypto")]
513 if let Ok(legacy_method) =
514 eggress_protocol_shadowsocks::legacy::LegacyMethod::parse(&ss_config.method)
515 {
516 let (ss_stream, target_addr) =
517 eggress_protocol_shadowsocks::legacy::legacy_accept(
518 stream,
519 legacy_method,
520 ss_config.password.as_bytes(),
521 )
522 .await
523 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
524
525 return Ok(AcceptedSession::Tunnel(PendingTunnel {
526 target: target_addr,
527 client: ss_stream,
528 protocol: TunnelProtocol::Shadowsocks,
529 reply_context: ReplyContext::Shadowsocks,
530 identity: ClientIdentity::Anonymous,
531 }));
532 }
533 if let Some(m) = shadowsocks_metrics {
534 m.record_tcp_unsupported_method_reject();
535 }
536 return Err(AcceptError::Protocol(Box::new(modern_error)));
537 }
538 }
539 }
540 return Err(AcceptError::Protocol(
541 "shadowsocks listener requires shadowsocks config".into(),
542 ));
543 }
544
545 #[cfg(feature = "pproxy-legacy")]
546 if protocols.len() == 1 && protocols.contains(&ProtocolId::ShadowsocksR) {
547 let ssr_config = shadowsocks_config
548 .filter(|config| config.method == "ssr")
549 .ok_or_else(|| AcceptError::Protocol("SSR listener requires SSR config".into()))?;
550 let plugins =
551 eggress_protocol_shadowsocks::compat::plugin::parse_plugins(&ssr_config.plugins)
552 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
553 let stream: BoxStream = Box::new(PrefixedStream::new(first_byte.to_vec(), stream));
554 let (ss_stream, target_addr) = eggress_protocol_shadowsocks::compat::ssr::ssr_accept(
555 stream,
556 &eggress_protocol_shadowsocks::compat::ssr::SsrConfig {
557 auth_prefix: ssr_config.auth_prefix.clone(),
558 plugins,
559 },
560 )
561 .await
562 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
563 return Ok(AcceptedSession::Tunnel(PendingTunnel {
564 target: target_addr,
565 client: ss_stream,
566 protocol: TunnelProtocol::ShadowsocksR,
567 reply_context: ReplyContext::Shadowsocks,
568 identity: ClientIdentity::Anonymous,
569 }));
570 }
571 #[cfg(not(feature = "pproxy-legacy"))]
572 if protocols.len() == 1 && protocols.contains(&ProtocolId::ShadowsocksR) {
573 return Err(AcceptError::Protocol(
574 "SSR compatibility support is not included in this build".into(),
575 ));
576 }
577 #[cfg(not(feature = "extended"))]
578 if protocols.len() == 1 && protocols.contains(&ProtocolId::Shadowsocks) {
579 return Err(AcceptError::Protocol(
580 "shadowsocks support not included in this build".into(),
581 ));
582 }
583
584 #[cfg(feature = "extended")]
586 if protocols.len() == 1 && protocols.contains(&ProtocolId::Trojan) {
587 if let Some(trojan_cfg) = trojan_config {
588 use tokio::io::AsyncReadExt;
589
590 let mut hash_prefix = [0u8; 56];
594 hash_prefix[0] = first_byte[0];
598 stream
599 .read_exact(&mut hash_prefix[1..])
600 .await
601 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
602
603 let password_matches =
604 eggress_protocol_trojan::trojan_check_password(&hash_prefix, &trojan_cfg.password);
605
606 if password_matches {
607 let prefixed = PrefixedStream::new(hash_prefix.to_vec(), stream);
609 let boxed: BoxStream = Box::new(prefixed);
610 let (trojan_stream, result) =
611 eggress_protocol_trojan::trojan_accept(boxed, &trojan_cfg.password)
612 .await
613 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
614
615 return Ok(AcceptedSession::Tunnel(PendingTunnel {
616 target: result.target,
617 client: trojan_stream,
618 protocol: TunnelProtocol::Trojan,
619 reply_context: ReplyContext::Trojan,
620 identity: ClientIdentity::Anonymous,
621 }));
622 }
623
624 if let Some(ref fallback_target) = trojan_cfg.fallback {
626 let target: TargetAddr = fallback_target.parse().map_err(|e: String| {
627 AcceptError::Protocol(format!("invalid trojan fallback address: {e}").into())
628 })?;
629 tracing::debug!("trojan auth failed, falling back to {}", fallback_target);
630 let prefixed = PrefixedStream::new(hash_prefix.to_vec(), stream);
631 let client: BoxStream = Box::new(prefixed);
632 return Ok(AcceptedSession::Tunnel(PendingTunnel {
633 target,
634 client,
635 protocol: TunnelProtocol::Trojan,
636 reply_context: ReplyContext::Trojan,
637 identity: ClientIdentity::Anonymous,
638 }));
639 }
640
641 return Err(AcceptError::AuthenticationFailed);
642 }
643 return Err(AcceptError::Protocol(
644 "trojan listener requires trojan config".into(),
645 ));
646 }
647 #[cfg(not(feature = "extended"))]
648 if protocols.len() == 1 && protocols.contains(&ProtocolId::Trojan) {
649 return Err(AcceptError::Protocol(
650 "trojan support not included in this build".into(),
651 ));
652 }
653
654 Err(AcceptError::Protocol(
655 "no matching protocol for listener".into(),
656 ))
657}
658
659enum DetectResult {
660 Match,
661 NeedMore,
662 NoMatch,
663}
664
665fn detect_http_method(prefix: &[u8]) -> DetectResult {
666 if let Some(space_pos) = prefix.iter().position(|&b| b == b' ') {
668 let method_token = &prefix[..space_pos];
669 if method_token.is_empty() || method_token.len() > 16 {
670 return DetectResult::NoMatch;
671 }
672 let is_valid_method = method_token
675 .iter()
676 .all(|&b| b.is_ascii_uppercase() || b == b'-' || b.is_ascii_lowercase());
677 if is_valid_method {
678 DetectResult::Match
679 } else {
680 DetectResult::NoMatch
681 }
682 } else {
683 if prefix.len() > 16 {
685 return DetectResult::NoMatch;
686 }
687 let is_valid_prefix = prefix
689 .iter()
690 .all(|&b| b.is_ascii_uppercase() || b == b'-' || b.is_ascii_lowercase());
691 if is_valid_prefix {
692 DetectResult::NeedMore
693 } else {
694 DetectResult::NoMatch
695 }
696 }
697}
698
699async fn accept_socks5(
700 stream: BoxStream,
701 auth: &InboundAuthentication,
702 peer_ip: Option<IpAddr>,
703) -> Result<AcceptedSession, AcceptError> {
704 use eggress_protocol_socks::socks5::server::{
705 read_auth_request, read_method_negotiation, read_socks5_request, send_auth_response,
706 send_connect_reply, Socks5Command, CMD_BIND, REP_COMMAND_NOT_SUPPORTED,
707 };
708
709 let (mut reader, mut writer) = tokio::io::split(stream);
710 let methods = read_method_negotiation(&mut reader)
711 .await
712 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
713
714 const AUTH_NONE: u8 = 0x00;
716 const AUTH_USERNAME_PASSWORD: u8 = 0x02;
717 const AUTH_NO_ACCEPTABLE: u8 = 0xFF;
718
719 let cached = cached_identity(auth, peer_ip);
720 let selected_method = match (auth_credentials(auth), cached.is_some()) {
721 (None, _) | (Some(_), true) if methods.contains(&AUTH_NONE) => AUTH_NONE,
722 (Some(_), _) if methods.contains(&AUTH_USERNAME_PASSWORD) => AUTH_USERNAME_PASSWORD,
723 (None, _) => AUTH_NO_ACCEPTABLE,
724 (Some(_), _) => AUTH_NO_ACCEPTABLE,
725 };
726
727 use tokio::io::AsyncWriteExt;
729 writer
730 .write_all(&[0x05, selected_method])
731 .await
732 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
733 writer
734 .flush()
735 .await
736 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
737
738 if selected_method == AUTH_NO_ACCEPTABLE {
739 return Err(AcceptError::Protocol(Box::new(
740 eggress_protocol_socks::error::Socks5Error::MethodNegotiationFailed,
741 )));
742 }
743
744 let mut identity = cached.unwrap_or(ClientIdentity::Anonymous);
746 if selected_method == AUTH_USERNAME_PASSWORD {
747 let (username, password, _) = auth_credentials(auth).expect("auth method requires policy");
748 match read_auth_request(&mut reader, password).await {
749 Ok(client_username) => {
750 use subtle::ConstantTimeEq;
751 let username_ok: bool =
752 client_username.as_bytes().ct_eq(username.as_bytes()).into();
753 if !username_ok {
754 let _ = send_auth_response(&mut writer, false).await;
755 return Err(AcceptError::AuthenticationFailed);
756 }
757 identity = ClientIdentity::Username(client_username);
758 record_authenticated(auth, peer_ip, &identity);
759 send_auth_response(&mut writer, true)
760 .await
761 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
762 }
763 Err(_) => {
764 let _ = send_auth_response(&mut writer, false).await;
765 return Err(AcceptError::AuthenticationFailed);
766 }
767 }
768 }
769
770 let (command, socks_addr) = read_socks5_request(&mut reader)
771 .await
772 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
773
774 match command {
775 Socks5Command::Connect => {
776 let target = socks_addr_to_target(&socks_addr);
777 let stream: BoxStream = Box::new(tokio::io::join(reader, writer));
778
779 Ok(AcceptedSession::Tunnel(PendingTunnel {
780 target,
781 client: stream,
782 protocol: TunnelProtocol::Socks5,
783 reply_context: ReplyContext::Socks5,
784 identity,
785 }))
786 }
787 Socks5Command::UdpAssociate => {
788 let client_hint = Some(socks_addr_to_target(&socks_addr));
789 let stream: BoxStream = Box::new(tokio::io::join(reader, writer));
790
791 Ok(AcceptedSession::UdpAssociate(PendingUdpAssociate {
792 client: stream,
793 protocol: TunnelProtocol::Socks5,
794 identity,
795 client_hint,
796 }))
797 }
798 Socks5Command::Bind => {
799 let _ = send_connect_reply(&mut writer, REP_COMMAND_NOT_SUPPORTED, &socks_addr).await;
800 Err(AcceptError::Protocol(Box::new(
801 eggress_protocol_socks::error::Socks5Error::UnsupportedCommand(CMD_BIND),
802 )))
803 }
804 }
805}
806
807async fn accept_socks4(
808 stream: BoxStream,
809 auth: &InboundAuthentication,
810 peer_ip: Option<IpAddr>,
811) -> Result<AcceptedSession, AcceptError> {
812 use eggress_protocol_socks::socks4::server::read_socks4_request;
813
814 let (mut reader, writer) = tokio::io::split(stream);
815 let request = read_socks4_request(&mut reader)
816 .await
817 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
818 let target = if let Some(ref domain) = request.domain {
819 TargetAddr {
820 host: TargetHost::Domain(domain.clone()),
821 port: request.port,
822 }
823 } else {
824 TargetAddr {
825 host: TargetHost::Ip(request.addr.ip()),
826 port: request.addr.port(),
827 }
828 };
829 let cached = cached_identity(auth, peer_ip);
830 if cached.is_none() {
831 if let Some((username, _, _)) = auth_credentials(auth) {
832 use subtle::ConstantTimeEq;
833 let user_ok: bool = request.user_id.as_bytes().ct_eq(username.as_bytes()).into();
834 if !user_ok {
835 return Err(AcceptError::AuthenticationFailed);
836 }
837 }
838 }
839 let identity = cached.unwrap_or({
840 if request.user_id.is_empty() {
841 ClientIdentity::Anonymous
842 } else {
843 ClientIdentity::Opaque(request.user_id)
844 }
845 });
846 if matches!(
847 identity,
848 ClientIdentity::Opaque(_) | ClientIdentity::Username(_)
849 ) {
850 record_authenticated(auth, peer_ip, &identity);
851 }
852 let stream: BoxStream = Box::new(tokio::io::join(reader, writer));
853
854 Ok(AcceptedSession::Tunnel(PendingTunnel {
855 target,
856 client: stream,
857 protocol: TunnelProtocol::Socks4,
858 reply_context: ReplyContext::Socks4,
859 identity,
860 }))
861}
862
863async fn accept_http(
864 mut stream: BoxStream,
865 auth: &InboundAuthentication,
866 peer_ip: Option<IpAddr>,
867) -> Result<AcceptedSession, AcceptError> {
868 let mut head_buf = Vec::with_capacity(256);
870 let mut temp = [0u8; 1];
871
872 loop {
873 if head_buf.len() >= MAX_HEAD_SIZE {
874 return Err(AcceptError::Protocol(
875 eggress_protocol_http::HttpError::HeaderTooLarge.into(),
876 ));
877 }
878 let n = stream
879 .read(&mut temp)
880 .await
881 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
882 if n == 0 {
883 return Err(AcceptError::Protocol(
884 eggress_protocol_http::HttpError::MalformedRequest("unexpected EOF".into()).into(),
885 ));
886 }
887 head_buf.push(temp[0]);
888 if head_buf.len() >= 2 && &head_buf[head_buf.len() - 2..] == b"\r\n" {
889 break;
890 }
891 }
892
893 let method = {
894 let request_line = String::from_utf8_lossy(&head_buf);
895 request_line
896 .split_whitespace()
897 .next()
898 .unwrap_or("")
899 .to_ascii_lowercase()
900 };
901
902 let mut stream: BoxStream = Box::new(PrefixedStream::new(head_buf, stream));
904
905 if method == "connect" {
906 let request = read_connect_request_from_stream(&mut stream, auth, peer_ip).await?;
907 Ok(AcceptedSession::Tunnel(PendingTunnel {
908 target: request.target,
909 client: stream,
910 protocol: TunnelProtocol::HttpConnect,
911 reply_context: ReplyContext::Http,
912 identity: request.identity,
913 }))
914 } else {
915 let mut head_buf = Vec::with_capacity(1024);
917 let mut temp = [0u8; 1];
918 let mut header_count = 0;
919
920 loop {
921 if head_buf.len() >= MAX_HEAD_SIZE {
922 return Err(AcceptError::Protocol(
923 eggress_protocol_http::HttpError::HeaderTooLarge.into(),
924 ));
925 }
926
927 let n = stream
928 .read(&mut temp)
929 .await
930 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
931 if n == 0 {
932 return Err(AcceptError::Protocol(
933 eggress_protocol_http::HttpError::MalformedRequest(
934 "unexpected EOF reading request".into(),
935 )
936 .into(),
937 ));
938 }
939
940 head_buf.push(temp[0]);
941
942 if head_buf.len() >= 4 {
943 let len = head_buf.len();
944 if &head_buf[len - 4..] == b"\r\n\r\n" {
945 break;
946 }
947 if head_buf.len() >= 2 && &head_buf[len - 2..] == b"\r\n" {
948 header_count += 1;
949 if header_count > MAX_HEADER_LINES {
950 return Err(AcceptError::Protocol(
951 eggress_protocol_http::HttpError::TooManyHeaders.into(),
952 ));
953 }
954 }
955 }
956 }
957
958 let head_str = String::from_utf8_lossy(&head_buf);
960 let cached = cached_identity(auth, peer_ip);
961 let proxy_auth = if cached.is_some() {
962 None
963 } else if let Some((username, password, _)) = auth_credentials(auth) {
964 let mut found_auth = None;
965 for line in head_str.split("\r\n") {
966 if let Some((name, value)) = parse_header_line_str(line) {
967 if name.eq_ignore_ascii_case("Proxy-Authorization") {
968 found_auth = parse_basic_auth(&value);
969 break;
970 }
971 }
972 }
973 match found_auth {
974 Some((user, pass)) => {
975 use subtle::ConstantTimeEq;
976 let user_ok: bool = user.as_bytes().ct_eq(username.as_bytes()).into();
977 let pass_ok: bool = pass.as_bytes().ct_eq(password.as_bytes()).into();
978 if !user_ok || !pass_ok {
979 let mut stream: BoxStream = Box::new(PrefixedStream::new(head_buf, stream));
981 let _ = write_proxy_auth_required(&mut stream).await;
982 return Err(AcceptError::AuthenticationFailed);
983 }
984 Some((user, pass))
985 }
986 None => {
987 let mut stream: BoxStream = Box::new(PrefixedStream::new(head_buf, stream));
988 let _ = write_proxy_auth_required(&mut stream).await;
989 return Err(AcceptError::AuthenticationFailed);
990 }
991 }
992 } else {
993 None
994 };
995 let identity = cached.unwrap_or_else(|| match &proxy_auth {
996 Some((user, _)) => ClientIdentity::Username(user.clone()),
997 None => ClientIdentity::Anonymous,
998 });
999 if matches!(identity, ClientIdentity::Username(_)) {
1000 record_authenticated(auth, peer_ip, &identity);
1001 }
1002 let _ = proxy_auth; let stream: BoxStream = Box::new(PrefixedStream::new(head_buf, stream));
1006
1007 let (request, client_stream) = eggress_protocol_http::forward_request(stream)
1008 .await
1009 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
1010
1011 let target = request.target.clone();
1012 Ok(AcceptedSession::HttpForward(PendingHttpForward {
1013 target,
1014 client: client_stream,
1015 request,
1016 identity,
1017 }))
1018 }
1019}
1020
1021struct ConnectRequest {
1022 target: TargetAddr,
1023 identity: ClientIdentity,
1024}
1025
1026async fn read_connect_request_from_stream(
1027 stream: &mut BoxStream,
1028 auth: &InboundAuthentication,
1029 peer_ip: Option<IpAddr>,
1030) -> Result<ConnectRequest, AcceptError> {
1031 let mut head_buf = Vec::with_capacity(1024);
1032 let mut temp = [0u8; 1];
1033 let mut header_count = 0;
1034
1035 loop {
1036 if head_buf.len() >= MAX_HEAD_SIZE {
1037 return Err(AcceptError::Protocol(
1038 eggress_protocol_http::HttpError::HeaderTooLarge.into(),
1039 ));
1040 }
1041
1042 let n = stream
1043 .read(&mut temp)
1044 .await
1045 .map_err(|e| AcceptError::Protocol(Box::new(e)))?;
1046 if n == 0 {
1047 return Err(AcceptError::Protocol(
1048 eggress_protocol_http::HttpError::MalformedRequest(
1049 "unexpected EOF reading request".into(),
1050 )
1051 .into(),
1052 ));
1053 }
1054
1055 head_buf.push(temp[0]);
1056
1057 if head_buf.len() >= 4 {
1058 let len = head_buf.len();
1059 if &head_buf[len - 4..] == b"\r\n\r\n" {
1060 break;
1061 }
1062 if head_buf.len() >= 2 && &head_buf[len - 2..] == b"\r\n" {
1063 header_count += 1;
1064 if header_count > MAX_HEADER_LINES {
1065 return Err(AcceptError::Protocol(
1066 eggress_protocol_http::HttpError::TooManyHeaders.into(),
1067 ));
1068 }
1069 }
1070 }
1071 }
1072
1073 let head_str = String::from_utf8_lossy(&head_buf);
1074 let mut lines = head_str.split("\r\n");
1075
1076 let request_line = lines.next().ok_or_else(|| {
1077 AcceptError::Protocol(
1078 eggress_protocol_http::HttpError::MalformedRequest("empty request".into()).into(),
1079 )
1080 })?;
1081
1082 let parts: Vec<&str> = request_line.split_whitespace().collect();
1083 if parts.len() != 3 {
1084 return Err(AcceptError::Protocol(
1085 eggress_protocol_http::HttpError::MalformedRequest(format!(
1086 "expected 3 parts in request line, got {}",
1087 parts.len()
1088 ))
1089 .into(),
1090 ));
1091 }
1092
1093 let authority = parts[1];
1094 let target = parse_authority(authority)?;
1095
1096 let mut proxy_auth = None;
1098 let mut parsed_username: Option<String> = None;
1099 for line in lines {
1100 if line.is_empty() {
1101 break;
1102 }
1103 if let Some((name, value)) = parse_header_line_str(line) {
1104 if name.eq_ignore_ascii_case("Proxy-Authorization") {
1105 proxy_auth = parse_basic_auth(&value);
1106 if let Some((user, _)) = &proxy_auth {
1107 parsed_username = Some(user.clone());
1108 }
1109 }
1110 }
1111 }
1112
1113 let cached = cached_identity(auth, peer_ip);
1117 if cached.is_none() {
1118 if let Some((username, password, _)) = auth_credentials(auth) {
1119 match proxy_auth {
1120 Some((user, pass)) => {
1121 use subtle::ConstantTimeEq;
1122 let user_ok: bool = user.as_bytes().ct_eq(username.as_bytes()).into();
1123 let pass_ok: bool = pass.as_bytes().ct_eq(password.as_bytes()).into();
1124 if !user_ok || !pass_ok {
1125 let _ = write_proxy_auth_required(stream).await;
1126 return Err(AcceptError::AuthenticationFailed);
1127 }
1128 }
1129 None => {
1130 let _ = write_proxy_auth_required(stream).await;
1131 return Err(AcceptError::AuthenticationFailed);
1132 }
1133 }
1134 }
1135 }
1136
1137 let identity = cached.unwrap_or(match parsed_username {
1138 Some(user) => ClientIdentity::Username(user),
1139 None => ClientIdentity::Anonymous,
1140 });
1141 if matches!(identity, ClientIdentity::Username(_)) {
1142 record_authenticated(auth, peer_ip, &identity);
1143 }
1144
1145 Ok(ConnectRequest { target, identity })
1146}
1147
1148fn parse_authority(
1149 authority: &str,
1150) -> Result<TargetAddr, Box<dyn std::error::Error + Send + Sync>> {
1151 if authority.starts_with('[') {
1152 let bracket_end = authority.find(']').ok_or_else(|| {
1153 eggress_protocol_http::HttpError::TargetParseError(
1154 "unclosed bracket in IPv6 address".into(),
1155 )
1156 })?;
1157
1158 let ip_str = &authority[1..bracket_end];
1159 let ip: std::net::IpAddr = ip_str.parse().map_err(|e| {
1160 eggress_protocol_http::HttpError::TargetParseError(format!("invalid IPv6 address: {e}"))
1161 })?;
1162
1163 let port_str = authority.get(bracket_end + 2..).ok_or_else(|| {
1164 eggress_protocol_http::HttpError::TargetParseError("missing port".into())
1165 })?;
1166
1167 if !authority
1168 .as_bytes()
1169 .get(bracket_end + 1)
1170 .is_some_and(|&b| b == b':')
1171 {
1172 return Err(eggress_protocol_http::HttpError::TargetParseError(
1173 "expected ':' between IPv6 address and port".into(),
1174 )
1175 .into());
1176 }
1177
1178 let port: u16 = port_str.parse().map_err(|e| {
1179 eggress_protocol_http::HttpError::TargetParseError(format!("invalid port: {e}"))
1180 })?;
1181
1182 return Ok(TargetAddr {
1183 host: TargetHost::Ip(ip),
1184 port,
1185 });
1186 }
1187
1188 let colon_pos = authority.rfind(':').ok_or_else(|| {
1189 eggress_protocol_http::HttpError::TargetParseError("missing port in authority".into())
1190 })?;
1191
1192 let host_str = &authority[..colon_pos];
1193 let port_str = &authority[colon_pos + 1..];
1194
1195 let port: u16 = port_str.parse().map_err(|e| {
1196 eggress_protocol_http::HttpError::TargetParseError(format!("invalid port: {e}"))
1197 })?;
1198
1199 if let Ok(ip) = host_str.parse::<std::net::IpAddr>() {
1200 return Ok(TargetAddr {
1201 host: TargetHost::Ip(ip),
1202 port,
1203 });
1204 }
1205
1206 if host_str.is_empty() {
1207 return Err(eggress_protocol_http::HttpError::TargetParseError("empty host".into()).into());
1208 }
1209
1210 Ok(TargetAddr {
1211 host: TargetHost::Domain(host_str.to_string()),
1212 port,
1213 })
1214}
1215
1216fn socks_addr_to_target(addr: &eggress_protocol_socks::socks5::server::SocksAddr) -> TargetAddr {
1217 use eggress_protocol_socks::socks5::server::SocksAddr;
1218 match addr {
1219 SocksAddr::IPv4(octets, port) => TargetAddr {
1220 host: TargetHost::Ip(std::net::IpAddr::V4((*octets).into())),
1221 port: *port,
1222 },
1223 SocksAddr::IPv6(octets, port) => TargetAddr {
1224 host: TargetHost::Ip(std::net::IpAddr::V6((*octets).into())),
1225 port: *port,
1226 },
1227 SocksAddr::Domain(domain, port) => TargetAddr {
1228 host: TargetHost::Domain(domain.clone()),
1229 port: *port,
1230 },
1231 }
1232}
1233
1234const MAX_HEAD_SIZE: usize = 32 * 1024;
1236
1237const MAX_HEADER_LINES: usize = 128;
1239
1240fn parse_header_line_str(line: &str) -> Option<(String, String)> {
1242 let colon_pos = line.find(':')?;
1243 let name = line[..colon_pos].trim().to_string();
1244 let value = line[colon_pos + 1..].trim().to_string();
1245 Some((name, value))
1246}
1247
1248fn base64_decode(input: &str) -> Option<Vec<u8>> {
1250 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1251
1252 let input = input.trim_end_matches('=');
1253 let input_bytes = input.as_bytes();
1254
1255 let mut result = Vec::with_capacity(input_bytes.len() * 3 / 4);
1256 let mut buf: u32 = 0;
1257 let mut bits: u32 = 0;
1258
1259 for &byte in input_bytes {
1260 let val = TABLE.iter().position(|&b| b == byte)? as u32;
1261 buf = (buf << 6) | val;
1262 bits += 6;
1263 if bits >= 8 {
1264 bits -= 8;
1265 result.push((buf >> bits) as u8);
1266 }
1267 }
1268
1269 Some(result)
1270}
1271
1272fn parse_basic_auth(value: &str) -> Option<(String, String)> {
1274 let value = value.trim();
1275 if !value.starts_with("Basic ") {
1276 return None;
1277 }
1278
1279 let encoded = &value[6..];
1280 let decoded = base64_decode(encoded)?;
1281 let decoded_str = String::from_utf8(decoded).ok()?;
1282 let colon_pos = decoded_str.find(':')?;
1283 let username = decoded_str[..colon_pos].to_string();
1284 let password = decoded_str[colon_pos + 1..].to_string();
1285 Some((username, password))
1286}
1287
1288async fn write_proxy_auth_required(stream: &mut BoxStream) -> Result<(), std::io::Error> {
1290 let response = b"HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"eggress\"\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
1291 stream.write_all(response).await?;
1292 stream.flush().await?;
1293 Ok(())
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1300
1301 #[tokio::test]
1302 async fn test_accept_socks5() {
1303 let all_protocols: Vec<ProtocolId> =
1304 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1305 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1306 let addr = listener.local_addr().unwrap();
1307
1308 let server_jh = tokio::spawn(async move {
1309 let (stream, _) = listener.accept().await.unwrap();
1310 let boxed: BoxStream = Box::new(stream);
1311 let session = accept(
1312 boxed,
1313 &all_protocols,
1314 &InboundAuthentication::None,
1315 None,
1316 None,
1317 None,
1318 )
1319 .await
1320 .unwrap();
1321 match session {
1322 AcceptedSession::Tunnel(pending) => {
1323 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
1324 assert_eq!(pending.target.port, 443);
1325 }
1326 _ => panic!("expected tunnel"),
1327 }
1328 });
1329
1330 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1331 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
1332 let mut response = [0u8; 2];
1333 stream.read_exact(&mut response).await.unwrap();
1334 assert_eq!(response, [0x05, 0x00]);
1335
1336 stream
1337 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
1338 .await
1339 .unwrap();
1340 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
1341
1342 server_jh.await.unwrap();
1343 }
1344
1345 #[tokio::test]
1346 async fn test_accept_socks4() {
1347 let all_protocols: Vec<ProtocolId> =
1348 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1349 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1350 let addr = listener.local_addr().unwrap();
1351
1352 let server_jh = tokio::spawn(async move {
1353 let (stream, _) = listener.accept().await.unwrap();
1354 let boxed: BoxStream = Box::new(stream);
1355 let session = accept(
1356 boxed,
1357 &all_protocols,
1358 &InboundAuthentication::None,
1359 None,
1360 None,
1361 None,
1362 )
1363 .await
1364 .unwrap();
1365 match session {
1366 AcceptedSession::Tunnel(pending) => {
1367 assert_eq!(pending.protocol, TunnelProtocol::Socks4);
1368 assert_eq!(pending.target.port, 80);
1369 }
1370 _ => panic!("expected tunnel"),
1371 }
1372 });
1373
1374 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1375 stream
1376 .write_all(&[0x04, 0x01, 0x00, 0x50, 10, 0, 0, 1])
1377 .await
1378 .unwrap();
1379 stream.write_all(&[0x00]).await.unwrap();
1380
1381 server_jh.await.unwrap();
1382 }
1383
1384 #[tokio::test]
1385 async fn test_accept_http_connect() {
1386 let all_protocols: Vec<ProtocolId> =
1387 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1388 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1389 let addr = listener.local_addr().unwrap();
1390
1391 let server_jh = tokio::spawn(async move {
1392 let (stream, _) = listener.accept().await.unwrap();
1393 let boxed: BoxStream = Box::new(stream);
1394 let session = accept(
1395 boxed,
1396 &all_protocols,
1397 &InboundAuthentication::None,
1398 None,
1399 None,
1400 None,
1401 )
1402 .await
1403 .unwrap();
1404 match session {
1405 AcceptedSession::Tunnel(pending) => {
1406 assert_eq!(pending.protocol, TunnelProtocol::HttpConnect);
1407 assert_eq!(pending.target.port, 443);
1408 }
1409 _ => panic!("expected tunnel"),
1410 }
1411 });
1412
1413 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1414 stream
1415 .write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
1416 .await
1417 .unwrap();
1418
1419 server_jh.await.unwrap();
1420 }
1421
1422 #[tokio::test]
1423 async fn test_accept_http_forward() {
1424 let all_protocols: Vec<ProtocolId> =
1425 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1426 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1427 let addr = listener.local_addr().unwrap();
1428
1429 let server_jh = tokio::spawn(async move {
1430 let (stream, _) = listener.accept().await.unwrap();
1431 let boxed: BoxStream = Box::new(stream);
1432 let session = accept(
1433 boxed,
1434 &all_protocols,
1435 &InboundAuthentication::None,
1436 None,
1437 None,
1438 None,
1439 )
1440 .await
1441 .unwrap();
1442 match session {
1443 AcceptedSession::HttpForward(pending) => {
1444 assert_eq!(pending.target.port, 80);
1445 assert_eq!(pending.request.method, "GET");
1446 }
1447 _ => panic!("expected http forward"),
1448 }
1449 });
1450
1451 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1452 stream
1453 .write_all(b"GET http://example.com/index.html HTTP/1.1\r\nHost: example.com\r\n\r\n")
1454 .await
1455 .unwrap();
1456
1457 server_jh.await.unwrap();
1458 }
1459
1460 #[tokio::test]
1461 async fn test_http_on_http_only_listener() {
1462 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http];
1463 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1464 let addr = listener.local_addr().unwrap();
1465
1466 let server_jh = tokio::spawn(async move {
1467 let (stream, _) = listener.accept().await.unwrap();
1468 let boxed: BoxStream = Box::new(stream);
1469 let session = accept(
1470 boxed,
1471 &protocols,
1472 &InboundAuthentication::None,
1473 None,
1474 None,
1475 None,
1476 )
1477 .await
1478 .unwrap();
1479 match session {
1480 AcceptedSession::Tunnel(pending) => {
1481 assert_eq!(pending.protocol, TunnelProtocol::HttpConnect);
1482 }
1483 _ => panic!("expected tunnel"),
1484 }
1485 });
1486
1487 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1488 stream
1489 .write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
1490 .await
1491 .unwrap();
1492
1493 server_jh.await.unwrap();
1494 }
1495
1496 #[tokio::test]
1497 async fn test_socks5_on_http_only_listener_rejected() {
1498 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http];
1499 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1500 let addr = listener.local_addr().unwrap();
1501
1502 let server_jh = tokio::spawn(async move {
1503 let (stream, _) = listener.accept().await.unwrap();
1504 let boxed: BoxStream = Box::new(stream);
1505 let result = accept(
1506 boxed,
1507 &protocols,
1508 &InboundAuthentication::None,
1509 None,
1510 None,
1511 None,
1512 )
1513 .await;
1514 assert!(result.is_err());
1515 });
1516
1517 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1518 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
1519
1520 server_jh.await.unwrap();
1521 }
1522
1523 #[tokio::test]
1524 async fn test_http_on_socks5_only_listener_rejected() {
1525 let protocols: Vec<ProtocolId> = vec![ProtocolId::Socks5];
1526 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1527 let addr = listener.local_addr().unwrap();
1528
1529 let server_jh = tokio::spawn(async move {
1530 let (stream, _) = listener.accept().await.unwrap();
1531 let boxed: BoxStream = Box::new(stream);
1532 let result = accept(
1533 boxed,
1534 &protocols,
1535 &InboundAuthentication::None,
1536 None,
1537 None,
1538 None,
1539 )
1540 .await;
1541 assert!(result.is_err());
1542 });
1543
1544 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1545 stream
1546 .write_all(b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
1547 .await
1548 .unwrap();
1549
1550 server_jh.await.unwrap();
1551 }
1552
1553 #[tokio::test]
1554 async fn test_socks5_on_mixed_listener_accepted() {
1555 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http, ProtocolId::Socks5];
1556 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1557 let addr = listener.local_addr().unwrap();
1558
1559 let server_jh = tokio::spawn(async move {
1560 let (stream, _) = listener.accept().await.unwrap();
1561 let boxed: BoxStream = Box::new(stream);
1562 let session = accept(
1563 boxed,
1564 &protocols,
1565 &InboundAuthentication::None,
1566 None,
1567 None,
1568 None,
1569 )
1570 .await
1571 .unwrap();
1572 match session {
1573 AcceptedSession::Tunnel(pending) => {
1574 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
1575 assert_eq!(pending.target.port, 443);
1576 }
1577 _ => panic!("expected tunnel"),
1578 }
1579 });
1580
1581 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1582 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
1583 let mut response = [0u8; 2];
1584 stream.read_exact(&mut response).await.unwrap();
1585 assert_eq!(response, [0x05, 0x00]);
1586
1587 stream
1588 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
1589 .await
1590 .unwrap();
1591 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
1592
1593 server_jh.await.unwrap();
1594 }
1595
1596 #[tokio::test]
1597 async fn test_http_on_mixed_listener_accepted() {
1598 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http, ProtocolId::Socks5];
1599 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1600 let addr = listener.local_addr().unwrap();
1601
1602 let server_jh = tokio::spawn(async move {
1603 let (stream, _) = listener.accept().await.unwrap();
1604 let boxed: BoxStream = Box::new(stream);
1605 let session = accept(
1606 boxed,
1607 &protocols,
1608 &InboundAuthentication::None,
1609 None,
1610 None,
1611 None,
1612 )
1613 .await
1614 .unwrap();
1615 match session {
1616 AcceptedSession::Tunnel(pending) => {
1617 assert_eq!(pending.protocol, TunnelProtocol::HttpConnect);
1618 }
1619 _ => panic!("expected tunnel"),
1620 }
1621 });
1622
1623 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1624 stream
1625 .write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
1626 .await
1627 .unwrap();
1628
1629 server_jh.await.unwrap();
1630 }
1631
1632 #[tokio::test]
1633 async fn test_random_binary_prefix_rejected() {
1634 let all_protocols: Vec<ProtocolId> =
1635 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1636 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1637 let addr = listener.local_addr().unwrap();
1638
1639 let server_jh = tokio::spawn(async move {
1640 let (stream, _) = listener.accept().await.unwrap();
1641 let boxed: BoxStream = Box::new(stream);
1642 let result = accept(
1643 boxed,
1644 &all_protocols,
1645 &InboundAuthentication::None,
1646 None,
1647 None,
1648 None,
1649 )
1650 .await;
1651 assert!(result.is_err());
1652 });
1653
1654 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1655 stream.write_all(&[0x00, 0x01, 0x02, 0x03]).await.unwrap();
1657
1658 server_jh.await.unwrap();
1659 }
1660
1661 #[tokio::test]
1662 async fn test_tls_client_hello_not_interpreted_as_http() {
1663 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http];
1664 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1665 let addr = listener.local_addr().unwrap();
1666
1667 let server_jh = tokio::spawn(async move {
1668 let (stream, _) = listener.accept().await.unwrap();
1669 let boxed: BoxStream = Box::new(stream);
1670 let result = accept(
1671 boxed,
1672 &protocols,
1673 &InboundAuthentication::None,
1674 None,
1675 None,
1676 None,
1677 )
1678 .await;
1679 assert!(result.is_err());
1680 });
1681
1682 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1683 stream
1685 .write_all(&[0x16, 0x03, 0x01, 0x00, 0x05])
1686 .await
1687 .unwrap();
1688
1689 server_jh.await.unwrap();
1690 }
1691
1692 #[tokio::test]
1695 async fn test_socks5_auth_correct_credentials() {
1696 let all_protocols: Vec<ProtocolId> =
1697 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1698 let auth = InboundAuthentication::UsernamePassword {
1699 username: "user".to_string(),
1700 password: "secret".to_string(),
1701 };
1702 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1703 let addr = listener.local_addr().unwrap();
1704
1705 let server_jh = tokio::spawn(async move {
1706 let (stream, _) = listener.accept().await.unwrap();
1707 let boxed: BoxStream = Box::new(stream);
1708 let session = accept(boxed, &all_protocols, &auth, None, None, None)
1709 .await
1710 .unwrap();
1711 match session {
1712 AcceptedSession::Tunnel(pending) => {
1713 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
1714 assert_eq!(pending.target.port, 443);
1715 }
1716 _ => panic!("expected tunnel"),
1717 }
1718 });
1719
1720 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1721 stream.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
1723 let mut response = [0u8; 2];
1725 stream.read_exact(&mut response).await.unwrap();
1726 assert_eq!(response, [0x05, 0x02]);
1727
1728 stream
1730 .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r', 0x06])
1731 .await
1732 .unwrap();
1733 stream.write_all(b"secret").await.unwrap();
1734 let mut auth_resp = [0u8; 2];
1736 stream.read_exact(&mut auth_resp).await.unwrap();
1737 assert_eq!(auth_resp, [0x01, 0x00]);
1738
1739 stream
1741 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
1742 .await
1743 .unwrap();
1744 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
1745
1746 server_jh.await.unwrap();
1747 }
1748
1749 #[tokio::test]
1750 async fn test_socks5_auth_wrong_password() {
1751 let all_protocols: Vec<ProtocolId> =
1752 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1753 let auth = InboundAuthentication::UsernamePassword {
1754 username: "user".to_string(),
1755 password: "secret".to_string(),
1756 };
1757 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1758 let addr = listener.local_addr().unwrap();
1759
1760 let server_jh = tokio::spawn(async move {
1761 let (stream, _) = listener.accept().await.unwrap();
1762 let boxed: BoxStream = Box::new(stream);
1763 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
1764 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
1765 });
1766
1767 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1768 stream.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
1769 let mut response = [0u8; 2];
1770 stream.read_exact(&mut response).await.unwrap();
1771 assert_eq!(response, [0x05, 0x02]);
1772
1773 stream
1775 .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r', 0x05])
1776 .await
1777 .unwrap();
1778 stream.write_all(b"wrong").await.unwrap();
1779 let mut auth_resp = [0u8; 2];
1781 stream.read_exact(&mut auth_resp).await.unwrap();
1782 assert_eq!(auth_resp, [0x01, 0x01]);
1783
1784 server_jh.await.unwrap();
1785 }
1786
1787 #[tokio::test]
1788 async fn test_socks5_auth_no_auth_client_rejected() {
1789 let all_protocols: Vec<ProtocolId> =
1790 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1791 let auth = InboundAuthentication::UsernamePassword {
1792 username: "user".to_string(),
1793 password: "secret".to_string(),
1794 };
1795 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1796 let addr = listener.local_addr().unwrap();
1797
1798 let server_jh = tokio::spawn(async move {
1799 let (stream, _) = listener.accept().await.unwrap();
1800 let boxed: BoxStream = Box::new(stream);
1801 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
1802 assert!(result.is_err());
1803 });
1804
1805 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1806 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
1808 let mut response = [0u8; 2];
1810 stream.read_exact(&mut response).await.unwrap();
1811 assert_eq!(response, [0x05, 0xFF]);
1812
1813 server_jh.await.unwrap();
1814 }
1815
1816 #[tokio::test]
1817 async fn test_http_connect_auth_correct_credentials() {
1818 let all_protocols: Vec<ProtocolId> =
1819 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1820 let auth = InboundAuthentication::UsernamePassword {
1821 username: "user".to_string(),
1822 password: "pass".to_string(),
1823 };
1824 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1825 let addr = listener.local_addr().unwrap();
1826
1827 let server_jh = tokio::spawn(async move {
1828 let (stream, _) = listener.accept().await.unwrap();
1829 let boxed: BoxStream = Box::new(stream);
1830 let session = accept(boxed, &all_protocols, &auth, None, None, None)
1831 .await
1832 .unwrap();
1833 match session {
1834 AcceptedSession::Tunnel(pending) => {
1835 assert_eq!(pending.protocol, TunnelProtocol::HttpConnect);
1836 assert_eq!(pending.target.port, 443);
1837 }
1838 _ => panic!("expected tunnel"),
1839 }
1840 });
1841
1842 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1843 stream
1845 .write_all(
1846 b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n",
1847 )
1848 .await
1849 .unwrap();
1850
1851 server_jh.await.unwrap();
1852 }
1853
1854 #[tokio::test]
1855 async fn test_http_connect_auth_missing_credentials() {
1856 let all_protocols: Vec<ProtocolId> =
1857 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1858 let auth = InboundAuthentication::UsernamePassword {
1859 username: "user".to_string(),
1860 password: "pass".to_string(),
1861 };
1862 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1863 let addr = listener.local_addr().unwrap();
1864
1865 let server_jh = tokio::spawn(async move {
1866 let (stream, _) = listener.accept().await.unwrap();
1867 let boxed: BoxStream = Box::new(stream);
1868 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
1869 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
1870 });
1871
1872 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1873 stream
1874 .write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
1875 .await
1876 .unwrap();
1877
1878 let mut response = vec![0u8; 512];
1880 let n = stream.read(&mut response).await.unwrap();
1881 let response_str = String::from_utf8_lossy(&response[..n]);
1882 assert!(
1883 response_str.contains("407"),
1884 "expected 407, got: {response_str}"
1885 );
1886 assert!(
1887 response_str.contains("Proxy-Authenticate"),
1888 "expected Proxy-Authenticate header"
1889 );
1890
1891 server_jh.await.unwrap();
1892 }
1893
1894 #[tokio::test]
1895 async fn test_http_connect_auth_wrong_credentials() {
1896 let all_protocols: Vec<ProtocolId> =
1897 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1898 let auth = InboundAuthentication::UsernamePassword {
1899 username: "user".to_string(),
1900 password: "pass".to_string(),
1901 };
1902 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1903 let addr = listener.local_addr().unwrap();
1904
1905 let server_jh = tokio::spawn(async move {
1906 let (stream, _) = listener.accept().await.unwrap();
1907 let boxed: BoxStream = Box::new(stream);
1908 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
1909 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
1910 });
1911
1912 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1913 stream
1915 .write_all(
1916 b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\nProxy-Authorization: Basic dXNlcjp3cm9uZw==\r\n\r\n",
1917 )
1918 .await
1919 .unwrap();
1920
1921 let mut response = vec![0u8; 512];
1922 let n = stream.read(&mut response).await.unwrap();
1923 let response_str = String::from_utf8_lossy(&response[..n]);
1924 assert!(
1925 response_str.contains("407"),
1926 "expected 407, got: {response_str}"
1927 );
1928
1929 server_jh.await.unwrap();
1930 }
1931
1932 #[tokio::test]
1933 async fn test_http_connect_auth_malformed_base64() {
1934 let all_protocols: Vec<ProtocolId> =
1935 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1936 let auth = InboundAuthentication::UsernamePassword {
1937 username: "user".to_string(),
1938 password: "pass".to_string(),
1939 };
1940 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1941 let addr = listener.local_addr().unwrap();
1942
1943 let server_jh = tokio::spawn(async move {
1944 let (stream, _) = listener.accept().await.unwrap();
1945 let boxed: BoxStream = Box::new(stream);
1946 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
1947 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
1948 });
1949
1950 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
1951 stream
1952 .write_all(
1953 b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\nProxy-Authorization: Basic !!!invalid!!!\r\n\r\n",
1954 )
1955 .await
1956 .unwrap();
1957
1958 let mut response = vec![0u8; 512];
1959 let n = stream.read(&mut response).await.unwrap();
1960 let response_str = String::from_utf8_lossy(&response[..n]);
1961 assert!(
1962 response_str.contains("407"),
1963 "expected 407, got: {response_str}"
1964 );
1965
1966 server_jh.await.unwrap();
1967 }
1968
1969 #[tokio::test]
1970 async fn test_http_forward_auth_correct_credentials() {
1971 let all_protocols: Vec<ProtocolId> =
1972 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
1973 let auth = InboundAuthentication::UsernamePassword {
1974 username: "user".to_string(),
1975 password: "pass".to_string(),
1976 };
1977 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1978 let addr = listener.local_addr().unwrap();
1979
1980 let server_jh = tokio::spawn(async move {
1981 let (stream, _) = listener.accept().await.unwrap();
1982 let boxed: BoxStream = Box::new(stream);
1983 let session = accept(boxed, &all_protocols, &auth, None, None, None)
1984 .await
1985 .unwrap();
1986 match session {
1987 AcceptedSession::HttpForward(pending) => {
1988 assert_eq!(pending.target.port, 80);
1989 assert_eq!(pending.request.method, "GET");
1990 assert!(!pending
1992 .request
1993 .headers
1994 .iter()
1995 .any(|(name, _)| name.eq_ignore_ascii_case("Proxy-Authorization")));
1996 }
1997 _ => panic!("expected http forward"),
1998 }
1999 });
2000
2001 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2002 stream
2003 .write_all(
2004 b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n",
2005 )
2006 .await
2007 .unwrap();
2008
2009 server_jh.await.unwrap();
2010 }
2011
2012 #[tokio::test]
2013 async fn test_http_forward_auth_missing_credentials() {
2014 let all_protocols: Vec<ProtocolId> =
2015 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2016 let auth = InboundAuthentication::UsernamePassword {
2017 username: "user".to_string(),
2018 password: "pass".to_string(),
2019 };
2020 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2021 let addr = listener.local_addr().unwrap();
2022
2023 let server_jh = tokio::spawn(async move {
2024 let (stream, _) = listener.accept().await.unwrap();
2025 let boxed: BoxStream = Box::new(stream);
2026 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
2027 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
2028 });
2029
2030 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2031 stream
2032 .write_all(b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
2033 .await
2034 .unwrap();
2035
2036 let mut response = vec![0u8; 512];
2037 let n = stream.read(&mut response).await.unwrap();
2038 let response_str = String::from_utf8_lossy(&response[..n]);
2039 assert!(
2040 response_str.contains("407"),
2041 "expected 407, got: {response_str}"
2042 );
2043
2044 server_jh.await.unwrap();
2045 }
2046
2047 #[tokio::test]
2048 async fn test_http_forward_auth_wrong_credentials() {
2049 let all_protocols: Vec<ProtocolId> =
2050 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2051 let auth = InboundAuthentication::UsernamePassword {
2052 username: "user".to_string(),
2053 password: "pass".to_string(),
2054 };
2055 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2056 let addr = listener.local_addr().unwrap();
2057
2058 let server_jh = tokio::spawn(async move {
2059 let (stream, _) = listener.accept().await.unwrap();
2060 let boxed: BoxStream = Box::new(stream);
2061 let result = accept(boxed, &all_protocols, &auth, None, None, None).await;
2062 assert!(matches!(result, Err(AcceptError::AuthenticationFailed)));
2063 });
2064
2065 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2066 stream
2068 .write_all(
2069 b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\nProxy-Authorization: Basic dXNlcjp3cm9uZw==\r\n\r\n",
2070 )
2071 .await
2072 .unwrap();
2073
2074 let mut response = vec![0u8; 512];
2075 let n = stream.read(&mut response).await.unwrap();
2076 let response_str = String::from_utf8_lossy(&response[..n]);
2077 assert!(
2078 response_str.contains("407"),
2079 "expected 407, got: {response_str}"
2080 );
2081
2082 server_jh.await.unwrap();
2083 }
2084
2085 #[tokio::test]
2086 async fn test_socks5_udp_associate_returns_pending() {
2087 let all_protocols: Vec<ProtocolId> =
2088 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2089 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2090 let addr = listener.local_addr().unwrap();
2091
2092 let server_jh = tokio::spawn(async move {
2093 let (stream, _) = listener.accept().await.unwrap();
2094 let boxed: BoxStream = Box::new(stream);
2095 let session = accept(
2096 boxed,
2097 &all_protocols,
2098 &InboundAuthentication::None,
2099 None,
2100 None,
2101 None,
2102 )
2103 .await
2104 .unwrap();
2105 match session {
2106 AcceptedSession::UdpAssociate(pending) => {
2107 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2108 assert_eq!(
2109 pending.client_hint,
2110 Some(TargetAddr {
2111 host: TargetHost::Ip(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
2112 0, 0, 0, 0
2113 ))),
2114 port: 0,
2115 })
2116 );
2117 }
2118 _ => panic!("expected UdpAssociate"),
2119 }
2120 });
2121
2122 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2123 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
2124 let mut response = [0u8; 2];
2125 stream.read_exact(&mut response).await.unwrap();
2126 assert_eq!(response, [0x05, 0x00]);
2127
2128 stream
2130 .write_all(&[0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0])
2131 .await
2132 .unwrap();
2133 stream.write_all(&0u16.to_be_bytes()).await.unwrap();
2134
2135 server_jh.await.unwrap();
2136 }
2137
2138 #[tokio::test]
2139 async fn test_socks5_bind_rejected() {
2140 let all_protocols: Vec<ProtocolId> =
2141 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2142 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2143 let addr = listener.local_addr().unwrap();
2144
2145 let server_jh = tokio::spawn(async move {
2146 let (stream, _) = listener.accept().await.unwrap();
2147 let boxed: BoxStream = Box::new(stream);
2148 let result = accept(
2149 boxed,
2150 &all_protocols,
2151 &InboundAuthentication::None,
2152 None,
2153 None,
2154 None,
2155 )
2156 .await;
2157 assert!(result.is_err());
2158 });
2159
2160 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2161 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
2162 let mut response = [0u8; 2];
2163 stream.read_exact(&mut response).await.unwrap();
2164 assert_eq!(response, [0x05, 0x00]);
2165
2166 stream
2168 .write_all(&[0x05, 0x02, 0x00, 0x01, 10, 0, 0, 1])
2169 .await
2170 .unwrap();
2171 stream.write_all(&80u16.to_be_bytes()).await.unwrap();
2172
2173 let mut reply = [0u8; 10];
2175 stream.read_exact(&mut reply).await.unwrap();
2176 assert_eq!(reply[0], 0x05);
2177 assert_eq!(reply[1], 0x07); server_jh.await.unwrap();
2180 }
2181
2182 #[tokio::test]
2183 async fn test_socks5_connect_still_works() {
2184 let all_protocols: Vec<ProtocolId> =
2185 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2186 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2187 let addr = listener.local_addr().unwrap();
2188
2189 let server_jh = tokio::spawn(async move {
2190 let (stream, _) = listener.accept().await.unwrap();
2191 let boxed: BoxStream = Box::new(stream);
2192 let session = accept(
2193 boxed,
2194 &all_protocols,
2195 &InboundAuthentication::None,
2196 None,
2197 None,
2198 None,
2199 )
2200 .await
2201 .unwrap();
2202 match session {
2203 AcceptedSession::Tunnel(pending) => {
2204 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2205 assert_eq!(pending.target.port, 443);
2206 }
2207 _ => panic!("expected tunnel"),
2208 }
2209 });
2210
2211 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2212 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
2213 let mut response = [0u8; 2];
2214 stream.read_exact(&mut response).await.unwrap();
2215 assert_eq!(response, [0x05, 0x00]);
2216
2217 stream
2219 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
2220 .await
2221 .unwrap();
2222 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2223
2224 server_jh.await.unwrap();
2225 }
2226
2227 #[tokio::test]
2228 async fn test_socks5_udp_associate_with_auth() {
2229 let all_protocols: Vec<ProtocolId> =
2230 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2231 let auth = InboundAuthentication::UsernamePassword {
2232 username: "user".to_string(),
2233 password: "secret".to_string(),
2234 };
2235 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2236 let addr = listener.local_addr().unwrap();
2237
2238 let server_jh = tokio::spawn(async move {
2239 let (stream, _) = listener.accept().await.unwrap();
2240 let boxed: BoxStream = Box::new(stream);
2241 let session = accept(boxed, &all_protocols, &auth, None, None, None)
2242 .await
2243 .unwrap();
2244 match session {
2245 AcceptedSession::UdpAssociate(pending) => {
2246 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2247 assert_eq!(
2248 pending.identity,
2249 ClientIdentity::Username("user".to_string())
2250 );
2251 }
2252 _ => panic!("expected UdpAssociate"),
2253 }
2254 });
2255
2256 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2257 stream.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
2258 let mut response = [0u8; 2];
2259 stream.read_exact(&mut response).await.unwrap();
2260 assert_eq!(response, [0x05, 0x02]);
2261
2262 stream
2264 .write_all(&[0x01, 0x04, b'u', b's', b'e', b'r', 0x06])
2265 .await
2266 .unwrap();
2267 stream.write_all(b"secret").await.unwrap();
2268 let mut auth_resp = [0u8; 2];
2269 stream.read_exact(&mut auth_resp).await.unwrap();
2270 assert_eq!(auth_resp, [0x01, 0x00]);
2271
2272 stream
2274 .write_all(&[0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0])
2275 .await
2276 .unwrap();
2277 stream.write_all(&0u16.to_be_bytes()).await.unwrap();
2278
2279 server_jh.await.unwrap();
2280 }
2281
2282 #[tokio::test]
2285 async fn test_fragmented_first_byte_http() {
2286 let all_protocols: Vec<ProtocolId> =
2287 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2288 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2289 let addr = listener.local_addr().unwrap();
2290
2291 let server_jh = tokio::spawn(async move {
2292 let (stream, _) = listener.accept().await.unwrap();
2293 let boxed: BoxStream = Box::new(stream);
2294 let session = accept(
2295 boxed,
2296 &all_protocols,
2297 &InboundAuthentication::None,
2298 None,
2299 None,
2300 None,
2301 )
2302 .await
2303 .unwrap();
2304 match session {
2305 AcceptedSession::HttpForward(pending) => {
2306 assert_eq!(pending.request.method, "GET");
2307 }
2308 _ => panic!("expected http forward"),
2309 }
2310 });
2311
2312 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2313 stream.write_all(b"G").await.unwrap();
2315 stream.write_all(b"E").await.unwrap();
2316 stream.write_all(b"T").await.unwrap();
2317 stream
2318 .write_all(b" http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
2319 .await
2320 .unwrap();
2321
2322 server_jh.await.unwrap();
2323 }
2324
2325 #[tokio::test]
2326 async fn test_garbage_bytes_rejected() {
2327 let all_protocols: Vec<ProtocolId> =
2328 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2329 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2330 let addr = listener.local_addr().unwrap();
2331
2332 let server_jh = tokio::spawn(async move {
2333 let (stream, _) = listener.accept().await.unwrap();
2334 let boxed: BoxStream = Box::new(stream);
2335 let result = accept(
2336 boxed,
2337 &all_protocols,
2338 &InboundAuthentication::None,
2339 None,
2340 None,
2341 None,
2342 )
2343 .await;
2344 assert!(result.is_err());
2345 });
2346
2347 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2348 stream.write_all(&[0xAA, 0xBB, 0xCC, 0xDD]).await.unwrap();
2349
2350 server_jh.await.unwrap();
2351 }
2352
2353 #[tokio::test]
2354 async fn test_slow_socks5_detection() {
2355 let all_protocols: Vec<ProtocolId> =
2356 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2357 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2358 let addr = listener.local_addr().unwrap();
2359
2360 let server_jh = tokio::spawn(async move {
2361 let (stream, _) = listener.accept().await.unwrap();
2362 let boxed: BoxStream = Box::new(stream);
2363 let session = accept(
2364 boxed,
2365 &all_protocols,
2366 &InboundAuthentication::None,
2367 None,
2368 None,
2369 None,
2370 )
2371 .await
2372 .unwrap();
2373 match session {
2374 AcceptedSession::Tunnel(pending) => {
2375 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2376 assert_eq!(pending.target.port, 443);
2377 }
2378 _ => panic!("expected tunnel"),
2379 }
2380 });
2381
2382 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2383 stream.write_all(&[0x05]).await.unwrap();
2385 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2386 stream.write_all(&[0x01, 0x00]).await.unwrap();
2388 let mut response = [0u8; 2];
2389 stream.read_exact(&mut response).await.unwrap();
2390 assert_eq!(response, [0x05, 0x00]);
2391
2392 stream
2394 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
2395 .await
2396 .unwrap();
2397 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2398
2399 server_jh.await.unwrap();
2400 }
2401
2402 #[tokio::test]
2403 async fn test_http_connect_and_socks5_same_listener() {
2404 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http, ProtocolId::Socks5];
2405 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2406 let addr = listener.local_addr().unwrap();
2407
2408 let client_jh1 = tokio::spawn(async move {
2410 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2411 stream
2412 .write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
2413 .await
2414 .unwrap();
2415 });
2416
2417 let (stream1, _) = listener.accept().await.unwrap();
2418 let p = protocols.clone();
2419 let server_jh1 = tokio::spawn(async move {
2420 let boxed: BoxStream = Box::new(stream1);
2421 let session = accept(boxed, &p, &InboundAuthentication::None, None, None, None)
2422 .await
2423 .unwrap();
2424 match session {
2425 AcceptedSession::Tunnel(pending) => {
2426 assert_eq!(pending.protocol, TunnelProtocol::HttpConnect);
2427 }
2428 _ => panic!("expected tunnel"),
2429 }
2430 });
2431
2432 client_jh1.await.unwrap();
2433 server_jh1.await.unwrap();
2434
2435 let client_jh2 = tokio::spawn(async move {
2437 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2438 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
2439 let mut response = [0u8; 2];
2440 stream.read_exact(&mut response).await.unwrap();
2441 assert_eq!(response, [0x05, 0x00]);
2442
2443 stream
2444 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
2445 .await
2446 .unwrap();
2447 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2448 });
2449
2450 let (stream2, _) = listener.accept().await.unwrap();
2451 let server_jh2 = tokio::spawn(async move {
2452 let boxed: BoxStream = Box::new(stream2);
2453 let session = accept(
2454 boxed,
2455 &protocols,
2456 &InboundAuthentication::None,
2457 None,
2458 None,
2459 None,
2460 )
2461 .await
2462 .unwrap();
2463 match session {
2464 AcceptedSession::Tunnel(pending) => {
2465 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2466 assert_eq!(pending.target.port, 443);
2467 }
2468 _ => panic!("expected tunnel"),
2469 }
2470 });
2471
2472 client_jh2.await.unwrap();
2473 server_jh2.await.unwrap();
2474 }
2475
2476 #[tokio::test]
2477 async fn test_http_forward_and_socks4_same_listener() {
2478 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http, ProtocolId::Socks4];
2479 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2480 let addr = listener.local_addr().unwrap();
2481
2482 let client_jh1 = tokio::spawn(async move {
2484 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2485 stream
2486 .write_all(b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
2487 .await
2488 .unwrap();
2489 });
2490
2491 let (stream1, _) = listener.accept().await.unwrap();
2492 let p = protocols.clone();
2493 let server_jh1 = tokio::spawn(async move {
2494 let boxed: BoxStream = Box::new(stream1);
2495 let session = accept(boxed, &p, &InboundAuthentication::None, None, None, None)
2496 .await
2497 .unwrap();
2498 match session {
2499 AcceptedSession::HttpForward(pending) => {
2500 assert_eq!(pending.request.method, "GET");
2501 assert_eq!(pending.target.port, 80);
2502 }
2503 _ => panic!("expected http forward"),
2504 }
2505 });
2506
2507 client_jh1.await.unwrap();
2508 server_jh1.await.unwrap();
2509
2510 let client_jh2 = tokio::spawn(async move {
2512 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2513 stream.write_all(&[0x04, 0x01]).await.unwrap();
2515 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2516 stream.write_all(&[10, 0, 0, 1]).await.unwrap();
2517 stream.write_all(&[0x00]).await.unwrap();
2518 });
2519
2520 let (stream2, _) = listener.accept().await.unwrap();
2521 let server_jh2 = tokio::spawn(async move {
2522 let boxed: BoxStream = Box::new(stream2);
2523 let session = accept(
2524 boxed,
2525 &protocols,
2526 &InboundAuthentication::None,
2527 None,
2528 None,
2529 None,
2530 )
2531 .await
2532 .unwrap();
2533 match session {
2534 AcceptedSession::Tunnel(pending) => {
2535 assert_eq!(pending.protocol, TunnelProtocol::Socks4);
2536 assert_eq!(pending.target.port, 443);
2537 }
2538 _ => panic!("expected tunnel"),
2539 }
2540 });
2541
2542 client_jh2.await.unwrap();
2543 server_jh2.await.unwrap();
2544 }
2545
2546 #[tokio::test]
2547 async fn test_fragmented_socks5_handshake() {
2548 let all_protocols: Vec<ProtocolId> =
2549 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2550 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2551 let addr = listener.local_addr().unwrap();
2552
2553 let server_jh = tokio::spawn(async move {
2554 let (stream, _) = listener.accept().await.unwrap();
2555 let boxed: BoxStream = Box::new(stream);
2556 let session = accept(
2557 boxed,
2558 &all_protocols,
2559 &InboundAuthentication::None,
2560 None,
2561 None,
2562 None,
2563 )
2564 .await
2565 .unwrap();
2566 match session {
2567 AcceptedSession::Tunnel(pending) => {
2568 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2569 assert_eq!(pending.target.port, 443);
2570 }
2571 _ => panic!("expected tunnel"),
2572 }
2573 });
2574
2575 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2576 stream.write_all(&[0x05]).await.unwrap();
2578 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2579 stream.write_all(&[0x01, 0x00]).await.unwrap();
2580
2581 let mut response = [0u8; 2];
2582 stream.read_exact(&mut response).await.unwrap();
2583 assert_eq!(response, [0x05, 0x00]);
2584
2585 stream
2587 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
2588 .await
2589 .unwrap();
2590 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2591
2592 server_jh.await.unwrap();
2593 }
2594
2595 #[tokio::test]
2596 async fn test_malformed_http_request_rejected() {
2597 let all_protocols: Vec<ProtocolId> =
2598 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2599 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2600 let addr = listener.local_addr().unwrap();
2601
2602 let server_jh = tokio::spawn(async move {
2603 let (stream, _) = listener.accept().await.unwrap();
2604 let boxed: BoxStream = Box::new(stream);
2605 let result = accept(
2606 boxed,
2607 &all_protocols,
2608 &InboundAuthentication::None,
2609 None,
2610 None,
2611 None,
2612 )
2613 .await;
2614 assert!(result.is_err());
2615 });
2616
2617 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2618 stream
2620 .write_all(b"GET http://example.com HTTP/1.1\r\n")
2621 .await
2622 .unwrap();
2623 stream.shutdown().await.unwrap();
2625
2626 server_jh.await.unwrap();
2627 }
2628
2629 #[tokio::test]
2630 async fn test_empty_connection_closed() {
2631 let all_protocols: Vec<ProtocolId> =
2632 vec![ProtocolId::Http, ProtocolId::Socks4, ProtocolId::Socks5];
2633 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2634 let addr = listener.local_addr().unwrap();
2635
2636 let server_jh = tokio::spawn(async move {
2637 let (stream, _) = listener.accept().await.unwrap();
2638 let boxed: BoxStream = Box::new(stream);
2639 let result = accept(
2640 boxed,
2641 &all_protocols,
2642 &InboundAuthentication::None,
2643 None,
2644 None,
2645 None,
2646 )
2647 .await;
2648 assert!(result.is_err());
2649 });
2650
2651 let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2652 drop(stream);
2654
2655 server_jh.await.unwrap();
2656 }
2657
2658 #[tokio::test]
2662 async fn test_mixed_protocols_with_auth_detection() {
2663 let protocols: Vec<ProtocolId> = vec![ProtocolId::Http, ProtocolId::Socks5];
2664 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
2665 let addr = listener.local_addr().unwrap();
2666
2667 let client_jh1 = tokio::spawn(async move {
2670 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2671 stream
2672 .write_all(b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
2673 .await
2674 .unwrap();
2675 });
2676
2677 let (stream1, _) = listener.accept().await.unwrap();
2678 let p = protocols.clone();
2679 let server_jh1 = tokio::spawn(async move {
2680 let boxed: BoxStream = Box::new(stream1);
2681 let session = accept(boxed, &p, &InboundAuthentication::None, None, None, None)
2682 .await
2683 .unwrap();
2684 match session {
2685 AcceptedSession::HttpForward(pending) => {
2686 assert_eq!(pending.request.method, "GET");
2687 }
2688 _ => panic!("expected http forward"),
2689 }
2690 });
2691
2692 client_jh1.await.unwrap();
2693 server_jh1.await.unwrap();
2694
2695 let client_jh2 = tokio::spawn(async move {
2697 let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2698 stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
2699 let mut response = [0u8; 2];
2700 stream.read_exact(&mut response).await.unwrap();
2701 assert_eq!(response, [0x05, 0x00]);
2702 stream
2703 .write_all(&[0x05, 0x01, 0x00, 0x01, 10, 0, 0, 1])
2704 .await
2705 .unwrap();
2706 stream.write_all(&443u16.to_be_bytes()).await.unwrap();
2707 });
2708
2709 let (stream2, _) = listener.accept().await.unwrap();
2710 let server_jh2 = tokio::spawn(async move {
2711 let boxed: BoxStream = Box::new(stream2);
2712 let session = accept(
2713 boxed,
2714 &protocols,
2715 &InboundAuthentication::None,
2716 None,
2717 None,
2718 None,
2719 )
2720 .await
2721 .unwrap();
2722 match session {
2723 AcceptedSession::Tunnel(pending) => {
2724 assert_eq!(pending.protocol, TunnelProtocol::Socks5);
2725 assert_eq!(pending.target.port, 443);
2726 }
2727 _ => panic!("expected tunnel"),
2728 }
2729 });
2730
2731 client_jh2.await.unwrap();
2732 server_jh2.await.unwrap();
2733 }
2734
2735 #[test]
2736 fn auth_reuse_is_ip_scoped_and_bounded() {
2737 let cache = AuthReuseCache::new(Duration::from_secs(60));
2738 let first: IpAddr = "127.0.0.1".parse().unwrap();
2739 let second: IpAddr = "127.0.0.2".parse().unwrap();
2740 cache.record(first, ClientIdentity::Username("alice".to_string()));
2741 assert_eq!(
2742 cache.lookup(first),
2743 Some(ClientIdentity::Username("alice".to_string()))
2744 );
2745 assert_eq!(cache.lookup(second), None);
2746 assert_eq!(cache.len(), 1);
2747 }
2748
2749 #[test]
2750 fn zero_timeout_expires_after_authentication() {
2751 let cache = AuthReuseCache::new(Duration::ZERO);
2752 let peer: IpAddr = "127.0.0.1".parse().unwrap();
2753 cache.record(peer, ClientIdentity::Username("alice".to_string()));
2754 while cache.lookup(peer).is_some() {
2755 std::hint::spin_loop();
2756 }
2757 assert_eq!(cache.len(), 0);
2758 }
2759}