Skip to content

WebAssembly VMM - #81

Open
mitsuhiko wants to merge 19 commits into
mainfrom
wasm-new
Open

WebAssembly VMM#81
mitsuhiko wants to merge 19 commits into
mainfrom
wasm-new

Conversation

@mitsuhiko

Copy link
Copy Markdown
Member

This PR adds a third vmm backend called wasm-node which runs a guest within WASM and executed by node's WASM support.

This does not fork Gondolin's host control plane. Up until this point the host talked to qemu / krun over Unix-socket virtio channels. As this is not an option for WASM, that transport layer is now abstracted behind ServerTransport, and wasm-node swaps the Unix socket transport for a function based bridge while keeping the same framed protocol and mostly the same host-side server logic.

Concretely:

  • Before: qemu / krun boot a real Linux guest, and Gondolin talks to it over separate control/fs/ssh/ingress channels.
  • Now: vmm=wasm-node starts a Node child (WasmFunctionBridgeRunner / WasmFunctionBridgeController) instead of a VM process.

The real wasm path works like this:

  1. The host spawns a Node runner.
  2. Host-runner traffic uses Node IPC plus FunctionBridgeTransport channels.
  3. The runner launches wasm-runner.ts, which instantiates the wasm module with Node's WASI support.
  4. Inside the guest, sandboxd now supports --transport=stdio, so it can speak the same CBOR/framed protocol over stdin/stdout instead of a virtio socket.

Currently stdout/stdin framing is wrapped in a line-safe base64 envelope so control traffic does not get mixed up with regular process stdio. That is a smell I want to remove.

Additionally the network support reuses the existing host stack: the wasm runner synthesizes socket-like WASI FDs backed by the existing host network backend, so httpHooks, DNS policy, TLS MITM, etc. still go through the same enforcement path.

Current intentional differences vs main / real VM backends:

  • no qcow2 checkpoints / resume
  • root disk is treated as read-only
  • no guest loopback tcp-forward parity (openTcpStream, enableSsh(), enableIngress())

For builds, this branch also adds a path for custom images to emit sandbox.wasm (via container2wasm), record it in manifest.assets.wasm, and let vmm=wasm-node resolve it automatically.

Resolve wasm guest modules from image manifests and tighten wasi-stdio mode handling.
Use stdio envelope framing in the wasm function-bridge runner and keep wasm runner env minimal.
Stabilize interactive PTY behavior with newline normalization and TTY attach output-mode fixes.
Refactor exec lifecycle cleanup helpers and add PTY line-ending regression tests.
Enable wasm-node to connect to the existing qemu-network backend via a net socket bridge in the WASI runner, including custom socket/poll shims for stdin + network interop.

Allow wasm-node VFS mounts to proceed by materializing mount placeholders in-guest and seeding the MITM CA certificate so HTTPS mediation works without -k. tcp-forward channels remain capability-gated.
Restore explicit capability gating for wasm-node VFS mounts and remove placeholder mount-path materialization that made /workspace appear mounted while empty.

Keep wasm-node behavior aligned with backend capabilities and update docs/tests to reflect the current parity state.
Stabilize stdio transport framing between sandboxd and the wasm runner,
including robust envelope parsing and malformed-frame recovery under PTY load.

Route wasm-node through the same FS RPC path as qemu/krun, tighten VFS bind
resolution, and update startup flow so readiness and bind materialization are
deterministic across backends.

Add/refresh wasm-node parity and bridge tests covering memfs, hostfs, and
custom provider round trips.
Document the intentionally unsupported wasm-node capabilities and include wasm-node in backend parity coverage where parity is expected.

Skip tcp-forward, SSH, and ingress parity checks via explicit capability-based unsupported reasons and add wasm runtime preflight helpers for the parity suite.
Add a wasm-node profiling script plus lightweight per-process summaries and traces for the host bridge, runner bridge, and wasm runtime.

Instrument the transport, bridge, and wasm runtime paths so boot, exec, PTY latency, throughput, framing, and runtime I/O costs can be measured directly while investigating wasm-node performance.
Always use the custom stdin bridge and poll_oneoff path so wasm-node exercises the same runtime I/O flow whether guest networking is enabled or not.

Also avoid the old fixed 20ms idle wait by only retry-waiting for unresolved stdin or network subscriptions and reducing the wait to 1ms, which brings the real networking-enabled path much closer to the previous fast path.
Add wasm target-arch configuration and patch the container2wasm Dockerfile during build so wasm images get CAP_SYS_ADMIN, /dev/fuse, and FUSE-enabled riscv kernel configs.

This makes gondolin.ts build produce wasm-node images that boot with sandboxfs/VFS mounts, preserve MITM cert injection, and carry baked environment variables as validated end-to-end.
Anchor relative clock subscriptions once per poll call and wait until the earliest deadline expires.

This restores guest sleep/poll behavior in the wasm runner so idle shells stop busy-spinning the CPU, and adds regression coverage for relative clock waits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant