internal/agent/vfkit/guestnet_test.go
Ref: Size: 5.7 KiB History
package vfkit
import (
"net"
"net/netip"
"testing"
)
// realLeases is the shape of macOS's lease database, taken from an M1 running
// eitri guests: brace-delimited stanzas, unpadded MAC octets behind an ARP
// hardware-type prefix, expiry in hex.
const realLeases = `{
name=proof-1
ip_address=192.168.64.10
hw_address=1,52:54:0:9d:c1:4
identifier=1,52:54:0:9d:c1:4
lease=0x68b0a1f2
}
{
name=proof-2
ip_address=192.168.64.11
hw_address=1,52:54:0:3a:7b:2
lease=0x68b0a2c9
}
`
// prefixes is shorthand for the host networks a reading of the interface list
// would produce.
func prefixes(t *testing.T, ss ...string) []netip.Prefix {
t.Helper()
var out []netip.Prefix
for _, s := range ss {
p, err := netip.ParsePrefix(s)
if err != nil {
t.Fatalf("bad test prefix %q: %v", s, err)
}
out = append(out, p)
}
return out
}
// TestGuestCIDRPairsALeaseWithItsInterface pins the whole idea: an address
// vmnet actually handed out, matched against the networks this host has
// configured, gives the guest subnet AND its prefix length without guessing
// either.
func TestGuestCIDRPairsALeaseWithItsInterface(t *testing.T) {
// What a Mac running a guest looks like: loopback, the uplink, Thunderbolt's
// bridge (no IPv4, so absent here), and vmnet's.
host := prefixes(t, "127.0.0.0/8", "192.168.1.0/24", "192.168.64.0/24")
if got := guestCIDR(realLeases, host); got != "192.168.64.0/24" {
t.Errorf("guestCIDR() = %q, want 192.168.64.0/24", got)
}
}
// TestGuestCIDRTakesThePrefixFromTheInterface pins that the prefix length is
// the interface's own and never assumed. Deriving from a lease alone would
// force a guess at /24, and vmnet's network is configurable.
func TestGuestCIDRTakesThePrefixFromTheInterface(t *testing.T) {
for _, tc := range []struct{ name, network, want string }{
{"the usual /24", "192.168.64.0/24", "192.168.64.0/24"},
{"a wider network", "192.168.0.0/16", "192.168.0.0/16"},
{"a narrower one", "192.168.64.0/26", "192.168.64.0/26"},
} {
t.Run(tc.name, func(t *testing.T) {
if got := guestCIDR(realLeases, prefixes(t, tc.network)); got != tc.want {
t.Errorf("guestCIDR() = %q, want %q", got, tc.want)
}
})
}
}
// TestGuestCIDRSaysNothingRatherThanGuessing pins the empty-means-no-answer
// rule at its source. Each of these leaves the fleet's record alone and retries
// on the next report, rather than reporting something invented.
func TestGuestCIDRSaysNothingRatherThanGuessing(t *testing.T) {
host := prefixes(t, "127.0.0.0/8", "192.168.1.0/24", "192.168.64.0/24")
t.Run("no lease database", func(t *testing.T) {
// A Mac that has never run a guest: nothing has asked for an address, so
// there is no guest network to describe yet.
if got := guestCIDR("", host); got != "" {
t.Errorf("guestCIDR() = %q, want empty", got)
}
})
t.Run("no interface holds the leased network", func(t *testing.T) {
// vmnet's bridge exists only while a guest is attached, so a lease left
// over from a destroyed VM matches nothing. Skipped, not guessed at.
if got := guestCIDR(realLeases, prefixes(t, "127.0.0.0/8", "192.168.1.0/24")); got != "" {
t.Errorf("guestCIDR() = %q, want empty", got)
}
})
t.Run("no host networks at all", func(t *testing.T) {
if got := guestCIDR(realLeases, nil); got != "" {
t.Errorf("guestCIDR() = %q, want empty", got)
}
})
t.Run("leases with no usable address", func(t *testing.T) {
if got := guestCIDR("{\n\tname=x\n\thw_address=1,52:54:0:1:2:3\n}\n", host); got != "" {
t.Errorf("guestCIDR() = %q, want empty", got)
}
})
}
// TestGuestCIDRSkipsStaleLeasesForLiveOnes pins that a lease database
// outliving its VMs is an asset rather than a hazard: an entry matching no live
// interface is skipped, and a later one that does match is used.
func TestGuestCIDRSkipsStaleLeasesForLiveOnes(t *testing.T) {
leases := "{\n\tip_address=10.9.9.9\n}\n" + realLeases
if got := guestCIDR(leases, prefixes(t, "192.168.64.0/24")); got != "192.168.64.0/24" {
t.Errorf("guestCIDR() = %q, want the live network", got)
}
}
// TestPrefixOfRejectsWhatIsNotANetwork pins the reading of one interface
// address, including shapes a host really carries.
func TestPrefixOfRejectsWhatIsNotANetwork(t *testing.T) {
ipnet := func(ip string, mask net.IPMask) net.Addr {
return &net.IPNet{IP: net.ParseIP(ip), Mask: mask}
}
for _, tc := range []struct {
name string
addr net.Addr
want string // "" = rejected
}{
{"a normal /24", ipnet("192.168.64.1", net.CIDRMask(24, 32)), "192.168.64.0/24"},
{"masked to its network", ipnet("192.168.64.200", net.CIDRMask(24, 32)), "192.168.64.0/24"},
{"IPv6", ipnet("fd00::1", net.CIDRMask(64, 128)), ""},
{"a /0", ipnet("10.0.0.1", net.CIDRMask(0, 32)), ""},
{"non-contiguous mask", ipnet("10.0.0.1", net.IPMask{255, 0, 255, 0}), ""},
{"not an IPNet at all", &net.TCPAddr{IP: net.ParseIP("10.0.0.1")}, ""},
} {
t.Run(tc.name, func(t *testing.T) {
pfx, got := prefixOf(tc.addr)
if tc.want == "" {
if got {
t.Errorf("prefixOf() accepted %v as %s", tc.addr, pfx)
}
return
}
if !got || pfx.String() != tc.want {
t.Errorf("prefixOf() = %s, %v; want %s", pfx, got, tc.want)
}
})
}
}
// TestHostNetworksReadsThisMachine is a smoke test against the real interface
// list. It cannot assert a subnet — it runs on whatever CI is — but it proves
// the reading needs no privileges and returns something well-formed, which is
// the property that sent the previous two implementations back.
func TestHostNetworksReadsThisMachine(t *testing.T) {
nets := hostNetworks()
if len(nets) == 0 {
t.Skip("no IPv4 interfaces on this machine")
}
for _, p := range nets {
if !p.Addr().Is4() {
t.Errorf("hostNetworks returned a non-IPv4 prefix: %s", p)
}
if p != p.Masked() {
t.Errorf("hostNetworks returned an unmasked prefix: %s", p)
}
}
}