Skip to main content

eggress_core/
chain.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use eggress_uri::{EndpointSpec, ProtocolSpec, ProxyHopSpec};
5
6use crate::connector::{ConnectOptions, DirectConnector};
7use crate::{BoxStream, ConnectError, TargetAddr, TargetHost};
8
9/// A boxed future that resolves to a handshake result.
10type HandshakeFuture<'a> = Pin<
11    Box<
12        dyn Future<Output = Result<BoxStream, Box<dyn std::error::Error + Send + Sync>>>
13            + Send
14            + 'a,
15    >,
16>;
17
18/// Errors that can occur during chain execution.
19#[derive(Debug, thiserror::Error)]
20pub enum ChainError {
21    #[error("hop {hop_index}: connection to {endpoint} failed: {source}")]
22    ConnectFailed {
23        hop_index: usize,
24        endpoint: String,
25        source: ConnectError,
26    },
27
28    #[error("hop {hop_index}: {protocol} handshake failed: {source}")]
29    HandshakeFailed {
30        hop_index: usize,
31        protocol: String,
32        source: Box<dyn std::error::Error + Send + Sync>,
33    },
34
35    #[error("chain is empty, at least one hop is required")]
36    EmptyChain,
37
38    #[error("invalid chain: {reason}")]
39    InvalidChain { reason: String },
40}
41
42/// Error type for protocol handshake operations.
43#[derive(Debug, thiserror::Error)]
44pub enum HandshakeError {
45    #[error("IO error: {0}")]
46    Io(#[from] std::io::Error),
47
48    #[error("protocol error: {0}")]
49    Protocol(String),
50
51    #[error("connection refused")]
52    ConnectionRefused,
53
54    #[error("authentication failed")]
55    AuthFailed,
56
57    #[error("{0}")]
58    Other(String),
59}
60
61/// Trait for performing protocol-specific handshakes through proxy hops.
62///
63/// Each protocol (HTTP CONNECT, SOCKS4, SOCKS5) provides an implementation
64/// of this trait to handle its specific handshake logic.
65///
66/// # Dyn Compatibility
67///
68/// This trait is dyn-compatible and can be used with `Box<dyn HopHandler>`.
69pub trait HopHandler: Send + Sync {
70    /// Returns the protocol this handler supports.
71    fn protocol(&self) -> ProtocolSpec;
72
73    /// Establish the transport for a hop whose protocol is not carried over
74    /// a TCP socket (for example QUIC). TCP-backed handlers use the default.
75    fn open<'a>(
76        &'a self,
77        _endpoint: &'a EndpointSpec,
78        _hop: &'a ProxyHopSpec,
79        _target: &'a TargetAddr,
80    ) -> Option<HandshakeFuture<'a>> {
81        None
82    }
83
84    /// Perform the protocol handshake over the given stream.
85    ///
86    /// The handler should:
87    /// 1. Perform the protocol-specific handshake (e.g., HTTP CONNECT, SOCKS5 greeting)
88    /// 2. Request connection to the specified target
89    /// 3. Return the upgraded stream on success
90    ///
91    /// `hop_index` is the 0-based position of this hop in the chain. Handlers
92    /// that use connection pooling (e.g., H2) must include this value in pool
93    /// keys to prevent cross-chain connection reuse.
94    fn handshake<'a>(
95        &'a self,
96        stream: BoxStream,
97        target: &'a TargetAddr,
98        hop: &'a ProxyHopSpec,
99        hop_index: usize,
100    ) -> HandshakeFuture<'a>;
101}
102
103/// A function that wraps a `BoxStream` in TLS for upstream connections.
104///
105/// Returns the TLS-wrapped stream, or an error if the handshake fails.
106/// The optional `alpn` parameter sets Application-Layer Protocol Negotiation
107/// protocols (e.g. `["h2", "http/1.1"]` for H2 connections). The `insecure`
108/// flag corresponds to `ProxyHopSpec::insecure` (`?insecure` in chain URIs)
109/// and, when true, selects a verifier that accepts any certificate.
110pub type TlsWrapper = Box<
111    dyn Fn(
112            BoxStream,
113            String,
114            Option<Vec<Vec<u8>>>,
115            bool,
116        ) -> std::pin::Pin<
117            Box<
118                dyn std::future::Future<
119                        Output = Result<BoxStream, Box<dyn std::error::Error + Send + Sync>>,
120                    > + Send,
121            >,
122        > + Send
123        + Sync,
124>;
125
126/// Executor for proxy chains.
127///
128/// Establishes a connection through a series of proxy hops, performing
129/// the appropriate protocol handshake at each step.
130///
131/// # Chain Execution Flow
132///
133/// ```text
134/// Transport to hop 1
135/// → TLS wrap (if hop.tls)
136/// → protocol handshake requesting hop 2
137/// → TLS wrap (if hop.tls)
138/// → protocol handshake requesting hop 3
139/// → final protocol handshake requesting destination
140/// ```
141pub struct ChainExecutor {
142    direct_connector: DirectConnector,
143    handlers: Vec<Box<dyn HopHandler>>,
144    tls_wrapper: Option<TlsWrapper>,
145    shared_tls_config: Option<std::sync::Arc<rustls::ClientConfig>>,
146    insecure_shared_tls_config: Option<std::sync::Arc<rustls::ClientConfig>>,
147}
148
149impl ChainExecutor {
150    /// Creates a new `ChainExecutor` with the given protocol handlers.
151    pub fn new(handlers: Vec<Box<dyn HopHandler>>) -> Self {
152        Self {
153            direct_connector: DirectConnector,
154            handlers,
155            tls_wrapper: None,
156            shared_tls_config: None,
157            insecure_shared_tls_config: None,
158        }
159    }
160
161    /// Set a TLS wrapper for upstream hops with `tls: true`.
162    pub fn with_tls_wrapper(mut self, wrapper: TlsWrapper) -> Self {
163        self.tls_wrapper = Some(wrapper);
164        self
165    }
166
167    /// Set a shared TLS client config for protocols that need their own TLS
168    /// handshake (e.g., Trojan).
169    pub fn with_shared_tls_config(
170        mut self,
171        config: Option<std::sync::Arc<rustls::ClientConfig>>,
172    ) -> Self {
173        self.shared_tls_config = config;
174        self
175    }
176
177    /// Set an insecure shared TLS client config used when a hop has
178    /// `insecure = true` (`?insecure` in the URI).
179    pub fn with_insecure_shared_tls_config(
180        mut self,
181        config: Option<std::sync::Arc<rustls::ClientConfig>>,
182    ) -> Self {
183        self.insecure_shared_tls_config = config;
184        self
185    }
186
187    /// Get the shared TLS client config, if set.
188    pub fn shared_tls_config(&self) -> Option<&std::sync::Arc<rustls::ClientConfig>> {
189        self.shared_tls_config.as_ref()
190    }
191
192    /// Get the insecure shared TLS config (if any).
193    pub fn insecure_shared_tls_config(&self) -> Option<&std::sync::Arc<rustls::ClientConfig>> {
194        self.insecure_shared_tls_config.as_ref()
195    }
196
197    /// Execute a proxy chain to connect to the target.
198    ///
199    /// # Arguments
200    /// * `chain` - The ordered list of proxy hops
201    /// * `target` - The final destination to connect to
202    ///
203    /// # Returns
204    /// A connected stream ready for data transfer, or a `ChainError`.
205    pub async fn execute(
206        &self,
207        chain: &[ProxyHopSpec],
208        target: &TargetAddr,
209    ) -> Result<BoxStream, ChainError> {
210        if chain.is_empty() {
211            return Err(ChainError::EmptyChain);
212        }
213
214        self.validate_chain(chain)?;
215
216        // Pre-flight: verify handlers exist for all application protocols before connecting.
217        for (i, hop) in chain.iter().enumerate() {
218            let application_protocols = application_protocols(&hop.protocols);
219            if !application_protocols.is_empty() {
220                find_handler(&self.handlers, &application_protocols).map_err(|_| {
221                    ChainError::InvalidChain {
222                        reason: format!(
223                            "hop {i}: no handler for protocols: [{}]",
224                            application_protocols
225                                .iter()
226                                .map(|p| format!("{p:?}"))
227                                .collect::<Vec<_>>()
228                                .join(", ")
229                        ),
230                    }
231                })?;
232            }
233        }
234
235        // Step 1: Connect to the first hop's endpoint
236        let first_hop = &chain[0];
237        let first_target = if chain.len() > 1 {
238            endpoint_to_target_addr(&chain[1].endpoint)?
239        } else {
240            target.clone()
241        };
242        let first_hop_addr = endpoint_to_target_addr(&first_hop.endpoint)?;
243
244        let mut current_stream: BoxStream = if first_hop.protocols.contains(&ProtocolSpec::Http3)
245            || first_hop.protocols.contains(&ProtocolSpec::Quic)
246        {
247            let transport_protocol = if first_hop.protocols.contains(&ProtocolSpec::Http3) {
248                ProtocolSpec::Http3
249            } else {
250                ProtocolSpec::Quic
251            };
252            let handler = find_handler(&self.handlers, &[transport_protocol])?;
253            handler
254                .open(&first_hop.endpoint, first_hop, &first_target)
255                .ok_or_else(|| ChainError::InvalidChain {
256                    reason: format!("hop 0: transport {transport_protocol:?} cannot be opened"),
257                })?
258                .await
259                .map_err(|e| ChainError::HandshakeFailed {
260                    hop_index: 0,
261                    protocol: format!("{transport_protocol:?}"),
262                    source: e,
263                })?
264        } else if first_hop.protocols.contains(&ProtocolSpec::Unix) {
265            #[cfg(unix)]
266            {
267                Box::new(
268                    tokio::net::UnixStream::connect(&first_hop.endpoint.host)
269                        .await
270                        .map_err(|e| ChainError::ConnectFailed {
271                            hop_index: 0,
272                            endpoint: first_hop.endpoint.host.clone(),
273                            source: crate::ConnectError::Io(e),
274                        })?,
275                ) as BoxStream
276            }
277            #[cfg(not(unix))]
278            {
279                return Err(ChainError::InvalidChain {
280                    reason: "unix upstreams are unsupported on this platform".to_string(),
281                });
282            }
283        } else {
284            let local_bind = first_hop
285                .local_bind
286                .as_deref()
287                .map(|value| {
288                    value.parse().map_err(|e| ChainError::InvalidChain {
289                        reason: format!("hop 0: invalid local bind '{}': {}", value, e),
290                    })
291                })
292                .transpose()?;
293            self.direct_connector
294                .connect_with_options(
295                    &first_hop_addr,
296                    &ConnectOptions {
297                        local_bind,
298                        ..Default::default()
299                    },
300                )
301                .await
302                .map_err(|e| ChainError::ConnectFailed {
303                    hop_index: 0,
304                    endpoint: first_hop_addr.to_string(),
305                    source: e,
306                })?
307        };
308
309        // Step 2: For each hop, perform the protocol handshake
310        for (i, hop) in chain.iter().enumerate() {
311            // Apply TLS wrapping if configured for this hop
312            if hop.tls {
313                let wrapper =
314                    self.tls_wrapper
315                        .as_ref()
316                        .ok_or_else(|| ChainError::InvalidChain {
317                            reason: format!("hop {i}: tls=true but no tls_wrapper configured"),
318                        })?;
319                let server_name = hop
320                    .server_name
321                    .clone()
322                    .unwrap_or_else(|| hop.endpoint.host.clone());
323                // Set H2 ALPN if this hop uses the HTTP/2 protocol
324                let alpn = if hop.protocols.contains(&ProtocolSpec::Http2) {
325                    Some(vec![b"h2".to_vec(), b"http/1.1".to_vec()])
326                } else {
327                    None
328                };
329                let insecure = hop.insecure;
330                if insecure && self.insecure_shared_tls_config.is_none() {
331                    // No insecure verifier available in this build; fail
332                    // explicitly rather than silently verifying. When the
333                    // `insecure-tls` feature is enabled the wrapper will
334                    // build an insecure config lazily, so only hard-fail
335                    // on builds without the feature.
336                    #[cfg(not(feature = "insecure-tls"))]
337                    return Err(ChainError::InvalidChain {
338                        reason: format!("hop {i}: insecure=true requires the insecure-tls feature"),
339                    });
340                }
341                current_stream = wrapper(current_stream, server_name, alpn, insecure)
342                    .await
343                    .map_err(|e| ChainError::HandshakeFailed {
344                        hop_index: i,
345                        protocol: "tls".to_string(),
346                        source: e,
347                    })?;
348            } else if hop.insecure {
349                return Err(ChainError::InvalidChain {
350                    reason: format!("hop {i}: insecure=true requires tls=true"),
351                });
352            }
353
354            // Determine the target for this hop's handshake
355            let next_target = if i + 1 < chain.len() {
356                // Target is the next hop's endpoint
357                endpoint_to_target_addr(&chain[i + 1].endpoint)?
358            } else {
359                // Last hop targets the actual destination
360                target.clone()
361            };
362
363            // QUIC/H3 opening already performs the application handshake. For
364            // raw QUIC, continue with the ordinary proxy application handler
365            // (e.g. quic+http) over the newly opened stream.
366            let application_protocols = application_protocols(&hop.protocols);
367            if application_protocols.is_empty() {
368                continue;
369            }
370            let handler = find_handler(&self.handlers, &application_protocols)?;
371
372            current_stream = handler
373                .handshake(current_stream, &next_target, hop, i)
374                .await
375                .map_err(|e| ChainError::HandshakeFailed {
376                    hop_index: i,
377                    protocol: format_protocols(&hop.protocols),
378                    source: e,
379                })?;
380        }
381
382        Ok(current_stream)
383    }
384
385    /// Validate the chain configuration.
386    fn validate_chain(&self, chain: &[ProxyHopSpec]) -> Result<(), ChainError> {
387        for (i, hop) in chain.iter().enumerate() {
388            if hop.protocols.is_empty() {
389                return Err(ChainError::InvalidChain {
390                    reason: format!("hop {i}: no protocols specified"),
391                });
392            }
393            if hop.endpoint.host.is_empty() {
394                return Err(ChainError::InvalidChain {
395                    reason: format!("hop {i}: empty endpoint host"),
396                });
397            }
398            if hop.endpoint.port == 0 && !hop.protocols.contains(&ProtocolSpec::Unix) {
399                return Err(ChainError::InvalidChain {
400                    reason: format!("hop {i}: port cannot be 0"),
401                });
402            }
403            if hop.insecure && !hop.tls {
404                return Err(ChainError::InvalidChain {
405                    reason: format!("hop {i}: insecure=true requires tls=true"),
406                });
407            }
408        }
409        Ok(())
410    }
411}
412
413/// Convert an `EndpointSpec` to a `TargetAddr`.
414fn endpoint_to_target_addr(endpoint: &EndpointSpec) -> Result<TargetAddr, ChainError> {
415    let host = if let Ok(ip) = endpoint.host.parse::<std::net::IpAddr>() {
416        TargetHost::Ip(ip)
417    } else {
418        TargetHost::Domain(endpoint.host.clone())
419    };
420    Ok(TargetAddr {
421        host,
422        port: endpoint.port,
423    })
424}
425
426/// Find a handler that supports one of the given protocols.
427fn find_handler<'a>(
428    handlers: &'a [Box<dyn HopHandler>],
429    protocols: &[ProtocolSpec],
430) -> Result<&'a dyn HopHandler, ChainError> {
431    for handler in handlers {
432        if protocols.contains(&handler.protocol()) {
433            return Ok(handler.as_ref());
434        }
435    }
436    Err(ChainError::InvalidChain {
437        reason: format!(
438            "no handler for protocols: [{}]",
439            protocols
440                .iter()
441                .map(|p| format!("{p:?}"))
442                .collect::<Vec<_>>()
443                .join(", ")
444        ),
445    })
446}
447
448fn application_protocols(protocols: &[ProtocolSpec]) -> Vec<ProtocolSpec> {
449    protocols
450        .iter()
451        .copied()
452        .filter(|protocol| !matches!(protocol, ProtocolSpec::Http3 | ProtocolSpec::Quic))
453        .collect()
454}
455
456/// Format a list of protocols as a string.
457fn format_protocols(protocols: &[ProtocolSpec]) -> String {
458    protocols
459        .iter()
460        .map(|p| format!("{p:?}"))
461        .collect::<Vec<_>>()
462        .join("+")
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468    use crate::TargetHost;
469    use eggress_uri::CredentialSpec;
470    use std::sync::Arc;
471
472    /// A mock handler that records the target and returns a successful result.
473    struct MockHandler {
474        protocol: ProtocolSpec,
475        captured_target: std::sync::Arc<std::sync::Mutex<Option<TargetAddr>>>,
476    }
477
478    impl MockHandler {
479        fn new(
480            protocol: ProtocolSpec,
481        ) -> (Self, std::sync::Arc<std::sync::Mutex<Option<TargetAddr>>>) {
482            let captured_target = std::sync::Arc::new(std::sync::Mutex::new(None));
483            let handler = Self {
484                protocol,
485                captured_target: captured_target.clone(),
486            };
487            (handler, captured_target)
488        }
489    }
490
491    impl HopHandler for MockHandler {
492        fn protocol(&self) -> ProtocolSpec {
493            self.protocol
494        }
495
496        fn handshake<'a>(
497            &'a self,
498            stream: BoxStream,
499            target: &'a TargetAddr,
500            _hop: &'a ProxyHopSpec,
501            _hop_index: usize,
502        ) -> HandshakeFuture<'a> {
503            Box::pin(async move {
504                *self.captured_target.lock().unwrap() = Some(target.clone());
505                Ok(stream)
506            })
507        }
508    }
509
510    /// A mock handler that always fails.
511    struct FailingHandler {
512        protocol: ProtocolSpec,
513        error_message: String,
514    }
515
516    impl HopHandler for FailingHandler {
517        fn protocol(&self) -> ProtocolSpec {
518            self.protocol
519        }
520
521        fn handshake<'a>(
522            &'a self,
523            _stream: BoxStream,
524            _target: &'a TargetAddr,
525            _hop: &'a ProxyHopSpec,
526            _hop_index: usize,
527        ) -> HandshakeFuture<'a> {
528            let msg = self.error_message.clone();
529            Box::pin(async move { Err(msg.into()) })
530        }
531    }
532
533    fn make_hop(protocol: ProtocolSpec, host: &str, port: u16) -> ProxyHopSpec {
534        ProxyHopSpec {
535            protocols: vec![protocol],
536            endpoint: EndpointSpec {
537                host: host.to_string(),
538                port,
539            },
540            credentials: None,
541            rule: None,
542            local_bind: None,
543            tls: false,
544            server_name: None,
545            insecure: false,
546            plugins: Vec::new(),
547            auth_prefix: None,
548        }
549    }
550
551    fn make_hop_with_creds(
552        protocol: ProtocolSpec,
553        host: &str,
554        port: u16,
555        username: &str,
556        password: &str,
557    ) -> ProxyHopSpec {
558        ProxyHopSpec {
559            protocols: vec![protocol],
560            endpoint: EndpointSpec {
561                host: host.to_string(),
562                port,
563            },
564            credentials: Some(CredentialSpec {
565                username: username.to_string(),
566                password: password.to_string(),
567            }),
568            rule: None,
569            local_bind: None,
570            tls: false,
571            server_name: None,
572            insecure: false,
573            plugins: Vec::new(),
574            auth_prefix: None,
575        }
576    }
577
578    fn make_target(domain: &str, port: u16) -> TargetAddr {
579        TargetAddr {
580            host: TargetHost::Domain(domain.to_string()),
581            port,
582        }
583    }
584
585    fn make_ip_target(ip: std::net::IpAddr, port: u16) -> TargetAddr {
586        TargetAddr {
587            host: TargetHost::Ip(ip),
588            port,
589        }
590    }
591
592    // ===== Empty/Invalid Chain Tests =====
593
594    #[tokio::test]
595    async fn test_empty_chain() {
596        let executor = ChainExecutor::new(vec![]);
597        let target = make_target("example.com", 80);
598        let result = executor.execute(&[], &target).await;
599        match result {
600            Err(e) => {
601                assert!(matches!(e, ChainError::EmptyChain));
602                assert_eq!(
603                    e.to_string(),
604                    "chain is empty, at least one hop is required"
605                );
606            }
607            Ok(_) => panic!("expected EmptyChain error"),
608        }
609    }
610
611    #[tokio::test]
612    async fn test_hop_no_protocols() {
613        let hop = ProxyHopSpec {
614            protocols: vec![],
615            endpoint: EndpointSpec {
616                host: "127.0.0.1".to_string(),
617                port: 8080,
618            },
619            credentials: None,
620            rule: None,
621            local_bind: None,
622            tls: false,
623            server_name: None,
624            insecure: false,
625            plugins: Vec::new(),
626            auth_prefix: None,
627        };
628        let executor = ChainExecutor::new(vec![]);
629        let target = make_target("example.com", 80);
630        let result = executor.execute(&[hop], &target).await;
631        match result {
632            Err(ChainError::InvalidChain { reason }) => {
633                assert!(reason.contains("no protocols specified"));
634            }
635            _ => panic!("expected InvalidChain error"),
636        }
637    }
638
639    #[tokio::test]
640    async fn test_hop_empty_host() {
641        let hop = make_hop(ProtocolSpec::Http, "", 8080);
642        let executor = ChainExecutor::new(vec![]);
643        let target = make_target("example.com", 80);
644        let result = executor.execute(&[hop], &target).await;
645        match result {
646            Err(ChainError::InvalidChain { reason }) => {
647                assert!(reason.contains("empty endpoint host"));
648            }
649            _ => panic!("expected InvalidChain error"),
650        }
651    }
652
653    #[tokio::test]
654    async fn test_hop_zero_port() {
655        let hop = ProxyHopSpec {
656            protocols: vec![ProtocolSpec::Http],
657            endpoint: EndpointSpec {
658                host: "127.0.0.1".to_string(),
659                port: 0,
660            },
661            credentials: None,
662            rule: None,
663            local_bind: None,
664            tls: false,
665            server_name: None,
666            insecure: false,
667            plugins: Vec::new(),
668            auth_prefix: None,
669        };
670        let executor = ChainExecutor::new(vec![]);
671        let target = make_target("example.com", 80);
672        let result = executor.execute(&[hop], &target).await;
673        match result {
674            Err(ChainError::InvalidChain { reason }) => {
675                assert!(reason.contains("port cannot be 0"));
676            }
677            _ => panic!("expected InvalidChain error"),
678        }
679    }
680
681    // ===== Missing Handler Tests =====
682
683    #[tokio::test]
684    async fn test_no_handler_for_protocol() {
685        let executor = ChainExecutor::new(vec![]);
686        let hop = make_hop(ProtocolSpec::Http, "127.0.0.1", 8080);
687        let target = make_target("example.com", 80);
688        let result = executor.execute(&[hop], &target).await;
689        match result {
690            Err(ChainError::InvalidChain { reason }) => {
691                assert!(reason.contains("no handler for protocols"));
692            }
693            _ => panic!("expected InvalidChain error"),
694        }
695    }
696
697    // ===== Connection Failure Tests =====
698
699    #[tokio::test]
700    async fn test_connect_failed() {
701        let (handler, _) = MockHandler::new(ProtocolSpec::Http);
702        let executor = ChainExecutor::new(vec![Box::new(handler)]);
703        let hop = make_hop(ProtocolSpec::Http, "127.0.0.1", 1);
704        let target = make_target("example.com", 80);
705        let result = executor.execute(&[hop], &target).await;
706        match result {
707            Err(ChainError::ConnectFailed {
708                hop_index, source, ..
709            }) => {
710                assert_eq!(hop_index, 0);
711                assert!(matches!(source, ConnectError::Io(_)));
712            }
713            Err(e) => panic!("expected ConnectFailed, got: {e}"),
714            Ok(_) => panic!("expected error"),
715        }
716    }
717
718    // ===== Handshake Failure Tests =====
719
720    #[tokio::test]
721    async fn test_handshake_failed() {
722        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
723        let addr = listener.local_addr().unwrap();
724
725        let server_jh = tokio::spawn(async move {
726            let (_stream, _) = listener.accept().await.unwrap();
727            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
728        });
729
730        let failing_handler: Box<dyn HopHandler> = Box::new(FailingHandler {
731            protocol: ProtocolSpec::Http,
732            error_message: "handshake timeout".to_string(),
733        });
734
735        let executor = ChainExecutor::new(vec![failing_handler]);
736        let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
737        let target = make_target("example.com", 80);
738        let result = executor.execute(&[hop], &target).await;
739
740        match result {
741            Err(ChainError::HandshakeFailed {
742                hop_index,
743                protocol,
744                source,
745            }) => {
746                assert_eq!(hop_index, 0);
747                assert_eq!(protocol, "Http");
748                assert_eq!(source.to_string(), "handshake timeout");
749            }
750            Err(e) => panic!("expected HandshakeFailed, got: {e}"),
751            Ok(_) => panic!("expected error"),
752        }
753
754        server_jh.abort();
755    }
756
757    // ===== Domain Name Preservation Tests =====
758
759    #[tokio::test]
760    async fn test_domain_preserved_for_single_hop() {
761        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
762        let addr = listener.local_addr().unwrap();
763
764        let server_jh = tokio::spawn(async move {
765            let (_stream, _) = listener.accept().await.unwrap();
766            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
767        });
768
769        let (handler, captured) = MockHandler::new(ProtocolSpec::Socks5);
770        let executor = ChainExecutor::new(vec![Box::new(handler)]);
771
772        let hop = make_hop(ProtocolSpec::Socks5, &addr.ip().to_string(), addr.port());
773        let target = make_target("example.com", 443);
774        let result = executor.execute(&[hop], &target).await;
775
776        assert!(result.is_ok());
777
778        let captured_target = captured.lock().unwrap().take().unwrap();
779        assert_eq!(
780            captured_target.host,
781            TargetHost::Domain("example.com".to_string())
782        );
783        assert_eq!(captured_target.port, 443);
784
785        server_jh.abort();
786    }
787
788    #[tokio::test]
789    async fn test_domain_preserved_through_two_hops() {
790        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
791        let addr1 = listener1.local_addr().unwrap();
792
793        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
794        let addr2 = listener2.local_addr().unwrap();
795
796        let server_jh1 = tokio::spawn(async move {
797            let (_stream, _) = listener1.accept().await.unwrap();
798            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
799        });
800
801        let server_jh2 = tokio::spawn(async move {
802            let (_stream, _) = listener2.accept().await.unwrap();
803            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
804        });
805
806        let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
807        let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
808        let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
809
810        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
811        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
812        let target = make_target("example.com", 443);
813        let result = executor.execute(&[hop1, hop2], &target).await;
814
815        assert!(result.is_ok());
816
817        let target1 = captured1.lock().unwrap().take().unwrap();
818        assert_eq!(target1, make_ip_target(addr2.ip(), addr2.port()));
819
820        let target2 = captured2.lock().unwrap().take().unwrap();
821        assert_eq!(target2.host, TargetHost::Domain("example.com".to_string()));
822        assert_eq!(target2.port, 443);
823
824        server_jh1.abort();
825        server_jh2.abort();
826    }
827
828    // ===== Multi-hop Chain Tests =====
829
830    #[tokio::test]
831    async fn test_single_hop_chain() {
832        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
833        let addr = listener.local_addr().unwrap();
834
835        let server_jh = tokio::spawn(async move {
836            let (_stream, _) = listener.accept().await.unwrap();
837            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
838        });
839
840        let (handler, captured) = MockHandler::new(ProtocolSpec::Http);
841        let executor = ChainExecutor::new(vec![Box::new(handler)]);
842
843        let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
844        let target = make_target("destination.example.com", 443);
845        let result = executor.execute(&[hop], &target).await;
846
847        assert!(result.is_ok());
848
849        let captured_target = captured.lock().unwrap().take().unwrap();
850        assert_eq!(
851            captured_target.host,
852            TargetHost::Domain("destination.example.com".to_string())
853        );
854        assert_eq!(captured_target.port, 443);
855
856        server_jh.abort();
857    }
858
859    #[tokio::test]
860    async fn test_two_hop_chain() {
861        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
862        let addr1 = listener1.local_addr().unwrap();
863
864        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
865        let addr2 = listener2.local_addr().unwrap();
866
867        let server_jh1 = tokio::spawn(async move {
868            let (_stream, _) = listener1.accept().await.unwrap();
869            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
870        });
871
872        let server_jh2 = tokio::spawn(async move {
873            let (_stream, _) = listener2.accept().await.unwrap();
874            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
875        });
876
877        let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
878        let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
879        let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
880
881        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
882        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
883        let target = make_target("final.example.com", 443);
884        let result = executor.execute(&[hop1, hop2], &target).await;
885
886        assert!(result.is_ok());
887
888        let target1 = captured1.lock().unwrap().take().unwrap();
889        assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
890        assert_eq!(target1.port, addr2.port());
891
892        let target2 = captured2.lock().unwrap().take().unwrap();
893        assert_eq!(
894            target2.host,
895            TargetHost::Domain("final.example.com".to_string())
896        );
897        assert_eq!(target2.port, 443);
898
899        server_jh1.abort();
900        server_jh2.abort();
901    }
902
903    #[tokio::test]
904    async fn test_three_hop_chain() {
905        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
906        let addr1 = listener1.local_addr().unwrap();
907
908        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
909        let addr2 = listener2.local_addr().unwrap();
910
911        let listener3 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
912        let addr3 = listener3.local_addr().unwrap();
913
914        let server_jh1 = tokio::spawn(async move {
915            let (_stream, _) = listener1.accept().await.unwrap();
916            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
917        });
918
919        let server_jh2 = tokio::spawn(async move {
920            let (_stream, _) = listener2.accept().await.unwrap();
921            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
922        });
923
924        let server_jh3 = tokio::spawn(async move {
925            let (_stream, _) = listener3.accept().await.unwrap();
926            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
927        });
928
929        let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
930        let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
931        let (handler3, captured3) = MockHandler::new(ProtocolSpec::Socks4);
932        let executor = ChainExecutor::new(vec![
933            Box::new(handler1),
934            Box::new(handler2),
935            Box::new(handler3),
936        ]);
937
938        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
939        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
940        let hop3 = make_hop(ProtocolSpec::Socks4, &addr3.ip().to_string(), addr3.port());
941        let target = make_target("final.example.com", 8080);
942        let result = executor.execute(&[hop1, hop2, hop3], &target).await;
943
944        assert!(result.is_ok());
945
946        let target1 = captured1.lock().unwrap().take().unwrap();
947        assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
948        assert_eq!(target1.port, addr2.port());
949
950        let target2 = captured2.lock().unwrap().take().unwrap();
951        assert_eq!(target2.host, TargetHost::Ip(addr3.ip()));
952        assert_eq!(target2.port, addr3.port());
953
954        let target3 = captured3.lock().unwrap().take().unwrap();
955        assert_eq!(
956            target3.host,
957            TargetHost::Domain("final.example.com".to_string())
958        );
959        assert_eq!(target3.port, 8080);
960
961        server_jh1.abort();
962        server_jh2.abort();
963        server_jh3.abort();
964    }
965
966    // ===== Credential Passing Tests =====
967
968    #[tokio::test]
969    async fn test_credentials_passed_to_handler() {
970        use std::sync::Arc;
971
972        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
973        let addr = listener.local_addr().unwrap();
974
975        let server_jh = tokio::spawn(async move {
976            let (_stream, _) = listener.accept().await.unwrap();
977            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
978        });
979
980        struct CapturingHandler {
981            protocol: ProtocolSpec,
982            captured_creds: Arc<std::sync::Mutex<Option<CredentialSpec>>>,
983        }
984
985        impl HopHandler for CapturingHandler {
986            fn protocol(&self) -> ProtocolSpec {
987                self.protocol
988            }
989
990            fn handshake<'a>(
991                &'a self,
992                stream: BoxStream,
993                _target: &'a TargetAddr,
994                hop: &'a ProxyHopSpec,
995                _hop_index: usize,
996            ) -> HandshakeFuture<'a> {
997                Box::pin(async move {
998                    if let Some(creds) = hop.credentials.as_ref() {
999                        *self.captured_creds.lock().unwrap() = Some(creds.clone());
1000                    }
1001                    Ok(stream)
1002                })
1003            }
1004        }
1005
1006        let captured_creds = Arc::new(std::sync::Mutex::new(None));
1007        let handler: Box<dyn HopHandler> = Box::new(CapturingHandler {
1008            protocol: ProtocolSpec::Http,
1009            captured_creds: captured_creds.clone(),
1010        });
1011
1012        let executor = ChainExecutor::new(vec![handler]);
1013
1014        let hop = make_hop_with_creds(
1015            ProtocolSpec::Http,
1016            &addr.ip().to_string(),
1017            addr.port(),
1018            "testuser",
1019            "testpass",
1020        );
1021        let target = make_target("example.com", 80);
1022        let result = executor.execute(&[hop], &target).await;
1023
1024        assert!(result.is_ok());
1025
1026        let creds = captured_creds.lock().unwrap().take().unwrap();
1027        assert_eq!(creds.username, "testuser");
1028        assert_eq!(creds.password, "testpass");
1029
1030        server_jh.abort();
1031    }
1032
1033    // ===== Handler Selection Tests =====
1034
1035    #[tokio::test]
1036    async fn test_handler_selection_first_matching() {
1037        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1038        let addr = listener.local_addr().unwrap();
1039
1040        let server_jh = tokio::spawn(async move {
1041            let (_stream, _) = listener.accept().await.unwrap();
1042            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1043        });
1044
1045        let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1046        let executor = ChainExecutor::new(vec![Box::new(handler)]);
1047
1048        let hop = ProxyHopSpec {
1049            protocols: vec![ProtocolSpec::Http, ProtocolSpec::Socks5],
1050            endpoint: EndpointSpec {
1051                host: addr.ip().to_string(),
1052                port: addr.port(),
1053            },
1054            credentials: None,
1055            rule: None,
1056            local_bind: None,
1057            tls: false,
1058            server_name: None,
1059            insecure: false,
1060            plugins: Vec::new(),
1061            auth_prefix: None,
1062        };
1063        let target = make_target("example.com", 80);
1064        let result = executor.execute(&[hop], &target).await;
1065
1066        assert!(result.is_ok());
1067
1068        server_jh.abort();
1069    }
1070
1071    // ===== Error Chain Index Tests =====
1072
1073    #[tokio::test]
1074    async fn test_error_identifies_failing_hop() {
1075        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1076        let addr1 = listener1.local_addr().unwrap();
1077
1078        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1079        let addr2 = listener2.local_addr().unwrap();
1080
1081        let server_jh1 = tokio::spawn(async move {
1082            let (_stream, _) = listener1.accept().await.unwrap();
1083            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1084        });
1085
1086        let server_jh2 = tokio::spawn(async move {
1087            let (_stream, _) = listener2.accept().await.unwrap();
1088            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1089        });
1090
1091        let (good_handler, _) = MockHandler::new(ProtocolSpec::Socks5);
1092        let bad_handler: Box<dyn HopHandler> = Box::new(FailingHandler {
1093            protocol: ProtocolSpec::Http,
1094            error_message: "proxy refused connection".to_string(),
1095        });
1096
1097        let executor = ChainExecutor::new(vec![Box::new(good_handler), bad_handler]);
1098
1099        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1100        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1101        let target = make_target("example.com", 80);
1102        let result = executor.execute(&[hop1, hop2], &target).await;
1103
1104        match result {
1105            Err(ChainError::HandshakeFailed {
1106                hop_index, source, ..
1107            }) => {
1108                assert_eq!(hop_index, 1, "error should identify hop 1 (second hop)");
1109                assert_eq!(source.to_string(), "proxy refused connection");
1110            }
1111            Err(e) => panic!("expected HandshakeFailed, got: {e}"),
1112            Ok(_) => panic!("expected error"),
1113        }
1114
1115        server_jh1.abort();
1116        server_jh2.abort();
1117    }
1118
1119    // ===== IP Address Endpoint Tests =====
1120
1121    #[tokio::test]
1122    async fn test_ip_endpoint_resolved() {
1123        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1124        let addr = listener.local_addr().unwrap();
1125
1126        let server_jh = tokio::spawn(async move {
1127            let (_stream, _) = listener.accept().await.unwrap();
1128            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1129        });
1130
1131        let (handler, captured) = MockHandler::new(ProtocolSpec::Http);
1132        let executor = ChainExecutor::new(vec![Box::new(handler)]);
1133
1134        let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1135        let target = make_ip_target("93.184.216.34".parse().unwrap(), 443);
1136        let result = executor.execute(&[hop], &target).await;
1137
1138        assert!(result.is_ok());
1139
1140        let captured_target = captured.lock().unwrap().take().unwrap();
1141        assert_eq!(
1142            captured_target.host,
1143            TargetHost::Ip("93.184.216.34".parse().unwrap())
1144        );
1145        assert_eq!(captured_target.port, 443);
1146
1147        server_jh.abort();
1148    }
1149
1150    // ===== Mixed Protocol Chain Tests =====
1151
1152    #[tokio::test]
1153    async fn test_socks5_to_http_chain() {
1154        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1155        let addr1 = listener1.local_addr().unwrap();
1156
1157        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1158        let addr2 = listener2.local_addr().unwrap();
1159
1160        let server_jh1 = tokio::spawn(async move {
1161            let (_stream, _) = listener1.accept().await.unwrap();
1162            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1163        });
1164
1165        let server_jh2 = tokio::spawn(async move {
1166            let (_stream, _) = listener2.accept().await.unwrap();
1167            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1168        });
1169
1170        let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
1171        let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
1172        let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
1173
1174        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1175        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1176        let target = make_target("target.example.com", 8080);
1177        let result = executor.execute(&[hop1, hop2], &target).await;
1178
1179        assert!(result.is_ok());
1180
1181        let target1 = captured1.lock().unwrap().take().unwrap();
1182        assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
1183
1184        let target2 = captured2.lock().unwrap().take().unwrap();
1185        assert_eq!(
1186            target2.host,
1187            TargetHost::Domain("target.example.com".to_string())
1188        );
1189
1190        server_jh1.abort();
1191        server_jh2.abort();
1192    }
1193
1194    #[tokio::test]
1195    async fn test_http_to_socks5_chain() {
1196        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1197        let addr1 = listener1.local_addr().unwrap();
1198
1199        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1200        let addr2 = listener2.local_addr().unwrap();
1201
1202        let server_jh1 = tokio::spawn(async move {
1203            let (_stream, _) = listener1.accept().await.unwrap();
1204            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1205        });
1206
1207        let server_jh2 = tokio::spawn(async move {
1208            let (_stream, _) = listener2.accept().await.unwrap();
1209            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1210        });
1211
1212        let (handler1, captured1) = MockHandler::new(ProtocolSpec::Http);
1213        let (handler2, captured2) = MockHandler::new(ProtocolSpec::Socks5);
1214        let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
1215
1216        let hop1 = make_hop(ProtocolSpec::Http, &addr1.ip().to_string(), addr1.port());
1217        let hop2 = make_hop(ProtocolSpec::Socks5, &addr2.ip().to_string(), addr2.port());
1218        let target = make_target("target.example.com", 443);
1219        let result = executor.execute(&[hop1, hop2], &target).await;
1220
1221        assert!(result.is_ok());
1222
1223        let target1 = captured1.lock().unwrap().take().unwrap();
1224        assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
1225
1226        let target2 = captured2.lock().unwrap().take().unwrap();
1227        assert_eq!(
1228            target2.host,
1229            TargetHost::Domain("target.example.com".to_string())
1230        );
1231
1232        server_jh1.abort();
1233        server_jh2.abort();
1234    }
1235
1236    #[tokio::test]
1237    async fn test_socks5_to_socks5_chain() {
1238        use std::sync::Arc;
1239
1240        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1241        let addr1 = listener1.local_addr().unwrap();
1242
1243        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1244        let addr2 = listener2.local_addr().unwrap();
1245
1246        let server_jh1 = tokio::spawn(async move {
1247            let (_stream, _) = listener1.accept().await.unwrap();
1248            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1249        });
1250
1251        let server_jh2 = tokio::spawn(async move {
1252            let (_stream, _) = listener2.accept().await.unwrap();
1253            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1254        });
1255
1256        struct MultiTargetRecorder {
1257            protocol: ProtocolSpec,
1258            targets: Arc<std::sync::Mutex<Vec<TargetAddr>>>,
1259        }
1260
1261        impl HopHandler for MultiTargetRecorder {
1262            fn protocol(&self) -> ProtocolSpec {
1263                self.protocol
1264            }
1265
1266            fn handshake<'a>(
1267                &'a self,
1268                stream: BoxStream,
1269                target: &'a TargetAddr,
1270                _hop: &'a ProxyHopSpec,
1271                _hop_index: usize,
1272            ) -> HandshakeFuture<'a> {
1273                let targets = self.targets.clone();
1274                let target_clone = target.clone();
1275                Box::pin(async move {
1276                    targets.lock().unwrap().push(target_clone);
1277                    Ok(stream)
1278                })
1279            }
1280        }
1281
1282        let targets = Arc::new(std::sync::Mutex::new(Vec::new()));
1283        let handler: Box<dyn HopHandler> = Box::new(MultiTargetRecorder {
1284            protocol: ProtocolSpec::Socks5,
1285            targets: targets.clone(),
1286        });
1287        let executor = ChainExecutor::new(vec![handler]);
1288
1289        let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1290        let hop2 = make_hop(ProtocolSpec::Socks5, &addr2.ip().to_string(), addr2.port());
1291        let target = make_target("target.example.com", 443);
1292        let result = executor.execute(&[hop1, hop2], &target).await;
1293
1294        assert!(result.is_ok());
1295
1296        let captured_targets = targets.lock().unwrap();
1297        assert_eq!(captured_targets.len(), 2);
1298
1299        // First SOCKS5 hop targets second SOCKS5 proxy
1300        assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1301        assert_eq!(captured_targets[0].port, addr2.port());
1302
1303        // Second SOCKS5 hop targets final destination (domain preserved)
1304        assert_eq!(
1305            captured_targets[1].host,
1306            TargetHost::Domain("target.example.com".to_string())
1307        );
1308        assert_eq!(captured_targets[1].port, 443);
1309
1310        server_jh1.abort();
1311        server_jh2.abort();
1312    }
1313
1314    #[tokio::test]
1315    async fn test_http_to_http_chain() {
1316        use std::sync::Arc;
1317
1318        let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1319        let addr1 = listener1.local_addr().unwrap();
1320
1321        let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1322        let addr2 = listener2.local_addr().unwrap();
1323
1324        let server_jh1 = tokio::spawn(async move {
1325            let (_stream, _) = listener1.accept().await.unwrap();
1326            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1327        });
1328
1329        let server_jh2 = tokio::spawn(async move {
1330            let (_stream, _) = listener2.accept().await.unwrap();
1331            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1332        });
1333
1334        // Handler that records all targets it receives
1335        struct MultiTargetRecorder {
1336            protocol: ProtocolSpec,
1337            targets: Arc<std::sync::Mutex<Vec<TargetAddr>>>,
1338        }
1339
1340        impl HopHandler for MultiTargetRecorder {
1341            fn protocol(&self) -> ProtocolSpec {
1342                self.protocol
1343            }
1344
1345            fn handshake<'a>(
1346                &'a self,
1347                stream: BoxStream,
1348                target: &'a TargetAddr,
1349                _hop: &'a ProxyHopSpec,
1350                _hop_index: usize,
1351            ) -> HandshakeFuture<'a> {
1352                let targets = self.targets.clone();
1353                let target_clone = target.clone();
1354                Box::pin(async move {
1355                    targets.lock().unwrap().push(target_clone);
1356                    Ok(stream)
1357                })
1358            }
1359        }
1360
1361        let targets = Arc::new(std::sync::Mutex::new(Vec::new()));
1362        let handler: Box<dyn HopHandler> = Box::new(MultiTargetRecorder {
1363            protocol: ProtocolSpec::Http,
1364            targets: targets.clone(),
1365        });
1366        let executor = ChainExecutor::new(vec![handler]);
1367
1368        let hop1 = make_hop(ProtocolSpec::Http, &addr1.ip().to_string(), addr1.port());
1369        let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1370        let target = make_target("target.example.com", 80);
1371        let result = executor.execute(&[hop1, hop2], &target).await;
1372
1373        assert!(result.is_ok());
1374
1375        let captured_targets = targets.lock().unwrap();
1376        assert_eq!(captured_targets.len(), 2);
1377
1378        // First hop should target second proxy
1379        assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1380        assert_eq!(captured_targets[0].port, addr2.port());
1381
1382        // Second hop should target final destination
1383        assert_eq!(
1384            captured_targets[1].host,
1385            TargetHost::Domain("target.example.com".to_string())
1386        );
1387        assert_eq!(captured_targets[1].port, 80);
1388
1389        server_jh1.abort();
1390        server_jh2.abort();
1391    }
1392
1393    // ===== Chain Validation Tests =====
1394
1395    #[test]
1396    fn test_validate_chain_valid() {
1397        let executor = ChainExecutor::new(vec![]);
1398        let chain = vec![
1399            make_hop(ProtocolSpec::Http, "127.0.0.1", 8080),
1400            make_hop(ProtocolSpec::Socks5, "127.0.0.1", 1080),
1401        ];
1402        assert!(executor.validate_chain(&chain).is_ok());
1403    }
1404
1405    #[test]
1406    fn test_validate_chain_empty_protocols() {
1407        let executor = ChainExecutor::new(vec![]);
1408        let chain = vec![ProxyHopSpec {
1409            protocols: vec![],
1410            endpoint: EndpointSpec {
1411                host: "127.0.0.1".to_string(),
1412                port: 8080,
1413            },
1414            credentials: None,
1415            rule: None,
1416            local_bind: None,
1417            tls: false,
1418            server_name: None,
1419            insecure: false,
1420            plugins: Vec::new(),
1421            auth_prefix: None,
1422        }];
1423        assert!(executor.validate_chain(&chain).is_err());
1424    }
1425
1426    #[test]
1427    fn test_validate_chain_empty_host() {
1428        let executor = ChainExecutor::new(vec![]);
1429        let chain = vec![make_hop(ProtocolSpec::Http, "", 8080)];
1430        assert!(executor.validate_chain(&chain).is_err());
1431    }
1432
1433    #[test]
1434    fn test_validate_chain_zero_port() {
1435        let executor = ChainExecutor::new(vec![]);
1436        let chain = vec![ProxyHopSpec {
1437            protocols: vec![ProtocolSpec::Http],
1438            endpoint: EndpointSpec {
1439                host: "127.0.0.1".to_string(),
1440                port: 0,
1441            },
1442            credentials: None,
1443            rule: None,
1444            local_bind: None,
1445            tls: false,
1446            server_name: None,
1447            insecure: false,
1448            plugins: Vec::new(),
1449            auth_prefix: None,
1450        }];
1451        assert!(executor.validate_chain(&chain).is_err());
1452    }
1453
1454    // ===== Error Display Tests =====
1455
1456    #[test]
1457    fn test_chain_error_display() {
1458        let err = ChainError::EmptyChain;
1459        assert_eq!(
1460            err.to_string(),
1461            "chain is empty, at least one hop is required"
1462        );
1463
1464        let err = ChainError::InvalidChain {
1465            reason: "test reason".to_string(),
1466        };
1467        assert_eq!(err.to_string(), "invalid chain: test reason");
1468
1469        let err = ChainError::ConnectFailed {
1470            hop_index: 0,
1471            endpoint: "127.0.0.1:8080".to_string(),
1472            source: ConnectError::ConnectionRefused,
1473        };
1474        assert!(err.to_string().contains("hop 0"));
1475        assert!(err.to_string().contains("127.0.0.1:8080"));
1476        assert!(err.to_string().contains("connection refused"));
1477    }
1478
1479    // ===== TLS Wrapping Tests =====
1480
1481    #[tokio::test]
1482    async fn test_tls_wrapper_called_when_hop_tls_true() {
1483        use std::sync::atomic::{AtomicBool, Ordering};
1484
1485        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1486        let addr = listener.local_addr().unwrap();
1487
1488        let server_jh = tokio::spawn(async move {
1489            let (_stream, _) = listener.accept().await.unwrap();
1490            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1491        });
1492
1493        let (_handler, _captured) = MockHandler::new(ProtocolSpec::Http);
1494        let executor = ChainExecutor::new(vec![Box::new(_handler)]);
1495
1496        let tls_called = Arc::new(AtomicBool::new(false));
1497        let tls_called_clone = tls_called.clone();
1498
1499        let tls_wrapper: TlsWrapper = Box::new(move |stream, _server_name, _alpn, _insecure| {
1500            let called = tls_called_clone.clone();
1501            Box::pin(async move {
1502                called.store(true, Ordering::Relaxed);
1503                // Just pass through - don't actually do TLS in this test
1504                Ok(stream)
1505            })
1506        });
1507
1508        let executor = executor.with_tls_wrapper(tls_wrapper);
1509
1510        let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1511        hop.tls = true;
1512        hop.server_name = Some("test.example.com".to_string());
1513
1514        let target = make_target("example.com", 80);
1515        let result = executor.execute(&[hop], &target).await;
1516
1517        assert!(result.is_ok());
1518        assert!(
1519            tls_called.load(Ordering::Relaxed),
1520            "TLS wrapper should have been called"
1521        );
1522
1523        server_jh.abort();
1524    }
1525
1526    #[tokio::test]
1527    async fn test_tls_wrapper_not_called_when_hop_tls_false() {
1528        use std::sync::atomic::{AtomicBool, Ordering};
1529
1530        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1531        let addr = listener.local_addr().unwrap();
1532
1533        let server_jh = tokio::spawn(async move {
1534            let (_stream, _) = listener.accept().await.unwrap();
1535            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1536        });
1537
1538        let (_handler, _captured) = MockHandler::new(ProtocolSpec::Http);
1539        let executor = ChainExecutor::new(vec![Box::new(_handler)]);
1540
1541        let tls_called = Arc::new(AtomicBool::new(false));
1542        let tls_called_clone = tls_called.clone();
1543
1544        let tls_wrapper: TlsWrapper = Box::new(move |stream, _server_name, _alpn, _insecure| {
1545            let called = tls_called_clone.clone();
1546            Box::pin(async move {
1547                called.store(true, Ordering::Relaxed);
1548                Ok(stream)
1549            })
1550        });
1551
1552        let executor = executor.with_tls_wrapper(tls_wrapper);
1553
1554        let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1555        // hop.tls defaults to false
1556
1557        let target = make_target("example.com", 80);
1558        let result = executor.execute(&[hop], &target).await;
1559
1560        assert!(result.is_ok());
1561        assert!(
1562            !tls_called.load(Ordering::Relaxed),
1563            "TLS wrapper should NOT have been called"
1564        );
1565
1566        server_jh.abort();
1567    }
1568
1569    #[tokio::test]
1570    async fn test_tls_wrapper_uses_server_name_from_hop() {
1571        use std::sync::Mutex;
1572
1573        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1574        let addr = listener.local_addr().unwrap();
1575
1576        let server_jh = tokio::spawn(async move {
1577            let (_stream, _) = listener.accept().await.unwrap();
1578            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1579        });
1580
1581        let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1582        let executor = ChainExecutor::new(vec![Box::new(handler)]);
1583
1584        let captured_name = Arc::new(Mutex::new(None::<String>));
1585        let captured_name_clone = captured_name.clone();
1586
1587        let tls_wrapper: TlsWrapper = Box::new(move |stream, server_name, _alpn, _insecure| {
1588            let captured = captured_name_clone.clone();
1589            Box::pin(async move {
1590                *captured.lock().unwrap() = Some(server_name);
1591                Ok(stream)
1592            })
1593        });
1594
1595        let executor = executor.with_tls_wrapper(tls_wrapper);
1596
1597        let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1598        hop.tls = true;
1599        hop.server_name = Some("custom-sni.example.com".to_string());
1600
1601        let target = make_target("example.com", 80);
1602        let result = executor.execute(&[hop], &target).await;
1603
1604        assert!(result.is_ok());
1605        let name = captured_name.lock().unwrap().take().unwrap();
1606        assert_eq!(name, "custom-sni.example.com");
1607
1608        server_jh.abort();
1609    }
1610
1611    #[tokio::test]
1612    async fn test_tls_wrapper_falls_back_to_endpoint_host() {
1613        use std::sync::Mutex;
1614
1615        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1616        let addr = listener.local_addr().unwrap();
1617
1618        let server_jh = tokio::spawn(async move {
1619            let (_stream, _) = listener.accept().await.unwrap();
1620            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1621        });
1622
1623        let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1624        let executor = ChainExecutor::new(vec![Box::new(handler)]);
1625
1626        let captured_name = Arc::new(Mutex::new(None::<String>));
1627        let captured_name_clone = captured_name.clone();
1628
1629        let tls_wrapper: TlsWrapper = Box::new(move |stream, server_name, _alpn, _insecure| {
1630            let captured = captured_name_clone.clone();
1631            Box::pin(async move {
1632                *captured.lock().unwrap() = Some(server_name);
1633                Ok(stream)
1634            })
1635        });
1636
1637        let executor = executor.with_tls_wrapper(tls_wrapper);
1638
1639        // hop with no server_name - should use endpoint host
1640        let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1641        // hop.tls defaults to false, so set it to true
1642        let mut hop = hop;
1643        hop.tls = true;
1644        // No server_name set - should fallback to endpoint.host
1645
1646        let target = make_target("example.com", 80);
1647        let result = executor.execute(&[hop], &target).await;
1648
1649        assert!(result.is_ok());
1650        let name = captured_name.lock().unwrap().take().unwrap();
1651        assert_eq!(name, addr.ip().to_string());
1652
1653        server_jh.abort();
1654    }
1655
1656    #[tokio::test]
1657    async fn test_tls_failure_propagates_as_handshake_error() {
1658        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1659        let addr = listener.local_addr().unwrap();
1660
1661        let server_jh = tokio::spawn(async move {
1662            let (_stream, _) = listener.accept().await.unwrap();
1663            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1664        });
1665
1666        let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1667        let executor = ChainExecutor::new(vec![Box::new(handler)]);
1668
1669        let tls_wrapper: TlsWrapper = Box::new(|_stream, _server_name, _alpn, _insecure| {
1670            Box::pin(async move {
1671                Err(Box::<dyn std::error::Error + Send + Sync>::from(
1672                    "TLS handshake failed: certificate rejected",
1673                ))
1674            })
1675        });
1676
1677        let executor = executor.with_tls_wrapper(tls_wrapper);
1678
1679        let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1680        hop.tls = true;
1681
1682        let target = make_target("example.com", 80);
1683        let result = executor.execute(&[hop], &target).await;
1684
1685        match result {
1686            Err(ChainError::HandshakeFailed {
1687                hop_index,
1688                protocol,
1689                source,
1690            }) => {
1691                assert_eq!(hop_index, 0);
1692                assert_eq!(protocol, "tls");
1693                assert!(source.to_string().contains("TLS handshake failed"));
1694            }
1695            Err(e) => panic!("expected HandshakeFailed, got: {e}"),
1696            Ok(_) => panic!("expected error"),
1697        }
1698
1699        server_jh.abort();
1700    }
1701}