a73x

internal/agent/dhcp/dhcp_test.go

Ref:   Size: 4.8 KiB   History

package dhcp

import (
	"net"
	"testing"
	"time"

	"github.com/insomniacslk/dhcp/dhcpv4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func testServer() *Server {
	return NewServer("eitri0", "10.77.1.0/24",
		net.IPv4(10, 77, 1, 1),
		net.CIDRMask(24, 32),
		[]net.IP{net.IPv4(1, 1, 1, 1)},
		12*time.Hour)
}

func newTestServer() *Server {
	return NewServer("eitri0", "10.77.1.0/24",
		net.IPv4(10, 77, 1, 1), net.CIDRMask(24, 32), nil, time.Hour)
}

func TestReserveAllocatesDistinctStickyAddresses(t *testing.T) {
	s := newTestServer()
	macA, _ := net.ParseMAC("52:54:00:aa:aa:aa")
	macB, _ := net.ParseMAC("52:54:00:bb:bb:bb")

	ipA, err := s.Reserve(macA)
	require.NoError(t, err)
	assert.Equal(t, "10.77.1.2", ipA.String(), "first free skips network(.0) + gateway(.1)")

	ipB, err := s.Reserve(macB)
	require.NoError(t, err)
	assert.Equal(t, "10.77.1.3", ipB.String(), "second VM gets a distinct address")

	ipA2, err := s.Reserve(macA)
	require.NoError(t, err)
	assert.Equal(t, ipA.String(), ipA2.String(), "re-reserving a MAC is sticky")
}

func TestReserveSkipsPreexistingReservation(t *testing.T) {
	s := newTestServer()
	pinned, _ := net.ParseMAC("52:54:00:cc:cc:cc")
	s.SetReservation(pinned, net.ParseIP("10.77.1.2"))

	fresh, _ := net.ParseMAC("52:54:00:dd:dd:dd")
	ip, err := s.Reserve(fresh)
	require.NoError(t, err)
	assert.Equal(t, "10.77.1.3", ip.String(), "an externally-set reservation is treated as used")
}

// TestReserveHandsBackAPreloadedReservation pins the handoff the startup replay
// depends on: an address preloaded into the table is what Reserve returns for
// its MAC, so a surviving guest keeps its address across an agent restart
// instead of being renumbered on its next boot.
func TestReserveHandsBackAPreloadedReservation(t *testing.T) {
	s := newTestServer()
	survivor, _ := net.ParseMAC("52:54:00:cc:cc:cc")
	s.SetReservation(survivor, net.ParseIP("10.77.1.55"))

	ip, err := s.Reserve(survivor)
	require.NoError(t, err)
	assert.Equal(t, "10.77.1.55", ip.String(), "a preloaded reservation is never renumbered")
}

func mustMAC(t *testing.T, s string) net.HardwareAddr {
	t.Helper()
	m, err := net.ParseMAC(s)
	if err != nil {
		t.Fatalf("parse mac %q: %v", s, err)
	}
	return m
}

func TestReservationSetLookupRemove(t *testing.T) {
	s := testServer()
	mac := mustMAC(t, "52:54:00:ab:cd:ef")

	if _, ok := s.lookup(mac); ok {
		t.Fatal("unknown MAC should not resolve")
	}
	s.SetReservation(mac, net.IPv4(10, 77, 1, 7))
	got, ok := s.lookup(mac)
	if !ok || !got.Equal(net.IPv4(10, 77, 1, 7)) {
		t.Fatalf("lookup after set = %v, %v; want 10.77.1.7, true", got, ok)
	}
	s.RemoveReservation(mac)
	if _, ok := s.lookup(mac); ok {
		t.Fatal("lookup after remove should be false")
	}
}

func TestReservationLookupIsCaseInsensitiveOnMAC(t *testing.T) {
	s := testServer()
	s.SetReservation(mustMAC(t, "52:54:00:AB:CD:EF"), net.IPv4(10, 77, 1, 9))
	if _, ok := s.lookup(mustMAC(t, "52:54:00:ab:cd:ef")); !ok {
		t.Fatal("reservation lookup must not depend on MAC hex case")
	}
}

func request(t *testing.T, mac net.HardwareAddr, mt dhcpv4.MessageType) *dhcpv4.DHCPv4 {
	t.Helper()
	m, err := dhcpv4.New(dhcpv4.WithHwAddr(mac), dhcpv4.WithMessageType(mt))
	if err != nil {
		t.Fatalf("build request: %v", err)
	}
	return m
}

func TestBuildReplyUnknownMACGetsNothing(t *testing.T) {
	s := testServer()
	resp, err := s.buildReply(request(t, mustMAC(t, "52:54:00:11:22:33"), dhcpv4.MessageTypeDiscover))
	if err != nil {
		t.Fatalf("buildReply err: %v", err)
	}
	if resp != nil {
		t.Fatalf("unknown MAC must get no reply, got %v", resp)
	}
}

func TestBuildReplyDiscoverOffersReservedIP(t *testing.T) {
	s := testServer()
	mac := mustMAC(t, "52:54:00:ab:cd:ef")
	s.SetReservation(mac, net.IPv4(10, 77, 1, 7))

	resp, err := s.buildReply(request(t, mac, dhcpv4.MessageTypeDiscover))
	if err != nil {
		t.Fatalf("buildReply err: %v", err)
	}
	if resp == nil {
		t.Fatal("reserved MAC must get a reply")
	}
	if resp.MessageType() != dhcpv4.MessageTypeOffer {
		t.Fatalf("Discover must yield Offer, got %v", resp.MessageType())
	}
	if !resp.YourIPAddr.Equal(net.IPv4(10, 77, 1, 7)) {
		t.Fatalf("YourIPAddr = %v; want 10.77.1.7", resp.YourIPAddr)
	}
	if !resp.Router()[0].Equal(net.IPv4(10, 77, 1, 1)) {
		t.Fatalf("router = %v; want gateway 10.77.1.1", resp.Router())
	}
	if len(resp.DNS()) == 0 || !resp.DNS()[0].Equal(net.IPv4(1, 1, 1, 1)) {
		t.Fatalf("DNS = %v; want [1.1.1.1]", resp.DNS())
	}
}

func TestBuildReplyRequestAcks(t *testing.T) {
	s := testServer()
	mac := mustMAC(t, "52:54:00:ab:cd:ef")
	s.SetReservation(mac, net.IPv4(10, 77, 1, 7))
	resp, err := s.buildReply(request(t, mac, dhcpv4.MessageTypeRequest))
	if err != nil {
		t.Fatalf("buildReply err: %v", err)
	}
	if resp == nil || resp.MessageType() != dhcpv4.MessageTypeAck {
		t.Fatalf("Request must yield Ack, got %v", resp)
	}
}