use std::net::SocketAddr; use std::sync::Arc; use color_eyre::Result; use tokio::net::{TcpListener, TcpStream}; use tokio_util::sync::CancellationToken; use tracing::{info, warn}; use crate::protocol::{StreamHeader, write_stream_header}; use crate::relay::{QuicBiStream, relay}; use crate::server::state::ServerState; pub async fn run( addr: SocketAddr, state: Arc, cancel: CancellationToken, ) -> Result<()> { let listener = TcpListener::bind(addr).await?; info!(%addr, "HTTP server listening"); loop { tokio::select! { _ = cancel.cancelled() => break, accepted = listener.accept() => { let (stream, peer) = match accepted { Ok(v) => v, Err(e) => { warn!("HTTP accept error: {e}"); continue; } }; let state = state.clone(); tokio::spawn(async move { if let Err(e) = handle_connection(stream, peer, state).await { warn!("HTTP connection error: {e:#}"); } }); } } } Ok(()) } /// Extract the Host header from raw HTTP bytes without consuming them. /// Returns (subdomain, peer_display_string). fn extract_host_from_headers(buf: &[u8], base_domain: &str) -> Option { let header_str = std::str::from_utf8(buf).ok()?; // Find Host header (case-insensitive) for line in header_str.split("\r\n").skip(1) { if line.is_empty() { break; } if let Some(value) = line.strip_prefix("Host:").or_else(|| line.strip_prefix("host:")) { let host = value.trim(); // Strip port if present let host = host.split(':').next().unwrap_or(host); let suffix = format!(".{base_domain}"); let subdomain = host.strip_suffix(&suffix).filter(|s| !s.is_empty())?; return Some(subdomain.to_string()); } } None } async fn handle_connection( stream: TcpStream, peer: SocketAddr, state: Arc, ) -> Result<()> { // Peek at the beginning of the HTTP request to extract the Host header. // We read into a buffer but then send ALL of it through the tunnel. let mut buf = vec![0u8; 8192]; let n = stream.peek(&mut buf).await?; let buf = &buf[..n]; let subdomain = extract_host_from_headers(buf, &state.base_domain) .ok_or_else(|| color_eyre::eyre::eyre!("no matching Host header in HTTP request"))?; // Look up the tunnel let tunnel_id = *state .http_routes .get(&subdomain) .ok_or_else(|| color_eyre::eyre::eyre!("no tunnel for subdomain {subdomain}"))?; let entry = state .tunnels .get(&tunnel_id) .ok_or_else(|| color_eyre::eyre::eyre!("tunnel {tunnel_id} not found"))?; let connection = entry.connection.clone(); drop(entry); // Open QUIC stream and write header let (mut quic_send, quic_recv) = connection.open_bi().await?; write_stream_header( &mut quic_send, &StreamHeader { tunnel_id, peer_addr: peer.to_string(), }, ) .await?; // Raw bidirectional relay — TCP stream carries the full HTTP conversation // including upgrades (WebSocket), SSE, chunked responses, etc. let quic_stream = QuicBiStream { send: quic_send, recv: quic_recv, }; relay(stream, quic_stream).await?; Ok(()) }