use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use bao_boringssl_bridge::connection::TlsState;
use bao_boringssl_bridge::{TlsConnection, TlsServer, generate_self_signed_pem};
use bun_core::MutableString;
use bun_http::header_builder::HeaderBuilder;
use bun_http::{AsyncHTTP, FetchRedirect, Method};
use bun_url::URL;
#[used]
static LSQUIC_FORCE_LINK: fn() = bun_lsquic_sys::force_link;
#[used]
static LSHPACK_FORCE_LINK: fn() = bun_lsquic_sys::force_link_lshpack;
#[used]
static ULOOP_FORCE_LINK: fn() = bao_uloop::force_link;
#[used]
static PRODUCT_NATIVE_SYMBOLS_FORCE_LINK: fn() =
bun_runtime::product_native_symbols::force_link_product_native_symbols;
struct ServerTlsIo {
tcp: TcpStream,
tls: TlsConnection,
pending_plain: Vec<u8>,
pending_off: usize,
}
impl ServerTlsIo {
fn handshake(tcp: &mut TcpStream, tls: &mut TlsConnection, pending: &mut Vec<u8>) -> std::io::Result<()> {
loop {
let res = tls
.process()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
for chunk in res.plaintext {
pending.extend_from_slice(&chunk);
}
loop {
let outgoing = tls.take_outgoing();
if outgoing.is_empty() {
break;
}
tcp.write_all(&outgoing)?;
}
if res.state == TlsState::Active || res.state == TlsState::PeerClosed {
return Ok(());
}
let mut buf = [0u8; 16_384];
match tcp.read(&mut buf) {
Ok(0) => {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"peer closed during tls handshake",
))
}
Ok(n) => tls.feed(&buf[..n]),
Err(e) => return Err(e),
}
}
}
fn read_http_head(&mut self) -> std::io::Result<String> {
let mut buf = [0u8; 4096];
let mut head = Vec::new();
loop {
let n = self.read(&mut buf)?;
head.extend_from_slice(&buf[..n]);
if head.windows(4).any(|w| w == b"\r\n\r\n") {
return Ok(String::from_utf8_lossy(&head).into_owned());
}
}
}
}
impl Read for ServerTlsIo {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pending_off < self.pending_plain.len() {
let avail = &self.pending_plain[self.pending_off..];
let n = avail.len().min(buf.len());
buf[..n].copy_from_slice(&avail[..n]);
self.pending_off += n;
return Ok(n);
}
loop {
let outgoing = self.tls.take_outgoing();
if !outgoing.is_empty() {
self.tcp.write_all(&outgoing)?;
}
let res = self
.tls
.process()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
if !res.plaintext.is_empty() {
let mut joined = Vec::new();
for chunk in res.plaintext {
joined.extend_from_slice(&chunk);
}
let n = joined.len().min(buf.len());
buf[..n].copy_from_slice(&joined[..n]);
return Ok(n);
}
let mut raw = [0u8; 16_384];
match self.tcp.read(&mut raw) {
Ok(0) => {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"tls peer closed",
))
}
Ok(n) => self.tls.feed(&raw[..n]),
Err(e) => return Err(e),
}
}
}
}
impl Write for ServerTlsIo {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self
.tls
.write(buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
let outgoing = self.tls.take_outgoing();
if !outgoing.is_empty() {
self.tcp.write_all(&outgoing)?;
}
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.tcp.flush()
}
}
fn serve_target_connection(
mut tcp: TcpStream,
server: &TlsServer,
resumed: &Arc<Mutex<Vec<bool>>>,
) {
tcp.set_read_timeout(Some(Duration::from_secs(10))).ok();
let mut tls = match server.accept() {
Ok(t) => t,
Err(e) => {
eprintln!("[tgt-server] accept failed: {}", e);
return;
}
};
let mut pending = Vec::new();
if let Err(e) = ServerTlsIo::handshake(&mut tcp, &mut tls, &mut pending) {
eprintln!("[tgt-server] tls handshake failed: {:?}", e);
return;
}
resumed
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(bao_boringssl_bridge::session_cache::session_reused(tls.ssl_ptr()));
let mut io = ServerTlsIo {
tcp,
tls,
pending_plain: pending,
pending_off: 0,
};
let head = match io.read_http_head() {
Ok(h) => h,
Err(e) => {
eprintln!("[tgt-server] request head read failed: {:?}", e);
return;
}
};
assert!(head.starts_with("GET /"), "expected GET request, got: {}", head);
let response = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nhi";
let _ = io.write_all(response.as_bytes());
let _ = io.flush();
}
fn serve_proxy_connection(mut client: TcpStream, target_port: u16) {
client.set_read_timeout(Some(Duration::from_secs(10))).ok();
let mut head = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = match client.read(&mut buf) {
Ok(n) => n,
Err(_) => return,
};
if n == 0 {
return;
}
head.extend_from_slice(&buf[..n]);
if head.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
let head_str = String::from_utf8_lossy(&head);
assert!(
head_str.starts_with(&format!("CONNECT 127.0.0.1:{target_port} ")),
"expected CONNECT to target, got: {}",
head_str
);
let mut target = match TcpStream::connect(("127.0.0.1", target_port)) {
Ok(t) => t,
Err(_) => return,
};
let _ = client.write_all(b"HTTP/1.1 200 Connection established\r\n\r\n");
let _ = client.flush();
client.set_read_timeout(None).ok();
target.set_read_timeout(None).ok();
let mut c2 = match client.try_clone() {
Ok(c) => c,
Err(_) => return,
};
let mut t2 = match target.try_clone() {
Ok(t) => t,
Err(_) => return,
};
let up = std::thread::spawn(move || {
let _ = std::io::copy(&mut c2, &mut target);
});
let _ = std::io::copy(&mut t2, &mut client);
let _ = up.join();
}
fn proxied_get(target_port: u16, proxy_port: u16) -> Result<u32, String> {
let url_str = format!("https://127.0.0.1:{target_port}/");
let proxy_str = format!("http://127.0.0.1:{proxy_port}");
let url = URL::parse(url_str.as_bytes());
let proxy = URL::parse(proxy_str.as_bytes());
let mut hb = HeaderBuilder::default();
if let Err(e) = hb.allocate() {
return Err(format!("header allocation failed: {:?}", e));
}
let entry_list = hb.entries;
let headers_buf: &[u8] = unsafe {
if let Some(ptr) = hb.content.ptr {
std::slice::from_raw_parts(ptr.as_ptr(), hb.content.len)
} else {
&[]
}
};
let response_buffer = Box::into_raw(Box::new(MutableString::default()));
let mut async_http = AsyncHTTP::init_sync(
Method::GET,
url,
entry_list,
headers_buf,
response_buffer,
b"",
Some(proxy),
None,
FetchRedirect::Follow,
);
async_http.client.flags.reject_unauthorized = false;
let result = async_http.send_sync().map_err(|e| format!("{:?}", e))?;
let status = result.status_code;
unsafe {
drop(Box::from_raw(response_buffer));
}
Ok(status)
}
#[test]
fn proxy_tunnel_second_connection_resumes_session() {
bun_core::output::init_test();
let (cert, key) =
generate_self_signed_pem("127.0.0.1", 365).expect("self-signed cert");
let target = Arc::new(TlsServer::new(&cert, &key).expect("TlsServer"));
let target_listener = TcpListener::bind("127.0.0.1:0").expect("bind target");
let target_port = target_listener.local_addr().unwrap().port();
let resumed: Arc<Mutex<Vec<bool>>> = Arc::new(Mutex::new(Vec::new()));
{
let target = target.clone();
let resumed = resumed.clone();
std::thread::spawn(move || {
for stream in target_listener.incoming() {
match stream {
Ok(s) => serve_target_connection(s, &target, &resumed),
Err(_) => return,
}
}
});
}
let proxy_listener = TcpListener::bind("127.0.0.1:0").expect("bind proxy");
let proxy_port = proxy_listener.local_addr().unwrap().port();
std::thread::spawn(move || {
for stream in proxy_listener.incoming() {
match stream {
Ok(s) => serve_proxy_connection(s, target_port),
Err(_) => return,
}
}
});
let cache = bao_boringssl_bridge::session_cache::global();
let origin_key =
bao_boringssl_bridge::session_cache::origin_key("127.0.0.1", target_port, 0);
let had_session_before = cache.contains_key(&origin_key);
let status = proxied_get(target_port, proxy_port).expect("first proxied GET");
assert_eq!(status, 200, "first proxied GET must succeed");
assert!(
cache.contains_key(&origin_key),
"tunnel new-session callback must populate the cache for the target origin"
);
let status = proxied_get(target_port, proxy_port).expect("second proxied GET");
assert_eq!(status, 200, "second proxied GET must succeed");
let flags = resumed.lock().unwrap_or_else(|e| e.into_inner()).clone();
assert_eq!(flags.len(), 2, "exactly two inner TLS connections expected");
assert!(!flags[0], "first tunnel must be a full handshake");
assert!(
flags[1],
"second tunnel to the same target origin must resume the cached session"
);
if !had_session_before {
cache.clear();
}
}