Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/openshell-cli/src/edge_tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ async fn accept_loop(listener: TcpListener, config: Arc<TunnelConfig>) {
match listener.accept().await {
Ok((stream, peer)) => {
debug!(peer = %peer, "accepted local tunnel connection");
// set TCP_NODELAY so small gRPC frames are not delayed. Okay if it fails;
// things just go a bit slower in some cases, so we log and continue.
if let Err(e) = stream.set_nodelay(true) {
debug!(peer = %peer, error = %e, "failed to set TCP_NODELAY");
}
let config = Arc::clone(&config);
tokio::spawn(async move {
if let Err(e) = handle_connection(stream, &config).await {
Expand Down Expand Up @@ -174,6 +179,22 @@ async fn open_ws(config: &TunnelConfig) -> Result<WebSocketStream<MaybeTlsStream
.await
.map_err(|e| miette::miette!("WebSocket connect failed: {e}"))?;

// set TCP_NODELAY on the WebSocket's TCP stream so small gRPC frames are
// not delayed. Okay if it fails; things just go a bit slower in some
// cases, so we log and continue.
let tcp = match ws_stream.get_ref() {
MaybeTlsStream::Plain(tcp) => Some(tcp),
MaybeTlsStream::Rustls(tls) => Some(tls.get_ref().0),
_ => None,
};
if let Some(tcp) = tcp {
if let Err(e) = tcp.set_nodelay(true) {
debug!(error = %e, "failed to set TCP_NODELAY on WebSocket stream");
}
} else {
debug!("unknown WebSocket stream variant; skipping TCP_NODELAY");
}

debug!(
status = %response.status(),
"WebSocket connected to edge"
Expand Down
5 changes: 5 additions & 0 deletions crates/openshell-cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2891,6 +2891,11 @@ pub async fn service_forward_tcp(
let (socket, peer) = accepted
.into_diagnostic()
.wrap_err("failed to accept local forward connection")?;
// set TCP_NODELAY so small forwarded writes are not delayed. Okay if it
// fails; things just go a bit slower in some cases, so we log and continue.
if let Err(err) = socket.set_nodelay(true) {
tracing::debug!(peer = %peer, error = %err, "failed to set TCP_NODELAY");
}
let mut client = client.clone();
let sandbox_id = sandbox_id.clone();
let target_host = target_host.to_string();
Expand Down
5 changes: 5 additions & 0 deletions crates/openshell-cli/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ impl tower::Service<hyper::Uri> for InsecureTlsConnector {
let port = uri.port_u16().unwrap_or(443);
let addr = format!("{host}:{port}");
let tcp = tokio::net::TcpStream::connect(addr).await?;
// set TCP_NODELAY so small gRPC frames are not delayed. Okay if it fails;
// things just go a bit slower in some cases, so we log and continue.
if let Err(e) = tcp.set_nodelay(true) {
debug!(error = %e, "failed to set TCP_NODELAY on TLS connection");
}
let server_name = ServerName::try_from(host)?;
let tls_stream = tls_connector.connect(server_name, tcp).await?;
Ok(hyper_util::rt::TokioIo::new(tls_stream))
Expand Down
6 changes: 6 additions & 0 deletions crates/openshell-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,12 @@ async fn serve_gateway_listener(
}
};

// set TCP_NODELAY so small gRPC frames are not delayed. Okay if it fails;
// things just go a bit slower in some cases, so we log and continue.
if let Err(e) = stream.set_nodelay(true) {
debug!(error = %e, client = %addr, "Failed to set TCP_NODELAY on accepted connection");
}

spawn_gateway_connection(
stream,
addr,
Expand Down
45 changes: 32 additions & 13 deletions crates/openshell-supervisor-network/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ impl ProxyHandle {
loop {
match listener.accept().await {
Ok((stream, _addr)) => {
// set TCP_NODELAY so small relayed writes are not delayed. Okay
// if it fails; things just go a bit slower in some cases, so we
// log and continue.
if let Err(e) = stream.set_nodelay(true) {
tracing::debug!(error = %e, "failed to set TCP_NODELAY on accepted proxy connection");
}
let opa = opa_engine.clone();
let cache = identity_cache.clone();
let spid = entrypoint_pid.clone();
Expand Down Expand Up @@ -771,9 +777,7 @@ async fn handle_tcp_connection(
// addresses (used by rootless Podman with pasta) are not hard-blocked.
// Cloud metadata IPs and control-plane ports are still rejected.
match resolve_and_check_trusted_gateway(&host, port, gw, sandbox_entrypoint_pid).await {
Ok(addrs) => TcpStream::connect(addrs.as_slice())
.await
.into_diagnostic()?,
Ok(addrs) => connect_upstream(addrs.as_slice()).await.into_diagnostic()?,
Err(reason) => {
{
let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx())
Expand Down Expand Up @@ -827,9 +831,7 @@ async fn handle_tcp_connection(
match resolve_and_check_allowed_ips(&host, port, &nets, sandbox_entrypoint_pid)
.await
{
Ok(addrs) => TcpStream::connect(addrs.as_slice())
.await
.into_diagnostic()?,
Ok(addrs) => connect_upstream(addrs.as_slice()).await.into_diagnostic()?,
Err(reason) => {
{
let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx())
Expand Down Expand Up @@ -929,9 +931,7 @@ async fn handle_tcp_connection(
// the resolved IP in allowed_ips. Always-blocked addresses and
// control-plane ports remain denied.
match resolve_and_check_declared_endpoint(&host, port, sandbox_entrypoint_pid).await {
Ok(addrs) => TcpStream::connect(addrs.as_slice())
.await
.into_diagnostic()?,
Ok(addrs) => connect_upstream(addrs.as_slice()).await.into_diagnostic()?,
Err(reason) => {
{
let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx())
Expand Down Expand Up @@ -981,9 +981,7 @@ async fn handle_tcp_connection(
} else {
// Default: reject all internal IPs (loopback, RFC 1918, link-local).
match resolve_and_reject_internal(&host, port, sandbox_entrypoint_pid).await {
Ok(addrs) => TcpStream::connect(addrs.as_slice())
.await
.into_diagnostic()?,
Ok(addrs) => connect_upstream(addrs.as_slice()).await.into_diagnostic()?,
Err(reason) => {
{
let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx())
Expand Down Expand Up @@ -2097,6 +2095,17 @@ async fn write_all(writer: &mut (impl tokio::io::AsyncWrite + Unpin), data: &[u8
Ok(())
}

/// Connect to `addrs` with `TCP_NODELAY` set so small relayed writes are not
/// delayed. Best-effort: okay if it fails, things just go a bit slower, so we
/// log and continue.
async fn connect_upstream(addrs: &[SocketAddr]) -> std::io::Result<TcpStream> {
let stream = TcpStream::connect(addrs).await?;
if let Err(e) = stream.set_nodelay(true) {
tracing::debug!(error = %e, "failed to set TCP_NODELAY on upstream connection");
}
Ok(stream)
}

#[derive(Debug, Clone)]
struct L7ConfigSnapshot {
config: crate::l7::L7EndpointConfig,
Expand Down Expand Up @@ -4121,7 +4130,7 @@ async fn handle_forward_proxy(
}

// 6. Connect upstream
let mut upstream = match TcpStream::connect(addrs.as_slice()).await {
let mut upstream = match connect_upstream(addrs.as_slice()).await {
Ok(s) => s,
Err(e) => {
let event = HttpActivityBuilder::new(openshell_ocsf::ctx::ctx())
Expand Down Expand Up @@ -4379,6 +4388,16 @@ mod tests {
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

/// Regression test: the upstream connect path sets `TCP_NODELAY`.
#[tokio::test]
async fn connect_upstream_sets_tcp_nodelay() {
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let addr = listener.local_addr().expect("local addr");

let stream = connect_upstream(&[addr]).await.expect("connect");
assert!(stream.nodelay().expect("query TCP_NODELAY"));
}

fn websocket_l7_config(
protocol: crate::l7::L7Protocol,
websocket_credential_rewrite: bool,
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-supervisor-process/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod child_env;
pub mod debug_rpc;
pub mod log_push;
pub mod managed_children;
mod net;
pub mod process;
pub mod run;
pub mod sandbox;
Expand Down
16 changes: 16 additions & 0 deletions crates/openshell-supervisor-process/src/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Small networking helpers shared across the supervisor-process crate.

use tokio::net::TcpStream;
use tracing::debug;

/// Set `TCP_NODELAY` on a relayed TCP stream so small writes are not delayed.
/// Okay if it fails; things just go a bit slower in some cases, so we log and
/// continue.
pub fn set_nodelay_best_effort(stream: &TcpStream) {
if let Err(e) = stream.set_nodelay(true) {
debug!(error = %e, "failed to set TCP_NODELAY");
}
}
22 changes: 20 additions & 2 deletions crates/openshell-supervisor-process/src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,13 +629,17 @@ pub async fn connect_in_netns(
.await
.map_err(|_| std::io::Error::other("netns connect thread panicked"))??;
std_stream.set_nonblocking(true)?;
return tokio::net::TcpStream::from_std(std_stream);
let stream = tokio::net::TcpStream::from_std(std_stream)?;
crate::net::set_nodelay_best_effort(&stream);
return Ok(stream);
}

#[cfg(not(target_os = "linux"))]
let _ = netns_fd;

tokio::net::TcpStream::connect(addr).await
let stream = tokio::net::TcpStream::connect(addr).await?;
crate::net::set_nodelay_best_effort(&stream);
Ok(stream)
}

#[derive(Clone)]
Expand Down Expand Up @@ -1275,6 +1279,20 @@ mod tests {
use super::*;
use std::process::Stdio;

/// Regression test: the direct-tcpip connect path sets `TCP_NODELAY`.
#[tokio::test]
async fn connect_in_netns_sets_tcp_nodelay() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind listener");
let addr = listener.local_addr().expect("local addr");

let stream = connect_in_netns(&addr.to_string(), None)
.await
.expect("connect");
assert!(stream.nodelay().expect("query TCP_NODELAY"));
}

/// Verify that dropping the input sender (the operation `channel_eof`
/// performs) causes the stdin writer loop to exit and close the child's
/// stdin pipe. Without this, commands like `cat | tar xf -` used by
Expand Down
26 changes: 23 additions & 3 deletions crates/openshell-supervisor-process/src/supervisor_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,14 @@ async fn connect_tcp_target(
.await
.map_err(|_| "netns tcp connect thread panicked")??;
stream.set_nonblocking(true)?;
return Ok(tokio::net::TcpStream::from_std(stream)?);
let stream = tokio::net::TcpStream::from_std(stream)?;
crate::net::set_nodelay_best_effort(&stream);
return Ok(stream);
}

Ok(tokio::net::TcpStream::connect((host.as_str(), port)).await?)
let stream = tokio::net::TcpStream::connect((host.as_str(), port)).await?;
crate::net::set_nodelay_best_effort(&stream);
Ok(stream)
}

#[cfg(not(target_os = "linux"))]
Expand All @@ -632,7 +636,9 @@ async fn connect_tcp_target(
port: u16,
_netns_fd: Option<i32>,
) -> Result<tokio::net::TcpStream, Box<dyn std::error::Error + Send + Sync>> {
Ok(tokio::net::TcpStream::connect((host.as_str(), port)).await?)
let stream = tokio::net::TcpStream::connect((host.as_str(), port)).await?;
crate::net::set_nodelay_best_effort(&stream);
Ok(stream)
}

#[cfg(test)]
Expand Down Expand Up @@ -674,6 +680,20 @@ mod target_tests {
}
}

/// Regression test: the TCP relay connect path sets `TCP_NODELAY`.
#[tokio::test]
async fn connect_tcp_target_sets_tcp_nodelay() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind listener");
let addr = listener.local_addr().expect("local addr");

let stream = connect_tcp_target(addr.ip().to_string(), addr.port(), None)
.await
.expect("connect");
assert!(stream.nodelay().expect("query TCP_NODELAY"));
}

#[test]
fn tcp_target_allows_loopback_hosts() {
validate_tcp_target(&tcp("127.0.0.1", 8080)).expect("ipv4 loopback");
Expand Down
Loading