Skip to content

VM Sockets (vsock)

Vsock provides a direct communication channel between the host and guest VM without requiring network configuration. It offers lower latency than network-based communication and uses port numbers for multiplexing multiple connections.

What is vsock?

Vsock (Virtual Machine Sockets) is a host-guest communication mechanism that:

  • Provides bidirectional byte streams between host and guest
  • Requires no network configuration or IP addresses
  • Uses Context IDs (CIDs) to identify endpoints: Host = 2, Guest = 3
  • Supports multiple independent connections via port numbers
  • Bridges to Unix domain sockets on the host for easy integration

INFO

Vsock is ideal for RPC, agent communication, and any scenario where you need reliable host-guest communication without the overhead of networking.

Connection Modes

Capsa supports two vsock connection modes, depending on which side initiates the connection.

Listen Mode (Guest Connects to Host)

In listen mode, the host listens on a Unix socket and the guest initiates the connection to the host's CID (2).

Use this when you have a service on the host that the guest needs to call.

rust
use capsa::{Capsa, LinuxDirectBootConfig};

let config = LinuxDirectBootConfig::new("./kernel", "./initrd");
let vm = Capsa::vm(config)
    .vsock_listen(1024)  // Host listens, guest connects
    .build().await?;

To specify a custom socket path instead of an auto-generated one:

rust
let vm = Capsa::vm(config)
    .vsock_listen_at(1024, "/tmp/my-service.sock")
    .build().await?;

INFO

With vsock_listen(), the socket file is auto-generated in /tmp and cleaned up when the VM stops. With vsock_listen_at(), you provide the path and are responsible for cleanup.

Connect Mode (Host Connects to Guest)

In connect mode, the guest listens on a specified port and the host initiates the connection after the VM boots.

Use this when you have a service running inside the guest that the host needs to call.

rust
use capsa::{Capsa, LinuxDirectBootConfig};

let config = LinuxDirectBootConfig::new("./kernel", "./initrd");
let vm = Capsa::vm(config)
    .vsock_connect(2049)  // Guest listens, host connects
    .build().await?;

To specify a custom socket path:

rust
let vm = Capsa::vm(config)
    .vsock_connect_at(2049, "/tmp/guest-service.sock")
    .build().await?;

Getting the Socket

After building the VM, you can retrieve vsock sockets by port number.

Single Socket

rust
if let Some(socket) = vm.vsock_socket(1024) {
    println!("Socket path: {:?}", socket.path());
    println!("Port: {}", socket.port());
}

All Sockets

rust
let sockets = vm.vsock_sockets();
for (port, socket) in sockets {
    println!("Port {} -> {:?}", port, socket.path());
}

Using the Connection

Call socket.connect() to establish a connection. This returns a standard tokio::net::UnixStream for async I/O.

rust
use tokio::io::{AsyncReadExt, AsyncWriteExt};

let socket = vm.vsock_socket(1024).expect("socket not configured");
let mut stream = socket.connect().await?;

// Write to guest
stream.write_all(b"hello guest").await?;

// Read response
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
println!("Response: {}", String::from_utf8_lossy(&buf[..n]));

INFO

The connect() method returns std::io::Result<UnixStream>. If the guest service is not yet listening, you may need to retry or wait for the guest to be ready before connecting.

Multiple Ports

You can configure multiple vsock ports on a single VM. Each port operates independently and can use different modes.

rust
use capsa::{Capsa, LinuxDirectBootConfig, VsockPortConfig};

let config = LinuxDirectBootConfig::new("./kernel", "./initrd");
let vm = Capsa::vm(config)
    .vsock_listen(1024)   // RPC service (host listens)
    .vsock_listen(1025)   // Logging service (host listens)
    .vsock_connect(2049)  // Agent service (guest listens)
    .build().await?;

// Access each socket independently
let rpc_socket = vm.vsock_socket(1024).unwrap();
let log_socket = vm.vsock_socket(1025).unwrap();
let agent_socket = vm.vsock_socket(2049).unwrap();

Complete Example

This example demonstrates a ping-pong protocol between host and guest:

rust
use capsa::{Capsa, LinuxDirectBootConfig};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::time::Duration;

#[tokio::main]
async fn main() -> capsa::Result<()> {
    let config = LinuxDirectBootConfig::new("./kernel", "./initrd");

    // Configure VM with vsock
    let vm = Capsa::vm(config)
        .console_enabled()
        .vsock_listen(1024)
        .build().await?;

    // Wait for guest to boot and start service
    let console = vm.console().await?;
    console.wait_for_timeout("Boot successful", Duration::from_secs(30)).await?;

    // Start a vsock service in the guest that connects to host
    console.exec("/bin/my-vsock-service &", Duration::from_secs(5)).await?;

    // Connect to the vsock socket
    let socket = vm.vsock_socket(1024).expect("socket configured");
    let mut stream = socket.connect().await?;

    // Send ping
    stream.write_all(b"ping").await?;

    // Read pong response
    let mut buf = [0u8; 16];
    let n = stream.read(&mut buf).await?;
    let response = String::from_utf8_lossy(&buf[..n]);

    assert_eq!(response, "pong");
    println!("Received: {}", response);

    vm.kill().await?;
    Ok(())
}

Advanced Configuration

For full control over vsock configuration, use VsockPortConfig directly:

rust
use capsa::{Capsa, LinuxDirectBootConfig, VsockPortConfig};

let config = LinuxDirectBootConfig::new("./kernel", "./initrd");
let vm = Capsa::vm(config)
    .vsock(VsockPortConfig::listen(1024, "/tmp/rpc.sock"))
    .vsock(VsockPortConfig::connect(2048, "/tmp/agent.sock").with_auto_cleanup())
    .build().await?;

The with_auto_cleanup() method marks the socket file for automatic deletion when the VM stops.

Use Cases

Vsock is commonly used for:

  • RPC between host and guest: Implement custom protocols for command execution, file transfer, or state synchronization
  • Agent communication: The Sandbox feature uses vsock internally for its agent protocol
  • Service exposure: Expose guest services to the host without network configuration
  • Low-latency communication: When network overhead is unacceptable

Current Limitations

  • Single connection per port: Each port currently supports only one connection. After the connection closes, the port becomes unavailable. This limitation is being addressed in a future release.
  • No reconnection: If a connection is dropped, you cannot reconnect on the same port without restarting the VM.

WARNING

Plan your vsock usage accordingly. For long-running VMs that may need to recover from connection failures, consider using multiple ports or implementing application-level keep-alive mechanisms.

Released under the MIT License.