Skip to content

Commit 16f8087

Browse files
committed
fix(node): implement Firecracker vsock CONNECT handshake
The UdsConnector was trying to connect directly to vsock.sock_52 as a file path, but Firecracker doesn't create per-port socket files. The host must connect to the base vsock.sock UDS and send CONNECT N\n to reach port N on the guest. Implement this handshake protocol.
1 parent 7a11d10 commit 16f8087

1 file changed

Lines changed: 44 additions & 6 deletions

File tree

crates/sandchest-node/src/agent_client.rs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,35 @@ async fn make_channel(
180180
}
181181
}
182182

183-
/// Tower service that connects to a Unix domain socket.
183+
/// Tower service that connects to a Firecracker vsock UDS.
184184
///
185-
/// Used as a custom tonic connector to reach the Firecracker vsock UDS
186-
/// proxy. The URI parameter is ignored — all connections go to the
187-
/// configured socket path.
185+
/// Firecracker exposes guest vsock ports via a single Unix domain socket
186+
/// on the host. To reach guest port N, the host must:
187+
/// 1. Connect to the base UDS (e.g. `vsock.sock`)
188+
/// 2. Send `CONNECT N\n`
189+
/// 3. Wait for `OK N\n`
190+
///
191+
/// The `path` field is in the format `{base_uds}_{port}` (e.g. `vsock.sock_52`).
192+
/// This connector parses the port, connects to the base socket, and performs
193+
/// the CONNECT handshake before handing the stream to tonic.
188194
#[derive(Clone)]
189195
struct UdsConnector {
190196
path: String,
191197
}
192198

199+
impl UdsConnector {
200+
/// Parse `vsock.sock_52` into (`vsock.sock`, 52).
201+
fn parse_path_and_port(&self) -> (String, u32) {
202+
if let Some(idx) = self.path.rfind('_') {
203+
if let Ok(port) = self.path[idx + 1..].parse::<u32>() {
204+
return (self.path[..idx].to_string(), port);
205+
}
206+
}
207+
// Fallback: treat the whole path as the socket (shouldn't happen)
208+
(self.path.clone(), DEFAULT_AGENT_VSOCK_PORT)
209+
}
210+
}
211+
193212
impl tower::Service<http::Uri> for UdsConnector {
194213
type Response = hyper_util::rt::TokioIo<tokio::net::UnixStream>;
195214
type Error = std::io::Error;
@@ -204,9 +223,28 @@ impl tower::Service<http::Uri> for UdsConnector {
204223
}
205224

206225
fn call(&mut self, _uri: http::Uri) -> Self::Future {
207-
let path = self.path.clone();
226+
let (base_path, port) = self.parse_path_and_port();
208227
Box::pin(async move {
209-
let stream = tokio::net::UnixStream::connect(&path).await?;
228+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
229+
230+
// Connect to the Firecracker vsock UDS
231+
let mut stream = tokio::net::UnixStream::connect(&base_path).await?;
232+
233+
// Perform the CONNECT handshake
234+
let connect_cmd = format!("CONNECT {}\n", port);
235+
stream.write_all(connect_cmd.as_bytes()).await?;
236+
237+
// Read the response (e.g. "OK 52\n")
238+
let mut buf = [0u8; 64];
239+
let n = stream.read(&mut buf).await?;
240+
let response = std::str::from_utf8(&buf[..n]).unwrap_or("");
241+
if !response.starts_with("OK ") {
242+
return Err(std::io::Error::new(
243+
std::io::ErrorKind::ConnectionRefused,
244+
format!("vsock CONNECT handshake failed: {}", response.trim()),
245+
));
246+
}
247+
210248
Ok(hyper_util::rt::TokioIo::new(stream))
211249
})
212250
}

0 commit comments

Comments
 (0)