diff --git a/internal/dhcp4d/dhcp4d.go b/internal/dhcp4d/dhcp4d.go index 9cadf96..a49726b 100644 --- a/internal/dhcp4d/dhcp4d.go +++ b/internal/dhcp4d/dhcp4d.go @@ -353,6 +353,35 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d reqIP, h.leasePeriodForDevice(hwAddr), h.options.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList])) + case dhcp4.Decline: + if h.expireLease(hwAddr) { + log.Printf("Expired leases for %v upon DHCPDECLINE", hwAddr) + } + // Decline does not expect an ACK response. + return nil } return nil } + +// expireLease expires the lease for hwAddr and reports whether or not the +// lease was actually expired by this call. +func (h *Handler) expireLease(hwAddr string) bool { + h.leasesMu.Lock() + defer h.leasesMu.Unlock() + + // TODO: deduplicate with h.leaseHW which also acquires h.leasesMu. + + num, ok := h.leasesHW[hwAddr] + if !ok { + return false + } + l, ok := h.leasesIP[num] + if !ok { + return false + } + if l.HardwareAddr != hwAddr { + return false + } + l.Expiry = time.Now() + return true +} diff --git a/internal/dhcp4d/dhcp4d_test.go b/internal/dhcp4d/dhcp4d_test.go index 58c85ec..cd92078 100644 --- a/internal/dhcp4d/dhcp4d_test.go +++ b/internal/dhcp4d/dhcp4d_test.go @@ -50,6 +50,10 @@ func discover(addr net.IP, hwaddr net.HardwareAddr, opts ...dhcp4.Option) dhcp4. return packet(dhcp4.Discover, addr, hwaddr, opts) } +func decline(addr net.IP, hwaddr net.HardwareAddr, opts ...dhcp4.Option) dhcp4.Packet { + return packet(dhcp4.Decline, addr, hwaddr, opts) +} + const goldenInterfaces = ` { "interfaces":[ @@ -500,3 +504,56 @@ func TestMinimumLeaseTime(t *testing.T) { } } } + +func TestClientDecline(t *testing.T) { + handler, cleanup := testHandler(t) + defer cleanup() + + now := time.Now() + handler.timeNow = func() time.Time { return now } + + addr := net.IP{192, 168, 42, 23} + + hardwareAddr := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} + + // macbook requests a new lease + t.Run("mbp grabs an address", func(t *testing.T) { + p := request(addr, hardwareAddr) + resp := handler.serveDHCP(p, dhcp4.Request, p.ParseOptions()) + if got, want := messageType(resp), dhcp4.ACK; got != want { + t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want) + } + }) + + // macbook lease expires + now = now.Add(3 * time.Hour) + + t.Run("mbp requests previous address", func(t *testing.T) { + p := request(addr, hardwareAddr) + resp := handler.serveDHCP(p, dhcp4.Discover, p.ParseOptions()) + if got, want := resp.YIAddr().To4(), addr.To4(); !got.Equal(want) { + t.Errorf("DHCPOFFER for wrong IP: got %v, want %v", got, want) + } + p = request(addr, hardwareAddr) + resp = handler.serveDHCP(p, dhcp4.Request, p.ParseOptions()) + if got, want := messageType(resp), dhcp4.ACK; got != want { + t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want) + } + }) + + t.Run("mbp declines address", func(t *testing.T) { + p := decline(addr, hardwareAddr) + resp := handler.serveDHCP(p, dhcp4.Decline, p.ParseOptions()) + if resp != nil { + t.Fatalf("DHCPDECLINE was unexpectedly answered: %v", messageType(resp)) + } + }) + + t.Run("mbp requests any address", func(t *testing.T) { + p := request(net.IPv4zero, hardwareAddr) + resp := handler.serveDHCP(p, dhcp4.Discover, p.ParseOptions()) + if got, want := resp.YIAddr().To4(), addr.To4(); got.Equal(want) { + t.Errorf("DHCPOFFER returned unexpected address: %v", got) + } + }) +}