1use std::collections::HashMap;
2use std::future::Future;
3use std::hash::{Hash, Hasher};
4use std::pin::Pin;
5use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
6use std::sync::{Arc, Mutex};
7use std::task::{Context, Poll};
8use std::time::{Duration, Instant};
9
10use bytes::Bytes;
11use h2::server::Connection;
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13use tokio::net::TcpStream;
14use tokio::sync::{Notify, Semaphore};
15
16use crate::error::HttpError;
17use eggress_core::connector::is_dns_rebinding_risk;
18use eggress_core::{TargetAddr, TargetHost};
19
20pub struct H2ProtocolMetrics {
25 pub connections_opened: AtomicU64,
26 pub connections_closed: AtomicU64,
27 pub streams_opened: AtomicU64,
28 pub streams_closed: AtomicU64,
29 pub goaway_received: AtomicU64,
30 pub handshake_failures: AtomicU64,
31 pub auth_failures: AtomicU64,
32 pub flow_control_stalls: AtomicU64,
33 pub pool_exhausted: AtomicU64,
34 pub bytes_relayed: AtomicU64,
35}
36
37impl H2ProtocolMetrics {
38 pub const fn new() -> Self {
39 Self {
40 connections_opened: AtomicU64::new(0),
41 connections_closed: AtomicU64::new(0),
42 streams_opened: AtomicU64::new(0),
43 streams_closed: AtomicU64::new(0),
44 goaway_received: AtomicU64::new(0),
45 handshake_failures: AtomicU64::new(0),
46 auth_failures: AtomicU64::new(0),
47 flow_control_stalls: AtomicU64::new(0),
48 pool_exhausted: AtomicU64::new(0),
49 bytes_relayed: AtomicU64::new(0),
50 }
51 }
52}
53
54impl Default for H2ProtocolMetrics {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60pub static H2_PROTOCOL_METRICS: once_cell::sync::Lazy<Arc<H2ProtocolMetrics>> =
62 once_cell::sync::Lazy::new(|| Arc::new(H2ProtocolMetrics::new()));
63
64#[derive(Debug, thiserror::Error)]
65pub enum H2ConnectError {
66 #[error("IO error: {0}")]
67 Io(#[from] std::io::Error),
68 #[error("H2 protocol error: {0}")]
69 H2(String),
70 #[error("HTTP error: {0}")]
71 Http(#[from] HttpError),
72 #[error("pool exhausted: no connections available and pool at capacity")]
73 PoolExhausted,
74 #[error("DNS rebinding detected: target resolved to reserved/private address {0}")]
75 DnsRebinding(std::net::IpAddr),
76}
77
78impl From<h2::Error> for H2ConnectError {
79 fn from(e: h2::Error) -> Self {
80 H2ConnectError::H2(e.to_string())
81 }
82}
83
84pub struct H2StreamWrite {
85 send_stream: h2::SendStream<Bytes>,
86 capacity: usize,
87}
88
89impl H2StreamWrite {
90 pub fn new(send_stream: h2::SendStream<Bytes>) -> Self {
91 Self {
92 send_stream,
93 capacity: 0,
94 }
95 }
96}
97
98impl tokio::io::AsyncWrite for H2StreamWrite {
99 fn poll_write(
100 mut self: Pin<&mut Self>,
101 cx: &mut Context<'_>,
102 buf: &[u8],
103 ) -> Poll<Result<usize, std::io::Error>> {
104 if self.capacity == 0 {
105 self.send_stream.reserve_capacity(buf.len());
106 match self.send_stream.poll_capacity(cx) {
107 Poll::Ready(Some(Ok(capacity))) => {
108 self.capacity = capacity;
109 }
110 Poll::Ready(Some(Err(e))) => {
111 return Poll::Ready(Err(std::io::Error::other(e)));
112 }
113 Poll::Ready(None) => {
114 return Poll::Ready(Err(std::io::Error::other("h2 stream closed")));
115 }
116 Poll::Pending => {
117 H2_PROTOCOL_METRICS
118 .flow_control_stalls
119 .fetch_add(1, Ordering::Relaxed);
120 return Poll::Pending;
121 }
122 }
123 }
124
125 let len = buf.len().min(self.capacity);
126 self.send_stream
127 .send_data(Bytes::copy_from_slice(&buf[..len]), false)
128 .map_err(std::io::Error::other)?;
129 self.capacity -= len;
130 Poll::Ready(Ok(len))
131 }
132
133 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
134 Poll::Ready(Ok(()))
135 }
136
137 fn poll_shutdown(
138 mut self: Pin<&mut Self>,
139 _cx: &mut Context<'_>,
140 ) -> Poll<Result<(), std::io::Error>> {
141 self.send_stream
142 .send_data(Bytes::new(), true)
143 .map_err(std::io::Error::other)?;
144 Poll::Ready(Ok(()))
145 }
146}
147
148pub async fn h2_connect_relay(
149 mut recv_stream: h2::RecvStream,
150 send_stream: h2::SendStream<Bytes>,
151 target: TargetAddr,
152) -> Result<(), H2ConnectError> {
153 let tcp = match &target.host {
154 TargetHost::Ip(_) => TcpStream::connect(target.to_string()).await?,
155 TargetHost::Domain(domain) => {
156 let lookup = format!("{}:{}", domain, target.port);
157 let mut addrs = tokio::net::lookup_host(&lookup)
158 .await
159 .map_err(|e| H2ConnectError::H2(format!("DNS resolution failed: {e}")))?;
160 let resolved = addrs.next().ok_or_else(|| {
161 H2ConnectError::H2("DNS resolution failed: no addresses found".to_string())
162 })?;
163 if is_dns_rebinding_risk(&resolved.ip()) {
164 return Err(H2ConnectError::DnsRebinding(resolved.ip()));
165 }
166 TcpStream::connect(resolved).await?
167 }
168 };
169 let (mut tcp_read, mut tcp_write) = tcp.into_split();
170 let mut h2_write = H2StreamWrite::new(send_stream);
171
172 let h2_to_tcp = async move {
173 loop {
174 match recv_stream.data().await {
175 Some(Ok(data)) => {
176 let len = data.len();
177 tcp_write.write_all(&data).await?;
178 H2_PROTOCOL_METRICS
179 .bytes_relayed
180 .fetch_add(len as u64, Ordering::Relaxed);
181 }
182 Some(Err(e)) => {
183 return Err(std::io::Error::other(e));
184 }
185 None => break,
186 }
187 }
188 Ok::<(), std::io::Error>(())
189 };
190
191 let tcp_to_h2 = async {
192 let mut buf = [0u8; 8192];
193 loop {
194 let n = tcp_read.read(&mut buf).await?;
195 if n == 0 {
196 break;
197 }
198 h2_write.write_all(&buf[..n]).await?;
199 H2_PROTOCOL_METRICS
200 .bytes_relayed
201 .fetch_add(n as u64, Ordering::Relaxed);
202 }
203 Ok::<(), std::io::Error>(())
204 };
205
206 let h2_task = tokio::spawn(h2_to_tcp);
207 let tcp_result = tcp_to_h2.await;
208 let h2_result = h2_task.await.unwrap();
209
210 h2_result?;
211 tcp_result?;
212 Ok(())
213}
214
215pub async fn handle_h2_connect(
216 mut connection: Connection<TcpStream, Bytes>,
217) -> Result<(), H2ConnectError> {
218 loop {
219 match connection.accept().await {
220 Some(Ok((request, mut send_response))) => {
221 if *request.method() == http::Method::CONNECT {
222 let authority = request
223 .uri()
224 .authority()
225 .ok_or_else(|| H2ConnectError::H2("missing authority".into()))?;
226
227 let target_str = match authority.port_u16() {
228 Some(port) => format!("{}:{}", authority.host(), port),
229 None => format!("{}:443", authority.host()),
230 };
231
232 let target: TargetAddr = target_str
233 .parse()
234 .map_err(|e: String| H2ConnectError::H2(e))?;
235
236 let response = http::Response::builder().status(200).body(()).unwrap();
237
238 let send_stream = send_response.send_response(response, false)?;
239 let recv_stream = request.into_body();
240
241 tokio::spawn(async move {
242 if let Err(e) = h2_connect_relay(recv_stream, send_stream, target).await {
243 tracing::warn!("h2 connect relay error: {}", e);
244 }
245 });
246 } else {
247 send_response.send_reset(h2::Reason::PROTOCOL_ERROR);
248 }
249 }
250 Some(Err(e)) => {
251 return Err(H2ConnectError::H2(e.to_string()));
252 }
253 None => break,
254 }
255 }
256 Ok(())
257}
258
259pub struct H2StreamRead {
260 recv: h2::RecvStream,
261 buffer: Bytes,
262}
263
264impl H2StreamRead {
265 pub fn new(recv: h2::RecvStream) -> Self {
266 Self {
267 recv,
268 buffer: Bytes::new(),
269 }
270 }
271}
272
273impl tokio::io::AsyncRead for H2StreamRead {
274 fn poll_read(
275 self: std::pin::Pin<&mut Self>,
276 cx: &mut Context<'_>,
277 buf: &mut tokio::io::ReadBuf<'_>,
278 ) -> Poll<std::io::Result<()>> {
279 let this = self.get_mut();
280
281 if !this.buffer.is_empty() {
282 let len = this.buffer.len().min(buf.remaining());
283 buf.put_slice(&this.buffer.split_to(len));
284 this.recv
285 .flow_control()
286 .release_capacity(len)
287 .map_err(std::io::Error::other)?;
288 return Poll::Ready(Ok(()));
289 }
290
291 let poll = {
292 let mut data_fut = Box::pin(this.recv.data());
293 data_fut.as_mut().poll(cx)
294 };
295 match poll {
296 Poll::Ready(Some(Ok(data))) => {
297 let len = data.len().min(buf.remaining());
298 buf.put_slice(&data[..len]);
299 if len < data.len() {
300 this.buffer = data.slice(len..);
301 }
302 this.recv
303 .flow_control()
304 .release_capacity(len)
305 .map_err(std::io::Error::other)?;
306 Poll::Ready(Ok(()))
307 }
308 Poll::Ready(Some(Err(e))) => Poll::Ready(Err(std::io::Error::other(e))),
309 Poll::Ready(None) => Poll::Ready(Ok(())),
310 Poll::Pending => Poll::Pending,
311 }
312 }
313}
314
315fn h2_base64_encode(input: &[u8]) -> String {
316 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
317 let mut result = String::with_capacity(input.len().div_ceil(3) * 4);
318 for chunk in input.chunks(3) {
319 let b0 = chunk[0] as u32;
320 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
321 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
322 let triple = (b0 << 16) | (b1 << 8) | b2;
323 result.push(TABLE[((triple >> 18) & 0x3F) as usize] as char);
324 result.push(TABLE[((triple >> 12) & 0x3F) as usize] as char);
325 if chunk.len() > 1 {
326 result.push(TABLE[((triple >> 6) & 0x3F) as usize] as char);
327 } else {
328 result.push('=');
329 }
330 if chunk.len() > 2 {
331 result.push(TABLE[(triple & 0x3F) as usize] as char);
332 } else {
333 result.push('=');
334 }
335 }
336 result
337}
338
339pub async fn h2_connect_client<S>(
348 stream: S,
349 target: &TargetAddr,
350 auth: Option<(&str, &str)>,
351) -> Result<
352 (
353 h2::SendStream<Bytes>,
354 h2::RecvStream,
355 tokio::task::JoinHandle<Result<(), h2::Error>>,
356 ),
357 H2ConnectError,
358>
359where
360 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
361{
362 let (mut send_request, conn) = h2::client::handshake(stream).await?;
363
364 let conn_handle = tokio::spawn(async move {
365 conn.await?;
366 Ok(())
367 });
368
369 let authority = match target.port {
370 443 => target.host.to_string(),
371 port => format!("{}:{}", target.host, port),
372 };
373
374 let mut builder = http::Request::builder()
375 .method(http::Method::CONNECT)
376 .uri(&authority)
377 .header(http::header::HOST, &authority);
378
379 if let Some((user, pass)) = auth {
380 let credentials = format!("{}:{}", user, pass);
381 let encoded = h2_base64_encode(credentials.as_bytes());
382 builder = builder.header(
383 http::header::PROXY_AUTHORIZATION,
384 format!("Basic {}", encoded),
385 );
386 }
387
388 let request = builder
389 .body(())
390 .map_err(|e| H2ConnectError::H2(e.to_string()))?;
391
392 let (response_future, send_stream) = send_request.send_request(request, false)?;
393
394 let response = response_future.await?;
395 if response.status() != http::StatusCode::OK {
396 return Err(H2ConnectError::H2(format!(
397 "CONNECT rejected with status {}",
398 response.status()
399 )));
400 }
401
402 let recv_stream = response.into_body();
403 Ok((send_stream, recv_stream, conn_handle))
404}
405
406#[derive(Debug, Clone, Eq, PartialEq, Hash)]
414pub struct H2PoolKey {
415 pub endpoint_host: String,
416 pub endpoint_port: u16,
417 pub use_tls: bool,
418 pub server_name: Option<String>,
419 pub auth_hash: Option<u64>,
420 pub hop_index: usize,
421}
422
423impl H2PoolKey {
424 pub fn new(
425 host: &str,
426 port: u16,
427 use_tls: bool,
428 server_name: Option<&str>,
429 auth: Option<(&str, &str)>,
430 ) -> Self {
431 Self::with_hop_index(host, port, use_tls, server_name, auth, 0)
432 }
433
434 pub fn with_hop_index(
436 host: &str,
437 port: u16,
438 use_tls: bool,
439 server_name: Option<&str>,
440 auth: Option<(&str, &str)>,
441 hop_index: usize,
442 ) -> Self {
443 let auth_hash = auth.map(|(u, p)| {
444 let mut hasher = std::collections::hash_map::DefaultHasher::new();
445 u.hash(&mut hasher);
446 p.hash(&mut hasher);
447 hasher.finish()
448 });
449 Self {
450 endpoint_host: host.to_string(),
451 endpoint_port: port,
452 use_tls,
453 server_name: server_name.map(|s| s.to_string()),
454 auth_hash,
455 hop_index,
456 }
457 }
458}
459
460pub struct H2ConnectionEntry {
462 sender: Arc<Mutex<h2::client::SendRequest<Bytes>>>,
463 conn_handle: tokio::task::JoinHandle<Result<(), h2::Error>>,
464 #[allow(dead_code)]
465 created_at: Instant,
466 last_used: Arc<Mutex<Instant>>,
467 active_streams: Arc<AtomicU64>,
468 retired: Arc<AtomicBool>,
469 notify: Arc<Notify>,
470}
471
472impl H2ConnectionEntry {
473 fn is_available(&self, max_concurrent_streams: u32) -> bool {
474 !self.retired.load(Ordering::Acquire)
475 && self.active_streams.load(Ordering::Acquire) < max_concurrent_streams as u64
476 }
477
478 fn mark_retired(&self) {
479 self.retired.store(true, Ordering::Release);
480 }
481}
482
483impl Drop for H2ConnectionEntry {
484 fn drop(&mut self) {
485 H2_PROTOCOL_METRICS
486 .connections_closed
487 .fetch_add(1, Ordering::Relaxed);
488 self.conn_handle.abort();
489 }
490}
491
492pub struct H2ConnectionPool {
494 entries: Mutex<Vec<Arc<H2ConnectionEntry>>>,
495 semaphore: Semaphore,
496 pool_size: u32,
497 idle_timeout: Duration,
498 max_concurrent_streams: u32,
499 #[allow(dead_code)]
500 created_at: Instant,
501 reaper_running: AtomicBool,
502}
503
504impl H2ConnectionPool {
505 pub fn new(pool_size: u32, idle_timeout: Duration, max_concurrent_streams: u32) -> Arc<Self> {
506 Arc::new(Self {
507 entries: Mutex::new(Vec::new()),
508 semaphore: Semaphore::new(pool_size as usize),
509 pool_size,
510 idle_timeout,
511 max_concurrent_streams,
512 created_at: Instant::now(),
513 reaper_running: AtomicBool::new(false),
514 })
515 }
516
517 fn try_acquire_entry(&self) -> Option<Arc<H2ConnectionEntry>> {
519 let entries = self.entries.lock().unwrap().clone();
520 let now = Instant::now();
521 for entry in entries.iter() {
522 if entry.is_available(self.max_concurrent_streams)
523 && now.duration_since(*entry.last_used.lock().unwrap()) < self.idle_timeout
524 {
525 entry.active_streams.fetch_add(1, Ordering::AcqRel);
526 *entry.last_used.lock().unwrap() = now;
527 return Some(Arc::clone(entry));
528 }
529 }
530 None
531 }
532
533 async fn create_entry<S>(
535 self: &Arc<Self>,
536 stream: S,
537 ) -> Result<Arc<H2ConnectionEntry>, H2ConnectError>
538 where
539 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
540 {
541 let (send_request, conn) = h2::client::handshake(stream).await?;
542
543 let conn_handle = tokio::spawn(async move {
544 conn.await?;
545 Ok(())
546 });
547
548 let sender = Arc::new(Mutex::new(send_request));
549 let entry = Arc::new(H2ConnectionEntry {
550 sender: Arc::clone(&sender),
551 conn_handle,
552 created_at: Instant::now(),
553 last_used: Arc::new(Mutex::new(Instant::now())),
554 active_streams: Arc::new(AtomicU64::new(1)),
555 retired: Arc::new(AtomicBool::new(false)),
556 notify: Arc::new(Notify::new()),
557 });
558
559 self.entries.lock().unwrap().push(Arc::clone(&entry));
560 H2_PROTOCOL_METRICS
561 .connections_opened
562 .fetch_add(1, Ordering::Relaxed);
563 self.maybe_start_reaper();
564 Ok(entry)
565 }
566
567 fn maybe_start_reaper(self: &Arc<Self>) {
568 if self
569 .reaper_running
570 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
571 .is_err()
572 {
573 return;
574 }
575 let pool = Arc::clone(self);
576 tokio::spawn(async move {
577 loop {
578 tokio::time::sleep(pool.idle_timeout / 2).await;
579 pool.reap_idle_entries();
580 }
581 });
582 }
583
584 fn reap_idle_entries(&self) {
585 let now = Instant::now();
586 let entries = self.entries.lock().unwrap().clone();
587 for entry in &entries {
588 if entry.retired.load(Ordering::Acquire) {
589 continue;
590 }
591 let last_used = *entry.last_used.lock().unwrap();
592 if now.duration_since(last_used) >= self.idle_timeout
593 && entry.active_streams.load(Ordering::Acquire) == 0
594 {
595 entry.mark_retired();
596 }
597 }
598 self.entries
599 .lock()
600 .unwrap()
601 .retain(|entry| !entry.retired.load(Ordering::Acquire));
602 }
603
604 pub fn release(&self, entry: &Arc<H2ConnectionEntry>) {
606 entry.active_streams.fetch_sub(1, Ordering::AcqRel);
607 *entry.last_used.lock().unwrap() = Instant::now();
608 entry.notify.notify_waiters();
609 }
610
611 pub fn retire(&self, entry: &Arc<H2ConnectionEntry>) {
613 entry.mark_retired();
614 }
615
616 pub fn stats(&self) -> H2PoolStats {
618 let entries = self.entries.lock().unwrap();
619 let active = entries
620 .iter()
621 .filter(|e| !e.retired.load(Ordering::Acquire))
622 .count();
623 let total_streams: u64 = entries
624 .iter()
625 .map(|e| e.active_streams.load(Ordering::Acquire))
626 .sum();
627 H2PoolStats {
628 pool_size: self.pool_size,
629 active_connections: active as u32,
630 total_streams,
631 idle_timeout_secs: self.idle_timeout.as_secs(),
632 }
633 }
634}
635
636#[derive(Debug, Clone)]
638pub struct H2PoolStats {
639 pub pool_size: u32,
640 pub active_connections: u32,
641 pub total_streams: u64,
642 pub idle_timeout_secs: u64,
643}
644
645pub struct H2PoolRegistry {
647 pools: std::sync::RwLock<HashMap<H2PoolKey, Arc<H2ConnectionPool>>>,
648 default_pool_size: u32,
649 default_idle_timeout: Duration,
650 default_max_concurrent_streams: u32,
651}
652
653impl H2PoolRegistry {
654 pub fn new() -> Self {
655 Self {
656 pools: std::sync::RwLock::new(HashMap::new()),
657 default_pool_size: 4,
658 default_idle_timeout: Duration::from_secs(60),
659 default_max_concurrent_streams: 100,
660 }
661 }
662
663 pub fn get_or_create(&self, key: &H2PoolKey) -> Arc<H2ConnectionPool> {
665 {
666 let pools = self.pools.read().unwrap();
667 if let Some(pool) = pools.get(key) {
668 return Arc::clone(pool);
669 }
670 }
671 let mut pools = self.pools.write().unwrap();
672 pools
673 .entry(key.clone())
674 .or_insert_with(|| {
675 H2ConnectionPool::new(
676 self.default_pool_size,
677 self.default_idle_timeout,
678 self.default_max_concurrent_streams,
679 )
680 })
681 .clone()
682 }
683
684 pub fn with_defaults(
686 pool_size: u32,
687 idle_timeout: Duration,
688 max_concurrent_streams: u32,
689 ) -> Self {
690 Self {
691 pools: std::sync::RwLock::new(HashMap::new()),
692 default_pool_size: pool_size,
693 default_idle_timeout: idle_timeout,
694 default_max_concurrent_streams: max_concurrent_streams,
695 }
696 }
697}
698
699impl Default for H2PoolRegistry {
700 fn default() -> Self {
701 Self::new()
702 }
703}
704
705pub static H2_POOL_REGISTRY: once_cell::sync::Lazy<H2PoolRegistry> =
707 once_cell::sync::Lazy::new(H2PoolRegistry::new);
708
709pub struct H2PoolGuard {
711 entry: Arc<H2ConnectionEntry>,
712 pool: Arc<H2ConnectionPool>,
713}
714
715impl Drop for H2PoolGuard {
716 fn drop(&mut self) {
717 H2_PROTOCOL_METRICS
718 .streams_closed
719 .fetch_add(1, Ordering::Relaxed);
720 self.pool.release(&self.entry);
721 }
722}
723
724impl H2PoolGuard {
725 pub fn retire(&self) {
727 H2_PROTOCOL_METRICS
728 .goaway_received
729 .fetch_add(1, Ordering::Relaxed);
730 H2_PROTOCOL_METRICS
731 .connections_closed
732 .fetch_add(1, Ordering::Relaxed);
733 self.pool.retire(&self.entry);
734 }
735
736 pub fn sender(&self) -> &Arc<Mutex<h2::client::SendRequest<Bytes>>> {
738 &self.entry.sender
739 }
740}
741
742pub async fn h2_connect_client_pooled<S>(
748 stream: S,
749 target: &TargetAddr,
750 auth: Option<(&str, &str)>,
751 pool_key: &H2PoolKey,
752) -> Result<(h2::SendStream<Bytes>, h2::RecvStream, H2PoolGuard), H2ConnectError>
753where
754 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
755{
756 let pool = H2_POOL_REGISTRY.get_or_create(pool_key);
757
758 if let Some(result) = try_pooled_connection(&pool, target, auth).await {
760 return result;
761 }
762
763 let _permit = pool.semaphore.acquire().await.map_err(|_| {
765 H2_PROTOCOL_METRICS
766 .pool_exhausted
767 .fetch_add(1, Ordering::Relaxed);
768 H2ConnectError::PoolExhausted
769 })?;
770
771 let entry = pool.create_entry(stream).await?;
772
773 let authority = match target.port {
774 443 => target.host.to_string(),
775 port => format!("{}:{}", target.host, port),
776 };
777
778 let mut builder = http::Request::builder()
779 .method(http::Method::CONNECT)
780 .uri(&authority)
781 .header(http::header::HOST, &authority);
782
783 if let Some((user, pass)) = auth {
784 let credentials = format!("{}:{}", user, pass);
785 let encoded = h2_base64_encode(credentials.as_bytes());
786 builder = builder.header(
787 http::header::PROXY_AUTHORIZATION,
788 format!("Basic {}", encoded),
789 );
790 }
791
792 let request = builder
793 .body(())
794 .map_err(|e| H2ConnectError::H2(e.to_string()))?;
795
796 let (response_future, send_stream) = {
797 let mut sender = entry.sender.lock().unwrap();
798 sender.send_request(request, false)?
799 };
800
801 let response = response_future.await?;
802 if response.status() != http::StatusCode::OK {
803 pool.retire(&entry);
804 if response.status() == http::StatusCode::PROXY_AUTHENTICATION_REQUIRED {
805 H2_PROTOCOL_METRICS
806 .auth_failures
807 .fetch_add(1, Ordering::Relaxed);
808 }
809 return Err(H2ConnectError::H2(format!(
810 "CONNECT rejected with status {}",
811 response.status()
812 )));
813 }
814
815 let recv_stream = response.into_body();
816 H2_PROTOCOL_METRICS
817 .streams_opened
818 .fetch_add(1, Ordering::Relaxed);
819 let guard = H2PoolGuard {
820 entry: Arc::clone(&entry),
821 pool: Arc::clone(&pool),
822 };
823 Ok((send_stream, recv_stream, guard))
824}
825
826async fn try_pooled_connection(
829 pool: &Arc<H2ConnectionPool>,
830 target: &TargetAddr,
831 auth: Option<(&str, &str)>,
832) -> Option<Result<(h2::SendStream<Bytes>, h2::RecvStream, H2PoolGuard), H2ConnectError>> {
833 let entry = pool.try_acquire_entry()?;
834
835 let authority = match target.port {
836 443 => target.host.to_string(),
837 port => format!("{}:{}", target.host, port),
838 };
839
840 let mut builder = http::Request::builder()
841 .method(http::Method::CONNECT)
842 .uri(&authority)
843 .header(http::header::HOST, &authority);
844
845 if let Some((user, pass)) = auth {
846 let credentials = format!("{}:{}", user, pass);
847 let encoded = h2_base64_encode(credentials.as_bytes());
848 builder = builder.header(
849 http::header::PROXY_AUTHORIZATION,
850 format!("Basic {}", encoded),
851 );
852 }
853
854 let request = match builder.body(()) {
855 Ok(r) => r,
856 Err(e) => return Some(Err(H2ConnectError::H2(e.to_string()))),
857 };
858
859 let result = {
860 let mut sender = entry.sender.lock().unwrap();
861 sender.send_request(request, false)
862 };
863
864 match result {
865 Ok((response_future, send_stream)) => {
866 let response = match response_future.await {
867 Ok(r) => r,
868 Err(e) => {
869 pool.retire(&entry);
870 return Some(Err(e.into()));
871 }
872 };
873 if response.status() != http::StatusCode::OK {
874 pool.retire(&entry);
875 if response.status() == http::StatusCode::PROXY_AUTHENTICATION_REQUIRED {
876 H2_PROTOCOL_METRICS
877 .auth_failures
878 .fetch_add(1, Ordering::Relaxed);
879 }
880 return Some(Err(H2ConnectError::H2(format!(
881 "CONNECT rejected with status {}",
882 response.status()
883 ))));
884 }
885 H2_PROTOCOL_METRICS
886 .streams_opened
887 .fetch_add(1, Ordering::Relaxed);
888 let recv_stream = response.into_body();
889 let guard = H2PoolGuard {
890 entry: Arc::clone(&entry),
891 pool: Arc::clone(pool),
892 };
893 Some(Ok((send_stream, recv_stream, guard)))
894 }
895 Err(_) => {
896 pool.retire(&entry);
898 None
899 }
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906
907 #[test]
908 fn test_h2_connect_error_display() {
909 let err = H2ConnectError::Io(std::io::Error::new(
910 std::io::ErrorKind::ConnectionRefused,
911 "test",
912 ));
913 assert!(err.to_string().contains("IO error"));
914 }
915
916 #[test]
917 fn test_h2_connect_error_from_h2() {
918 let err = H2ConnectError::H2("test error".into());
919 assert_eq!(err.to_string(), "H2 protocol error: test error");
920 }
921
922 #[test]
923 fn test_h2_connect_error_display_variants() {
924 let err = H2ConnectError::Io(std::io::Error::new(
925 std::io::ErrorKind::BrokenPipe,
926 "broken",
927 ));
928 assert!(err.to_string().contains("broken"));
929
930 let err = H2ConnectError::H2("stream reset".into());
931 assert!(err.to_string().contains("stream reset"));
932 }
933
934 #[test]
935 fn test_h2_connect_error_from_std_io() {
936 let io_err = std::io::Error::other("test io");
937 let err: H2ConnectError = io_err.into();
938 assert!(matches!(err, H2ConnectError::Io(_)));
939 }
940
941 #[test]
942 fn test_h2_connect_error_pool_exhausted() {
943 let err = H2ConnectError::PoolExhausted;
944 assert!(err.to_string().contains("pool exhausted"));
945 }
946
947 #[test]
948 fn test_pool_key_equality() {
949 let k1 = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
950 let k2 = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
951 assert_eq!(k1, k2);
952
953 let k3 = H2PoolKey::new("127.0.0.1", 8080, true, None, None);
954 assert_ne!(k1, k3);
955
956 let k4 = H2PoolKey::new("127.0.0.1", 8080, false, Some("sni.example.com"), None);
957 assert_ne!(k1, k4);
958 }
959
960 #[test]
961 fn test_pool_key_auth_hash() {
962 let k1 = H2PoolKey::new("h", 1, false, None, Some(("u", "p")));
963 let k2 = H2PoolKey::new("h", 1, false, None, Some(("u", "p")));
964 let k3 = H2PoolKey::new("h", 1, false, None, Some(("u", "q")));
965 assert_eq!(k1, k2);
966 assert_ne!(k1, k3);
967 }
968
969 #[test]
970 fn test_pool_stats() {
971 let pool = H2ConnectionPool::new(4, Duration::from_secs(60), 100);
972 let stats = pool.stats();
973 assert_eq!(stats.pool_size, 4);
974 assert_eq!(stats.active_connections, 0);
975 assert_eq!(stats.total_streams, 0);
976 }
977
978 #[test]
979 fn test_pool_registry_get_or_create() {
980 let registry = H2PoolRegistry::new();
981 let key = H2PoolKey::new("127.0.0.1", 8080, false, None, None);
982 let p1 = registry.get_or_create(&key);
983 let p2 = registry.get_or_create(&key);
984 assert!(Arc::ptr_eq(&p1, &p2));
985
986 let key2 = H2PoolKey::new("127.0.0.1", 9090, false, None, None);
987 let p3 = registry.get_or_create(&key2);
988 assert!(!Arc::ptr_eq(&p1, &p3));
989 }
990
991 #[test]
992 fn test_pool_key_isolates_different_auth_credentials() {
993 let k_user_a = H2PoolKey::new(
994 "proxy.example.com",
995 443,
996 true,
997 None,
998 Some(("alice", "secret")),
999 );
1000 let k_user_b = H2PoolKey::new(
1001 "proxy.example.com",
1002 443,
1003 true,
1004 None,
1005 Some(("bob", "secret")),
1006 );
1007 let k_no_auth = H2PoolKey::new("proxy.example.com", 443, true, None, None);
1008
1009 assert_ne!(
1010 k_user_a, k_user_b,
1011 "different users must produce different pool keys"
1012 );
1013 assert_ne!(
1014 k_user_a, k_no_auth,
1015 "auth vs no-auth must produce different pool keys"
1016 );
1017 assert_ne!(k_user_b, k_no_auth);
1018
1019 let registry = H2PoolRegistry::new();
1020 let p1 = registry.get_or_create(&k_user_a);
1021 let p2 = registry.get_or_create(&k_user_b);
1022 let p3 = registry.get_or_create(&k_no_auth);
1023 assert!(!Arc::ptr_eq(&p1, &p2));
1024 assert!(!Arc::ptr_eq(&p1, &p3));
1025 assert!(!Arc::ptr_eq(&p2, &p3));
1026 }
1027
1028 #[test]
1029 fn test_pool_key_isolates_tls_vs_plaintext() {
1030 let k_tls = H2PoolKey::new(
1031 "proxy.example.com",
1032 443,
1033 true,
1034 Some("proxy.example.com"),
1035 None,
1036 );
1037 let k_plain = H2PoolKey::new(
1038 "proxy.example.com",
1039 443,
1040 false,
1041 Some("proxy.example.com"),
1042 None,
1043 );
1044 assert_ne!(
1045 k_tls, k_plain,
1046 "TLS vs plaintext must produce different pool keys"
1047 );
1048 }
1049
1050 #[test]
1051 fn test_pool_key_isolates_server_name() {
1052 let k_sni_a = H2PoolKey::new("1.2.3.4", 443, true, Some("a.example.com"), None);
1053 let k_sni_b = H2PoolKey::new("1.2.3.4", 443, true, Some("b.example.com"), None);
1054 assert_ne!(
1055 k_sni_a, k_sni_b,
1056 "different SNI must produce different pool keys"
1057 );
1058 }
1059
1060 #[tokio::test]
1061 async fn test_handle_h2_connect_accepts() {
1062 let server_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1063 let server_addr = server_listener.local_addr().unwrap();
1064
1065 let server_handle = tokio::spawn(async move {
1066 let (stream, _) = server_listener.accept().await.unwrap();
1067 let conn = h2::server::handshake(stream).await.unwrap();
1068 handle_h2_connect(conn).await.ok();
1069 });
1070
1071 let client_stream = TcpStream::connect(server_addr).await.unwrap();
1072 let (mut send_request, conn) = h2::client::handshake(client_stream).await.unwrap();
1073
1074 let conn_handle = tokio::spawn(async move {
1075 conn.await.ok();
1076 });
1077
1078 let request = http::Request::builder()
1079 .method(http::Method::CONNECT)
1080 .uri("127.0.0.1:9999")
1081 .body(())
1082 .unwrap();
1083
1084 let (response_future, _send_stream) = send_request.send_request(request, true).unwrap();
1085
1086 let response = tokio::time::timeout(std::time::Duration::from_secs(3), response_future)
1087 .await
1088 .unwrap()
1089 .unwrap();
1090 assert_eq!(response.status(), http::StatusCode::OK);
1091
1092 drop(send_request);
1093 drop(_send_stream);
1094 conn_handle.abort();
1095 server_handle.abort();
1096 }
1097
1098 }