Capsa is experimental software. APIs may change without notice.
Skip to content

Architecture

This guide covers Capsa's internal architecture for contributors.

Crate Structure

text
capsa/
├── crates/
│   ├── capsa/           # Main library, public API
│   ├── core/            # Shared types, traits, errors
│   ├── cli/             # Command-line interface
│   ├── net/             # Userspace NAT networking (smoltcp)
│   ├── vmm/             # Unified VMM daemon (capsa-vmm)
│   ├── common/
│   │   └── vmm-ipc/     # IPC protocol and transport
│   └── backends/
│       ├── common/
│       │   └── subprocess/  # Cross-platform subprocess backend
│       ├── linux/
│       │   └── kvm/         # Linux KVM backend
│       └── apple/
│           └── vz/          # macOS Virtualization.framework bindings
└── nix/
    └── test-vms/        # Test VM definitions

Crate Responsibilities

CratePurpose
capsaPublic API surface. Re-exports types from capsa-core. Contains VmBuilder, VmHandle, VmConsole.
capsa-coreShared types used across backends: configs, errors, traits
capsa-netUserspace networking stack using smoltcp. Handles NAT, DHCP, DNS proxy, network policies.
capsa-vmmUnified VMM daemon for subprocess mode (both macOS and Linux)
capsa-vmm-ipcIPC protocol, transport, and spawning utilities for subprocess communication
capsa-subprocessCross-platform subprocess backend that spawns capsa-vmm and communicates via RPC
capsa-linux-kvmKVM backend implementation. Memory setup, device emulation, interrupt routing.
capsa-apple-vzSwift/ObjC bindings to Virtualization.framework

Linux KVM Backend

Memory Layout

The Linux KVM backend sets up guest memory with this layout:

text
0x0000_7000   Boot parameters
0x0000_8000   Stack
0x0000_9000   Page tables (PML4)
0x0002_0000   Kernel command line
0x0100_0000   Kernel load address (16 MB)
0x0400_0000   Initrd load address (64 MB)

Virtio Devices

All devices use MMIO transport with direct interrupt injection:

DeviceBase AddressIRQ
Console0x1000005
Network0x14000004
vsock0x15000006
Filesystem0x1600000+10+

Interrupts

  • IOAPIC with 24 pins
  • MP table at 0x9fc00 for interrupt routing
  • Each virtio device gets a dedicated IRQ line

Backend Modes

Linux supports two execution modes for the KVM backend:

ModeDescriptionDefault
SubprocessKVM runs in a separate capsa-vmm daemonYes
In-processKVM runs directly in your application processNo

Choosing a Mode

Subprocess mode (default):

  • Defense-in-depth (process isolation)
  • Fault isolation (VM crash doesn't take down your app)
  • Recommended for untrusted workloads
  • Consistent architecture with macOS

In-process mode (opt-in):

  • Lower latency (no IPC overhead)
  • Simpler deployment (single binary)
  • Suitable for trusted workloads
  • Easier debugging (single process)

To use in-process mode on Linux:

bash
# Via environment variable
CAPSA_USE_IN_PROCESS=1 cargo run

# Or programmatically
let vm = capsa::vm(boot)
    .subprocess(false)
    .build()
    .await?;

The capsa-vmm binary is discovered using the same mechanism as macOS (see VMM Daemon below).

When to Use In-Process Mode

Use in-process mode when:

  • Running trusted workloads (e.g., integration tests, development environments)
  • Latency is critical
  • Simpler deployment is preferred
  • Debugging VM issues directly

Use subprocess mode (default) when:

  • Running untrusted code in the VM (e.g., sandboxed CI builds, user-submitted code)
  • You need fault isolation (VM crash doesn't take down your app)
  • You want consistent behavior with macOS
  • Security is more important than latency

Jail Isolation (Linux Only)

When running in subprocess mode, capsa-vmm jails itself for defense-in-depth isolation. The jail uses:

  • Namespaces: Mount and IPC isolation
  • Pivot root: Minimal filesystem with only required paths
  • Capability dropping: Runs with zero capabilities
  • Seccomp filters: Restricts syscalls to necessary minimum

The jail is enabled by default but can be disabled with --no-jail for debugging.

See Linux Jail Security Model for details.

macOS Backend

Subprocess Architecture

Virtualization.framework requires certain operations to run on the main thread. Since Rust async applications typically use worker threads, Capsa uses a subprocess architecture:

text
┌─────────────────────────────────────┐
│     Your Application (Tokio)        │
│         capsa library               │
└─────────────────┬───────────────────┘
                  │ IPC (tarpc)
┌─────────────────▼───────────────────┐
│        capsa-vmm daemon             │
│    (manages main thread for VZ)     │
└─────────────────┬───────────────────┘

┌─────────────────▼───────────────────┐
│    Virtualization.framework         │
└─────────────────────────────────────┘

VMM Daemon

The capsa-vmm binary is automatically spawned when needed. It's searched for in order:

  1. Explicit path passed to SubprocessBackend::with_vmm_path()
  2. CAPSA_VMM_PATH environment variable
  3. Bundled path from build (set at compile time by capsa's build.rs)
  4. Same directory as your executable
  5. Parent directory of your executable (handles test binaries in target/debug/deps/)
  6. System PATH

For development, the dev shell ensures capsa-vmm is built and available.

Code Signing

Virtualization.framework requires the com.apple.security.virtualization entitlement. During development, the dev shell provides codesign-run which signs binaries on-the-fly:

bash
# The dev shell handles this automatically
cargo test

For distribution, you must sign your binary with proper entitlements:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.virtualization</key>
    <true/>
</dict>
</plist>

Released under the MIT License.