Files
mcp/internal/agent/runtime.go
Kyle Isom 47ec4e60ad unikernel: isolated host-only bridge networking (Phase 2)
When the mcp-br0 bridge exists, the agent runs unikernels on it instead
of QEMU user-mode networking: each VM gets a TAP device on the bridge
and a static 10.99.0.0/24 IP (baked into the Nanos image via ops
RunConfig). With the host firewall dropping off-bridge VM traffic and no
NAT, a VM can reach only the gateway -- making mc-proxy mediation
mandatory by topology rather than convention.

- runtime/qemu.go: bridge mode (createTAP/destroyTAP, IP allocator,
  deterministic MAC, static-IP ops config, VMAddr for proxy backends).
- agent auto-enables bridge mode when /sys/class/net/mcp-br0 exists.

Verified on straylight: uktest unikernel boots on mcp-br0 at 10.99.0.2,
serves via the gateway, TAP enslaved to the bridge; bridge has no uplink
and off-bridge forwarding is dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 01:07:49 -07:00

75 lines
2.2 KiB
Go

package agent
import (
"context"
"os"
"os/exec"
"path/filepath"
"git.wntrmute.dev/mc/mcp/internal/config"
"git.wntrmute.dev/mc/mcp/internal/runtime"
)
// Isolated unikernel bridge parameters. The bridge (mcp-br0) is created by
// the node's NixOS config; when present, the agent runs unikernels on it with
// a host firewall confining each VM to reaching only the gateway (mc-proxy).
const (
unikernelBridge = "mcp-br0"
unikernelGateway = "10.99.0.1"
unikernelSubnetPrefix = "10.99.0"
)
// unikernelSupported reports whether this node can run Nanos unikernels:
// it needs KVM (/dev/kvm) and the `ops` toolchain on PATH.
func unikernelSupported() bool {
if _, err := os.Stat("/dev/kvm"); err != nil {
return false
}
if _, err := exec.LookPath("ops"); err != nil {
return false
}
return true
}
// homeDir returns the agent's working directory (where images/ and vm/ live),
// derived from the registry database path (e.g. /srv/mcp/mcp.db -> /srv/mcp).
func homeDir(cfg *config.AgentConfig) string {
if cfg != nil && cfg.Database.Path != "" {
return filepath.Dir(cfg.Database.Path)
}
if h := os.Getenv("HOME"); h != "" {
return h
}
return "/srv/mcp"
}
// runtimeFor selects the runtime backend for a component's declared runtime.
// Unknown or empty runtimes fall back to the container runtime. If a service
// requests "unikernel" but this node lacks the unikernel runtime, it falls
// back to the container runtime (the master should not place it here).
func (a *Agent) runtimeFor(rt string) runtime.Runtime {
if rt == "unikernel" && a.Unikernel != nil {
return a.Unikernel
}
return a.Runtime
}
// listAllContainers returns the observed state across every configured
// runtime (containers + unikernel VMs) so reconciliation, status, and drift
// detection see the whole picture.
func (a *Agent) listAllContainers(ctx context.Context) ([]runtime.ContainerInfo, error) {
infos, err := a.Runtime.List(ctx)
if err != nil {
return nil, err
}
if a.Unikernel != nil {
vms, vmErr := a.Unikernel.List(ctx)
if vmErr == nil {
infos = append(infos, vms...)
} else if a.Logger != nil {
a.Logger.Warn("list unikernel VMs failed", "err", vmErr)
}
}
return infos, nil
}