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/// Format: `path[,key=value,...][;path[,key=value,...];...]`
75///
76/// - `path` — guest mount path (required, always the first element)
77/// - `size=N` — size limit in MiB (optional)
78/// - `noexec` — mount with noexec flag (optional)
79/// - `ro` — mount read-only (optional)
80/// - `mode=N` — permission mode as octal integer (optional, e.g. `mode=1777`)
81///
82/// Entries are separated by `;`. Within an entry, the path comes first
83/// followed by comma-separated options. Options compose order-independently
84/// (e.g. `,ro,noexec` and `,noexec,ro` are equivalent).
85///
86/// Examples:
87/// - `MSB_TMPFS=/tmp,size=256` — 256 MiB tmpfs at `/tmp`
88/// - `MSB_TMPFS=/tmp,size=256;/var/tmp,size=128` — two tmpfs mounts
89/// - `MSB_TMPFS=/tmp` — tmpfs at `/tmp` with defaults
90/// - `MSB_TMPFS=/tmp,size=256,noexec` — with noexec flag
91/// - `MSB_TMPFS=/seed,size=64,ro` — read-only tmpfs
92pub const ENV_TMPFS: &str = "MSB_TMPFS";
93
94/// Environment variable specifying how agentd assembles the root filesystem.
95///
96/// Format: comma-separated `key=value` pairs, semicolons for multi-value fields.
97///
98/// Variants:
99/// - `kind=disk-image,device=/dev/vda[,fstype=ext4]`
100/// - `kind=oci-layered,lowers=/dev/vdb;/dev/vdc;/dev/vdd,lower_fstype=erofs,upper=/dev/vde,upper_fstype=ext4`
101/// - `kind=oci-flat,lower=/dev/vdb,lower_fstype=erofs,upper=/dev/vdc,upper_fstype=ext4`
102///
103/// Legacy format (`/dev/vda[,fstype=ext4]`) is accepted and treated as `kind=disk-image`.
104pub const ENV_BLOCK_ROOT: &str = "MSB_BLOCK_ROOT";
105
106/// Environment variable carrying the guest network interface configuration.
107///
108/// Format: `key=value,...`
109///
110/// - `iface=NAME` — interface name (required)
111/// - `mac=AA:BB:CC:DD:EE:FF` — MAC address (required)
112/// - `mtu=N` — MTU (optional)
113///
114/// Example:
115/// - `MSB_NET=iface=eth0,mac=02:5a:7b:13:01:02,mtu=1500`
116pub const ENV_NET: &str = "MSB_NET";
117
118/// Environment variable carrying the guest IPv4 network configuration.
119///
120/// Format: `key=value,...`
121///
122/// - `addr=A.B.C.D/N` — address with prefix length (required)
123/// - `gw=A.B.C.D` — default gateway (required)
124/// - `dns=A.B.C.D` — DNS server (optional)
125///
126/// Example:
127/// - `MSB_NET_IPV4=addr=172.16.1.2/30,gw=172.16.1.1,dns=172.16.1.1`
128pub const ENV_NET_IPV4: &str = "MSB_NET_IPV4";
129
130/// Environment variable carrying the guest IPv6 network configuration.
131///
132/// Format: `key=value,...`
133///
134/// - `addr=ADDR/N` — address with prefix length (required)
135/// - `gw=ADDR` — default gateway (required)
136/// - `dns=ADDR` — DNS server (optional)
137///
138/// Example:
139/// - `MSB_NET_IPV6=addr=fd42:6d73:62:2a::2/64,gw=fd42:6d73:62:2a::1,dns=fd42:6d73:62:2a::1`
140pub const ENV_NET_IPV6: &str = "MSB_NET_IPV6";
141
142/// Environment variable carrying virtiofs directory volume mount specs for guest init.
143///
144/// Format: `tag:guest_path[:ro][;tag:guest_path[:ro];...]`
145///
146/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
147/// - `guest_path` — mount point inside the guest (required)
148/// - `ro` — mount read-only (optional suffix)
149///
150/// Entries are separated by `;`.
151///
152/// Examples:
153/// - `MSB_DIR_MOUNTS=data:/data` — mount virtiofs tag `data` at `/data`
154/// - `MSB_DIR_MOUNTS=data:/data:ro` — mount read-only
155/// - `MSB_DIR_MOUNTS=data:/data;cache:/cache:ro` — two mounts
156pub const ENV_DIR_MOUNTS: &str = "MSB_DIR_MOUNTS";
157
158/// Environment variable carrying virtiofs **file** volume mount specs for guest init.
159///
160/// Used when the host path is a single file rather than a directory. The SDK
161/// wraps each file in an isolated staging directory (hard-linked to preserve
162/// the same inode) and shares that directory via virtiofs. Agentd mounts the
163/// share at [`FILE_MOUNTS_DIR`]`/<tag>/` and bind-mounts the file to the
164/// guest path.
165///
166/// Format: `tag:filename:guest_path[:ro][;tag:filename:guest_path[:ro];...]`
167///
168/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
169/// - `filename` — name of the file inside the virtiofs share (required)
170/// - `guest_path` — final file path inside the guest (required)
171/// - `ro` — mount read-only (optional suffix)
172///
173/// Entries are separated by `;`.
174///
175/// Examples:
176/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf`
177/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf:ro`
178/// - `MSB_FILE_MOUNTS=fm_a:a.sh:/usr/bin/a.sh;fm_b:b.sh:/usr/bin/b.sh`
179pub const ENV_FILE_MOUNTS: &str = "MSB_FILE_MOUNTS";
180
181/// Environment variable carrying disk-image volume mount specs for guest init.
182///
183/// Each spec describes one virtio-blk device attached for the sole purpose
184/// of being mounted at a guest path by agentd (distinct from the rootfs
185/// block device, which is described by [`ENV_BLOCK_ROOT`]).
186///
187/// Format: `id:guest_path[:fstype][:ro][;id:guest_path[:fstype][:ro];...]`
188///
189/// - `id` — the `virtio_blk_config.serial` value set by the VMM. Agentd
190/// resolves it to a device node via `/dev/disk/by-id/virtio-<id>`, or
191/// by scanning `/sys/block/*/serial` as a fallback.
192/// - `guest_path` — absolute mount path in the guest (required).
193/// - `fstype` — inner filesystem type (optional). When empty or absent,
194/// agentd probes `/proc/filesystems` to find a type that mounts cleanly.
195/// - `ro` — optional flag: mount read-only.
196///
197/// Entries are separated by `;`. The `fstype` slot is positional, not
198/// keyed. To express "no fstype + ro" the slot must be empty:
199/// `id:/path::ro`. The form `id:/path:ro` would parse as `fstype=ro`,
200/// not the readonly flag.
201///
202/// Examples:
203/// - `MSB_DISK_MOUNTS=data_12ab:/data:ext4` — ext4 disk at `/data`
204/// - `MSB_DISK_MOUNTS=seed_7f:/seed::ro` — autodetect fstype, read-only
205/// - `MSB_DISK_MOUNTS=a_1:/a:ext4;b_2:/b::ro` — two disks
206pub const ENV_DISK_MOUNTS: &str = "MSB_DISK_MOUNTS";
207
208/// Environment variable carrying the default guest user for agentd execs.
209///
210/// Format: `USER[:GROUP]` or `UID[:GID]`
211///
212/// - `USER`
213/// - `UID`
214/// - `USER:GROUP`
215/// - `UID:GID`
216///
217/// Example:
218/// - `MSB_USER=alice` — default to user `alice`
219/// - `MSB_USER=1000` — default to UID 1000
220/// - `MSB_USER=alice:developers` — default to user `alice` and group `developers`
221/// - `MSB_USER=1000:100` — default to UID 1000 and GID 100
222pub const ENV_USER: &str = "MSB_USER";
223
224/// Environment variable carrying the guest hostname for agentd.
225///
226/// Format: bare string
227///
228/// Example:
229/// - `MSB_HOSTNAME=worker-01`
230///
231/// agentd calls `sethostname()` and adds the name to `/etc/hosts`.
232/// Defaults to the sandbox name when not explicitly set.
233pub const ENV_HOSTNAME: &str = "MSB_HOSTNAME";
234
235/// Environment variable carrying the DNS name the guest uses to reach
236/// the sandbox host (Docker's `host.docker.internal` equivalent).
237///
238/// The host-side network stack emits this value via its
239/// `guest_env_vars()` method; agentd reads it into
240/// [`crate::exec`]-adjacent boot params and writes the mapping into
241/// `/etc/hosts`. The value the network stack emits is a fixed
242/// protocol constant — today always `host.microsandbox.internal`.
243pub const ENV_HOST_ALIAS: &str = "MSB_HOST_ALIAS";
244
245/// Environment variable carrying sandbox-wide resource limits.
246///
247/// Format: `resource=limit[:hard][;resource=limit[:hard];...]`
248///
249/// - `resource` — lowercase rlimit name such as `nofile` or `nproc`
250/// - `limit` — soft limit
251/// - `hard` — hard limit (optional; if omitted, uses the soft limit)
252///
253/// Examples:
254/// - `MSB_RLIMITS=nofile=65535`
255/// - `MSB_RLIMITS=nofile=65535:65535;nproc=4096:4096`
256///
257/// agentd applies these during PID 1 startup so every later guest process
258/// inherits the raised baseline instead of having to opt into per-exec rlimits.
259pub const ENV_RLIMITS: &str = "MSB_RLIMITS";
260
261/// Separator byte for argv/env entries in handoff-init env vars.
262///
263/// ASCII Unit Separator (`0x1F`). Argv entries and `KEY=VAL` env pairs
264/// are arbitrary user strings, so the `;` separator other MSB_* vars use
265/// is unsafe — they collide with realistic shell input. `0x1F` is
266/// purpose-built for this and absent from any printable string.
267pub const HANDOFF_INIT_SEP: char = '\x1f';
268
269/// String form of [`HANDOFF_INIT_SEP`] for use with `&str`-friendly
270/// APIs like `[T]::join`. Avoids per-call `char.to_string()` allocations
271/// on the host's encoder side.
272pub const HANDOFF_INIT_SEP_STR: &str = "\x1f";
273
274/// Environment variable selecting a guest init binary for PID 1 handoff.
275///
276/// When set, agentd performs initial setup (mounts, runtime dirs), then
277/// forks. The parent execs the binary at this path, becoming the new
278/// PID 1. The child stays alive as a normal grandchild process serving
279/// host requests over virtio-serial.
280///
281/// Format: bare absolute path inside the guest rootfs, or the literal
282/// sentinel [`HANDOFF_INIT_AUTO`] which triggers a candidate probe in
283/// agentd (see [`HANDOFF_INIT_AUTO_CANDIDATES`]).
284///
285/// Examples:
286/// - `MSB_HANDOFF_INIT=/lib/systemd/systemd`
287/// - `MSB_HANDOFF_INIT=auto`
288pub const ENV_HANDOFF_INIT: &str = "MSB_HANDOFF_INIT";
289
290/// Sentinel value for [`ENV_HANDOFF_INIT`] requesting auto-detection.
291///
292/// When the env var matches this exact string, agentd probes
293/// [`HANDOFF_INIT_AUTO_CANDIDATES`] in order and uses the first path
294/// that exists and is executable. If none match, boot fails with a
295/// clear error in `kernel.log` listing the paths it checked.
296pub const HANDOFF_INIT_AUTO: &str = "auto";
297
298/// Ordered list of init-binary paths agentd probes when
299/// [`ENV_HANDOFF_INIT`] is set to [`HANDOFF_INIT_AUTO`].
300///
301/// Order matters: the first match wins. The list covers the three
302/// well-known locations across major distros:
303/// - `/sbin/init` — BusyBox (Alpine), sysvinit, OpenRC's wrapper.
304/// Usually a symlink to the actual init on systemd distros, so it
305/// resolves naturally on Debian/Ubuntu too.
306/// - `/lib/systemd/systemd` — Debian, Ubuntu, derivatives.
307/// - `/usr/lib/systemd/systemd` — Fedora, RHEL, modern Debian.
308pub const HANDOFF_INIT_AUTO_CANDIDATES: &[&str] = &[
309 "/sbin/init",
310 "/lib/systemd/systemd",
311 "/usr/lib/systemd/systemd",
312];
313
314/// Argv list for the handoff init binary.
315///
316/// Format: entries separated by [`HANDOFF_INIT_SEP`] (ASCII `0x1F`).
317/// Empty or unset means the init is exec'd with `argv = [program]`.
318///
319/// Example:
320/// - `MSB_HANDOFF_INIT_ARGS=--unit=multi-user.target\x1f--log-level=warning`
321pub const ENV_HANDOFF_INIT_ARGS: &str = "MSB_HANDOFF_INIT_ARGS";
322
323/// Extra environment variables for the handoff init binary.
324///
325/// Format: `KEY=VAL` pairs separated by [`HANDOFF_INIT_SEP`]
326/// (ASCII `0x1F`). Each entry must contain at least one `=`. Merged on
327/// top of the inherited env.
328///
329/// Example:
330/// - `MSB_HANDOFF_INIT_ENV=container=microsandbox\x1fLANG=C.UTF-8`
331pub const ENV_HANDOFF_INIT_ENV: &str = "MSB_HANDOFF_INIT_ENV";
332
333/// Guest-side path to the CA certificate for TLS interception.
334///
335/// Placed by the sandbox process via the runtime virtiofs mount.
336/// agentd checks for this file during init and installs it into the guest
337/// trust store.
338pub const GUEST_TLS_CA_PATH: &str = "/.msb/tls/ca.pem";
339
340/// Guest-side path to a PEM bundle of the host's extra trusted CAs.
341///
342/// Placed by the sandbox process via the runtime virtiofs mount when
343/// host-CA trust is enabled (default). agentd checks for this file during
344/// init and appends it to the guest's trust bundle, so outbound TLS works
345/// even behind a corporate MITM proxy whose gateway CA is installed on
346/// the host but unknown to the guest.
347pub const GUEST_TLS_HOST_CAS_PATH: &str = "/.msb/tls/host-cas.pem";
348
349//--------------------------------------------------------------------------------------------------
350// Exports
351//--------------------------------------------------------------------------------------------------
352
353pub mod codec;
354pub mod core;
355pub mod exec;
356pub mod fs;
357pub mod heartbeat;
358pub mod message;
359
360pub use error::*;