Architecture
This guide covers Capsa's internal architecture for contributors.
Crate Structure
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 definitionsCrate Responsibilities
| Crate | Purpose |
|---|---|
capsa | Public API surface. Re-exports types from capsa-core. Contains VmBuilder, VmHandle, VmConsole. |
capsa-core | Shared types used across backends: configs, errors, traits |
capsa-net | Userspace networking stack using smoltcp. Handles NAT, DHCP, DNS proxy, network policies. |
capsa-vmm | Unified VMM daemon for subprocess mode (both macOS and Linux) |
capsa-vmm-ipc | IPC protocol, transport, and spawning utilities for subprocess communication |
capsa-subprocess | Cross-platform subprocess backend that spawns capsa-vmm and communicates via RPC |
capsa-linux-kvm | KVM backend implementation. Memory setup, device emulation, interrupt routing. |
capsa-apple-vz | Swift/ObjC bindings to Virtualization.framework |
Linux KVM Backend
Memory Layout
The Linux KVM backend sets up guest memory with this layout:
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:
| Device | Base Address | IRQ |
|---|---|---|
| Console | 0x100000 | 5 |
| Network | 0x1400000 | 4 |
| vsock | 0x1500000 | 6 |
| Filesystem | 0x1600000+ | 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:
| Mode | Description | Default |
|---|---|---|
| Subprocess | KVM runs in a separate capsa-vmm daemon | Yes |
| In-process | KVM runs directly in your application process | No |
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:
# 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:
┌─────────────────────────────────────┐
│ 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:
- Explicit path passed to
SubprocessBackend::with_vmm_path() CAPSA_VMM_PATHenvironment variable- Bundled path from build (set at compile time by capsa's build.rs)
- Same directory as your executable
- Parent directory of your executable (handles test binaries in
target/debug/deps/) - 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:
# The dev shell handles this automatically
cargo testFor distribution, you must sign your binary with proper entitlements:
<?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>