Skip to main content

microsandbox_protocol/
lib.rs

1//! `microsandbox-protocol` defines the shared protocol types used for communication
2//! between the host and the guest agent over CBOR-over-virtio-serial.
3
4#![warn(missing_docs)]
5
6mod error;
7
8//--------------------------------------------------------------------------------------------------
9// Constants: Host↔Guest Shutdown Timings
10//--------------------------------------------------------------------------------------------------
11
12/// Maximum time agentd spends in its handoff-mode poweroff sequence.
13///
14/// In init-handoff sandboxes (systemd, openrc, …) agentd's shutdown
15/// handler signals the new PID 1 with `SIGRTMIN+4`, sleeps for this
16/// duration to give the init a chance to act, then falls back to
17/// `SIGTERM`. The host's [`SHUTDOWN_FLUSH_TIMEOUT`] must exceed this
18/// so the host's fallback exit doesn't cut the sequence short.
19pub const HANDOFF_POWEROFF_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
20
21/// How long the host waits after forwarding `core.shutdown` to agentd
22/// before triggering its own VMM exit fallback.
23///
24/// agentd uses this window to `sync()` block-backed root filesystems
25/// and power off the kernel cleanly (or run its handoff sequence —
26/// see [`HANDOFF_POWEROFF_TIMEOUT`]). On a healthy guest the VMM
27/// exits well inside the window and the host fallback is a no-op;
28/// the fallback only fires when the guest is wedged.
29///
30/// Must exceed [`HANDOFF_POWEROFF_TIMEOUT`] plus margin for the
31/// init's own signal handling — enforced at compile time below.
32pub const SHUTDOWN_FLUSH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(8);
33
34// Compile-time invariant: the host must wait at least as long as
35// agentd's longest internal grace, otherwise the host fallback will
36// cut agentd's handoff sequence short and we'll silently strand
37// init-handoff sandboxes.
38const _: () = assert!(
39    SHUTDOWN_FLUSH_TIMEOUT.as_secs() > HANDOFF_POWEROFF_TIMEOUT.as_secs(),
40    "SHUTDOWN_FLUSH_TIMEOUT must exceed HANDOFF_POWEROFF_TIMEOUT",
41);
42
43//--------------------------------------------------------------------------------------------------
44// Constants: Host↔Guest Protocol
45//--------------------------------------------------------------------------------------------------
46
47/// Virtio-console port name for the agent channel.
48pub const AGENT_PORT_NAME: &str = "agent";
49
50/// Virtiofs tag for the runtime filesystem (scripts, heartbeat).
51pub const RUNTIME_FS_TAG: &str = "msb_runtime";
52
53/// Guest mount point for the runtime filesystem.
54pub const RUNTIME_MOUNT_POINT: &str = "/.msb";
55
56/// Guest directory for file mount virtiofs shares.
57pub const FILE_MOUNTS_DIR: &str = "/.msb/file-mounts";
58
59/// Guest path for named scripts (added to PATH by agentd).
60pub const SCRIPTS_PATH: &str = "/.msb/scripts";
61
62/// Maximum number of simultaneous SDK clients the host relay admits.
63pub const AGENT_RELAY_MAX_CLIENTS: u32 = 128;
64
65/// Size of the correlation ID range allocated to each relay client.
66pub const AGENT_RELAY_ID_RANGE_STEP: u32 = u32::MAX / AGENT_RELAY_MAX_CLIENTS;
67
68//--------------------------------------------------------------------------------------------------
69// Constants: Guest Init Environment Variables
70//--------------------------------------------------------------------------------------------------
71
72/// Environment variable carrying tmpfs mount specs for guest init.
73///
74/// - `path` — guest mount path (required, always the first element)
75/// - `size=N` — size limit in MiB (optional)
76/// - `noexec` — mount with noexec flag (optional)
77/// - `nosuid` — accepted as an explicit assertion; tmpfs mounts always use nosuid
78/// - `ro` — mount read-only (optional)
79/// - `rw` — explicit writable default (optional)
80/// - `mode=N` — permission mode as octal integer (optional, e.g. `mode=1777`)
81///
82/// Format: `path[:opts][;path[:opts];...]`.
83///
84/// Entries are separated by `;`. Within an entry, the path comes first,
85/// followed by an optional colon and comma-separated options. Options compose
86/// order-independently (e.g. `:ro,noexec` and `:noexec,ro` are equivalent).
87///
88/// Examples:
89/// - `MSB_TMPFS=/tmp:size=256` — 256 MiB tmpfs at `/tmp`
90/// - `MSB_TMPFS=/tmp:size=256;/var/tmp:size=128` — two tmpfs mounts
91/// - `MSB_TMPFS=/tmp` — tmpfs at `/tmp` with defaults
92/// - `MSB_TMPFS=/tmp:size=256,noexec` — with noexec flag
93/// - `MSB_TMPFS=/seed:size=64,ro` — read-only tmpfs
94pub const ENV_TMPFS: &str = "MSB_TMPFS";
95
96/// Environment variable specifying how agentd assembles the root filesystem.
97///
98/// Format: comma-separated `key=value` pairs, semicolons for multi-value fields.
99///
100/// Variants:
101/// - `kind=disk-image,device=/dev/vda[,fstype=ext4]`
102/// - `kind=oci-layered,lowers=/dev/vdb;/dev/vdc;/dev/vdd,lower_fstype=erofs,upper=/dev/vde,upper_fstype=ext4`
103/// - `kind=oci-flat,lower=/dev/vdb,lower_fstype=erofs,upper=/dev/vdc,upper_fstype=ext4`
104///
105/// Legacy format (`/dev/vda[,fstype=ext4]`) is accepted and treated as `kind=disk-image`.
106pub const ENV_BLOCK_ROOT: &str = "MSB_BLOCK_ROOT";
107
108/// Environment variable carrying the guest network interface configuration.
109///
110/// Format: `key=value,...`
111///
112/// - `iface=NAME` — interface name (required)
113/// - `mac=AA:BB:CC:DD:EE:FF` — MAC address (required)
114/// - `mtu=N` — MTU (optional)
115///
116/// Example:
117/// - `MSB_NET=iface=eth0,mac=02:5a:7b:13:01:02,mtu=1500`
118pub const ENV_NET: &str = "MSB_NET";
119
120/// Environment variable carrying the guest IPv4 network configuration.
121///
122/// Format: `key=value,...`
123///
124/// - `addr=A.B.C.D/N` — address with prefix length (required)
125/// - `gw=A.B.C.D` — default gateway (required)
126/// - `dns=A.B.C.D` — DNS server (optional)
127///
128/// Example:
129/// - `MSB_NET_IPV4=addr=172.16.1.2/30,gw=172.16.1.1,dns=172.16.1.1`
130pub const ENV_NET_IPV4: &str = "MSB_NET_IPV4";
131
132/// Environment variable carrying the guest IPv6 network configuration.
133///
134/// Format: `key=value,...`
135///
136/// - `addr=ADDR/N` — address with prefix length (required)
137/// - `gw=ADDR` — default gateway (required)
138/// - `dns=ADDR` — DNS server (optional)
139///
140/// Example:
141/// - `MSB_NET_IPV6=addr=fd42:6d73:62:2a::2/64,gw=fd42:6d73:62:2a::1,dns=fd42:6d73:62:2a::1`
142pub const ENV_NET_IPV6: &str = "MSB_NET_IPV6";
143
144/// Environment variable carrying virtiofs directory volume mount specs for guest init.
145///
146/// Format: `tag:guest_path[:opts][;tag:guest_path[:opts];...]`
147///
148/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
149/// - `guest_path` — mount point inside the guest (required)
150/// - `ro` / `rw` — access mode option (optional)
151/// - `noexec` — disable direct execution from the mount (optional)
152/// - `nosuid` — accepted as an explicit assertion; directory mounts always use nosuid
153///
154/// Entries are separated by `;`.
155///
156/// Examples:
157/// - `MSB_DIR_MOUNTS=data:/data` — mount virtiofs tag `data` at `/data`
158/// - `MSB_DIR_MOUNTS=data:/data:ro,noexec` — mount read-only and noexec
159/// - `MSB_DIR_MOUNTS=data:/data;cache:/cache:ro` — two mounts
160pub const ENV_DIR_MOUNTS: &str = "MSB_DIR_MOUNTS";
161
162/// Environment variable carrying virtiofs **file** volume mount specs for guest init.
163///
164/// Used when the host path is a single file rather than a directory. The SDK
165/// wraps each file in an isolated staging directory (hard-linked to preserve
166/// the same inode) and shares that directory via virtiofs. Agentd mounts the
167/// share at [`FILE_MOUNTS_DIR`]`/<tag>/` and bind-mounts the file to the
168/// guest path.
169///
170/// Format: `tag:filename:guest_path[:opts][;tag:filename:guest_path[:opts];...]`
171///
172/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
173/// - `filename` — name of the file inside the virtiofs share (required)
174/// - `guest_path` — final file path inside the guest (required)
175/// - `ro` / `rw` — access mode option (optional)
176/// - `noexec` — disable direct execution from the mount (optional)
177/// - `nosuid` — accepted as an explicit assertion; file mounts always use nosuid
178///
179/// Entries are separated by `;`.
180///
181/// Examples:
182/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf`
183/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf:ro,noexec`
184/// - `MSB_FILE_MOUNTS=fm_a:a.sh:/usr/bin/a.sh;fm_b:b.sh:/usr/bin/b.sh`
185pub const ENV_FILE_MOUNTS: &str = "MSB_FILE_MOUNTS";
186
187/// Environment variable carrying disk-image volume mount specs for guest init.
188///
189/// Each spec describes one virtio-blk device attached for the sole purpose
190/// of being mounted at a guest path by agentd (distinct from the rootfs
191/// block device, which is described by [`ENV_BLOCK_ROOT`]).
192///
193/// Format: `id:guest_path[:opts][;id:guest_path[:opts];...]`
194///
195/// - `id` — the `virtio_blk_config.serial` value set by the VMM. Agentd
196///   resolves it to a device node via `/dev/disk/by-id/virtio-<id>`, or
197///   by scanning `/sys/block/*/serial` as a fallback.
198/// - `guest_path` — absolute mount path in the guest (required).
199/// - `fstype=...` — inner filesystem type (optional). When absent,
200///   agentd probes `/proc/filesystems` to find a type that mounts cleanly.
201/// - `ro` / `rw` — access mode option (optional).
202/// - `noexec` — disable direct execution from the mount (optional).
203/// - `nosuid` — accepted as an explicit assertion; disk-image mounts always use nosuid.
204///
205/// Entries are separated by `;`. Options are comma-separated flags or
206/// key-value pairs in the final option block.
207///
208/// Examples:
209/// - `MSB_DISK_MOUNTS=data_12ab:/data:fstype=ext4` — ext4 disk at `/data`
210/// - `MSB_DISK_MOUNTS=seed_7f:/seed:ro` — autodetect fstype, read-only
211/// - `MSB_DISK_MOUNTS=a_1:/a:fstype=ext4;b_2:/b:ro,noexec` — two disks
212pub const ENV_DISK_MOUNTS: &str = "MSB_DISK_MOUNTS";
213
214/// Environment variable carrying the default guest user for agentd execs.
215///
216/// Format: `USER[:GROUP]` or `UID[:GID]`
217///
218/// - `USER`
219/// - `UID`
220/// - `USER:GROUP`
221/// - `UID:GID`
222///
223/// Example:
224/// - `MSB_USER=alice` — default to user `alice`
225/// - `MSB_USER=1000` — default to UID 1000
226/// - `MSB_USER=alice:developers` — default to user `alice` and group `developers`
227/// - `MSB_USER=1000:100` — default to UID 1000 and GID 100
228pub const ENV_USER: &str = "MSB_USER";
229
230/// Environment variable carrying the guest hostname for agentd.
231///
232/// Format: bare string
233///
234/// Example:
235/// - `MSB_HOSTNAME=worker-01`
236///
237/// agentd calls `sethostname()` and adds the name to `/etc/hosts`.
238/// Defaults to the sandbox name when not explicitly set.
239pub const ENV_HOSTNAME: &str = "MSB_HOSTNAME";
240
241/// Environment variable carrying the DNS name the guest uses to reach
242/// the sandbox host (Docker's `host.docker.internal` equivalent).
243///
244/// The host-side network stack emits this value via its
245/// `guest_env_vars()` method; agentd reads it into
246/// [`crate::exec`]-adjacent boot params and writes the mapping into
247/// `/etc/hosts`. The value the network stack emits is a fixed
248/// protocol constant — today always `host.microsandbox.internal`.
249pub const ENV_HOST_ALIAS: &str = "MSB_HOST_ALIAS";
250
251/// Environment variable carrying sandbox-wide resource limits.
252///
253/// Format: `resource=limit[:hard][;resource=limit[:hard];...]`
254///
255/// - `resource` — lowercase rlimit name such as `nofile` or `nproc`
256/// - `limit` — soft limit
257/// - `hard` — hard limit (optional; if omitted, uses the soft limit)
258///
259/// Examples:
260/// - `MSB_RLIMITS=nofile=65535`
261/// - `MSB_RLIMITS=nofile=65535:65535;nproc=4096:4096`
262///
263/// agentd applies these during PID 1 startup so every later guest process
264/// inherits the raised baseline instead of having to opt into per-exec rlimits.
265pub const ENV_RLIMITS: &str = "MSB_RLIMITS";
266
267/// Separator byte for argv/env entries in handoff-init env vars.
268///
269/// ASCII Unit Separator (`0x1F`). Argv entries and `KEY=VAL` env pairs
270/// are arbitrary user strings, so the `;` separator other MSB_* vars use
271/// is unsafe — they collide with realistic shell input. `0x1F` is
272/// purpose-built for this and absent from any printable string.
273pub const HANDOFF_INIT_SEP: char = '\x1f';
274
275/// String form of [`HANDOFF_INIT_SEP`] for use with `&str`-friendly
276/// APIs like `[T]::join`. Avoids per-call `char.to_string()` allocations
277/// on the host's encoder side.
278pub const HANDOFF_INIT_SEP_STR: &str = "\x1f";
279
280/// Environment variable selecting a guest init binary for PID 1 handoff.
281///
282/// When set, agentd performs initial setup (mounts, runtime dirs), then
283/// forks. The parent execs the binary at this path, becoming the new
284/// PID 1. The child stays alive as a normal grandchild process serving
285/// host requests over virtio-serial.
286///
287/// Format: bare absolute path inside the guest rootfs, or the literal
288/// sentinel [`HANDOFF_INIT_AUTO`] which triggers a candidate probe in
289/// agentd (see [`HANDOFF_INIT_AUTO_CANDIDATES`]).
290///
291/// Examples:
292/// - `MSB_HANDOFF_INIT=/lib/systemd/systemd`
293/// - `MSB_HANDOFF_INIT=auto`
294pub const ENV_HANDOFF_INIT: &str = "MSB_HANDOFF_INIT";
295
296/// Sentinel value for [`ENV_HANDOFF_INIT`] requesting auto-detection.
297///
298/// When the env var matches this exact string, agentd probes
299/// [`HANDOFF_INIT_AUTO_CANDIDATES`] in order and uses the first path
300/// that exists and is executable. If none match, boot fails with a
301/// clear error in `kernel.log` listing the paths it checked.
302pub const HANDOFF_INIT_AUTO: &str = "auto";
303
304/// Ordered list of init-binary paths agentd probes when
305/// [`ENV_HANDOFF_INIT`] is set to [`HANDOFF_INIT_AUTO`].
306///
307/// Order matters: the first match wins. The list covers the three
308/// well-known locations across major distros:
309/// - `/sbin/init` — BusyBox (Alpine), sysvinit, OpenRC's wrapper.
310///   Usually a symlink to the actual init on systemd distros, so it
311///   resolves naturally on Debian/Ubuntu too.
312/// - `/lib/systemd/systemd` — Debian, Ubuntu, derivatives.
313/// - `/usr/lib/systemd/systemd` — Fedora, RHEL, modern Debian.
314pub const HANDOFF_INIT_AUTO_CANDIDATES: &[&str] = &[
315    "/sbin/init",
316    "/lib/systemd/systemd",
317    "/usr/lib/systemd/systemd",
318];
319
320/// Argv list for the handoff init binary.
321///
322/// Format: entries separated by [`HANDOFF_INIT_SEP`] (ASCII `0x1F`).
323/// Empty or unset means the init is exec'd with `argv = [program]`.
324///
325/// Example:
326/// - `MSB_HANDOFF_INIT_ARGS=--unit=multi-user.target\x1f--log-level=warning`
327pub const ENV_HANDOFF_INIT_ARGS: &str = "MSB_HANDOFF_INIT_ARGS";
328
329/// Extra environment variables for the handoff init binary.
330///
331/// Format: `KEY=VAL` pairs separated by [`HANDOFF_INIT_SEP`]
332/// (ASCII `0x1F`). Each entry must contain at least one `=`. Merged on
333/// top of the inherited env.
334///
335/// Example:
336/// - `MSB_HANDOFF_INIT_ENV=container=microsandbox\x1fLANG=C.UTF-8`
337pub const ENV_HANDOFF_INIT_ENV: &str = "MSB_HANDOFF_INIT_ENV";
338
339/// Guest-side path to the CA certificate for TLS interception.
340///
341/// Placed by the sandbox process via the runtime virtiofs mount.
342/// agentd checks for this file during init and installs it into the guest
343/// trust store.
344pub const GUEST_TLS_CA_PATH: &str = "/.msb/tls/ca.pem";
345
346/// Guest-side path to a PEM bundle of the host's extra trusted CAs.
347///
348/// Placed by the sandbox process via the runtime virtiofs mount when
349/// host-CA trust is enabled (default). agentd checks for this file during
350/// init and appends it to the guest's trust bundle, so outbound TLS works
351/// even behind a corporate MITM proxy whose gateway CA is installed on
352/// the host but unknown to the guest.
353pub const GUEST_TLS_HOST_CAS_PATH: &str = "/.msb/tls/host-cas.pem";
354
355//--------------------------------------------------------------------------------------------------
356// Exports
357//--------------------------------------------------------------------------------------------------
358
359pub mod codec;
360pub mod core;
361pub mod exec;
362pub mod fs;
363pub mod heartbeat;
364pub mod message;
365
366pub use error::*;