cocoon-macos

Networking & VNC

Network modes (--net)

Mode What it does
user (default) User-mode SLIRP. Combine with --ssh-port N to forward localhost:N → guest:22. Works everywhere.
tap Attach to a pre-created host TAP verbatim (--tap tap0) — the bridge/plane owns IP + forwarding. Without --tap, tap mode auto-creates like bridge and so also requires --bridge <dev>.
bridge Auto-create a TAP on an existing Linux bridge (--bridge br0) via cocoon’s network/bridge.
cni Auto-create a TAP inside a per-VM netns via cocoon’s network/cni (TC-redirect plane).

tap/bridge/cni make a macOS VM join the same forwarding plane as cocoon’s Cloud Hypervisor / Firecracker VMs on the node, so the guest can DHCP a real LAN IP from the upstream network. With a fresh SMBIOS identity, tap and bridge use its ROM as the guest NIC MAC; otherwise auto-created TAPs use the provider MAC and a pre-created --tap uses QEMU’s default. Auto-creation (tap without --tap, bridge, cni) is Linux-only (needs CAP_NET_ADMIN); user and a pre-created --tap work everywhere.

In cni mode, the guest NIC uses the MAC CNI assigned to eth0 because macspoofchk allow-lists that address; the SMBIOS ROM identity remains unchanged.

Auto-created devices carry cocoon-macos’s own host name family (net_scope cm: bridge-mode TAPs cm<vmid8>-<nic>, netns cm-<vmid>; a CNI-mode TAP is named tap<vmid8>-<nic> inside that netns), so a cocoon daemon’s GC on the same node never reads a live macOS guest’s TAP or netns as an orphan (see cocoon’s net_scope in its networking docs).

--net cni and TC redirect

CNI runs QEMU inside a per-VM network namespace. cocoon’s CNI wires the netns veth to the QEMU TAP with TC ingress redirect (mirred), not a bridge — the guest’s DHCP/traffic flows tap → eth0 → cni0 → upstream, and it comes back the same way. The guest gets a real ToR IP; SSH goes straight to that IP (no port-forward).

--cni-conf-dir (default /etc/cni/net.d) and --cni-bin-dir (default /opt/cni/bin) point at a non-standard CNI installation; both are ignored by the other net modes and are remembered in the VM record, so rm tears the NIC down without repeating them; a clone inherits them from its source. A VM created before the record carried them takes rm --cni-conf-dir and rm --cni-bin-dir once.

Clones

vm clone inherits the source’s --net mode, and for bridge its bridge, unless --net or --bridge is given. Auto-created tap/bridge/cni networking gives the clone a new TAP (and netns under cni) and a provider-assigned MAC, except that host-bridge modes keep a fresh SMBIOS ROM MAC when present. Without a fresh identity, user and pre-created --tap use QEMU’s default MAC, which is not unique across VMs. A source attached to a pre-created host TAP (--tap tap0) cannot share it, so its clone needs its own --tap; a tap source that auto-created its TAP on a bridge clones like bridge.

vm stop leaves an auto-created TAP in place for a fast restart; once QEMU closes it the TAP has no carrier and its bridge port forwards nothing, so bridge mode has nothing to quiesce. Under cni, vm stop (and the stop inside vm restore --force) downs the netns eth0 so the idle TC redirect stops storming softirqs, and vm start (or the relaunch) brings it back up. vm rm (and a failed create) deletes the TAP and the netns; a netns or TAP left behind by a create or clone that was killed before its record was written is reclaimed by cocoon-macos gc.

vm start (and a forced restore’s relaunch) rebuilds an auto-created TAP (under cni also its netns and TC redirect) with the recorded MAC when the host lost it, e.g. after a reboot.

VNC exposure

VNC exposure depends on the net mode:

VNC is launch-scoped: it is off unless --vnc is given for that launch, it is cleared on vm stop, and it is re-enabled per start:

cocoon-macos vm start m1 --vnc 1 --vnc-password s3cret   # this run only
cocoon-macos vm stop  m1                                 # VNC gone with the qemu it belonged to

The VNC password is never written to disk — it is read from the flag on each start.

vm start --vnc / --vnc-password on an already-running VM is a no-op: live QEMU can’t be retargeted, so the CLI prints that the supplied VNC settings were ignored and changes nothing.

macOS Screen Sharing

QEMU’s default None auth hangs macOS Screen Sharing. Pass --vnc-password <≤8 bytes> (applied via the QEMU monitor post-launch) so Screen Sharing prompts and connects. The launch is rejected (not truncated) if the password exceeds 8 bytes or contains whitespace, control characters or double quotes — QEMU’s VNC DES auth only supports 8-byte passwords. Plain VNC clients (RealVNC/TigerVNC) work without a password on the loopback modes.

In-guest macOS Screen Sharing (to the guest’s own IP) is not enabled headlessly — macOS requires the Screen Recording TCC grant from the GUI or MDM. Use QEMU’s built-in VNC above instead.

Display sleep blanks VNC

macOS only repaints the emulated framebuffer while the display is awake; once it sleeps (~idle), VNC shows a blank white/black screen with just the cursor even though the guest is healthy (SSH works, WindowServer is up). It is not a GPU/driver problem — a mouse move repaints it. The golden image’s first-boot daemon runs pmset -a displaysleep 0 sleep 0 disablesleep 1 system-wide (covering the pre-login loginwindow and disabling system sleep too) to keep the framebuffer painted; older images need a setup-stage rebuild. See also the GPU/video note in Known Issues.