1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
5use std::sync::{Arc, LazyLock, Mutex};
6use std::task::{Context, Poll};
7use std::time::{Duration, Instant};
8
9use base64::Engine;
10use bytes::Bytes;
11#[cfg(test)]
12use h2::server::Connection;
13use tokio::io::{AsyncReadExt, AsyncWriteExt};
14#[cfg(test)]
15use tokio::net::TcpStream;
16use tokio::sync::{Notify, Semaphore};
17use tokio_util::sync::CancellationToken;
18
19use crate::error::HttpError;
20use eggress_core::connector::{ConnectOptions, DirectConnector};
21use eggress_core::{BoxStream, ConnectError, TargetAddr};
22
23const H2_RELAY_DRAIN_TIMEOUT: Duration = Duration::from_secs(5);
24
25pub struct H2ProtocolMetrics {
30 pub connections_opened: AtomicU64,
31 pub connections_closed: AtomicU64,
32 pub streams_opened: AtomicU64,
33 pub streams_closed: AtomicU64,
34 pub goaway_received: AtomicU64,
35 pub handshake_failures: AtomicU64,
36 pub auth_failures: AtomicU64,
37 pub flow_control_stalls: AtomicU64,
38 pub pool_exhausted: AtomicU64,
39 pub bytes_relayed: AtomicU64,
40}
41
42impl H2ProtocolMetrics {
43 pub const fn new() -> Self {
44 Self {
45 connections_opened: AtomicU64::new(0),
46 connections_closed: AtomicU64::new(0),
47 streams_opened: AtomicU64::new(0),
48 streams_closed: AtomicU64::new(0),
49 goaway_received: AtomicU64::new(0),
50 handshake_failures: AtomicU64::new(0),
51 auth_failures: AtomicU64::new(0),
52 flow_control_stalls: AtomicU64::new(0),
53 pool_exhausted: AtomicU64::new(0),
54 bytes_relayed: AtomicU64::new(0),
55 }
56 }
57}
58
59impl Default for H2ProtocolMetrics {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65pub static H2_PROTOCOL_METRICS: LazyLock<Arc<H2ProtocolMetrics>> =
67 LazyLock::new(|| Arc::new(H2ProtocolMetrics::new()));
68
69#[derive(Debug, thiserror::Error)]
70pub enum H2ConnectError {
71 #[error("IO error: {0}")]
72 Io(#[from] std::io::Error),
73 #[error("H2 protocol error: {0}")]
74 H2(String),
75 #[error("HTTP error: {0}")]
76 Http(#[from] HttpError),
77 #[error("pool exhausted: no connections available and pool at capacity")]
78 PoolExhausted,
79 #[error("DNS rebinding detected: target resolved to reserved/private address {0}")]
80 DnsRebinding(std::net::IpAddr),
81}
82
83impl From<h2::Error> for H2ConnectError {
84 fn from(e: h2::Error) -> Self {
85 H2ConnectError::H2(e.to_string())
86 }
87}
88
89pub struct H2StreamWrite {
90 send_stream: h2::SendStream<Bytes>,
91 capacity: usize,
92}
93
94impl H2StreamWrite {
95 pub fn new(send_stream: h2::SendStream<Bytes>) -> Self {
96 Self {
97 send_stream,
98 capacity: 0,
99 }
100 }
101}
102
103impl tokio::io::AsyncWrite for H2StreamWrite {
104 fn poll_write(
105 mut self: Pin<&mut Self>,
106 cx: &mut Context<'_>,
107 buf: &[u8],
108 ) -> Poll<Result<usize, std::io::Error>> {
109 if self.capacity == 0 {
110 self.send_stream.reserve_capacity(buf.len());
111 match self.send_stream.poll_capacity(cx) {
112 Poll::Ready(Some(Ok(capacity))) => {
113 if capacity == 0 {
114 H2_PROTOCOL_METRICS
119 .flow_control_stalls
120 .fetch_add(1, Ordering::Relaxed);
121 return Poll::Pending;
122 }
123 self.capacity = capacity;
124 }
125 Poll::Ready(Some(Err(e))) => {
126 return Poll::Ready(Err(std::io::Error::other(e)));
127 }
128 Poll::Ready(None) => {
129 return Poll::Ready(Err(std::io::Error::other("h2 stream closed")));
130 }
131 Poll::Pending => {
132 H2_PROTOCOL_METRICS
133 .flow_control_stalls
134 .fetch_add(1, Ordering::Relaxed);
135 return Poll::Pending;
136 }
137 }
138 }
139
140 let len = buf.len().min(self.capacity);
141 self.send_stream
142 .send_data(Bytes::copy_from_slice(&buf[..len]), false)
143 .map_err(std::io::Error::other)?;
144 self.capacity -= len;
145 Poll::Ready(Ok(len))
146 }
147
148 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
149 Poll::Ready(Ok(()))
150 }
151
152 fn poll_shutdown(
153 mut self: Pin<&mut Self>,
154 _cx: &mut Context<'_>,
155 ) -> Poll<Result<(), std::io::Error>> {
156 self.send_stream
157 .send_data(Bytes::new(), true)
158 .map_err(std::io::Error::other)?;
159 Poll::Ready(Ok(()))
160 }
161}
162
163pub async fn h2_connect_relay(
169 mut recv_stream: h2::RecvStream,
170 send_stream: h2::SendStream<Bytes>,
171 target: TargetAddr,
172) -> Result<(), H2ConnectError> {
173 let tcp: BoxStream = DirectConnector
174 .connect_with_options(
175 &target,
176 &ConnectOptions {
177 enforce_dns_rebinding_check: true,
178 enforce_literal_ip_check: true,
179 ..ConnectOptions::default()
180 },
181 )
182 .await
183 .map_err(|error| match error {
184 ConnectError::ReservedTarget(ip) => H2ConnectError::DnsRebinding(ip),
185 ConnectError::Io(error) => H2ConnectError::Io(error),
186 error => H2ConnectError::H2(error.to_string()),
187 })?;
188 let (mut tcp_read, mut tcp_write) = tokio::io::split(tcp);
189 let mut h2_write = H2StreamWrite::new(send_stream);
190
191 let h2_to_tcp = async move {
192 loop {
193 match recv_stream.data().await {
194 Some(Ok(data)) => {
195 let len = data.len();
196 tcp_write.write_all(&data).await?;
197 H2_PROTOCOL_METRICS
198 .bytes_relayed
199 .fetch_add(len as u64, Ordering::Relaxed);
200 }
201 Some(Err(e)) => {
202 return Err(std::io::Error::other(e));
203 }
204 None => break,
205 }
206 }
207 Ok::<(), std::io::Error>(())
208 };
209
210 let tcp_to_h2 = async {
211 let mut buf = [0u8; 65536];
212 loop {
213 let n = tcp_read.read(&mut buf).await?;
214 if n == 0 {
215 h2_write.shutdown().await?;
216 break;
217 }
218 h2_write.write_all(&buf[..n]).await?;
219 H2_PROTOCOL_METRICS
220 .bytes_relayed
221 .fetch_add(n as u64, Ordering::Relaxed);
222 }
223 Ok::<(), std::io::Error>(())
224 };
225
226 let h2_task = tokio::spawn(h2_to_tcp);
227 let tcp_result = tcp_to_h2.await;
228 let mut h2_task = h2_task;
229 let h2_result = match tokio::time::timeout(H2_RELAY_DRAIN_TIMEOUT, &mut h2_task).await {
230 Ok(result) => {
231 result.map_err(|error| H2ConnectError::H2(format!("H2 relay task failed: {error}")))?
232 }
233 Err(_) => {
234 tracing::warn!(
235 "H2 relay drain timed out after target close; aborting h2->tcp direction"
236 );
237 h2_task.abort();
238 let _ = h2_task.await;
239 tcp_result?;
243 return Ok(());
244 }
245 };
246
247 h2_result?;
248 tcp_result?;
249 Ok(())
250}
251
252#[cfg(test)]
260pub(crate) async fn handle_h2_connect(
261 mut connection: Connection<TcpStream, Bytes>,
262) -> Result<(), H2ConnectError> {
263 loop {
264 match connection.accept().await {
265 Some(Ok((request, mut send_response))) => {
266 if *request.method() == http::Method::CONNECT {
267 let authority = request
268 .uri()
269 .authority()
270 .ok_or_else(|| H2ConnectError::H2("missing authority".into()))?;
271
272 let target_str = match authority.port_u16() {
273 Some(port) => format!("{}:{}", authority.host(), port),
274 None => format!("{}:443", authority.host()),
275 };
276
277 let target: TargetAddr = target_str
278 .parse()
279 .map_err(|e: String| H2ConnectError::H2(e))?;
280
281 let response = http::Response::builder()
282 .status(200)
283 .body(())
284 .expect("static response builds");
285
286 let send_stream = send_response.send_response(response, false)?;
287 let recv_stream = request.into_body();
288
289 tokio::spawn(async move {
290 if let Err(e) = h2_connect_relay(recv_stream, send_stream, target).await {
291 tracing::warn!("h2 connect relay error: {}", e);
292 }
293 });
294 } else {
295 send_response.send_reset(h2::Reason::PROTOCOL_ERROR);
296 }
297 }
298 Some(Err(e)) => {
299 return Err(H2ConnectError::H2(e.to_string()));
300 }
301 None => break,
302 }
303 }
304 Ok(())
305}
306
307pub struct H2StreamRead {
308 recv: h2::RecvStream,
309 buffer: Bytes,
310}
311
312impl H2StreamRead {
313 pub fn new(recv: h2::RecvStream) -> Self {
314 Self {
315 recv,
316 buffer: Bytes::new(),
317 }
318 }
319}
320
321impl tokio::io::AsyncRead for H2StreamRead {
322 fn poll_read(
323 self: std::pin::Pin<&mut Self>,
324 cx: &mut Context<'_>,
325 buf: &mut tokio::io::ReadBuf<'_>,
326 ) -> Poll<std::io::Result<()>> {
327 let this = self.get_mut();
328
329 if !this.buffer.is_empty() {
330 let len = this.buffer.len().min(buf.remaining());
331 buf.put_slice(&this.buffer.split_to(len));
332 this.recv
333 .flow_control()
334 .release_capacity(len)
335 .map_err(std::io::Error::other)?;
336 return Poll::Ready(Ok(()));
337 }
338
339 let poll = {
340 let mut data_fut = Box::pin(this.recv.data());
341 data_fut.as_mut().poll(cx)
342 };
343 match poll {
344 Poll::Ready(Some(Ok(data))) => {
345 let len = data.len().min(buf.remaining());
346 buf.put_slice(&data[..len]);
347 if len < data.len() {
348 this.buffer = data.slice(len..);
349 }
350 this.recv
351 .flow_control()
352 .release_capacity(len)
353 .map_err(std::io::Error::other)?;
354 Poll::Ready(Ok(()))
355 }
356 Poll::Ready(Some(Err(e))) => Poll::Ready(Err(std::io::Error::other(e))),
357 Poll::Ready(None) => Poll::Ready(Ok(())),
358 Poll::Pending => Poll::Pending,
359 }
360 }
361}
362
363pub async fn h2_connect_client<S>(
372 stream: S,
373 target: &TargetAddr,
374 auth: Option<(&str, &str)>,
375) -> Result<
376 (
377 h2::SendStream<Bytes>,
378 h2::RecvStream,
379 tokio::task::JoinHandle<Result<(), h2::Error>>,
380 ),
381 H2ConnectError,
382>
383where
384 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
385{
386 let (mut send_request, conn) = h2::client::handshake(stream).await?;
387
388 let conn_handle = tokio::spawn(async move {
389 conn.await?;
390 Ok(())
391 });
392
393 let authority = match target.port {
394 443 => target.host.to_string(),
395 port => format!("{}:{}", target.host, port),
396 };
397
398 let mut builder = http::Request::builder()
399 .method(http::Method::CONNECT)
400 .uri(&authority)
401 .header(http::header::HOST, &authority);
402
403 if let Some((user, pass)) = auth {
404 let credentials = format!("{}:{}", user, pass);
405 let encoded = base64::engine::general_purpose::STANDARD.encode(credentials);
406 builder = builder.header(
407 http::header::PROXY_AUTHORIZATION,
408 format!("Basic {}", encoded),
409 );
410 }
411
412 let request = builder
413 .body(())
414 .map_err(|e| H2ConnectError::H2(e.to_string()))?;
415
416 let (response_future, send_stream) = send_request.send_request(request, false)?;
417
418 let response = response_future.await?;
419 if response.status() != http::StatusCode::OK {
420 return Err(H2ConnectError::H2(format!(
421 "CONNECT rejected with status {}",
422 response.status()
423 )));
424 }
425
426 let recv_stream = response.into_body();
427 Ok((send_stream, recv_stream, conn_handle))
428}
429
430#[derive(Debug, Clone, Eq, PartialEq, Hash)]
438pub struct H2PoolKey {
439 pub endpoint_host: String,
440 pub endpoint_port: u16,
441 pub use_tls: bool,
442 pub server_name: Option<String>,
443 pub auth_hash: Option<[u8; 32]>,
448 pub hop_index: usize,
449}
450
451impl H2PoolKey {
452 pub fn new(
453 host: &str,
454 port: u16,
455 use_tls: bool,
456 server_name: Option<&str>,
457 auth: Option<(&str, &str)>,
458 ) -> Self {
459 Self::with_hop_index(host, port, use_tls, server_name, auth, 0)
460 }
461
462 pub fn with_hop_index(
464 host: &str,
465 port: u16,
466 use_tls: bool,
467 server_name: Option<&str>,
468 auth: Option<(&str, &str)>,
469 hop_index: usize,
470 ) -> Self {
471 let auth_hash = auth.map(|(u, p)| {
472 use sha2::{Digest, Sha256};
473 let mut hasher = Sha256::new();
474 hasher.update(u.as_bytes());
475 hasher.update([0]);
476 hasher.update(p.as_bytes());
477 hasher.finalize().into()
478 });
479 Self {
480 endpoint_host: host.to_string(),
481 endpoint_port: port,
482 use_tls,
483 server_name: server_name.map(|s| s.to_string()),
484 auth_hash,
485 hop_index,
486 }
487 }
488}
489
490pub struct H2ConnectionEntry {
492 sender: Arc<Mutex<h2::client::SendRequest<Bytes>>>,
495 conn_handle: tokio::task::JoinHandle<Result<(), h2::Error>>,
496 #[allow(dead_code)]
497 created_at: Instant,
498 last_used: AtomicU64,
499 active_streams: AtomicU64,
500 retired: AtomicBool,
501 notify: Notify,
502}
503
504impl H2ConnectionEntry {
505 fn try_acquire(&self, max_concurrent_streams: u32) -> bool {
506 if self.retired.load(Ordering::Acquire) {
507 return false;
508 }
509 let mut active = self.active_streams.load(Ordering::Acquire);
510 loop {
511 if active >= max_concurrent_streams as u64 {
512 return false;
513 }
514 match self.active_streams.compare_exchange_weak(
515 active,
516 active + 1,
517 Ordering::AcqRel,
518 Ordering::Acquire,
519 ) {
520 Ok(_) => {
521 if self.retired.load(Ordering::Acquire) {
522 self.active_streams.fetch_sub(1, Ordering::Release);
523 return false;
524 }
525 return true;
526 }
527 Err(next) => active = next,
528 }
529 }
530 }
531
532 fn mark_retired(&self) {
533 self.retired.store(true, Ordering::Release);
534 }
535}
536
537impl Drop for H2ConnectionEntry {
538 fn drop(&mut self) {
539 H2_PROTOCOL_METRICS
540 .connections_closed
541 .fetch_add(1, Ordering::Relaxed);
542 self.conn_handle.abort();
543 }
544}
545
546pub struct H2ConnectionPool {
548 entries: Mutex<Vec<Arc<H2ConnectionEntry>>>,
551 semaphore: Semaphore,
552 pool_size: u32,
553 idle_timeout: Duration,
554 max_concurrent_streams: u32,
555 created_at: Instant,
556 reaper_running: AtomicBool,
557 reaper_cancel: CancellationToken,
558}
559
560impl H2ConnectionPool {
561 pub fn new(pool_size: u32, idle_timeout: Duration, max_concurrent_streams: u32) -> Arc<Self> {
562 Arc::new(Self {
563 entries: Mutex::new(Vec::new()),
564 semaphore: Semaphore::new(pool_size as usize),
565 pool_size,
566 idle_timeout,
567 max_concurrent_streams,
568 created_at: Instant::now(),
569 reaper_running: AtomicBool::new(false),
570 reaper_cancel: CancellationToken::new(),
571 })
572 }
573
574 fn now_ticks(&self) -> u64 {
575 Instant::now()
576 .duration_since(self.created_at)
577 .as_nanos()
578 .min(u64::MAX as u128) as u64
579 }
580
581 fn try_acquire_entry(&self) -> Option<Arc<H2ConnectionEntry>> {
583 let now = self.now_ticks();
584 let idle_timeout = self.idle_timeout.as_nanos().min(u64::MAX as u128) as u64;
585 let entries: Vec<Arc<H2ConnectionEntry>> = self
588 .entries
589 .lock()
590 .unwrap_or_else(|e| e.into_inner())
591 .iter()
592 .cloned()
593 .collect();
594 for entry in &entries {
595 if now.saturating_sub(entry.last_used.load(Ordering::Acquire)) < idle_timeout
596 && entry.try_acquire(self.max_concurrent_streams)
597 {
598 entry.last_used.store(now, Ordering::Release);
599 return Some(Arc::clone(entry));
600 }
601 }
602 None
603 }
604
605 async fn create_entry<S>(
607 self: &Arc<Self>,
608 stream: S,
609 ) -> Result<Arc<H2ConnectionEntry>, H2ConnectError>
610 where
611 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
612 {
613 let (send_request, conn) = h2::client::handshake(stream).await?;
614
615 let conn_handle = tokio::spawn(async move {
616 conn.await?;
617 Ok(())
618 });
619
620 let sender = Arc::new(Mutex::new(send_request));
621 let entry = Arc::new(H2ConnectionEntry {
622 sender: Arc::clone(&sender),
623 conn_handle,
624 created_at: Instant::now(),
625 last_used: AtomicU64::new(self.now_ticks()),
626 active_streams: AtomicU64::new(1),
627 retired: AtomicBool::new(false),
628 notify: Notify::new(),
629 });
630
631 self.entries
632 .lock()
633 .unwrap_or_else(|e| e.into_inner())
634 .push(Arc::clone(&entry));
635 H2_PROTOCOL_METRICS
636 .connections_opened
637 .fetch_add(1, Ordering::Relaxed);
638 self.maybe_start_reaper();
639 Ok(entry)
640 }
641
642 fn maybe_start_reaper(self: &Arc<Self>) {
643 if self
644 .reaper_running
645 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
646 .is_err()
647 {
648 return;
649 }
650 let pool = Arc::downgrade(self);
651 let cancel = self.reaper_cancel.clone();
652 let interval = std::cmp::max(self.idle_timeout / 2, Duration::from_millis(1));
653 tokio::spawn(async move {
654 loop {
655 tokio::select! {
656 _ = cancel.cancelled() => break,
657 _ = tokio::time::sleep(interval) => {
658 let Some(pool) = pool.upgrade() else { break };
659 pool.reap_idle_entries();
660 }
661 }
662 }
663 });
664 }
665
666 fn reap_idle_entries(&self) {
667 let now = self.now_ticks();
668 let idle_timeout = self.idle_timeout.as_nanos().min(u64::MAX as u128) as u64;
669 self.entries
670 .lock()
671 .unwrap_or_else(|e| e.into_inner())
672 .retain(|entry| {
673 if entry.retired.load(Ordering::Acquire) {
674 return false;
675 }
676 if now.saturating_sub(entry.last_used.load(Ordering::Acquire)) >= idle_timeout
677 && entry.active_streams.load(Ordering::Acquire) == 0
678 {
679 entry.mark_retired();
680 return false;
681 }
682 true
683 });
684 }
685
686 fn is_empty(&self) -> bool {
687 self.entries
688 .lock()
689 .unwrap_or_else(|e| e.into_inner())
690 .is_empty()
691 }
692
693 pub fn release(&self, entry: &Arc<H2ConnectionEntry>) {
695 entry.active_streams.fetch_sub(1, Ordering::AcqRel);
696 entry.last_used.store(self.now_ticks(), Ordering::Release);
697 entry.notify.notify_waiters();
698 }
699
700 pub fn retire(&self, entry: &Arc<H2ConnectionEntry>) {
702 entry.mark_retired();
703 }
704
705 pub fn stats(&self) -> H2PoolStats {
707 let entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
708 let active = entries
709 .iter()
710 .filter(|e| !e.retired.load(Ordering::Acquire))
711 .count();
712 let total_streams: u64 = entries
713 .iter()
714 .map(|e| e.active_streams.load(Ordering::Acquire))
715 .sum();
716 H2PoolStats {
717 pool_size: self.pool_size,
718 active_connections: active as u32,
719 total_streams,
720 idle_timeout_secs: self.idle_timeout.as_secs(),
721 }
722 }
723}
724
725impl Drop for H2ConnectionPool {
726 fn drop(&mut self) {
727 self.reaper_cancel.cancel();
728 }
729}
730
731#[derive(Debug, Clone)]
733pub struct H2PoolStats {
734 pub pool_size: u32,
735 pub active_connections: u32,
736 pub total_streams: u64,
737 pub idle_timeout_secs: u64,
738}
739
740pub struct H2PoolRegistry {
742 pools: std::sync::RwLock<HashMap<H2PoolKey, Arc<H2ConnectionPool>>>,
745 default_pool_size: u32,
746 default_idle_timeout: Duration,
747 default_max_concurrent_streams: u32,
748}
749
750impl H2PoolRegistry {
751 pub fn new() -> Self {
752 Self {
753 pools: std::sync::RwLock::new(HashMap::new()),
754 default_pool_size: 4,
755 default_idle_timeout: Duration::from_secs(60),
756 default_max_concurrent_streams: 100,
757 }
758 }
759
760 pub fn get_or_create(&self, key: &H2PoolKey) -> Arc<H2ConnectionPool> {
762 let mut pools = self.pools.write().unwrap_or_else(|e| e.into_inner());
763 if pools.len() >= 64 {
764 pools.retain(|_, pool| Arc::strong_count(pool) > 1 || !pool.is_empty());
765 }
766 pools
767 .entry(key.clone())
768 .or_insert_with(|| {
769 H2ConnectionPool::new(
770 self.default_pool_size,
771 self.default_idle_timeout,
772 self.default_max_concurrent_streams,
773 )
774 })
775 .clone()
776 }
777
778 pub fn prune_idle_pools(&self) {
783 let mut pools = self.pools.write().unwrap_or_else(|e| e.into_inner());
784 if pools.len() < 64 {
785 return;
786 }
787 pools.retain(|_, pool| Arc::strong_count(pool) > 1 || !pool.is_empty());
788 }
789
790 pub fn clear(&self) {
792 self.pools
793 .write()
794 .unwrap_or_else(|e| e.into_inner())
795 .clear();
796 }
797
798 pub fn with_defaults(
800 pool_size: u32,
801 idle_timeout: Duration,
802 max_concurrent_streams: u32,
803 ) -> Self {
804 Self {
805 pools: std::sync::RwLock::new(HashMap::new()),
806 default_pool_size: pool_size,
807 default_idle_timeout: idle_timeout,
808 default_max_concurrent_streams: max_concurrent_streams,
809 }
810 }
811}
812
813impl Default for H2PoolRegistry {
814 fn default() -> Self {
815 Self::new()
816 }
817}
818
819pub static H2_POOL_REGISTRY: LazyLock<H2PoolRegistry> = LazyLock::new(H2PoolRegistry::new);
821
822pub struct H2PoolGuard {
824 entry: Arc<H2ConnectionEntry>,
825 pool: Arc<H2ConnectionPool>,
826}
827
828impl Drop for H2PoolGuard {
829 fn drop(&mut self) {
830 H2_PROTOCOL_METRICS
831 .streams_closed
832 .fetch_add(1, Ordering::Relaxed);
833 self.pool.release(&self.entry);
834 }
835}
836
837impl H2PoolGuard {
838 pub fn retire(&self) {
840 H2_PROTOCOL_METRICS
841 .goaway_received
842 .fetch_add(1, Ordering::Relaxed);
843 H2_PROTOCOL_METRICS
844 .connections_closed
845 .fetch_add(1, Ordering::Relaxed);
846 self.pool.retire(&self.entry);
847 }
848
849 pub fn sender(&self) -> &Arc<Mutex<h2::client::SendRequest<Bytes>>> {
851 &self.entry.sender
852 }
853}
854
855pub async fn h2_connect_client_pooled<S>(
861 stream: S,
862 target: &TargetAddr,
863 auth: Option<(&str, &str)>,
864 pool_key: &H2PoolKey,
865) -> Result<(h2::SendStream<Bytes>, h2::RecvStream, H2PoolGuard), H2ConnectError>
866where
867 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
868{
869 H2_POOL_REGISTRY.prune_idle_pools();
870 let pool = H2_POOL_REGISTRY.get_or_create(pool_key);
871
872 if let Some(result) = try_pooled_connection(&pool, target, auth).await {
874 return result;
875 }
876
877 let _permit = pool.semaphore.acquire().await.map_err(|_| {
881 H2_PROTOCOL_METRICS
882 .pool_exhausted
883 .fetch_add(1, Ordering::Relaxed);
884 H2ConnectError::PoolExhausted
885 })?;
886
887 let entry = pool.create_entry(stream).await?;
888
889 let authority = match target.port {
890 443 => target.host.to_string(),
891 port => format!("{}:{}", target.host, port),
892 };
893
894 let mut builder = http::Request::builder()
895 .method(http::Method::CONNECT)
896 .uri(&authority)
897 .header(http::header::HOST, &authority);
898
899 if let Some((user, pass)) = auth {
900 let credentials = format!("{}:{}", user, pass);
901 let encoded = base64::engine::general_purpose::STANDARD.encode(credentials);
902 builder = builder.header(
903 http::header::PROXY_AUTHORIZATION,
904 format!("Basic {}", encoded),
905 );
906 }
907
908 let request = builder
909 .body(())
910 .map_err(|e| H2ConnectError::H2(e.to_string()))?;
911
912 let (response_future, send_stream) = {
913 let mut sender = entry.sender.lock().unwrap_or_else(|e| e.into_inner());
914 sender.send_request(request, false)?
915 };
916
917 let response = response_future.await?;
918 if response.status() != http::StatusCode::OK {
919 pool.retire(&entry);
920 if response.status() == http::StatusCode::PROXY_AUTHENTICATION_REQUIRED {
921 H2_PROTOCOL_METRICS
922 .auth_failures
923 .fetch_add(1, Ordering::Relaxed);
924 }
925 return Err(H2ConnectError::H2(format!(
926 "CONNECT rejected with status {}",
927 response.status()
928 )));
929 }
930
931 let recv_stream = response.into_body();
932 H2_PROTOCOL_METRICS
933 .streams_opened
934 .fetch_add(1, Ordering::Relaxed);
935 let guard = H2PoolGuard {
936 entry: Arc::clone(&entry),
937 pool: Arc::clone(&pool),
938 };
939 Ok((send_stream, recv_stream, guard))
940}
941
942async fn try_pooled_connection(
945 pool: &Arc<H2ConnectionPool>,
946 target: &TargetAddr,
947 auth: Option<(&str, &str)>,
948) -> Option<Result<(h2::SendStream<Bytes>, h2::RecvStream, H2PoolGuard), H2ConnectError>> {
949 let entry = pool.try_acquire_entry()?;
950
951 let authority = match target.port {
952 443 => target.host.to_string(),
953 port => format!("{}:{}", target.host, port),
954 };
955
956 let mut builder = http::Request::builder()
957 .method(http::Method::CONNECT)
958 .uri(&authority)
959 .header(http::header::HOST, &authority);
960
961 if let Some((user, pass)) = auth {
962 let credentials = format!("{}:{}", user, pass);
963 let encoded = base64::engine::general_purpose::STANDARD.encode(credentials);
964 builder = builder.header(
965 http::header::PROXY_AUTHORIZATION,
966 format!("Basic {}", encoded),
967 );
968 }
969
970 let request = match builder.body(()) {
971 Ok(r) => r,
972 Err(e) => return Some(Err(H2ConnectError::H2(e.to_string()))),
973 };
974
975 let result = {
976 let mut sender = entry.sender.lock().unwrap_or_else(|e| e.into_inner());
977 sender.send_request(request, false)
978 };
979
980 match result {
981 Ok((response_future, send_stream)) => {
982 let response = match response_future.await {
983 Ok(r) => r,
984 Err(e) => {
985 entry.active_streams.fetch_sub(1, Ordering::AcqRel);
988 pool.retire(&entry);
989 return Some(Err(e.into()));
990 }
991 };
992 if response.status() != http::StatusCode::OK {
993 entry.active_streams.fetch_sub(1, Ordering::AcqRel);
994 pool.retire(&entry);
995 if response.status() == http::StatusCode::PROXY_AUTHENTICATION_REQUIRED {
996 H2_PROTOCOL_METRICS
997 .auth_failures
998 .fetch_add(1, Ordering::Relaxed);
999 }
1000 return Some(Err(H2ConnectError::H2(format!(
1001 "CONNECT rejected with status {}",
1002 response.status()
1003 ))));
1004 }
1005 H2_PROTOCOL_METRICS
1006 .streams_opened
1007 .fetch_add(1, Ordering::Relaxed);
1008 let recv_stream = response.into_body();
1009 let guard = H2PoolGuard {
1010 entry: Arc::clone(&entry),
1011 pool: Arc::clone(pool),
1012 };
1013 Some(Ok((send_stream, recv_stream, guard)))
1014 }
1015 Err(_) => {
1016 entry.active_streams.fetch_sub(1, Ordering::AcqRel);
1018 pool.retire(&entry);
1019 None
1020 }
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_h2_connect_error_display() {
1030 let err = H2ConnectError::Io(std::io::Error::new(
1031 std::io::ErrorKind::ConnectionRefused,
1032 "test",
1033 ));
1034 assert!(err.to_string().contains("IO error"));
1035 }
1036
1037 #[test]
1038 fn test_h2_connect_error_from_h2() {
1039 let err = H2ConnectError::H2("test error".into());
1040 assert_eq!(err.to_string(), "H2 protocol error: test error");
1041 }
1042
1043 #[test]
1044 fn test_h2_connect_error_display_variants() {
1045 let err = H2ConnectError::Io(std::io::Error::new(
1046 std::io::ErrorKind::BrokenPipe,
1047 "broken",
1048 ));
1049 assert!(err.to_string().contains("broken"));
1050
1051 let err = H2ConnectError::H2("stream reset".into());
1052 assert!(err.to_string().contains("stream reset"));
1053 }
1054
1055 #[test]
1056 fn test_h2_connect_error_from_std_io() {
1057 let io_err = std::io::Error::other("test io");
1058 let err: H2ConnectError = io_err.into();
1059 assert!(matches!(err, H2ConnectError::Io(_)));
1060 }
1061
1062 #[test]
1063 fn test_h2_connect_error_pool_exhausted() {
1064 let err = H2ConnectError::PoolExhausted;
1065 assert!(err.to_string().contains("pool exhausted"));
1066 }
1067
1068 #[test]
1069 fn test_pool_key_equality() {
1070 let k1 = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
1071 let k2 = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
1072 assert_eq!(k1, k2);
1073
1074 let k3 = H2PoolKey::new("127.0.0.1", 8080, true, None, None);
1075 assert_ne!(k1, k3);
1076
1077 let k4 = H2PoolKey::new("127.0.0.1", 8080, false, Some("sni.example.com"), None);
1078 assert_ne!(k1, k4);
1079 }
1080
1081 #[test]
1082 fn test_pool_key_auth_hash() {
1083 let k1 = H2PoolKey::new("h", 1, false, None, Some(("u", "p")));
1084 let k2 = H2PoolKey::new("h", 1, false, None, Some(("u", "p")));
1085 let k3 = H2PoolKey::new("h", 1, false, None, Some(("u", "q")));
1086 assert_eq!(k1, k2);
1087 assert_ne!(k1, k3);
1088 }
1089
1090 #[test]
1091 fn test_pool_stats() {
1092 let pool = H2ConnectionPool::new(4, Duration::from_secs(60), 100);
1093 let stats = pool.stats();
1094 assert_eq!(stats.pool_size, 4);
1095 assert_eq!(stats.active_connections, 0);
1096 assert_eq!(stats.total_streams, 0);
1097 }
1098
1099 #[test]
1100 fn test_pool_registry_get_or_create() {
1101 let registry = H2PoolRegistry::new();
1102 let key = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
1103 let p1 = registry.get_or_create(&key);
1104 let p2 = registry.get_or_create(&key);
1105 assert!(Arc::ptr_eq(&p1, &p2));
1106
1107 let key2 = H2PoolKey::new("127.0.0.1", 9090, false, None, None);
1108 let p3 = registry.get_or_create(&key2);
1109 assert!(!Arc::ptr_eq(&p1, &p3));
1110 }
1111
1112 #[test]
1113 fn test_pool_registry_prunes_idle_and_clears() {
1114 let registry = H2PoolRegistry::new();
1115 let key = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
1116 let pool = registry.get_or_create(&key);
1117 drop(pool);
1118 registry.prune_idle_pools();
1121 assert!(!registry.pools.read().unwrap().is_empty());
1122 registry.clear();
1123 assert!(registry.pools.read().unwrap().is_empty());
1124
1125 let _pool = registry.get_or_create(&key);
1126 registry.clear();
1127 assert!(registry.pools.read().unwrap().is_empty());
1128 }
1129
1130 #[test]
1131 fn test_pool_key_isolates_different_auth_credentials() {
1132 let k_user_a = H2PoolKey::new(
1133 "proxy.example.com",
1134 443,
1135 true,
1136 None,
1137 Some(("alice", "secret")),
1138 );
1139 let k_user_b = H2PoolKey::new(
1140 "proxy.example.com",
1141 443,
1142 true,
1143 None,
1144 Some(("bob", "secret")),
1145 );
1146 let k_no_auth = H2PoolKey::new("proxy.example.com", 443, true, None, None);
1147
1148 assert_ne!(
1149 k_user_a, k_user_b,
1150 "different users must produce different pool keys"
1151 );
1152 assert_ne!(
1153 k_user_a, k_no_auth,
1154 "auth vs no-auth must produce different pool keys"
1155 );
1156 assert_ne!(k_user_b, k_no_auth);
1157
1158 let registry = H2PoolRegistry::new();
1159 let p1 = registry.get_or_create(&k_user_a);
1160 let p2 = registry.get_or_create(&k_user_b);
1161 let p3 = registry.get_or_create(&k_no_auth);
1162 assert!(!Arc::ptr_eq(&p1, &p2));
1163 assert!(!Arc::ptr_eq(&p1, &p3));
1164 assert!(!Arc::ptr_eq(&p2, &p3));
1165 }
1166
1167 #[test]
1168 fn test_pool_key_isolates_tls_vs_plaintext() {
1169 let k_tls = H2PoolKey::new(
1170 "proxy.example.com",
1171 443,
1172 true,
1173 Some("proxy.example.com"),
1174 None,
1175 );
1176 let k_plain = H2PoolKey::new(
1177 "proxy.example.com",
1178 443,
1179 false,
1180 Some("proxy.example.com"),
1181 None,
1182 );
1183 assert_ne!(
1184 k_tls, k_plain,
1185 "TLS vs plaintext must produce different pool keys"
1186 );
1187 }
1188
1189 #[test]
1190 fn test_pool_key_isolates_server_name() {
1191 let k_sni_a = H2PoolKey::new("1.2.3.4", 443, true, Some("a.example.com"), None);
1192 let k_sni_b = H2PoolKey::new("1.2.3.4", 443, true, Some("b.example.com"), None);
1193 assert_ne!(
1194 k_sni_a, k_sni_b,
1195 "different SNI must produce different pool keys"
1196 );
1197 }
1198
1199 #[tokio::test]
1200 async fn test_handle_h2_connect_accepts() {
1201 let server_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1202 let server_addr = server_listener.local_addr().unwrap();
1203
1204 let server_handle = tokio::spawn(async move {
1205 let (stream, _) = server_listener.accept().await.unwrap();
1206 let conn = h2::server::handshake(stream).await.unwrap();
1207 handle_h2_connect(conn).await.ok();
1208 });
1209
1210 let client_stream = TcpStream::connect(server_addr).await.unwrap();
1211 let (mut send_request, conn) = h2::client::handshake(client_stream).await.unwrap();
1212
1213 let conn_handle = tokio::spawn(async move {
1214 conn.await.ok();
1215 });
1216
1217 let request = http::Request::builder()
1218 .method(http::Method::CONNECT)
1219 .uri("127.0.0.1:9999")
1220 .body(())
1221 .unwrap();
1222
1223 let (response_future, _send_stream) = send_request.send_request(request, true).unwrap();
1224
1225 let response = tokio::time::timeout(std::time::Duration::from_secs(3), response_future)
1226 .await
1227 .unwrap()
1228 .unwrap();
1229 assert_eq!(response.status(), http::StatusCode::OK);
1230
1231 drop(send_request);
1232 drop(_send_stream);
1233 conn_handle.abort();
1234 server_handle.abort();
1235 }
1236
1237 #[tokio::test]
1238 async fn test_h2_connect_relay_rejects_reserved_literal_target() {
1239 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1240 let address = listener.local_addr().unwrap();
1241
1242 let server_handle = tokio::spawn(async move {
1243 let (stream, _) = listener.accept().await.unwrap();
1244 let mut connection = h2::server::handshake(stream).await.unwrap();
1245 let (request, mut response) = connection.accept().await.unwrap().unwrap();
1246 let send_stream = response
1247 .send_response(
1248 http::Response::builder().status(200).body(()).unwrap(),
1249 false,
1250 )
1251 .unwrap();
1252 h2_connect_relay(
1253 request.into_body(),
1254 send_stream,
1255 "127.0.0.1:1".parse().unwrap(),
1256 )
1257 .await
1258 });
1259
1260 let client_stream = TcpStream::connect(address).await.unwrap();
1261 let (mut send_request, connection) = h2::client::handshake(client_stream).await.unwrap();
1262 let connection_handle = tokio::spawn(async move { connection.await.ok() });
1263 let request = http::Request::builder()
1264 .method(http::Method::CONNECT)
1265 .uri("127.0.0.1:1")
1266 .body(())
1267 .unwrap();
1268 let (response, _send_stream) = send_request.send_request(request, true).unwrap();
1269 let _ = response.await;
1270
1271 assert!(matches!(
1272 server_handle.await.unwrap(),
1273 Err(H2ConnectError::DnsRebinding(_))
1274 ));
1275 connection_handle.abort();
1276 }
1277
1278 }