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
9type 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#[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#[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
61pub trait HopHandler: Send + Sync {
70 fn protocol(&self) -> ProtocolSpec;
72
73 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 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
103pub 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
126pub 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 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 pub fn with_tls_wrapper(mut self, wrapper: TlsWrapper) -> Self {
163 self.tls_wrapper = Some(wrapper);
164 self
165 }
166
167 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 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 pub fn shared_tls_config(&self) -> Option<&std::sync::Arc<rustls::ClientConfig>> {
189 self.shared_tls_config.as_ref()
190 }
191
192 pub fn insecure_shared_tls_config(&self) -> Option<&std::sync::Arc<rustls::ClientConfig>> {
194 self.insecure_shared_tls_config.as_ref()
195 }
196
197 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 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 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 for (i, hop) in chain.iter().enumerate() {
311 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 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 #[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 let next_target = if i + 1 < chain.len() {
356 endpoint_to_target_addr(&chain[i + 1].endpoint)?
358 } else {
359 target.clone()
361 };
362
363 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 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
413fn 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
426fn 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
456fn 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1301 assert_eq!(captured_targets[0].port, addr2.port());
1302
1303 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 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 assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1380 assert_eq!(captured_targets[0].port, addr2.port());
1381
1382 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 #[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 #[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 #[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 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 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 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1641 let mut hop = hop;
1643 hop.tls = true;
1644 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}