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<
109 dyn Fn(
110 BoxStream,
111 String,
112 Option<Vec<Vec<u8>>>,
113 ) -> std::pin::Pin<
114 Box<
115 dyn std::future::Future<
116 Output = Result<BoxStream, Box<dyn std::error::Error + Send + Sync>>,
117 > + Send,
118 >,
119 > + Send
120 + Sync,
121>;
122
123pub struct ChainExecutor {
139 direct_connector: DirectConnector,
140 handlers: Vec<Box<dyn HopHandler>>,
141 tls_wrapper: Option<TlsWrapper>,
142 shared_tls_config: Option<std::sync::Arc<rustls::ClientConfig>>,
143}
144
145impl ChainExecutor {
146 pub fn new(handlers: Vec<Box<dyn HopHandler>>) -> Self {
148 Self {
149 direct_connector: DirectConnector,
150 handlers,
151 tls_wrapper: None,
152 shared_tls_config: None,
153 }
154 }
155
156 pub fn with_tls_wrapper(mut self, wrapper: TlsWrapper) -> Self {
158 self.tls_wrapper = Some(wrapper);
159 self
160 }
161
162 pub fn with_shared_tls_config(
165 mut self,
166 config: Option<std::sync::Arc<rustls::ClientConfig>>,
167 ) -> Self {
168 self.shared_tls_config = config;
169 self
170 }
171
172 pub fn shared_tls_config(&self) -> Option<&std::sync::Arc<rustls::ClientConfig>> {
174 self.shared_tls_config.as_ref()
175 }
176
177 pub async fn execute(
186 &self,
187 chain: &[ProxyHopSpec],
188 target: &TargetAddr,
189 ) -> Result<BoxStream, ChainError> {
190 if chain.is_empty() {
191 return Err(ChainError::EmptyChain);
192 }
193
194 self.validate_chain(chain)?;
195
196 for (i, hop) in chain.iter().enumerate() {
198 let application_protocols = application_protocols(&hop.protocols);
199 if !application_protocols.is_empty() {
200 find_handler(&self.handlers, &application_protocols).map_err(|_| {
201 ChainError::InvalidChain {
202 reason: format!(
203 "hop {i}: no handler for protocols: [{}]",
204 application_protocols
205 .iter()
206 .map(|p| format!("{p:?}"))
207 .collect::<Vec<_>>()
208 .join(", ")
209 ),
210 }
211 })?;
212 }
213 }
214
215 let first_hop = &chain[0];
217 let first_target = if chain.len() > 1 {
218 endpoint_to_target_addr(&chain[1].endpoint)?
219 } else {
220 target.clone()
221 };
222 let first_hop_addr = endpoint_to_target_addr(&first_hop.endpoint)?;
223
224 let mut current_stream: BoxStream = if first_hop.protocols.contains(&ProtocolSpec::Http3)
225 || first_hop.protocols.contains(&ProtocolSpec::Quic)
226 {
227 let transport_protocol = if first_hop.protocols.contains(&ProtocolSpec::Http3) {
228 ProtocolSpec::Http3
229 } else {
230 ProtocolSpec::Quic
231 };
232 let handler = find_handler(&self.handlers, &[transport_protocol])?;
233 handler
234 .open(&first_hop.endpoint, first_hop, &first_target)
235 .ok_or_else(|| ChainError::InvalidChain {
236 reason: format!("hop 0: transport {transport_protocol:?} cannot be opened"),
237 })?
238 .await
239 .map_err(|e| ChainError::HandshakeFailed {
240 hop_index: 0,
241 protocol: format!("{transport_protocol:?}"),
242 source: e,
243 })?
244 } else if first_hop.protocols.contains(&ProtocolSpec::Unix) {
245 #[cfg(unix)]
246 {
247 Box::new(
248 tokio::net::UnixStream::connect(&first_hop.endpoint.host)
249 .await
250 .map_err(|e| ChainError::ConnectFailed {
251 hop_index: 0,
252 endpoint: first_hop.endpoint.host.clone(),
253 source: crate::ConnectError::Io(e),
254 })?,
255 ) as BoxStream
256 }
257 #[cfg(not(unix))]
258 {
259 return Err(ChainError::InvalidChain {
260 reason: "unix upstreams are unsupported on this platform".to_string(),
261 });
262 }
263 } else {
264 let local_bind = first_hop
265 .local_bind
266 .as_deref()
267 .map(|value| {
268 value.parse().map_err(|e| ChainError::InvalidChain {
269 reason: format!("hop 0: invalid local bind '{}': {}", value, e),
270 })
271 })
272 .transpose()?;
273 self.direct_connector
274 .connect_with_options(&first_hop_addr, &ConnectOptions { local_bind })
275 .await
276 .map_err(|e| ChainError::ConnectFailed {
277 hop_index: 0,
278 endpoint: first_hop_addr.to_string(),
279 source: e,
280 })?
281 };
282
283 for (i, hop) in chain.iter().enumerate() {
285 if hop.tls {
287 if let Some(ref tls_wrapper) = self.tls_wrapper {
288 let server_name = hop
289 .server_name
290 .clone()
291 .unwrap_or_else(|| hop.endpoint.host.clone());
292 let alpn = if hop.protocols.contains(&ProtocolSpec::Http2) {
294 Some(vec![b"h2".to_vec(), b"http/1.1".to_vec()])
295 } else {
296 None
297 };
298 current_stream = tls_wrapper(current_stream, server_name, alpn)
299 .await
300 .map_err(|e| ChainError::HandshakeFailed {
301 hop_index: i,
302 protocol: "tls".to_string(),
303 source: e,
304 })?;
305 }
306 }
307
308 let next_target = if i + 1 < chain.len() {
310 endpoint_to_target_addr(&chain[i + 1].endpoint)?
312 } else {
313 target.clone()
315 };
316
317 let application_protocols = application_protocols(&hop.protocols);
321 if application_protocols.is_empty() {
322 continue;
323 }
324 let handler = find_handler(&self.handlers, &application_protocols)?;
325
326 current_stream = handler
327 .handshake(current_stream, &next_target, hop, i)
328 .await
329 .map_err(|e| ChainError::HandshakeFailed {
330 hop_index: i,
331 protocol: format_protocols(&hop.protocols),
332 source: e,
333 })?;
334 }
335
336 Ok(current_stream)
337 }
338
339 fn validate_chain(&self, chain: &[ProxyHopSpec]) -> Result<(), ChainError> {
341 for (i, hop) in chain.iter().enumerate() {
342 if hop.protocols.is_empty() {
343 return Err(ChainError::InvalidChain {
344 reason: format!("hop {i}: no protocols specified"),
345 });
346 }
347 if hop.endpoint.host.is_empty() {
348 return Err(ChainError::InvalidChain {
349 reason: format!("hop {i}: empty endpoint host"),
350 });
351 }
352 if hop.endpoint.port == 0 && !hop.protocols.contains(&ProtocolSpec::Unix) {
353 return Err(ChainError::InvalidChain {
354 reason: format!("hop {i}: port cannot be 0"),
355 });
356 }
357 }
358 Ok(())
359 }
360}
361
362fn endpoint_to_target_addr(endpoint: &EndpointSpec) -> Result<TargetAddr, ChainError> {
364 let host = if let Ok(ip) = endpoint.host.parse::<std::net::IpAddr>() {
365 TargetHost::Ip(ip)
366 } else {
367 TargetHost::Domain(endpoint.host.clone())
368 };
369 Ok(TargetAddr {
370 host,
371 port: endpoint.port,
372 })
373}
374
375fn find_handler<'a>(
377 handlers: &'a [Box<dyn HopHandler>],
378 protocols: &[ProtocolSpec],
379) -> Result<&'a dyn HopHandler, ChainError> {
380 for handler in handlers {
381 if protocols.contains(&handler.protocol()) {
382 return Ok(handler.as_ref());
383 }
384 }
385 Err(ChainError::InvalidChain {
386 reason: format!(
387 "no handler for protocols: [{}]",
388 protocols
389 .iter()
390 .map(|p| format!("{p:?}"))
391 .collect::<Vec<_>>()
392 .join(", ")
393 ),
394 })
395}
396
397fn application_protocols(protocols: &[ProtocolSpec]) -> Vec<ProtocolSpec> {
398 protocols
399 .iter()
400 .copied()
401 .filter(|protocol| !matches!(protocol, ProtocolSpec::Http3 | ProtocolSpec::Quic))
402 .collect()
403}
404
405fn format_protocols(protocols: &[ProtocolSpec]) -> String {
407 protocols
408 .iter()
409 .map(|p| format!("{p:?}"))
410 .collect::<Vec<_>>()
411 .join("+")
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::TargetHost;
418 use eggress_uri::CredentialSpec;
419 use std::sync::Arc;
420
421 struct MockHandler {
423 protocol: ProtocolSpec,
424 captured_target: std::sync::Arc<std::sync::Mutex<Option<TargetAddr>>>,
425 }
426
427 impl MockHandler {
428 fn new(
429 protocol: ProtocolSpec,
430 ) -> (Self, std::sync::Arc<std::sync::Mutex<Option<TargetAddr>>>) {
431 let captured_target = std::sync::Arc::new(std::sync::Mutex::new(None));
432 let handler = Self {
433 protocol,
434 captured_target: captured_target.clone(),
435 };
436 (handler, captured_target)
437 }
438 }
439
440 impl HopHandler for MockHandler {
441 fn protocol(&self) -> ProtocolSpec {
442 self.protocol
443 }
444
445 fn handshake<'a>(
446 &'a self,
447 stream: BoxStream,
448 target: &'a TargetAddr,
449 _hop: &'a ProxyHopSpec,
450 _hop_index: usize,
451 ) -> HandshakeFuture<'a> {
452 Box::pin(async move {
453 *self.captured_target.lock().unwrap() = Some(target.clone());
454 Ok(stream)
455 })
456 }
457 }
458
459 struct FailingHandler {
461 protocol: ProtocolSpec,
462 error_message: String,
463 }
464
465 impl HopHandler for FailingHandler {
466 fn protocol(&self) -> ProtocolSpec {
467 self.protocol
468 }
469
470 fn handshake<'a>(
471 &'a self,
472 _stream: BoxStream,
473 _target: &'a TargetAddr,
474 _hop: &'a ProxyHopSpec,
475 _hop_index: usize,
476 ) -> HandshakeFuture<'a> {
477 let msg = self.error_message.clone();
478 Box::pin(async move { Err(msg.into()) })
479 }
480 }
481
482 fn make_hop(protocol: ProtocolSpec, host: &str, port: u16) -> ProxyHopSpec {
483 ProxyHopSpec {
484 protocols: vec![protocol],
485 endpoint: EndpointSpec {
486 host: host.to_string(),
487 port,
488 },
489 credentials: None,
490 rule: None,
491 local_bind: None,
492 tls: false,
493 server_name: None,
494 insecure: false,
495 plugins: Vec::new(),
496 auth_prefix: None,
497 }
498 }
499
500 fn make_hop_with_creds(
501 protocol: ProtocolSpec,
502 host: &str,
503 port: u16,
504 username: &str,
505 password: &str,
506 ) -> ProxyHopSpec {
507 ProxyHopSpec {
508 protocols: vec![protocol],
509 endpoint: EndpointSpec {
510 host: host.to_string(),
511 port,
512 },
513 credentials: Some(CredentialSpec {
514 username: username.to_string(),
515 password: password.to_string(),
516 }),
517 rule: None,
518 local_bind: None,
519 tls: false,
520 server_name: None,
521 insecure: false,
522 plugins: Vec::new(),
523 auth_prefix: None,
524 }
525 }
526
527 fn make_target(domain: &str, port: u16) -> TargetAddr {
528 TargetAddr {
529 host: TargetHost::Domain(domain.to_string()),
530 port,
531 }
532 }
533
534 fn make_ip_target(ip: std::net::IpAddr, port: u16) -> TargetAddr {
535 TargetAddr {
536 host: TargetHost::Ip(ip),
537 port,
538 }
539 }
540
541 #[tokio::test]
544 async fn test_empty_chain() {
545 let executor = ChainExecutor::new(vec![]);
546 let target = make_target("example.com", 80);
547 let result = executor.execute(&[], &target).await;
548 match result {
549 Err(e) => {
550 assert!(matches!(e, ChainError::EmptyChain));
551 assert_eq!(
552 e.to_string(),
553 "chain is empty, at least one hop is required"
554 );
555 }
556 Ok(_) => panic!("expected EmptyChain error"),
557 }
558 }
559
560 #[tokio::test]
561 async fn test_hop_no_protocols() {
562 let hop = ProxyHopSpec {
563 protocols: vec![],
564 endpoint: EndpointSpec {
565 host: "127.0.0.1".to_string(),
566 port: 8080,
567 },
568 credentials: None,
569 rule: None,
570 local_bind: None,
571 tls: false,
572 server_name: None,
573 insecure: false,
574 plugins: Vec::new(),
575 auth_prefix: None,
576 };
577 let executor = ChainExecutor::new(vec![]);
578 let target = make_target("example.com", 80);
579 let result = executor.execute(&[hop], &target).await;
580 match result {
581 Err(ChainError::InvalidChain { reason }) => {
582 assert!(reason.contains("no protocols specified"));
583 }
584 _ => panic!("expected InvalidChain error"),
585 }
586 }
587
588 #[tokio::test]
589 async fn test_hop_empty_host() {
590 let hop = make_hop(ProtocolSpec::Http, "", 8080);
591 let executor = ChainExecutor::new(vec![]);
592 let target = make_target("example.com", 80);
593 let result = executor.execute(&[hop], &target).await;
594 match result {
595 Err(ChainError::InvalidChain { reason }) => {
596 assert!(reason.contains("empty endpoint host"));
597 }
598 _ => panic!("expected InvalidChain error"),
599 }
600 }
601
602 #[tokio::test]
603 async fn test_hop_zero_port() {
604 let hop = ProxyHopSpec {
605 protocols: vec![ProtocolSpec::Http],
606 endpoint: EndpointSpec {
607 host: "127.0.0.1".to_string(),
608 port: 0,
609 },
610 credentials: None,
611 rule: None,
612 local_bind: None,
613 tls: false,
614 server_name: None,
615 insecure: false,
616 plugins: Vec::new(),
617 auth_prefix: None,
618 };
619 let executor = ChainExecutor::new(vec![]);
620 let target = make_target("example.com", 80);
621 let result = executor.execute(&[hop], &target).await;
622 match result {
623 Err(ChainError::InvalidChain { reason }) => {
624 assert!(reason.contains("port cannot be 0"));
625 }
626 _ => panic!("expected InvalidChain error"),
627 }
628 }
629
630 #[tokio::test]
633 async fn test_no_handler_for_protocol() {
634 let executor = ChainExecutor::new(vec![]);
635 let hop = make_hop(ProtocolSpec::Http, "127.0.0.1", 8080);
636 let target = make_target("example.com", 80);
637 let result = executor.execute(&[hop], &target).await;
638 match result {
639 Err(ChainError::InvalidChain { reason }) => {
640 assert!(reason.contains("no handler for protocols"));
641 }
642 _ => panic!("expected InvalidChain error"),
643 }
644 }
645
646 #[tokio::test]
649 async fn test_connect_failed() {
650 let (handler, _) = MockHandler::new(ProtocolSpec::Http);
651 let executor = ChainExecutor::new(vec![Box::new(handler)]);
652 let hop = make_hop(ProtocolSpec::Http, "127.0.0.1", 1);
653 let target = make_target("example.com", 80);
654 let result = executor.execute(&[hop], &target).await;
655 match result {
656 Err(ChainError::ConnectFailed {
657 hop_index, source, ..
658 }) => {
659 assert_eq!(hop_index, 0);
660 assert!(matches!(source, ConnectError::Io(_)));
661 }
662 Err(e) => panic!("expected ConnectFailed, got: {e}"),
663 Ok(_) => panic!("expected error"),
664 }
665 }
666
667 #[tokio::test]
670 async fn test_handshake_failed() {
671 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
672 let addr = listener.local_addr().unwrap();
673
674 let server_jh = tokio::spawn(async move {
675 let (_stream, _) = listener.accept().await.unwrap();
676 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
677 });
678
679 let failing_handler: Box<dyn HopHandler> = Box::new(FailingHandler {
680 protocol: ProtocolSpec::Http,
681 error_message: "handshake timeout".to_string(),
682 });
683
684 let executor = ChainExecutor::new(vec![failing_handler]);
685 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
686 let target = make_target("example.com", 80);
687 let result = executor.execute(&[hop], &target).await;
688
689 match result {
690 Err(ChainError::HandshakeFailed {
691 hop_index,
692 protocol,
693 source,
694 }) => {
695 assert_eq!(hop_index, 0);
696 assert_eq!(protocol, "Http");
697 assert_eq!(source.to_string(), "handshake timeout");
698 }
699 Err(e) => panic!("expected HandshakeFailed, got: {e}"),
700 Ok(_) => panic!("expected error"),
701 }
702
703 server_jh.abort();
704 }
705
706 #[tokio::test]
709 async fn test_domain_preserved_for_single_hop() {
710 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
711 let addr = listener.local_addr().unwrap();
712
713 let server_jh = tokio::spawn(async move {
714 let (_stream, _) = listener.accept().await.unwrap();
715 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
716 });
717
718 let (handler, captured) = MockHandler::new(ProtocolSpec::Socks5);
719 let executor = ChainExecutor::new(vec![Box::new(handler)]);
720
721 let hop = make_hop(ProtocolSpec::Socks5, &addr.ip().to_string(), addr.port());
722 let target = make_target("example.com", 443);
723 let result = executor.execute(&[hop], &target).await;
724
725 assert!(result.is_ok());
726
727 let captured_target = captured.lock().unwrap().take().unwrap();
728 assert_eq!(
729 captured_target.host,
730 TargetHost::Domain("example.com".to_string())
731 );
732 assert_eq!(captured_target.port, 443);
733
734 server_jh.abort();
735 }
736
737 #[tokio::test]
738 async fn test_domain_preserved_through_two_hops() {
739 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
740 let addr1 = listener1.local_addr().unwrap();
741
742 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
743 let addr2 = listener2.local_addr().unwrap();
744
745 let server_jh1 = tokio::spawn(async move {
746 let (_stream, _) = listener1.accept().await.unwrap();
747 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
748 });
749
750 let server_jh2 = tokio::spawn(async move {
751 let (_stream, _) = listener2.accept().await.unwrap();
752 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
753 });
754
755 let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
756 let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
757 let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
758
759 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
760 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
761 let target = make_target("example.com", 443);
762 let result = executor.execute(&[hop1, hop2], &target).await;
763
764 assert!(result.is_ok());
765
766 let target1 = captured1.lock().unwrap().take().unwrap();
767 assert_eq!(target1, make_ip_target(addr2.ip(), addr2.port()));
768
769 let target2 = captured2.lock().unwrap().take().unwrap();
770 assert_eq!(target2.host, TargetHost::Domain("example.com".to_string()));
771 assert_eq!(target2.port, 443);
772
773 server_jh1.abort();
774 server_jh2.abort();
775 }
776
777 #[tokio::test]
780 async fn test_single_hop_chain() {
781 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
782 let addr = listener.local_addr().unwrap();
783
784 let server_jh = tokio::spawn(async move {
785 let (_stream, _) = listener.accept().await.unwrap();
786 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
787 });
788
789 let (handler, captured) = MockHandler::new(ProtocolSpec::Http);
790 let executor = ChainExecutor::new(vec![Box::new(handler)]);
791
792 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
793 let target = make_target("destination.example.com", 443);
794 let result = executor.execute(&[hop], &target).await;
795
796 assert!(result.is_ok());
797
798 let captured_target = captured.lock().unwrap().take().unwrap();
799 assert_eq!(
800 captured_target.host,
801 TargetHost::Domain("destination.example.com".to_string())
802 );
803 assert_eq!(captured_target.port, 443);
804
805 server_jh.abort();
806 }
807
808 #[tokio::test]
809 async fn test_two_hop_chain() {
810 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
811 let addr1 = listener1.local_addr().unwrap();
812
813 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
814 let addr2 = listener2.local_addr().unwrap();
815
816 let server_jh1 = tokio::spawn(async move {
817 let (_stream, _) = listener1.accept().await.unwrap();
818 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
819 });
820
821 let server_jh2 = tokio::spawn(async move {
822 let (_stream, _) = listener2.accept().await.unwrap();
823 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
824 });
825
826 let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
827 let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
828 let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
829
830 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
831 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
832 let target = make_target("final.example.com", 443);
833 let result = executor.execute(&[hop1, hop2], &target).await;
834
835 assert!(result.is_ok());
836
837 let target1 = captured1.lock().unwrap().take().unwrap();
838 assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
839 assert_eq!(target1.port, addr2.port());
840
841 let target2 = captured2.lock().unwrap().take().unwrap();
842 assert_eq!(
843 target2.host,
844 TargetHost::Domain("final.example.com".to_string())
845 );
846 assert_eq!(target2.port, 443);
847
848 server_jh1.abort();
849 server_jh2.abort();
850 }
851
852 #[tokio::test]
853 async fn test_three_hop_chain() {
854 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
855 let addr1 = listener1.local_addr().unwrap();
856
857 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
858 let addr2 = listener2.local_addr().unwrap();
859
860 let listener3 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
861 let addr3 = listener3.local_addr().unwrap();
862
863 let server_jh1 = tokio::spawn(async move {
864 let (_stream, _) = listener1.accept().await.unwrap();
865 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
866 });
867
868 let server_jh2 = tokio::spawn(async move {
869 let (_stream, _) = listener2.accept().await.unwrap();
870 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
871 });
872
873 let server_jh3 = tokio::spawn(async move {
874 let (_stream, _) = listener3.accept().await.unwrap();
875 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
876 });
877
878 let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
879 let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
880 let (handler3, captured3) = MockHandler::new(ProtocolSpec::Socks4);
881 let executor = ChainExecutor::new(vec![
882 Box::new(handler1),
883 Box::new(handler2),
884 Box::new(handler3),
885 ]);
886
887 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
888 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
889 let hop3 = make_hop(ProtocolSpec::Socks4, &addr3.ip().to_string(), addr3.port());
890 let target = make_target("final.example.com", 8080);
891 let result = executor.execute(&[hop1, hop2, hop3], &target).await;
892
893 assert!(result.is_ok());
894
895 let target1 = captured1.lock().unwrap().take().unwrap();
896 assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
897 assert_eq!(target1.port, addr2.port());
898
899 let target2 = captured2.lock().unwrap().take().unwrap();
900 assert_eq!(target2.host, TargetHost::Ip(addr3.ip()));
901 assert_eq!(target2.port, addr3.port());
902
903 let target3 = captured3.lock().unwrap().take().unwrap();
904 assert_eq!(
905 target3.host,
906 TargetHost::Domain("final.example.com".to_string())
907 );
908 assert_eq!(target3.port, 8080);
909
910 server_jh1.abort();
911 server_jh2.abort();
912 server_jh3.abort();
913 }
914
915 #[tokio::test]
918 async fn test_credentials_passed_to_handler() {
919 use std::sync::Arc;
920
921 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
922 let addr = listener.local_addr().unwrap();
923
924 let server_jh = tokio::spawn(async move {
925 let (_stream, _) = listener.accept().await.unwrap();
926 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
927 });
928
929 struct CapturingHandler {
930 protocol: ProtocolSpec,
931 captured_creds: Arc<std::sync::Mutex<Option<CredentialSpec>>>,
932 }
933
934 impl HopHandler for CapturingHandler {
935 fn protocol(&self) -> ProtocolSpec {
936 self.protocol
937 }
938
939 fn handshake<'a>(
940 &'a self,
941 stream: BoxStream,
942 _target: &'a TargetAddr,
943 hop: &'a ProxyHopSpec,
944 _hop_index: usize,
945 ) -> HandshakeFuture<'a> {
946 Box::pin(async move {
947 if let Some(creds) = hop.credentials.as_ref() {
948 *self.captured_creds.lock().unwrap() = Some(creds.clone());
949 }
950 Ok(stream)
951 })
952 }
953 }
954
955 let captured_creds = Arc::new(std::sync::Mutex::new(None));
956 let handler: Box<dyn HopHandler> = Box::new(CapturingHandler {
957 protocol: ProtocolSpec::Http,
958 captured_creds: captured_creds.clone(),
959 });
960
961 let executor = ChainExecutor::new(vec![handler]);
962
963 let hop = make_hop_with_creds(
964 ProtocolSpec::Http,
965 &addr.ip().to_string(),
966 addr.port(),
967 "testuser",
968 "testpass",
969 );
970 let target = make_target("example.com", 80);
971 let result = executor.execute(&[hop], &target).await;
972
973 assert!(result.is_ok());
974
975 let creds = captured_creds.lock().unwrap().take().unwrap();
976 assert_eq!(creds.username, "testuser");
977 assert_eq!(creds.password, "testpass");
978
979 server_jh.abort();
980 }
981
982 #[tokio::test]
985 async fn test_handler_selection_first_matching() {
986 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
987 let addr = listener.local_addr().unwrap();
988
989 let server_jh = tokio::spawn(async move {
990 let (_stream, _) = listener.accept().await.unwrap();
991 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
992 });
993
994 let (handler, _) = MockHandler::new(ProtocolSpec::Http);
995 let executor = ChainExecutor::new(vec![Box::new(handler)]);
996
997 let hop = ProxyHopSpec {
998 protocols: vec![ProtocolSpec::Http, ProtocolSpec::Socks5],
999 endpoint: EndpointSpec {
1000 host: addr.ip().to_string(),
1001 port: addr.port(),
1002 },
1003 credentials: None,
1004 rule: None,
1005 local_bind: None,
1006 tls: false,
1007 server_name: None,
1008 insecure: false,
1009 plugins: Vec::new(),
1010 auth_prefix: None,
1011 };
1012 let target = make_target("example.com", 80);
1013 let result = executor.execute(&[hop], &target).await;
1014
1015 assert!(result.is_ok());
1016
1017 server_jh.abort();
1018 }
1019
1020 #[tokio::test]
1023 async fn test_error_identifies_failing_hop() {
1024 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1025 let addr1 = listener1.local_addr().unwrap();
1026
1027 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1028 let addr2 = listener2.local_addr().unwrap();
1029
1030 let server_jh1 = tokio::spawn(async move {
1031 let (_stream, _) = listener1.accept().await.unwrap();
1032 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1033 });
1034
1035 let server_jh2 = tokio::spawn(async move {
1036 let (_stream, _) = listener2.accept().await.unwrap();
1037 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1038 });
1039
1040 let (good_handler, _) = MockHandler::new(ProtocolSpec::Socks5);
1041 let bad_handler: Box<dyn HopHandler> = Box::new(FailingHandler {
1042 protocol: ProtocolSpec::Http,
1043 error_message: "proxy refused connection".to_string(),
1044 });
1045
1046 let executor = ChainExecutor::new(vec![Box::new(good_handler), bad_handler]);
1047
1048 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1049 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1050 let target = make_target("example.com", 80);
1051 let result = executor.execute(&[hop1, hop2], &target).await;
1052
1053 match result {
1054 Err(ChainError::HandshakeFailed {
1055 hop_index, source, ..
1056 }) => {
1057 assert_eq!(hop_index, 1, "error should identify hop 1 (second hop)");
1058 assert_eq!(source.to_string(), "proxy refused connection");
1059 }
1060 Err(e) => panic!("expected HandshakeFailed, got: {e}"),
1061 Ok(_) => panic!("expected error"),
1062 }
1063
1064 server_jh1.abort();
1065 server_jh2.abort();
1066 }
1067
1068 #[tokio::test]
1071 async fn test_ip_endpoint_resolved() {
1072 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1073 let addr = listener.local_addr().unwrap();
1074
1075 let server_jh = tokio::spawn(async move {
1076 let (_stream, _) = listener.accept().await.unwrap();
1077 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1078 });
1079
1080 let (handler, captured) = MockHandler::new(ProtocolSpec::Http);
1081 let executor = ChainExecutor::new(vec![Box::new(handler)]);
1082
1083 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1084 let target = make_ip_target("93.184.216.34".parse().unwrap(), 443);
1085 let result = executor.execute(&[hop], &target).await;
1086
1087 assert!(result.is_ok());
1088
1089 let captured_target = captured.lock().unwrap().take().unwrap();
1090 assert_eq!(
1091 captured_target.host,
1092 TargetHost::Ip("93.184.216.34".parse().unwrap())
1093 );
1094 assert_eq!(captured_target.port, 443);
1095
1096 server_jh.abort();
1097 }
1098
1099 #[tokio::test]
1102 async fn test_socks5_to_http_chain() {
1103 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1104 let addr1 = listener1.local_addr().unwrap();
1105
1106 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1107 let addr2 = listener2.local_addr().unwrap();
1108
1109 let server_jh1 = tokio::spawn(async move {
1110 let (_stream, _) = listener1.accept().await.unwrap();
1111 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1112 });
1113
1114 let server_jh2 = tokio::spawn(async move {
1115 let (_stream, _) = listener2.accept().await.unwrap();
1116 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1117 });
1118
1119 let (handler1, captured1) = MockHandler::new(ProtocolSpec::Socks5);
1120 let (handler2, captured2) = MockHandler::new(ProtocolSpec::Http);
1121 let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
1122
1123 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1124 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1125 let target = make_target("target.example.com", 8080);
1126 let result = executor.execute(&[hop1, hop2], &target).await;
1127
1128 assert!(result.is_ok());
1129
1130 let target1 = captured1.lock().unwrap().take().unwrap();
1131 assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
1132
1133 let target2 = captured2.lock().unwrap().take().unwrap();
1134 assert_eq!(
1135 target2.host,
1136 TargetHost::Domain("target.example.com".to_string())
1137 );
1138
1139 server_jh1.abort();
1140 server_jh2.abort();
1141 }
1142
1143 #[tokio::test]
1144 async fn test_http_to_socks5_chain() {
1145 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1146 let addr1 = listener1.local_addr().unwrap();
1147
1148 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1149 let addr2 = listener2.local_addr().unwrap();
1150
1151 let server_jh1 = tokio::spawn(async move {
1152 let (_stream, _) = listener1.accept().await.unwrap();
1153 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1154 });
1155
1156 let server_jh2 = tokio::spawn(async move {
1157 let (_stream, _) = listener2.accept().await.unwrap();
1158 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1159 });
1160
1161 let (handler1, captured1) = MockHandler::new(ProtocolSpec::Http);
1162 let (handler2, captured2) = MockHandler::new(ProtocolSpec::Socks5);
1163 let executor = ChainExecutor::new(vec![Box::new(handler1), Box::new(handler2)]);
1164
1165 let hop1 = make_hop(ProtocolSpec::Http, &addr1.ip().to_string(), addr1.port());
1166 let hop2 = make_hop(ProtocolSpec::Socks5, &addr2.ip().to_string(), addr2.port());
1167 let target = make_target("target.example.com", 443);
1168 let result = executor.execute(&[hop1, hop2], &target).await;
1169
1170 assert!(result.is_ok());
1171
1172 let target1 = captured1.lock().unwrap().take().unwrap();
1173 assert_eq!(target1.host, TargetHost::Ip(addr2.ip()));
1174
1175 let target2 = captured2.lock().unwrap().take().unwrap();
1176 assert_eq!(
1177 target2.host,
1178 TargetHost::Domain("target.example.com".to_string())
1179 );
1180
1181 server_jh1.abort();
1182 server_jh2.abort();
1183 }
1184
1185 #[tokio::test]
1186 async fn test_socks5_to_socks5_chain() {
1187 use std::sync::Arc;
1188
1189 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1190 let addr1 = listener1.local_addr().unwrap();
1191
1192 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1193 let addr2 = listener2.local_addr().unwrap();
1194
1195 let server_jh1 = tokio::spawn(async move {
1196 let (_stream, _) = listener1.accept().await.unwrap();
1197 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1198 });
1199
1200 let server_jh2 = tokio::spawn(async move {
1201 let (_stream, _) = listener2.accept().await.unwrap();
1202 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1203 });
1204
1205 struct MultiTargetRecorder {
1206 protocol: ProtocolSpec,
1207 targets: Arc<std::sync::Mutex<Vec<TargetAddr>>>,
1208 }
1209
1210 impl HopHandler for MultiTargetRecorder {
1211 fn protocol(&self) -> ProtocolSpec {
1212 self.protocol
1213 }
1214
1215 fn handshake<'a>(
1216 &'a self,
1217 stream: BoxStream,
1218 target: &'a TargetAddr,
1219 _hop: &'a ProxyHopSpec,
1220 _hop_index: usize,
1221 ) -> HandshakeFuture<'a> {
1222 let targets = self.targets.clone();
1223 let target_clone = target.clone();
1224 Box::pin(async move {
1225 targets.lock().unwrap().push(target_clone);
1226 Ok(stream)
1227 })
1228 }
1229 }
1230
1231 let targets = Arc::new(std::sync::Mutex::new(Vec::new()));
1232 let handler: Box<dyn HopHandler> = Box::new(MultiTargetRecorder {
1233 protocol: ProtocolSpec::Socks5,
1234 targets: targets.clone(),
1235 });
1236 let executor = ChainExecutor::new(vec![handler]);
1237
1238 let hop1 = make_hop(ProtocolSpec::Socks5, &addr1.ip().to_string(), addr1.port());
1239 let hop2 = make_hop(ProtocolSpec::Socks5, &addr2.ip().to_string(), addr2.port());
1240 let target = make_target("target.example.com", 443);
1241 let result = executor.execute(&[hop1, hop2], &target).await;
1242
1243 assert!(result.is_ok());
1244
1245 let captured_targets = targets.lock().unwrap();
1246 assert_eq!(captured_targets.len(), 2);
1247
1248 assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1250 assert_eq!(captured_targets[0].port, addr2.port());
1251
1252 assert_eq!(
1254 captured_targets[1].host,
1255 TargetHost::Domain("target.example.com".to_string())
1256 );
1257 assert_eq!(captured_targets[1].port, 443);
1258
1259 server_jh1.abort();
1260 server_jh2.abort();
1261 }
1262
1263 #[tokio::test]
1264 async fn test_http_to_http_chain() {
1265 use std::sync::Arc;
1266
1267 let listener1 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1268 let addr1 = listener1.local_addr().unwrap();
1269
1270 let listener2 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1271 let addr2 = listener2.local_addr().unwrap();
1272
1273 let server_jh1 = tokio::spawn(async move {
1274 let (_stream, _) = listener1.accept().await.unwrap();
1275 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1276 });
1277
1278 let server_jh2 = tokio::spawn(async move {
1279 let (_stream, _) = listener2.accept().await.unwrap();
1280 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1281 });
1282
1283 struct MultiTargetRecorder {
1285 protocol: ProtocolSpec,
1286 targets: Arc<std::sync::Mutex<Vec<TargetAddr>>>,
1287 }
1288
1289 impl HopHandler for MultiTargetRecorder {
1290 fn protocol(&self) -> ProtocolSpec {
1291 self.protocol
1292 }
1293
1294 fn handshake<'a>(
1295 &'a self,
1296 stream: BoxStream,
1297 target: &'a TargetAddr,
1298 _hop: &'a ProxyHopSpec,
1299 _hop_index: usize,
1300 ) -> HandshakeFuture<'a> {
1301 let targets = self.targets.clone();
1302 let target_clone = target.clone();
1303 Box::pin(async move {
1304 targets.lock().unwrap().push(target_clone);
1305 Ok(stream)
1306 })
1307 }
1308 }
1309
1310 let targets = Arc::new(std::sync::Mutex::new(Vec::new()));
1311 let handler: Box<dyn HopHandler> = Box::new(MultiTargetRecorder {
1312 protocol: ProtocolSpec::Http,
1313 targets: targets.clone(),
1314 });
1315 let executor = ChainExecutor::new(vec![handler]);
1316
1317 let hop1 = make_hop(ProtocolSpec::Http, &addr1.ip().to_string(), addr1.port());
1318 let hop2 = make_hop(ProtocolSpec::Http, &addr2.ip().to_string(), addr2.port());
1319 let target = make_target("target.example.com", 80);
1320 let result = executor.execute(&[hop1, hop2], &target).await;
1321
1322 assert!(result.is_ok());
1323
1324 let captured_targets = targets.lock().unwrap();
1325 assert_eq!(captured_targets.len(), 2);
1326
1327 assert_eq!(captured_targets[0].host, TargetHost::Ip(addr2.ip()));
1329 assert_eq!(captured_targets[0].port, addr2.port());
1330
1331 assert_eq!(
1333 captured_targets[1].host,
1334 TargetHost::Domain("target.example.com".to_string())
1335 );
1336 assert_eq!(captured_targets[1].port, 80);
1337
1338 server_jh1.abort();
1339 server_jh2.abort();
1340 }
1341
1342 #[test]
1345 fn test_validate_chain_valid() {
1346 let executor = ChainExecutor::new(vec![]);
1347 let chain = vec![
1348 make_hop(ProtocolSpec::Http, "127.0.0.1", 8080),
1349 make_hop(ProtocolSpec::Socks5, "127.0.0.1", 1080),
1350 ];
1351 assert!(executor.validate_chain(&chain).is_ok());
1352 }
1353
1354 #[test]
1355 fn test_validate_chain_empty_protocols() {
1356 let executor = ChainExecutor::new(vec![]);
1357 let chain = vec![ProxyHopSpec {
1358 protocols: vec![],
1359 endpoint: EndpointSpec {
1360 host: "127.0.0.1".to_string(),
1361 port: 8080,
1362 },
1363 credentials: None,
1364 rule: None,
1365 local_bind: None,
1366 tls: false,
1367 server_name: None,
1368 insecure: false,
1369 plugins: Vec::new(),
1370 auth_prefix: None,
1371 }];
1372 assert!(executor.validate_chain(&chain).is_err());
1373 }
1374
1375 #[test]
1376 fn test_validate_chain_empty_host() {
1377 let executor = ChainExecutor::new(vec![]);
1378 let chain = vec![make_hop(ProtocolSpec::Http, "", 8080)];
1379 assert!(executor.validate_chain(&chain).is_err());
1380 }
1381
1382 #[test]
1383 fn test_validate_chain_zero_port() {
1384 let executor = ChainExecutor::new(vec![]);
1385 let chain = vec![ProxyHopSpec {
1386 protocols: vec![ProtocolSpec::Http],
1387 endpoint: EndpointSpec {
1388 host: "127.0.0.1".to_string(),
1389 port: 0,
1390 },
1391 credentials: None,
1392 rule: None,
1393 local_bind: None,
1394 tls: false,
1395 server_name: None,
1396 insecure: false,
1397 plugins: Vec::new(),
1398 auth_prefix: None,
1399 }];
1400 assert!(executor.validate_chain(&chain).is_err());
1401 }
1402
1403 #[test]
1406 fn test_chain_error_display() {
1407 let err = ChainError::EmptyChain;
1408 assert_eq!(
1409 err.to_string(),
1410 "chain is empty, at least one hop is required"
1411 );
1412
1413 let err = ChainError::InvalidChain {
1414 reason: "test reason".to_string(),
1415 };
1416 assert_eq!(err.to_string(), "invalid chain: test reason");
1417
1418 let err = ChainError::ConnectFailed {
1419 hop_index: 0,
1420 endpoint: "127.0.0.1:8080".to_string(),
1421 source: ConnectError::ConnectionRefused,
1422 };
1423 assert!(err.to_string().contains("hop 0"));
1424 assert!(err.to_string().contains("127.0.0.1:8080"));
1425 assert!(err.to_string().contains("connection refused"));
1426 }
1427
1428 #[tokio::test]
1431 async fn test_tls_wrapper_called_when_hop_tls_true() {
1432 use std::sync::atomic::{AtomicBool, Ordering};
1433
1434 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1435 let addr = listener.local_addr().unwrap();
1436
1437 let server_jh = tokio::spawn(async move {
1438 let (_stream, _) = listener.accept().await.unwrap();
1439 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1440 });
1441
1442 let (_handler, _captured) = MockHandler::new(ProtocolSpec::Http);
1443 let executor = ChainExecutor::new(vec![Box::new(_handler)]);
1444
1445 let tls_called = Arc::new(AtomicBool::new(false));
1446 let tls_called_clone = tls_called.clone();
1447
1448 let tls_wrapper: TlsWrapper = Box::new(move |stream, _server_name, _alpn| {
1449 let called = tls_called_clone.clone();
1450 Box::pin(async move {
1451 called.store(true, Ordering::Relaxed);
1452 Ok(stream)
1454 })
1455 });
1456
1457 let executor = executor.with_tls_wrapper(tls_wrapper);
1458
1459 let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1460 hop.tls = true;
1461 hop.server_name = Some("test.example.com".to_string());
1462
1463 let target = make_target("example.com", 80);
1464 let result = executor.execute(&[hop], &target).await;
1465
1466 assert!(result.is_ok());
1467 assert!(
1468 tls_called.load(Ordering::Relaxed),
1469 "TLS wrapper should have been called"
1470 );
1471
1472 server_jh.abort();
1473 }
1474
1475 #[tokio::test]
1476 async fn test_tls_wrapper_not_called_when_hop_tls_false() {
1477 use std::sync::atomic::{AtomicBool, Ordering};
1478
1479 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1480 let addr = listener.local_addr().unwrap();
1481
1482 let server_jh = tokio::spawn(async move {
1483 let (_stream, _) = listener.accept().await.unwrap();
1484 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1485 });
1486
1487 let (_handler, _captured) = MockHandler::new(ProtocolSpec::Http);
1488 let executor = ChainExecutor::new(vec![Box::new(_handler)]);
1489
1490 let tls_called = Arc::new(AtomicBool::new(false));
1491 let tls_called_clone = tls_called.clone();
1492
1493 let tls_wrapper: TlsWrapper = Box::new(move |stream, _server_name, _alpn| {
1494 let called = tls_called_clone.clone();
1495 Box::pin(async move {
1496 called.store(true, Ordering::Relaxed);
1497 Ok(stream)
1498 })
1499 });
1500
1501 let executor = executor.with_tls_wrapper(tls_wrapper);
1502
1503 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1504 let target = make_target("example.com", 80);
1507 let result = executor.execute(&[hop], &target).await;
1508
1509 assert!(result.is_ok());
1510 assert!(
1511 !tls_called.load(Ordering::Relaxed),
1512 "TLS wrapper should NOT have been called"
1513 );
1514
1515 server_jh.abort();
1516 }
1517
1518 #[tokio::test]
1519 async fn test_tls_wrapper_uses_server_name_from_hop() {
1520 use std::sync::Mutex;
1521
1522 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1523 let addr = listener.local_addr().unwrap();
1524
1525 let server_jh = tokio::spawn(async move {
1526 let (_stream, _) = listener.accept().await.unwrap();
1527 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1528 });
1529
1530 let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1531 let executor = ChainExecutor::new(vec![Box::new(handler)]);
1532
1533 let captured_name = Arc::new(Mutex::new(None::<String>));
1534 let captured_name_clone = captured_name.clone();
1535
1536 let tls_wrapper: TlsWrapper = Box::new(move |stream, server_name, _alpn| {
1537 let captured = captured_name_clone.clone();
1538 Box::pin(async move {
1539 *captured.lock().unwrap() = Some(server_name);
1540 Ok(stream)
1541 })
1542 });
1543
1544 let executor = executor.with_tls_wrapper(tls_wrapper);
1545
1546 let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1547 hop.tls = true;
1548 hop.server_name = Some("custom-sni.example.com".to_string());
1549
1550 let target = make_target("example.com", 80);
1551 let result = executor.execute(&[hop], &target).await;
1552
1553 assert!(result.is_ok());
1554 let name = captured_name.lock().unwrap().take().unwrap();
1555 assert_eq!(name, "custom-sni.example.com");
1556
1557 server_jh.abort();
1558 }
1559
1560 #[tokio::test]
1561 async fn test_tls_wrapper_falls_back_to_endpoint_host() {
1562 use std::sync::Mutex;
1563
1564 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1565 let addr = listener.local_addr().unwrap();
1566
1567 let server_jh = tokio::spawn(async move {
1568 let (_stream, _) = listener.accept().await.unwrap();
1569 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1570 });
1571
1572 let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1573 let executor = ChainExecutor::new(vec![Box::new(handler)]);
1574
1575 let captured_name = Arc::new(Mutex::new(None::<String>));
1576 let captured_name_clone = captured_name.clone();
1577
1578 let tls_wrapper: TlsWrapper = Box::new(move |stream, server_name, _alpn| {
1579 let captured = captured_name_clone.clone();
1580 Box::pin(async move {
1581 *captured.lock().unwrap() = Some(server_name);
1582 Ok(stream)
1583 })
1584 });
1585
1586 let executor = executor.with_tls_wrapper(tls_wrapper);
1587
1588 let hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1590 let mut hop = hop;
1592 hop.tls = true;
1593 let target = make_target("example.com", 80);
1596 let result = executor.execute(&[hop], &target).await;
1597
1598 assert!(result.is_ok());
1599 let name = captured_name.lock().unwrap().take().unwrap();
1600 assert_eq!(name, addr.ip().to_string());
1601
1602 server_jh.abort();
1603 }
1604
1605 #[tokio::test]
1606 async fn test_tls_failure_propagates_as_handshake_error() {
1607 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1608 let addr = listener.local_addr().unwrap();
1609
1610 let server_jh = tokio::spawn(async move {
1611 let (_stream, _) = listener.accept().await.unwrap();
1612 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
1613 });
1614
1615 let (handler, _) = MockHandler::new(ProtocolSpec::Http);
1616 let executor = ChainExecutor::new(vec![Box::new(handler)]);
1617
1618 let tls_wrapper: TlsWrapper = Box::new(|_stream, _server_name, _alpn| {
1619 Box::pin(async move {
1620 Err(Box::<dyn std::error::Error + Send + Sync>::from(
1621 "TLS handshake failed: certificate rejected",
1622 ))
1623 })
1624 });
1625
1626 let executor = executor.with_tls_wrapper(tls_wrapper);
1627
1628 let mut hop = make_hop(ProtocolSpec::Http, &addr.ip().to_string(), addr.port());
1629 hop.tls = true;
1630
1631 let target = make_target("example.com", 80);
1632 let result = executor.execute(&[hop], &target).await;
1633
1634 match result {
1635 Err(ChainError::HandshakeFailed {
1636 hop_index,
1637 protocol,
1638 source,
1639 }) => {
1640 assert_eq!(hop_index, 0);
1641 assert_eq!(protocol, "tls");
1642 assert!(source.to_string().contains("TLS handshake failed"));
1643 }
1644 Err(e) => panic!("expected HandshakeFailed, got: {e}"),
1645 Ok(_) => panic!("expected error"),
1646 }
1647
1648 server_jh.abort();
1649 }
1650}