1pub mod http;
18pub mod l4;
19#[cfg(feature = "any_tls")]
20mod tls;
21
22#[cfg(not(feature = "any_tls"))]
23use crate::tls::connectors as tls;
24
25use crate::protocols::l4::ext::attach_connect_local_addr;
26use crate::protocols::{GetSocketDigest, Stream};
27use crate::server::configuration::ServerConf;
28use crate::upstreams::peer::{Peer, ALPN};
29
30use crate::offload::OffloadRuntime;
31pub use l4::Connect as L4Connect;
32use l4::{connect as l4_connect, BindTo};
33use log::{debug, error, warn};
34use parking_lot::RwLock;
35use pingora_error::{Error, ErrorType::*, OrErr, Result};
36use pingora_pool::{ConnectionMeta, ConnectionPool};
37use std::collections::HashMap;
38use std::net::SocketAddr;
39use std::sync::atomic::{AtomicU64, Ordering};
40use std::sync::Arc;
41use std::time::{Duration, Instant};
42use tls::TlsConnector;
43use tokio::sync::Mutex;
44
45#[derive(Clone, Debug)]
46pub(crate) struct IdleConnection {
47 connection: ConnectionMeta,
48 idle_since: Instant,
49}
50
51impl IdleConnection {
52 pub(crate) fn new(connection: ConnectionMeta) -> Self {
53 Self {
54 connection,
55 idle_since: Instant::now(),
56 }
57 }
58
59 fn elapsed(&self) -> Duration {
60 self.idle_since.elapsed()
61 }
62}
63
64pub type PoolCallback = Arc<dyn Fn(Duration) + Send + Sync>;
67
68#[derive(Clone)]
70pub struct ConnectorOptions {
71 pub ca_file: Option<String>,
76 #[cfg(feature = "s2n")]
82 pub s2n_config_cache_size: Option<usize>,
83 pub cert_key_file: Option<(String, String)>,
87 pub debug_ssl_keylog: bool,
91 pub keepalive_pool_size: usize,
97 pub offload_threadpool: Option<(usize, usize)>,
106 pub bind_to_v4: Vec<SocketAddr>,
108 pub bind_to_v6: Vec<SocketAddr>,
110 pub keepalive_pool_callback: Option<PoolCallback>,
113}
114
115impl ConnectorOptions {
116 pub fn from_server_conf(server_conf: &ServerConf) -> Self {
118 let bind_to_v4 = server_conf
121 .client_bind_to_ipv4
122 .iter()
123 .map(|v4| {
124 let ip = v4.parse().unwrap();
125 SocketAddr::new(ip, 0)
126 })
127 .collect();
128
129 let bind_to_v6 = server_conf
130 .client_bind_to_ipv6
131 .iter()
132 .map(|v6| {
133 let ip = v6.parse().unwrap();
134 SocketAddr::new(ip, 0)
135 })
136 .collect();
137 ConnectorOptions {
138 ca_file: server_conf.ca_file.clone(),
139 cert_key_file: None, #[cfg(feature = "s2n")]
141 s2n_config_cache_size: server_conf.s2n_config_cache_size,
142 debug_ssl_keylog: server_conf.upstream_debug_ssl_keylog,
143 keepalive_pool_size: server_conf
144 .upstream_keepalive_pool_size
145 .saturating_mul(server_conf.threads.max(1)),
146 offload_threadpool: server_conf.upstream_connect_offload_threadpool(),
147 bind_to_v4,
148 bind_to_v6,
149 keepalive_pool_callback: None,
150 }
151 }
152
153 pub fn new(keepalive_pool_size: usize) -> Self {
155 ConnectorOptions {
156 ca_file: None,
157 #[cfg(feature = "s2n")]
158 s2n_config_cache_size: None,
159 cert_key_file: None,
160 debug_ssl_keylog: false,
161 keepalive_pool_size,
162 offload_threadpool: None,
163 bind_to_v4: vec![],
164 bind_to_v6: vec![],
165 keepalive_pool_callback: None,
166 }
167 }
168}
169
170pub struct TransportConnector {
172 tls_ctx: tls::Connector,
173 connection_pool: Arc<ConnectionPool<Arc<Mutex<Stream>>>>,
174 offload: Option<OffloadRuntime>,
175 bind_to_v4: Vec<SocketAddr>,
176 bind_to_v6: Vec<SocketAddr>,
177 preferred_http_version: PreferredHttpVersion,
178 unexpected_data_conn_count: Arc<AtomicU64>,
181 keepalive_pool_callback: Option<PoolCallback>,
182}
183
184const DEFAULT_POOL_SIZE: usize = 128;
185
186impl TransportConnector {
187 pub fn new(mut options: Option<ConnectorOptions>) -> Self {
189 let pool_size = options
190 .as_ref()
191 .map_or(DEFAULT_POOL_SIZE, |c| c.keepalive_pool_size);
192 let offload = options.as_mut().and_then(|o| o.offload_threadpool.take());
195 let bind_to_v4 = options
196 .as_ref()
197 .map_or_else(Vec::new, |o| o.bind_to_v4.clone());
198 let bind_to_v6 = options
199 .as_ref()
200 .map_or_else(Vec::new, |o| o.bind_to_v6.clone());
201 let keepalive_pool_callback = options
202 .as_ref()
203 .and_then(|o| o.keepalive_pool_callback.clone());
204 TransportConnector {
205 tls_ctx: tls::Connector::new(options),
206 connection_pool: Arc::new(ConnectionPool::new(pool_size)),
207 offload: offload.map(|v| OffloadRuntime::new("upstream connect offload", v.0, v.1)),
208 bind_to_v4,
209 bind_to_v6,
210 preferred_http_version: PreferredHttpVersion::new(),
211 unexpected_data_conn_count: Arc::new(AtomicU64::new(0)),
212 keepalive_pool_callback,
213 }
214 }
215
216 pub async fn new_stream<P: Peer + Send + Sync + 'static>(&self, peer: &P) -> Result<Stream> {
220 let rt = self
221 .offload
222 .as_ref()
223 .map(|o| o.get_runtime(peer.reuse_hash()));
224 let bind_to = l4::bind_to_random(peer, &self.bind_to_v4, &self.bind_to_v6);
225 let alpn_override = self.preferred_http_version.get(peer);
226 let stream = if let Some(rt) = rt {
227 let peer = peer.clone();
228 let tls_ctx = self.tls_ctx.clone();
229 let offload_start = Instant::now();
230 rt.spawn(async move {
231 let offload_wait = offload_start.elapsed();
232 do_connect(
233 &peer,
234 bind_to,
235 alpn_override,
236 &tls_ctx.ctx,
237 Some(offload_wait),
238 )
239 .await
240 })
241 .await
242 .or_err(InternalError, "offload runtime failure")??
243 } else {
244 do_connect(peer, bind_to, alpn_override, &self.tls_ctx.ctx, None).await?
245 };
246
247 Ok(stream)
248 }
249
250 pub async fn reused_stream<P: Peer + Send + Sync>(&self, peer: &P) -> Option<Stream> {
252 match self.connection_pool.get(&peer.reuse_hash()) {
253 Some(s) => {
254 debug!("find reusable stream, trying to acquire it");
255 {
256 let _ = s.lock().await;
257 } match Arc::try_unwrap(s) {
259 Ok(l) => {
260 let mut stream = l.into_inner();
261 #[cfg(unix)]
264 if peer.matches_fd(stream.id())
265 && test_reusable_stream(&mut stream, &self.unexpected_data_conn_count)
266 {
267 Some(stream)
268 } else {
269 None
270 }
271 #[cfg(windows)]
272 {
273 use std::os::windows::io::{AsRawSocket, RawSocket};
274 struct WrappedRawSocket(RawSocket);
275 impl AsRawSocket for WrappedRawSocket {
276 fn as_raw_socket(&self) -> RawSocket {
277 self.0
278 }
279 }
280 if peer.matches_sock(WrappedRawSocket(stream.id() as RawSocket))
281 && test_reusable_stream(
282 &mut stream,
283 &self.unexpected_data_conn_count,
284 )
285 {
286 Some(stream)
287 } else {
288 None
289 }
290 }
291 }
292 Err(_) => {
293 error!("failed to acquire reusable stream");
294 None
295 }
296 }
297 }
298 None => {
299 debug!("No reusable connection found for {peer}");
300 None
301 }
302 }
303 }
304
305 pub fn release_stream(
313 &self,
314 mut stream: Stream,
315 key: u64, idle_timeout: Option<Duration>,
317 ) {
318 if !test_reusable_stream(&mut stream, &self.unexpected_data_conn_count) {
319 return;
320 }
321 let id = stream.id();
322 let meta = ConnectionMeta::new(key, id);
323 debug!("Try to keepalive client session");
324 let stream = Arc::new(Mutex::new(stream));
325 let locked_stream = stream.clone().try_lock_owned().unwrap(); let (notify_close, watch_use) = self.connection_pool.put(&meta, stream);
327 let idle_meta = IdleConnection::new(meta);
328 let pool = self.connection_pool.clone(); let keepalive_pool_callback = self.keepalive_pool_callback.clone();
330 let rt = pingora_runtime::current_handle();
331 rt.spawn(async move {
332 if pool
333 .idle_poll(
334 locked_stream,
335 &idle_meta.connection,
336 idle_timeout,
337 notify_close,
338 watch_use,
339 )
340 .await
341 {
342 if let Some(callback) = keepalive_pool_callback {
343 callback(idle_meta.elapsed());
344 }
345 }
346 });
347 }
348
349 pub async fn get_stream<P: Peer + Send + Sync + 'static>(
356 &self,
357 peer: &P,
358 ) -> Result<(Stream, bool)> {
359 let reused_stream = self.reused_stream(peer).await;
360 if let Some(s) = reused_stream {
361 Ok((s, true))
362 } else {
363 let s = self.new_stream(peer).await?;
364 Ok((s, false))
365 }
366 }
367
368 pub fn prefer_h1(&self, peer: &impl Peer) {
370 self.preferred_http_version.add(peer, 1);
371 }
372
373 pub fn unexpected_data_connection_count(&self) -> u64 {
376 self.unexpected_data_conn_count.load(Ordering::Relaxed)
377 }
378
379 pub fn unexpected_data_connection_counter(&self) -> Arc<AtomicU64> {
385 self.unexpected_data_conn_count.clone()
386 }
387}
388
389async fn do_connect<P: Peer + Send + Sync>(
392 peer: &P,
393 bind_to: Option<BindTo>,
394 alpn_override: Option<ALPN>,
395 tls_ctx: &TlsConnector,
396 offload_wait_duration: Option<Duration>,
397) -> Result<Stream> {
398 let connect_future =
401 do_connect_inner(peer, bind_to, alpn_override, tls_ctx, offload_wait_duration);
402
403 match peer.total_connection_timeout() {
404 Some(t) => match pingora_timeout::timeout(t, connect_future).await {
405 Ok(res) => res,
406 Err(_) => Error::e_explain(
407 ConnectTimedout,
408 format!("connecting to server {peer}, total-connection timeout {t:?}"),
409 ),
410 },
411 None => connect_future.await,
412 }
413}
414
415async fn do_connect_inner<P: Peer + Send + Sync>(
417 peer: &P,
418 bind_to: Option<BindTo>,
419 alpn_override: Option<ALPN>,
420 tls_ctx: &TlsConnector,
421 offload_wait_duration: Option<Duration>,
422) -> Result<Stream> {
423 let l4_connect_start = Instant::now();
424 let mut stream = l4_connect(peer, bind_to).await?;
425 stream.set_establishment_timing(l4_connect_start.elapsed(), offload_wait_duration);
426 if peer.tls() {
427 let local_addr = stream
431 .get_socket_digest()
432 .and_then(|digest| digest.local_addr().and_then(|addr| addr.as_inet()).copied());
433 let tls_stream = tls::connect(stream, peer, alpn_override, tls_ctx)
434 .await
435 .map_err(|e| attach_connect_local_addr(e, local_addr))?;
436 Ok(Box::new(tls_stream))
437 } else {
438 Ok(Box::new(stream))
439 }
440}
441
442struct PreferredHttpVersion {
443 versions: RwLock<HashMap<u64, u8>>, }
446
447impl PreferredHttpVersion {
450 pub fn new() -> Self {
451 PreferredHttpVersion {
452 versions: RwLock::default(),
453 }
454 }
455
456 pub fn add(&self, peer: &impl Peer, version: u8) {
457 let key = peer.reuse_hash();
458 let mut v = self.versions.write();
459 v.insert(key, version);
460 }
461
462 pub fn get(&self, peer: &impl Peer) -> Option<ALPN> {
463 let key = peer.reuse_hash();
464 let v = self.versions.read();
465 v.get(&key)
466 .copied()
467 .map(|v| if v == 1 { ALPN::H1 } else { ALPN::H2H1 })
468 }
469}
470
471use futures::future::FutureExt;
472use tokio::io::AsyncReadExt;
473
474fn test_reusable_stream(stream: &mut Stream, unexpected_data_conn_count: &AtomicU64) -> bool {
476 let mut buf = [0; 1];
477 let result = tokio::task::unconstrained(stream.read(&mut buf[..])).now_or_never();
479 if let Some(data_result) = result {
480 match data_result {
481 Ok(n) => {
482 if n == 0 {
483 debug!("Idle connection is closed");
484 } else {
485 warn!("Unexpected data read in idle connection");
486 unexpected_data_conn_count.fetch_add(1, Ordering::Relaxed);
487 }
488 }
489 Err(e) => {
490 debug!("Idle connection is broken: {e:?}");
491 }
492 }
493 false
494 } else {
495 true
496 }
497}
498
499#[cfg(all(test, unix))]
501pub(crate) mod test_utils {
502 use tokio::io::AsyncWriteExt;
503 use tokio::net::UnixListener;
504
505 pub fn unique_uds_path(test_name: &str) -> String {
507 format!(
508 "/tmp/test_{test_name}_{:?}_{}.sock",
509 std::thread::current().id(),
510 std::process::id()
511 )
512 }
513
514 pub fn spawn_mock_uds_server(
521 socket_path: String,
522 response: &'static [u8],
523 ) -> (
524 tokio::sync::oneshot::Receiver<()>,
525 tokio::sync::oneshot::Sender<()>,
526 tokio::task::JoinHandle<()>,
527 ) {
528 let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();
529 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
530
531 let server_handle = tokio::spawn(async move {
532 let _ = std::fs::remove_file(&socket_path);
533 let listener = UnixListener::bind(&socket_path).unwrap();
534 let _ = ready_tx.send(());
536
537 if let Ok((mut stream, _addr)) = listener.accept().await {
538 let _ = stream.write_all(response).await;
539 let _ = shutdown_rx.await;
541 }
542 let _ = std::fs::remove_file(&socket_path);
543 });
544
545 (ready_rx, shutdown_tx, server_handle)
546 }
547
548 pub fn spawn_mock_uds_server_close_immediate(
552 socket_path: String,
553 ) -> (
554 tokio::sync::oneshot::Receiver<()>,
555 tokio::sync::oneshot::Sender<()>,
556 tokio::task::JoinHandle<()>,
557 ) {
558 let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();
559 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
560
561 let server_handle = tokio::spawn(async move {
562 let _ = std::fs::remove_file(&socket_path);
563 let listener = UnixListener::bind(&socket_path).unwrap();
564 let _ = ready_tx.send(());
566
567 if let Ok((mut stream, _addr)) = listener.accept().await {
568 let _ = stream.shutdown().await;
569 let _ = shutdown_rx.await;
571 }
572 let _ = std::fs::remove_file(&socket_path);
573 });
574
575 (ready_rx, shutdown_tx, server_handle)
576 }
577}
578
579#[cfg(test)]
580#[cfg(feature = "any_tls")]
581mod tests {
582 use std::time::Duration;
583
584 use pingora_error::ErrorType;
585 use tls::Connector;
586
587 use super::*;
588 use crate::upstreams::peer::BasicPeer;
589
590 #[tokio::test]
591 async fn test_connect() {
592 let connector = TransportConnector::new(None);
593 let peer = BasicPeer::new("1.1.1.1:80");
594 let stream = connector.new_stream(&peer).await.unwrap();
596 let timing = stream.get_timing_digest();
597 assert_eq!(timing.len(), 1);
598 let l4_timing = timing[0].as_ref().unwrap();
599 assert!(l4_timing.establishment_duration.is_some());
600 assert!(l4_timing.offload_wait_duration.is_none());
601 connector.release_stream(stream, peer.reuse_hash(), None);
602
603 let (_, reused) = connector.get_stream(&peer).await.unwrap();
604 assert!(reused);
605 }
606
607 #[tokio::test]
608 async fn test_connect_with_offload_timing() {
609 let mut conf = ConnectorOptions::new(1);
610 conf.offload_threadpool = Some((1, 1));
611 let connector = TransportConnector::new(Some(conf));
612 let peer = BasicPeer::new("1.1.1.1:80");
613
614 let stream = connector.new_stream(&peer).await.unwrap();
615 let timing = stream.get_timing_digest();
616 let l4_timing = timing[0].as_ref().unwrap();
617 assert!(l4_timing.establishment_duration.is_some());
618 assert!(l4_timing.offload_wait_duration.is_some());
619 }
620
621 #[tokio::test]
622 async fn test_connect_tls() {
623 let connector = TransportConnector::new(None);
624 let mut peer = BasicPeer::new("1.1.1.1:443");
625 peer.sni = "one.one.one.one".to_string();
627 let stream = connector.new_stream(&peer).await.unwrap();
629 let timing = stream.get_timing_digest();
630 assert_eq!(timing.len(), 2);
631 let l4_timing = timing[0].as_ref().unwrap();
632 assert!(l4_timing.establishment_duration.is_some());
633 assert!(l4_timing.offload_wait_duration.is_none());
634 let tls_timing = timing[1].as_ref().unwrap();
635 assert!(tls_timing.establishment_duration.is_some());
636 assert!(tls_timing.offload_wait_duration.is_none());
637 connector.release_stream(stream, peer.reuse_hash(), None);
638
639 let (_, reused) = connector.get_stream(&peer).await.unwrap();
640 assert!(reused);
641 }
642
643 #[tokio::test(flavor = "multi_thread")]
644 #[cfg(unix)]
645 async fn test_connect_uds() {
646 let socket_path = test_utils::unique_uds_path("transport_connector");
647 let (ready_rx, shutdown_tx, server_handle) =
648 test_utils::spawn_mock_uds_server(socket_path.clone(), b"it works!");
649
650 ready_rx.await.unwrap();
652
653 let connector = TransportConnector::new(None);
655 let peer = BasicPeer::new_uds(&socket_path).unwrap();
656 let mut stream = connector.new_stream(&peer).await.unwrap();
658 let mut buf = [0; 9];
659 stream.read_exact(&mut buf).await.unwrap();
660 assert_eq!(&buf, b"it works!");
661
662 connector.release_stream(stream, peer.reuse_hash(), None);
664 let (stream, reused) = connector.get_stream(&peer).await.unwrap();
665 assert!(reused);
666
667 drop(stream);
669 let _ = shutdown_tx.send(());
670 server_handle.await.unwrap();
671 }
672
673 const BLACKHOLE: &str = "192.0.2.1:79";
677
678 async fn do_test_conn_timeout(conf: Option<ConnectorOptions>) {
679 let connector = TransportConnector::new(conf);
680 let mut peer = BasicPeer::new(BLACKHOLE);
681 peer.options.connection_timeout = Some(Duration::from_millis(1));
682 let Err(e) = connector.new_stream(&peer).await else {
683 panic!("should throw an error");
684 };
685 assert!(
686 e.etype() == &ConnectTimedout || e.etype() == &ConnectNoRoute,
687 "unexpected error type: {:?}",
688 e.etype()
689 );
690 }
691
692 #[tokio::test]
693 async fn test_conn_timeout() {
694 do_test_conn_timeout(None).await;
695 }
696
697 #[tokio::test]
698 async fn test_conn_timeout_with_offload() {
699 let mut conf = ConnectorOptions::new(8);
700 conf.offload_threadpool = Some((2, 2));
701 do_test_conn_timeout(Some(conf)).await;
702 }
703
704 #[tokio::test]
705 async fn test_connector_bind_to() {
706 let peer = BasicPeer::new("240.0.0.1:80");
708 let mut conf = ConnectorOptions::new(1);
709 conf.bind_to_v4.push("127.0.0.1:0".parse().unwrap());
710 let connector = TransportConnector::new(Some(conf));
711
712 let stream = connector.new_stream(&peer).await;
713 let error = stream.unwrap_err();
714 assert!(
718 error.etype() == &ConnectError
719 || error.etype() == &ConnectTimedout
720 || error.etype() == &ConnectNoRoute,
721 "unexpected error type: {:?}",
722 error.etype()
723 )
724 }
725
726 async fn get_do_connect_failure_with_peer(peer: &BasicPeer) -> (ErrorType, String) {
730 let tls_connector = Connector::new(None);
731 let stream = do_connect(peer, None, None, &tls_connector.ctx, None).await;
732 match stream {
733 Ok(_) => panic!("should throw an error"),
734 Err(e) => (
735 e.etype().clone(),
736 e.context
737 .as_ref()
738 .map(|ctx| ctx.as_str().to_owned())
739 .unwrap_or_default(),
740 ),
741 }
742 }
743
744 #[tokio::test]
745 async fn test_do_connect_with_total_timeout() {
746 let mut peer = BasicPeer::new(BLACKHOLE);
747 peer.options.total_connection_timeout = Some(Duration::from_millis(1));
748 let (etype, context) = get_do_connect_failure_with_peer(&peer).await;
749 assert!(
750 etype == ConnectTimedout || etype == ConnectNoRoute,
751 "unexpected error type: {etype:?}"
752 );
753 if etype == ConnectTimedout {
754 assert!(context.contains("total-connection timeout"));
755 }
756 }
757
758 #[tokio::test]
759 async fn test_tls_connect_timeout_supersedes_total() {
760 let mut peer = BasicPeer::new(BLACKHOLE);
761 peer.options.total_connection_timeout = Some(Duration::from_millis(10));
762 peer.options.connection_timeout = Some(Duration::from_millis(1));
763 let (etype, context) = get_do_connect_failure_with_peer(&peer).await;
764 assert!(
765 etype == ConnectTimedout || etype == ConnectNoRoute,
766 "unexpected error type: {etype:?}"
767 );
768 if etype == ConnectTimedout {
769 assert!(!context.contains("total-connection timeout"));
770 }
771 }
772
773 #[tokio::test]
774 async fn test_do_connect_without_total_timeout() {
775 let peer = BasicPeer::new(BLACKHOLE);
776 let (etype, context) = get_do_connect_failure_with_peer(&peer).await;
777 assert!(etype != ConnectTimedout || !context.contains("total-connection timeout"));
778 }
779
780 #[tokio::test]
781 async fn test_tls_handshake_failure_records_local_addr() {
782 use crate::connectors::l4::ConnectErrorExt;
783
784 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
787 let server_addr = listener.local_addr().unwrap();
788 tokio::spawn(async move {
789 while let Ok((stream, _)) = listener.accept().await {
790 drop(stream);
791 }
792 });
793
794 let mut peer = BasicPeer::new(&server_addr.to_string());
796 peer.sni = "openrusty.org".to_string();
797 peer.options.connection_timeout = Some(Duration::from_secs(5));
800
801 let tls_connector = Connector::new(None);
802 let err = do_connect(&peer, None, None, &tls_connector.ctx, None)
803 .await
804 .unwrap_err();
805
806 let local_addr = err
807 .connect_local_addr()
808 .expect("TLS handshake failure should retain the assigned local address");
809 assert_ne!(local_addr.port(), 0);
810 }
811
812 #[tokio::test]
813 async fn test_unexpected_data_connection_count_increments() {
814 let (mut server, client) = tokio::io::duplex(64);
816
817 let counter = AtomicU64::new(0);
818 let mut stream: Stream = Box::new(client);
819
820 assert!(test_reusable_stream(&mut stream, &counter));
822 assert_eq!(counter.load(Ordering::Relaxed), 0);
823
824 use tokio::io::AsyncWriteExt;
826 server.write_all(b"unexpected").await.unwrap();
827
828 tokio::task::yield_now().await;
830
831 assert!(!test_reusable_stream(&mut stream, &counter));
833 assert_eq!(
834 counter.load(Ordering::Relaxed),
835 1,
836 "unexpected_data_connection_count should have incremented"
837 );
838 }
839}