internal/agent/vfkit/guestnet.go
Ref: Size: 4.5 KiB History
package vfkit
import (
"net"
"net/netip"
"os"
"strings"
)
// GuestCIDR reports the subnet this Mac's guests are on: the network of
// whichever host interface contains an address vmnet has actually handed out.
// It returns "" when it cannot say, which by the empty-means-no-answer rule
// leaves the fleet's record alone and retries on the next report.
//
// Both halves are things an unprivileged agent can see, which is the whole
// reason for this shape. The obvious source — vmnet's own configuration at
// /Library/Preferences/SystemConfiguration/com.apple.vmnet.plist, which states
// the address and netmask outright — is ROOT-ONLY, and the agent deliberately
// does not run as root on macOS: the guest network belongs to
// Virtualization.framework and the entitlement to vfkit, so there is nothing
// else it would need privileges for. Reading one config file is a poor reason
// to take them. (`defaults` cannot read that file either, for an unrelated
// reason: it resolves domains through cfprefsd rather than opening the path.)
//
// The lease database is world-readable and the interface list needs no
// privileges at all, and between them they give exactly what the plist would
// have: an address inside the guest network, and a netmask to size it with.
// Neither is guessed — in particular the prefix length is the interface's own,
// so nothing here assumes a /24 the way deriving from a lease alone would.
//
// A host with no guests reports nothing. vmnet's bridge exists only while a
// guest is attached to it, and the lease database is empty until one has asked
// for an address, so a Mac that has never run a guest has no guest network to
// describe. That is honest rather than a failure, and it costs nothing: the
// answer arrives with the first guest, and on every report after it.
func GuestCIDR() string {
return guestCIDR(readFile(defaultLeasesPath), hostNetworks())
}
// guestCIDR is GuestCIDR with both readings injected, so the pairing is
// testable on a machine that has neither of them.
func guestCIDR(leases string, networks []netip.Prefix) string {
for _, addr := range leasedAddresses(leases) {
for _, pfx := range networks {
if pfx.Contains(addr) {
return pfx.String()
}
}
}
return ""
}
// hostNetworks returns every IPv4 network configured on this host's interfaces,
// each masked to its own prefix length. Addresses are flattened across
// interfaces deliberately: which interface holds the guest network is not
// something this needs to know, and the name vmnet uses is not contractual —
// bridge100 today, bridge101 for a second vmnet network, while bridge0 is
// Thunderbolt's and holds no IPv4 address at all. A network containing an
// address vmnet leased to a guest IS the guest network, whatever it is called.
func hostNetworks() []netip.Prefix {
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
var out []netip.Prefix
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, a := range addrs {
if pfx, ok := prefixOf(a); ok {
out = append(out, pfx)
}
}
}
return out
}
func prefixOf(a net.Addr) (netip.Prefix, bool) {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP.To4() == nil {
return netip.Prefix{}, false
}
ones, bits := ipnet.Mask.Size()
// A non-contiguous mask reports (0, 0), and a /0 on a host interface is not
// a network anything is meaningfully "on".
if bits != 32 || ones == 0 {
return netip.Prefix{}, false
}
host, ok := netip.AddrFromSlice(ipnet.IP.To4())
if !ok {
return netip.Prefix{}, false
}
return netip.PrefixFrom(host, ones).Masked(), true
}
// readFile returns path's contents, or "" when it cannot be read.
func readFile(path string) string {
raw, err := os.ReadFile(path)
if err != nil {
return ""
}
return string(raw)
}
// leasedAddresses returns every address in the lease database. Any one of them
// locates the guest network — they are all on it — so the caller takes the
// first that lands inside a live interface. Stale entries from destroyed VMs
// are useful here rather than a problem: they still name the subnet, and one
// that no longer matches any interface is simply skipped.
func leasedAddresses(leases string) []netip.Addr {
var out []netip.Addr
for _, line := range strings.Split(leases, "\n") {
key, value, ok := strings.Cut(strings.TrimSpace(line), "=")
if !ok || key != "ip_address" {
continue
}
if a, err := netip.ParseAddr(parseIP(value)); err == nil && a.Is4() {
out = append(out, a)
}
}
return out
}