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