Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ macOS VM engine for x86 Linux/KVM, built on [cocoon](https://github.com/cocoonst
- **Fully-automated macOS install** — a GitHub Action boots OpenCore, erases APFS, drives the installer by OCR, and publishes a golden qcow2 (Tahoe 26 / Sequoia 15) to ghcr; the `:26` image is **SSH-ready** (`cocoon`/`cocoon`)
- **Docker-like CLI** — `create`, `run`, `start`, `stop`, `list`, `inspect`, `console`, `rm`, `snapshot`, `restore`, `clone`
- **Parallel Range image pull** — the multi-GB qcow2 is pulled in 8 concurrent HTTP Range chunks with an sha256 digest check (oras-go for auth; no `oras` binary needed)
- **Per-VM Apple identity** — `--random-smbios` injects a unique serial/MLB/UUID/ROM (guest MAC = ROM) into a per-VM OpenCore, so clones never share a serial
- **Per-VM Apple identity** — `--random-smbios` injects a unique serial/MLB/UUID/ROM into a per-VM OpenCore; the ROM is the guest MAC outside CNI, so clones never share a serial
- **CNI networking with TC redirect** — `--net cni` joins cocoon's forwarding plane so the guest DHCPs a real LAN IP; also user/tap/bridge; reachable VNC (launch-scoped, password-gated on CNI)
- **Snapshot, clone & data disks** — offline qcow2-internal snapshots, CoW clones that cold-boot a fresh Apple identity, and up to 4 extra AHCI data disks
- **Intel & AMD, built on cocoon** — one boot recipe boots both hosts; imports cocoon's `cloudimg` store, `network` plane, and copy-on-write conventions
Expand Down
2 changes: 1 addition & 1 deletion cmd/vm/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (h *Handler) create(cmd *cobra.Command, image string) (*record, error) {
if r.DataDisks, err = createDataDisks(ctx, dir, diskSpecs); err != nil {
return nil, err
}
// OpenCore before networking: a random SMBIOS sets r.MAC = ROM, which prepareNet keeps as the guest MAC.
// OpenCore seeds the default guest MAC from ROM before network provisioning.
randomSMBIOS, _ := cmd.Flags().GetBool("random-smbios")
if err = prepareOpenCore(ctx, dir, oc, randomSMBIOS, r); err != nil {
return nil, err
Expand Down
6 changes: 4 additions & 2 deletions cmd/vm/net_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ func provisionNet(cmd *cobra.Command, r *record) (tap, netns, mac string, err er
if len(cfgs) == 0 {
return "", "", "", errors.New("network add returned no NIC")
}
// SMBIOS ROM wins as the guest MAC; cocoon's generated MAC is only a fallback
mac = cmp.Or(r.MAC, cfgs[0].MAC)
mac = cfgs[0].MAC
if r.NetMode != netCNI {
mac = cmp.Or(r.MAC, mac)
}
return cfgs[0].TAP, nsPath, mac, nil
}

Expand Down
4 changes: 1 addition & 3 deletions cmd/vm/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ func applyNet(cmd *cobra.Command, r *record) error {
if err != nil {
return err
}
if r.MAC == "" {
r.MAC = mac
}
r.MAC = mac
if netTap != "" {
r.Tap, r.Netns, r.TapOwned = netTap, netns, userTap == ""
}
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ cocoon-macos CLI ──► image: pull the golden macOS qcow2 from ghcr (paralle
- **COW overlays** — an instant copy-on-write clone of the immutable golden base
per VM
- **Per-VM Apple identity** — `--random-smbios` injects a unique serial/MLB/UUID/ROM
(guest MAC = ROM) into a per-VM OpenCore, so clones never share a serial
into a per-VM OpenCore; the ROM is the guest MAC outside CNI
- **CNI networking with TC redirect** — `--net cni` joins cocoon's forwarding
plane so the guest DHCPs a real LAN IP; also `user`/`tap`/`bridge`
- **Reachable VNC** — loopback VNC on user/tap/bridge; a host-side proxy fronts
Expand Down
8 changes: 6 additions & 2 deletions docs/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@

`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. The guest NIC MAC stays equal to the SMBIOS ROM. Auto-create (`bridge`/`cni`) is Linux-only
(needs `CAP_NET_ADMIN`); `user` and a pre-created `--tap` work everywhere.
network. In `tap` and `bridge` mode the guest NIC MAC stays equal to the SMBIOS ROM. Auto-create
(`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`: TAPs
`cm<vmid8>-<nic>`, netns `cm-<vmid>`), so a cocoon daemon's GC on the same node never reads a live
Expand Down
2 changes: 1 addition & 1 deletion qemu/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type Spec struct {
OpenCore string
OVMFCode string
OVMFVars string
MAC string // set to the SMBIOS ROM for --random-smbios
MAC string // guest NIC MAC: SMBIOS ROM by default, CNI eth0 in CNI mode
Tap string // pre-created host TAP; set => -netdev tap (bridged/routed), empty => user-mode SLIRP

MonSock string
Expand Down
4 changes: 2 additions & 2 deletions qemu/smbios.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type SMBIOS struct {
Serial string `json:"serial"` // SystemSerialNumber
MLB string `json:"mlb"` // board serial
UUID string `json:"uuid"` // SystemUUID
ROM string `json:"rom"` // 6-byte ROM as hex; also the guest en0 MAC
ROM string `json:"rom"` // 6-byte ROM as hex; default guest NIC MAC outside CNI
}

// RandomSMBIOS generates a unique per-VM identity (fixed model + random serial/MLB/UUID/ROM).
Expand All @@ -41,7 +41,7 @@ func RandomSMBIOS() (SMBIOS, error) {
return SMBIOS{Model: smbiosModel, Serial: serial, MLB: mlb, UUID: uuid, ROM: rom}, nil
}

// MAC returns the ROM formatted as the guest NIC MAC (so en0 == ROM, as iServices expects).
// MAC returns the ROM formatted as the default guest NIC MAC; CNI supplies its own runtime MAC.
func (s SMBIOS) MAC() string {
b, err := hex.DecodeString(s.ROM)
if err != nil || len(b) != 6 {
Expand Down