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
16const HANDOFF_POWEROFF_TIMEOUT_SECS: u64 = 5;
17const SHUTDOWN_FLUSH_MARGIN_SECS: u64 = 3;
18const NORMAL_SHUTDOWN_FLUSH_TIMEOUT_SECS: u64 = 2;
19
20/// Base grace used by explicit host lifetime policies for handoff-init sandboxes.
21///
22/// This is not a graceful Stop deadline. Agentd signals foreign PID 1 once and
23/// leaves its shutdown schedule intact; normal Stop never escalates on a timer.
24pub const HANDOFF_POWEROFF_TIMEOUT: std::time::Duration =
25 std::time::Duration::from_secs(HANDOFF_POWEROFF_TIMEOUT_SECS);
26
27/// Additional host-side margin for an explicitly selected lifetime policy.
28///
29/// Ordinary graceful Stop does not use this fallback margin.
30pub const SHUTDOWN_FLUSH_MARGIN: std::time::Duration =
31 std::time::Duration::from_secs(SHUTDOWN_FLUSH_MARGIN_SECS);
32
33/// Explicit lifetime-policy fallback window when agentd remains PID 1.
34///
35/// agentd can synchronously `sync()`, remount the root read-only, and request
36/// kernel poweroff directly in this mode, so normal development sandboxes
37/// should not pay the longer handoff-init grace.
38pub const NORMAL_SHUTDOWN_FLUSH_TIMEOUT: std::time::Duration =
39 std::time::Duration::from_secs(NORMAL_SHUTDOWN_FLUSH_TIMEOUT_SECS);
40
41/// Explicit lifetime-policy fallback window for a foreign guest PID 1.
42///
43/// Idle, startup-command completion and parent-death policies may bound guest
44/// shutdown before host teardown. Public Stop and its timeout variant do not
45/// install this timer, and agentd no longer sends a fallback SIGTERM.
46///
47/// Equals [`HANDOFF_POWEROFF_TIMEOUT`] plus [`SHUTDOWN_FLUSH_MARGIN`] for the
48/// init's own signal handling — enforced at compile time below.
49pub const HANDOFF_SHUTDOWN_FLUSH_TIMEOUT: std::time::Duration =
50 std::time::Duration::from_secs(HANDOFF_POWEROFF_TIMEOUT_SECS + SHUTDOWN_FLUSH_MARGIN_SECS);
51
52/// Legacy name for the handoff-init shutdown fallback window.
53///
54/// New runtime code should choose between [`NORMAL_SHUTDOWN_FLUSH_TIMEOUT`]
55/// and [`HANDOFF_SHUTDOWN_FLUSH_TIMEOUT`] based on whether a sandbox uses
56/// handoff init.
57pub const SHUTDOWN_FLUSH_TIMEOUT: std::time::Duration = HANDOFF_SHUTDOWN_FLUSH_TIMEOUT;
58
59// Keep a positive host margin in the explicit lifetime-policy window.
60const _: () = assert!(
61 HANDOFF_SHUTDOWN_FLUSH_TIMEOUT.as_secs() > HANDOFF_POWEROFF_TIMEOUT.as_secs(),
62 "HANDOFF_SHUTDOWN_FLUSH_TIMEOUT must exceed HANDOFF_POWEROFF_TIMEOUT",
63);
64
65//--------------------------------------------------------------------------------------------------
66// Constants: Host↔Guest Protocol
67//--------------------------------------------------------------------------------------------------
68
69/// Virtio-console port name for the agent channel.
70pub const AGENT_PORT_NAME: &str = "agent";
71
72/// Virtio-console port name for the optional generation-8 bulk lane.
73#[doc(hidden)]
74pub const AGENT_BULK_PORT_NAME: &str = "agent-bulk";
75
76/// Internal kernel command-line selector for the first dual-port transport profile.
77#[doc(hidden)]
78pub const AGENT_TRANSPORT_DUAL_PORT_CMDLINE: &str = "microsandbox.agent_transport=dual-port-v1";
79
80/// Virtiofs tag for the runtime filesystem (scripts, heartbeat).
81pub const RUNTIME_FS_TAG: &str = "msb_runtime";
82
83/// Guest-write byte budget for the runtime (`/.msb`) virtiofs mount.
84///
85/// `/.msb` is a host↔guest control channel, not bulk storage: the only
86/// guest-written payload is a ~1 KiB heartbeat (host-written scripts and TLS
87/// certs form the mount's baseline and are not charged). This 16 MiB ceiling is
88/// therefore almost entirely abuse headroom — it exists so the channel cannot be
89/// used to fill the host disk. It is intentionally a fixed constant rather than
90/// a user-facing knob.
91pub const RUNTIME_FS_QUOTA_BYTES: u64 = 16 * 1024 * 1024;
92
93/// Guest mount point for the runtime filesystem.
94pub const RUNTIME_MOUNT_POINT: &str = "/.msb";
95
96/// Guest directory for file mount virtiofs shares.
97pub const FILE_MOUNTS_DIR: &str = "/.msb/file-mounts";
98
99/// Guest path for named scripts (added to PATH by agentd).
100pub const SCRIPTS_PATH: &str = "/.msb/scripts";
101
102/// Maximum number of simultaneous SDK clients the host relay admits.
103pub const AGENT_RELAY_MAX_CLIENTS: u32 = 128;
104
105/// Size of the correlation ID range allocated to each relay client.
106pub const AGENT_RELAY_ID_RANGE_STEP: u32 = u32::MAX / AGENT_RELAY_MAX_CLIENTS;
107
108//--------------------------------------------------------------------------------------------------
109// Constants: Guest Init Environment Variables
110//--------------------------------------------------------------------------------------------------
111
112/// Environment variable carrying the sandbox in-guest security profile.
113///
114/// Values:
115/// - `default` — preserve normal guest-root semantics. Exec sessions do not
116/// set `no_new_privs` and keep `CAP_SYS_ADMIN`.
117/// - `restricted` — set `no_new_privs` and drop `CAP_SYS_ADMIN` before user
118/// exec sessions. Agentd also forces `nosuid,nodev` on user mounts.
119///
120/// Example:
121/// - `MSB_SECURITY_PROFILE=restricted`
122pub const ENV_SECURITY_PROFILE: &str = "MSB_SECURITY_PROFILE";
123
124/// Environment variable carrying tmpfs mount specs for guest init.
125///
126/// - `path` — guest mount path (required, always the first element)
127/// - `size=N` — size limit in MiB (optional)
128/// - `noexec` — mount with noexec flag (optional)
129/// - `nosuid` — mount with nosuid flag (optional)
130/// - `nodev` — mount with nodev flag (optional)
131/// - `ro` — mount read-only (optional)
132/// - `rw` — explicit writable default (optional)
133/// - `mode=N` — permission mode as octal integer (optional, e.g. `mode=1777`)
134///
135/// Format: `path[:opts][;path[:opts];...]`.
136///
137/// Entries are separated by `;`. Within an entry, the path comes first,
138/// followed by an optional colon and comma-separated options. Options compose
139/// order-independently (e.g. `:ro,noexec` and `:noexec,ro` are equivalent).
140///
141/// Examples:
142/// - `MSB_TMPFS=/tmp:size=256` — 256 MiB tmpfs at `/tmp`
143/// - `MSB_TMPFS=/tmp:size=256;/var/tmp:size=128` — two tmpfs mounts
144/// - `MSB_TMPFS=/tmp` — tmpfs at `/tmp` with defaults
145/// - `MSB_TMPFS=/tmp:size=256,noexec` — with noexec flag
146/// - `MSB_TMPFS=/seed:size=64,ro` — read-only tmpfs
147pub const ENV_TMPFS: &str = "MSB_TMPFS";
148
149/// Environment variable specifying how agentd assembles the root filesystem.
150///
151/// Format: comma-separated `key=value` pairs, semicolons for multi-value fields.
152///
153/// Variants:
154/// - `kind=disk-image,device=/dev/vda[,fstype=ext4]`
155/// - `kind=oci-layered,lowers=/dev/vdb;/dev/vdc;/dev/vdd,lower_fstype=erofs,upper=/dev/vde,upper_fstype=ext4`
156/// - `kind=oci-flat,lower=/dev/vdb,lower_fstype=erofs,upper=/dev/vdc,upper_fstype=ext4`
157///
158/// Legacy format (`/dev/vda[,fstype=ext4]`) is accepted and treated as `kind=disk-image`.
159pub const ENV_BLOCK_ROOT: &str = "MSB_BLOCK_ROOT";
160
161/// Environment variable carrying the guest network interface configuration.
162///
163/// Format: `key=value,...`
164///
165/// - `iface=NAME` — interface name (required)
166/// - `mac=AA:BB:CC:DD:EE:FF` — MAC address (required)
167/// - `mtu=N` — MTU (optional)
168///
169/// Example:
170/// - `MSB_NET=iface=eth0,mac=02:5a:7b:13:01:02,mtu=1500`
171pub const ENV_NET: &str = "MSB_NET";
172
173/// Environment variable carrying the guest IPv4 network configuration.
174///
175/// Format: `key=value,...`
176///
177/// - `addr=A.B.C.D/N` — address with prefix length (required)
178/// - `gw=A.B.C.D` — default gateway (required)
179/// - `dns=A.B.C.D` — DNS server (optional)
180///
181/// Example:
182/// - `MSB_NET_IPV4=addr=172.16.1.2/30,gw=172.16.1.1,dns=172.16.1.1`
183pub const ENV_NET_IPV4: &str = "MSB_NET_IPV4";
184
185/// Environment variable carrying the guest IPv6 network configuration.
186///
187/// Format: `key=value,...`
188///
189/// - `addr=ADDR/N` — address with prefix length (required)
190/// - `gw=ADDR` — default gateway (required)
191/// - `dns=ADDR` — DNS server (optional)
192///
193/// Example:
194/// - `MSB_NET_IPV6=addr=fd42:6d73:62:2a::2/64,gw=fd42:6d73:62:2a::1,dns=fd42:6d73:62:2a::1`
195pub const ENV_NET_IPV6: &str = "MSB_NET_IPV6";
196
197/// Environment variable carrying virtiofs directory volume mount specs for guest init.
198///
199/// Format: `tag:guest_path[:opts][;tag:guest_path[:opts];...]`
200///
201/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
202/// - `guest_path` — mount point inside the guest (required)
203/// - `ro` / `rw` — access mode option (optional)
204/// - `noexec` — disable direct execution from the mount (optional)
205/// - `nosuid` — mount with nosuid flag (optional)
206/// - `nodev` — mount with nodev flag (optional)
207///
208/// Entries are separated by `;`.
209///
210/// Examples:
211/// - `MSB_DIR_MOUNTS=data:/data` — mount virtiofs tag `data` at `/data`
212/// - `MSB_DIR_MOUNTS=data:/data:ro,noexec` — mount read-only and noexec
213/// - `MSB_DIR_MOUNTS=data:/data;cache:/cache:ro` — two mounts
214pub const ENV_DIR_MOUNTS: &str = "MSB_DIR_MOUNTS";
215
216/// Environment variable carrying virtiofs **file** volume mount specs for guest init.
217///
218/// Used when the host path is a single file rather than a directory. The SDK
219/// asks the runtime to expose the source through a synthetic one-entry
220/// filesystem. Agentd mounts that share at [`FILE_MOUNTS_DIR`]`/<tag>/` and
221/// bind-mounts the file to the guest path.
222///
223/// Format: `tag:filename:guest_path[:opts][;tag:filename:guest_path[:opts];...]`
224///
225/// - `tag` — virtiofs tag name (required, matches the tag used in `--mount`)
226/// - `filename` — name of the file inside the virtiofs share (required)
227/// - `guest_path` — final file path inside the guest (required)
228/// - `ro` / `rw` — access mode option (optional)
229/// - `noexec` — disable direct execution from the mount (optional)
230/// - `nosuid` — mount with nosuid flag (optional)
231/// - `nodev` — mount with nodev flag (optional)
232///
233/// Entries are separated by `;`.
234///
235/// Examples:
236/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf`
237/// - `MSB_FILE_MOUNTS=fm_config:app.conf:/etc/app.conf:ro,noexec`
238/// - `MSB_FILE_MOUNTS=fm_a:a.sh:/usr/bin/a.sh;fm_b:b.sh:/usr/bin/b.sh`
239pub const ENV_FILE_MOUNTS: &str = "MSB_FILE_MOUNTS";
240
241/// Environment variable carrying disk-image volume mount specs for guest init.
242///
243/// Each spec describes one virtio-blk device attached for the sole purpose
244/// of being mounted at a guest path by agentd (distinct from the rootfs
245/// block device, which is described by [`ENV_BLOCK_ROOT`]).
246///
247/// Format: `id:guest_path[:opts][;id:guest_path[:opts];...]`
248///
249/// - `id` — the `virtio_blk_config.serial` value set by the VMM. Agentd
250/// resolves it to a device node via `/dev/disk/by-id/virtio-<id>`, or
251/// by scanning `/sys/block/*/serial` as a fallback.
252/// - `guest_path` — absolute mount path in the guest (required).
253/// - `fstype=...` — inner filesystem type (optional). When absent,
254/// agentd probes `/proc/filesystems` to find a type that mounts cleanly.
255/// - `ro` / `rw` — access mode option (optional).
256/// - `noexec` — disable direct execution from the mount (optional).
257/// - `nosuid` — mount with nosuid flag (optional).
258/// - `nodev` — mount with nodev flag (optional).
259///
260/// Entries are separated by `;`. Options are comma-separated flags or
261/// key-value pairs in the final option block.
262///
263/// Examples:
264/// - `MSB_DISK_MOUNTS=data_12ab:/data:fstype=ext4` — ext4 disk at `/data`
265/// - `MSB_DISK_MOUNTS=seed_7f:/seed:ro` — autodetect fstype, read-only
266/// - `MSB_DISK_MOUNTS=a_1:/a:fstype=ext4;b_2:/b:ro,noexec` — two disks
267pub const ENV_DISK_MOUNTS: &str = "MSB_DISK_MOUNTS";
268
269/// Environment variable carrying the default guest user for agentd execs.
270///
271/// Format: `USER[:GROUP]` or `UID[:GID]`
272///
273/// - `USER`
274/// - `UID`
275/// - `USER:GROUP`
276/// - `UID:GID`
277///
278/// Example:
279/// - `MSB_USER=alice` — default to user `alice`
280/// - `MSB_USER=1000` — default to UID 1000
281/// - `MSB_USER=alice:developers` — default to user `alice` and group `developers`
282/// - `MSB_USER=1000:100` — default to UID 1000 and GID 100
283pub const ENV_USER: &str = "MSB_USER";
284
285/// Environment variable carrying the guest hostname for agentd.
286///
287/// Format: bare string
288///
289/// Example:
290/// - `MSB_HOSTNAME=worker-01`
291///
292/// agentd calls `sethostname()` and adds the name to `/etc/hosts`.
293/// Defaults to a sandbox-name-derived hostname when not explicitly set.
294pub const ENV_HOSTNAME: &str = "MSB_HOSTNAME";
295
296/// Environment variable carrying the DNS name the guest uses to reach
297/// the sandbox host (Docker's `host.docker.internal` equivalent).
298///
299/// Legacy environment spelling for the host alias now carried in the typed
300/// guest bootstrap. Agentd writes the mapping into `/etc/hosts`. The value the
301/// network stack emits is fixed at `host.microsandbox.internal`.
302pub const ENV_HOST_ALIAS: &str = "MSB_HOST_ALIAS";
303
304/// Environment variable carrying sandbox-wide resource limits.
305///
306/// Format: `resource=limit[:hard][;resource=limit[:hard];...]`
307///
308/// - `resource` — lowercase rlimit name such as `nofile` or `nproc`
309/// - `limit` — soft limit
310/// - `hard` — hard limit (optional; if omitted, uses the soft limit)
311///
312/// Examples:
313/// - `MSB_RLIMITS=nofile=65535`
314/// - `MSB_RLIMITS=nofile=65535:65535;nproc=4096:4096`
315///
316/// agentd applies these during PID 1 startup so every later guest process
317/// inherits the raised baseline instead of having to opt into per-exec rlimits.
318pub const ENV_RLIMITS: &str = "MSB_RLIMITS";
319
320/// Environment variable selecting a guest init binary for PID 1 handoff.
321///
322/// When set, agentd performs initial setup (mounts, runtime dirs), then
323/// forks. The parent execs the binary at this path, becoming the new
324/// PID 1. The child stays alive as a normal grandchild process serving
325/// host requests over virtio-serial.
326///
327/// Format: bare absolute path inside the guest rootfs, or the literal
328/// sentinel [`HANDOFF_INIT_AUTO`] which triggers a candidate probe in
329/// agentd (see [`HANDOFF_INIT_AUTO_CANDIDATES`]).
330///
331/// Examples:
332/// - `MSB_HANDOFF_INIT=/lib/systemd/systemd`
333/// - `MSB_HANDOFF_INIT=auto`
334pub const ENV_HANDOFF_INIT: &str = "MSB_HANDOFF_INIT";
335
336/// Sentinel value for [`ENV_HANDOFF_INIT`] requesting auto-detection.
337///
338/// The host may resolve this sentinel before boot when an OCI image
339/// declares a known init as the first entrypoint token. If the sentinel
340/// reaches the guest unchanged, agentd probes [`HANDOFF_INIT_AUTO_CANDIDATES`]
341/// in order and uses the first path that exists and is executable. If
342/// none match, boot fails with a clear error in `kernel.log` listing the
343/// paths it checked.
344pub const HANDOFF_INIT_AUTO: &str = "auto";
345
346/// Ordered list of image entrypoint paths that `--init auto` may treat
347/// as an explicit handoff init.
348///
349/// This host-side list is intentionally slightly wider than
350/// [`HANDOFF_INIT_AUTO_CANDIDATES`]: `/init` is common in s6-overlay
351/// images but too broad to probe blindly inside every guest rootfs.
352/// Matching it only when the image declares it as ENTRYPOINT keeps the
353/// behavior image-directed.
354pub const HANDOFF_INIT_IMAGE_ENTRYPOINT_CANDIDATES: &[&str] = &[
355 "/init",
356 "/sbin/init",
357 "/lib/systemd/systemd",
358 "/usr/lib/systemd/systemd",
359];
360
361/// Ordered list of init-binary paths agentd probes when
362/// [`ENV_HANDOFF_INIT`] is set to [`HANDOFF_INIT_AUTO`].
363///
364/// Order matters: the first match wins. The list covers the three
365/// well-known locations across major distros:
366/// - `/sbin/init` — BusyBox (Alpine), sysvinit, OpenRC's wrapper.
367/// Usually a symlink to the actual init on systemd distros, so it
368/// resolves naturally on Debian/Ubuntu too.
369/// - `/lib/systemd/systemd` — Debian, Ubuntu, derivatives.
370/// - `/usr/lib/systemd/systemd` — Fedora, RHEL, modern Debian.
371pub const HANDOFF_INIT_AUTO_CANDIDATES: &[&str] = &[
372 "/sbin/init",
373 "/lib/systemd/systemd",
374 "/usr/lib/systemd/systemd",
375];
376
377/// Argv list for the handoff init binary.
378///
379/// Format: base64url-no-padding encoded JSON array of strings.
380/// Empty or unset means the init is exec'd with `argv = [program]`.
381/// This deliberately differs from the delimiter-based `MSB_*` boot env
382/// formats because argv entries are arbitrary strings; wrapping JSON in
383/// base64url preserves spaces, separators, empty strings, and Unicode
384/// without inventing a second escaping language.
385///
386/// Example:
387/// - `MSB_HANDOFF_INIT_ARGS=WyItdW5pdD1tdWx0aS11c2VyLnRhcmdldCJd`
388pub const ENV_HANDOFF_INIT_ARGS: &str = "MSB_HANDOFF_INIT_ARGS";
389
390/// Working directory for the handoff init binary.
391///
392/// Docker applies `WORKDIR` before executing `ENTRYPOINT + CMD`. Init handoff
393/// uses this optional path so image-declared init entrypoints receive the same
394/// process cwd as they would under container startup.
395///
396/// Example:
397/// - `MSB_HANDOFF_INIT_CWD=/opt/app`
398pub const ENV_HANDOFF_INIT_CWD: &str = "MSB_HANDOFF_INIT_CWD";
399
400/// Extra environment variables for the handoff init binary.
401///
402/// Format: base64url-no-padding encoded JSON array of `[key, value]`
403/// pairs. Merged on top of the inherited env.
404/// This uses the same structured payload exception as
405/// [`ENV_HANDOFF_INIT_ARGS`] so env values can contain the delimiter
406/// characters used by older `MSB_*` boot env formats.
407///
408/// Example:
409/// - `MSB_HANDOFF_INIT_ENV=W1siY29udGFpbmVyIiwibWljcm9zYW5kYm94Il1d`
410pub const ENV_HANDOFF_INIT_ENV: &str = "MSB_HANDOFF_INIT_ENV";
411
412/// Guest-side path to the CA certificate for TLS interception.
413///
414/// Placed by the sandbox process via the runtime virtiofs mount.
415/// agentd checks for this file during init and installs it into the guest
416/// trust store.
417pub const GUEST_TLS_CA_PATH: &str = "/.msb/tls/ca.pem";
418
419/// Guest-side path to a PEM bundle of the host's extra trusted CAs.
420///
421/// Placed by the sandbox process via the runtime virtiofs mount when
422/// host-CA trust is enabled (default). agentd checks for this file during
423/// init and appends it to the guest's trust bundle, so outbound TLS works
424/// even behind a corporate MITM proxy whose gateway CA is installed on
425/// the host but unknown to the guest.
426pub const GUEST_TLS_HOST_CAS_PATH: &str = "/.msb/tls/host-cas.pem";
427
428//--------------------------------------------------------------------------------------------------
429// Exports
430//--------------------------------------------------------------------------------------------------
431
432pub mod bootstrap;
433pub mod bulk;
434pub mod codec;
435pub mod control;
436pub mod core;
437pub mod exec;
438pub mod fs;
439pub mod heartbeat;
440pub mod message;
441pub mod tcp;
442#[doc(hidden)]
443pub mod transport;
444pub mod wire;
445
446pub use error::*;