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