//! Socket handler relay for connecting client connections to a TypeScript handler //! via a Unix domain socket. //! //! Protocol: Send a JSON metadata line terminated by `\n`, then bidirectional relay. use tokio::net::UnixStream; use tokio::io::{AsyncWriteExt, AsyncReadExt}; use tokio::net::TcpStream; use serde::Serialize; use tracing::debug; #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct RelayMetadata { connection_id: u64, remote_ip: String, remote_port: u16, local_port: u16, sni: Option, route_name: String, initial_data_base64: Option, } /// Relay a client connection to a TypeScript handler via Unix domain socket. /// /// Protocol: Send a JSON metadata line terminated by `\n`, then bidirectional relay. pub async fn relay_to_handler( client: TcpStream, relay_socket_path: &str, connection_id: u64, remote_ip: String, remote_port: u16, local_port: u16, sni: Option, route_name: String, initial_data: Option<&[u8]>, ) -> std::io::Result<()> { debug!( "Relaying connection {} to handler socket {}", connection_id, relay_socket_path ); // Connect to TypeScript handler Unix socket let mut handler = UnixStream::connect(relay_socket_path).await?; // Build and send metadata header let initial_data_base64 = initial_data.map(base64_encode); let metadata = RelayMetadata { connection_id, remote_ip, remote_port, local_port, sni, route_name, initial_data_base64, }; let metadata_json = serde_json::to_string(&metadata) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; handler.write_all(metadata_json.as_bytes()).await?; handler.write_all(b"\n").await?; // Bidirectional relay between client and handler let (mut client_read, mut client_write) = client.into_split(); let (mut handler_read, mut handler_write) = handler.into_split(); let c2h = tokio::spawn(async move { let mut buf = vec![0u8; 65536]; loop { let n = match client_read.read(&mut buf).await { Ok(0) | Err(_) => break, Ok(n) => n, }; if handler_write.write_all(&buf[..n]).await.is_err() { break; } } let _ = handler_write.shutdown().await; }); let h2c = tokio::spawn(async move { let mut buf = vec![0u8; 65536]; loop { let n = match handler_read.read(&mut buf).await { Ok(0) | Err(_) => break, Ok(n) => n, }; if client_write.write_all(&buf[..n]).await.is_err() { break; } } let _ = client_write.shutdown().await; }); let _ = tokio::join!(c2h, h2c); debug!("Relay connection {} completed", connection_id); Ok(()) } /// Simple base64 encoding without external dependency. fn base64_encode(data: &[u8]) -> String { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let mut result = String::new(); for chunk in data.chunks(3) { let b0 = chunk[0] as u32; let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 }; let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 }; let n = (b0 << 16) | (b1 << 8) | b2; result.push(CHARS[((n >> 18) & 0x3F) as usize] as char); result.push(CHARS[((n >> 12) & 0x3F) as usize] as char); if chunk.len() > 1 { result.push(CHARS[((n >> 6) & 0x3F) as usize] as char); } else { result.push('='); } if chunk.len() > 2 { result.push(CHARS[(n & 0x3F) as usize] as char); } else { result.push('='); } } result }