a73x

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)
		}
	}
}