Skip to main content

eggress_server/
accept.rs

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
13/// Authentication policy for inbound connections.
14/// Bounded compatibility authentication state keyed by source IP.
15///
16/// This intentionally lives in the server crate but is only constructed by
17/// the pproxy compatibility runtime. Native Eggress listeners continue to
18/// authenticate every connection independently.
19pub 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/// Error type for accept operations.
158#[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
172/// The result of accepting an inbound connection.
173pub enum AcceptedSession {
174    Tunnel(PendingTunnel),
175    HttpForward(PendingHttpForward),
176    UdpAssociate(PendingUdpAssociate),
177    Echo(BoxStream),
178}
179
180/// A pending tunnel connection (HTTP CONNECT, SOCKS4, SOCKS5).
181/// Success reply has NOT been sent yet.
182pub 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
190/// A pending HTTP forward-proxy request.
191pub struct PendingHttpForward {
192    pub target: TargetAddr,
193    pub client: BoxStream,
194    pub request: eggress_protocol_http::forward::ForwardRequest,
195    pub identity: ClientIdentity,
196}
197
198/// A pending SOCKS5 UDP ASSOCIATE session.
199pub struct PendingUdpAssociate {
200    pub client: BoxStream,
201    pub protocol: TunnelProtocol,
202    pub identity: ClientIdentity,
203    pub client_hint: Option<TargetAddr>,
204}
205
206/// Which tunnel protocol was used.
207#[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
221/// Information needed to send a protocol-specific reply later.
222pub enum ReplyContext {
223    Http,
224    Http2,
225    Http3,
226    WebSocket,
227    Socks4,
228    Socks5,
229    Shadowsocks,
230    Trojan,
231    Raw,
232}
233
234/// Configuration for Shadowsocks inbound listener.
235#[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/// Configuration for Trojan inbound listener.
246#[derive(Clone)]
247pub struct InboundTrojanConfig {
248    pub password: String,
249    /// Optional fallback target for auth-failed connections.
250    /// When set, connections with invalid Trojan passwords are relayed to this
251    /// target instead of being rejected (matches pproxy's chaining behavior).
252    pub fallback: Option<String>,
253}
254
255/// A stream that returns `prefix` bytes first, then delegates to `inner`.
256struct 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
307/// Read the first byte from the stream, detect the protocol, perform the
308/// handshake parsing, and return an `AcceptedSession` **without** opening
309/// any outbound connection or sending any success/failure reply.
310pub 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    // Check SOCKS5
410    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    // Check SOCKS4
420    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    // Try HTTP detection if HTTP is allowed
430    if has_http {
431        // Read more bytes to detect the HTTP method
432        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                // Read more bytes
451                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    // Check if Shadowsocks is the only protocol (auto-detection not possible)
487    #[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    // Check if Trojan is the only protocol (TLS termination already happened upstream)
585    #[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            // Read the 56-byte hash prefix to check password before consuming
591            // the rest of the handshake. This enables fallback routing on auth
592            // failure without consuming bytes needed by the fallback target.
593            let mut hash_prefix = [0u8; 56];
594            // The protocol detector already consumed the first hash byte.
595            // Preserve it so password verification and the full Trojan parser
596            // see the original 56-byte hash.
597            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                // Replay the 56-byte hash so trojan_accept reads the full handshake
608                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            // Password did not match — check for fallback routing
625            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    // Look for a space in the prefix to find the end of the method token
667    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        // Check if all bytes are valid HTTP method characters:
673        // uppercase ASCII letters, lowercase ASCII letters, or hyphens
674        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        // No space found yet - check if what we have so far looks like a valid method prefix
684        if prefix.len() > 16 {
685            return DetectResult::NoMatch;
686        }
687        // Check if all bytes so far are valid method characters
688        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    // Determine method selection based on auth policy
715    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    // Send method selection
728    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    // Handle auth if required
745    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    // Read the request line to determine method
869    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    // Reconstruct stream with the request line bytes prepended
903    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        // Read the complete head to extract Proxy-Authorization before forward_request strips it
916        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        // Parse Proxy-Authorization from the raw head
959        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                        // Reconstruct stream and send 407
980                        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; // Auth already validated above
1003
1004        // Reconstruct stream for forward_request
1005        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    // Parse Proxy-Authorization header
1097    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    // Validate auth if required. A compatibility cache hit is sufficient and
1114    // intentionally ignores credentials on the new connection, matching the
1115    // pproxy AuthTable behavior.
1116    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
1234/// Maximum size for the HTTP request head (request line + headers).
1235const MAX_HEAD_SIZE: usize = 32 * 1024;
1236
1237/// Maximum number of header lines.
1238const MAX_HEADER_LINES: usize = 128;
1239
1240/// Parse a header line into (name, value).
1241fn 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
1248/// Simple base64 decoder.
1249fn 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
1272/// Parse Basic authentication from a Proxy-Authorization header value.
1273fn 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
1288/// Write a 407 Proxy Authentication Required response.
1289async 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        // Send random binary prefix that isn't 0x04 or 0x05 and not valid HTTP
1656        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        // TLS ClientHello starts with 0x16, 0x03, which isn't valid HTTP method
1684        stream
1685            .write_all(&[0x16, 0x03, 0x01, 0x00, 0x05])
1686            .await
1687            .unwrap();
1688
1689        server_jh.await.unwrap();
1690    }
1691
1692    // === Authentication tests ===
1693
1694    #[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        // Client offers both no-auth and username/password
1722        stream.write_all(&[0x05, 0x02, 0x00, 0x02]).await.unwrap();
1723        // Server selects username/password (0x02)
1724        let mut response = [0u8; 2];
1725        stream.read_exact(&mut response).await.unwrap();
1726        assert_eq!(response, [0x05, 0x02]);
1727
1728        // Send auth: version=1, ulen=4, "user", plen=6, "secret"
1729        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        // Read auth response (success)
1735        let mut auth_resp = [0u8; 2];
1736        stream.read_exact(&mut auth_resp).await.unwrap();
1737        assert_eq!(auth_resp, [0x01, 0x00]);
1738
1739        // Send CONNECT request
1740        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        // Send auth with wrong password
1774        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        // Read auth response (failure)
1780        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        // Client only offers no-auth
1807        stream.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
1808        // Server should send 0xFF (no acceptable methods)
1809        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        // "user:pass" base64 encoded is "dXNlcjpwYXNz"
1844        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        // Read 407 response
1879        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        // "user:wrong" base64 encoded is "dXNlcjp3cm9uZw=="
1914        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                    // Proxy-Authorization should be stripped
1991                    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        // "user:wrong" base64 encoded is "dXNlcjp3cm9uZw=="
2067        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        // UDP ASSOCIATE (cmd=0x03), target 0.0.0.0:0
2129        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        // BIND (cmd=0x02)
2167        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        // Server sends rejection reply (RFC 1928 0x07 command not supported)
2174        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); // command not supported
2178
2179        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        // CONNECT request
2218        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        // Auth
2263        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        // UDP ASSOCIATE
2273        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    // === Mixed-protocol listener robustness tests ===
2283
2284    #[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        // Send HTTP GET fragmented into individual bytes
2314        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        // Send first byte (version) then delay
2384        stream.write_all(&[0x05]).await.unwrap();
2385        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2386        // Send rest of method negotiation
2387        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        // Send CONNECT request
2393        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        // First connection: HTTP CONNECT
2409        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        // Second connection: SOCKS5
2436        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        // First connection: HTTP forward
2483        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        // Second connection: SOCKS4
2511        let client_jh2 = tokio::spawn(async move {
2512            let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
2513            // SOCKS4 CONNECT: version=0x04, cmd=0x01, port=443, addr=0.0.0.1, userid=0
2514            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        // Send version byte separately from method negotiation
2577        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        // Now send CONNECT request, also fragmented
2586        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        // Send a partial HTTP request that never completes headers
2619        stream
2620            .write_all(b"GET http://example.com HTTP/1.1\r\n")
2621            .await
2622            .unwrap();
2623        // Never send the final \r\n to end headers, then close the connection
2624        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        // Close immediately without sending anything
2653        drop(stream);
2654
2655        server_jh.await.unwrap();
2656    }
2657
2658    /// Mixed-protocol listener with auth: HTTP with auth and SOCKS5 with auth
2659    /// on the same listener. Both connections should be detected correctly
2660    /// when correct credentials are provided.
2661    #[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        // First connection: HTTP forward (non-CONNECT) without auth —
2668        // protocol detection still works, auth is checked in serve_connection.
2669        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        // Second connection: SOCKS5 without auth — detected correctly.
2696        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}