diff --git a/changelog.md b/changelog.md index aa76ecb..db8f437 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-03-16 - 4.5.7 - fix(remoteingress-core) +improve tunnel reconnect and frame write efficiency + +- Reuse the TLS connector across edge reconnections to preserve session resumption state and reduce reconnect latency. +- Buffer hub and edge frame writes to coalesce small control and data frames into fewer TLS records and syscalls while still flushing each frame promptly. + ## 2026-03-16 - 4.5.6 - fix(remoteingress-core) disable Nagle's algorithm on edge, hub, and upstream TCP sockets to reduce control-frame latency diff --git a/rust/crates/remoteingress-core/src/edge.rs b/rust/crates/remoteingress-core/src/edge.rs index fb9b70d..1a1b7fb 100644 --- a/rust/crates/remoteingress-core/src/edge.rs +++ b/rust/crates/remoteingress-core/src/edge.rs @@ -194,6 +194,14 @@ async fn edge_main_loop( let mut backoff_ms: u64 = 1000; let max_backoff_ms: u64 = 30000; + // Build TLS config ONCE outside the reconnect loop — preserves session + // cache across reconnections for TLS session resumption (saves 1 RTT). + let tls_config = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(NoCertVerifier)) + .with_no_client_auth(); + let connector = TlsConnector::from(Arc::new(tls_config)); + loop { // Create a per-connection child token let connection_token = cancel_token.child_token(); @@ -209,6 +217,7 @@ async fn edge_main_loop( &listen_ports, &mut shutdown_rx, &connection_token, + &connector, ) .await; @@ -259,14 +268,8 @@ async fn connect_to_hub_and_run( listen_ports: &Arc>>, shutdown_rx: &mut mpsc::Receiver<()>, connection_token: &CancellationToken, + connector: &TlsConnector, ) -> EdgeLoopResult { - // Build TLS connector that skips cert verification (auth is via secret) - let tls_config = rustls::ClientConfig::builder() - .dangerous() - .with_custom_certificate_verifier(Arc::new(NoCertVerifier)) - .with_no_client_auth(); - - let connector = TlsConnector::from(Arc::new(tls_config)); let addr = format!("{}:{}", config.hub_host, config.hub_port); let tcp = match TcpStream::connect(&addr).await { @@ -378,15 +381,17 @@ async fn connect_to_hub_and_run( let tunnel_writer_tx = tunnel_ctrl_tx.clone(); let tw_token = connection_token.clone(); let tunnel_writer_handle = tokio::spawn(async move { + // BufWriter coalesces small writes (frame headers, control frames) into fewer + // TLS records and syscalls. Flushed after each frame to avoid holding data. + let mut writer = tokio::io::BufWriter::with_capacity(65536, write_half); loop { tokio::select! { biased; // control frames always take priority over data ctrl = tunnel_ctrl_rx.recv() => { match ctrl { Some(frame_data) => { - if write_half.write_all(&frame_data).await.is_err() { - break; - } + if writer.write_all(&frame_data).await.is_err() { break; } + if writer.flush().await.is_err() { break; } } None => break, } @@ -394,9 +399,8 @@ async fn connect_to_hub_and_run( data = tunnel_data_rx.recv() => { match data { Some(frame_data) => { - if write_half.write_all(&frame_data).await.is_err() { - break; - } + if writer.write_all(&frame_data).await.is_err() { break; } + if writer.flush().await.is_err() { break; } } None => break, } diff --git a/rust/crates/remoteingress-core/src/hub.rs b/rust/crates/remoteingress-core/src/hub.rs index e92df02..d2c6e2f 100644 --- a/rust/crates/remoteingress-core/src/hub.rs +++ b/rust/crates/remoteingress-core/src/hub.rs @@ -381,15 +381,17 @@ async fn handle_edge_connection( let frame_writer_tx = ctrl_tx.clone(); let writer_token = edge_token.clone(); let writer_handle = tokio::spawn(async move { + // BufWriter coalesces small writes (frame headers, control frames) into fewer + // TLS records and syscalls. Flushed after each frame to avoid holding data. + let mut writer = tokio::io::BufWriter::with_capacity(65536, write_half); loop { tokio::select! { biased; // control frames always take priority over data ctrl = ctrl_rx.recv() => { match ctrl { Some(frame_data) => { - if write_half.write_all(&frame_data).await.is_err() { - break; - } + if writer.write_all(&frame_data).await.is_err() { break; } + if writer.flush().await.is_err() { break; } } None => break, } @@ -397,9 +399,8 @@ async fn handle_edge_connection( data = data_rx.recv() => { match data { Some(frame_data) => { - if write_half.write_all(&frame_data).await.is_err() { - break; - } + if writer.write_all(&frame_data).await.is_err() { break; } + if writer.flush().await.is_err() { break; } } None => break, } diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index a61fc70..d54c086 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/remoteingress', - version: '4.5.6', + version: '4.5.7', description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.' }