diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index 81413b9..a559e79 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -27,7 +27,7 @@ func loadLeases(h *dhcp4d.Handler, fn string) error { return err } var leases []*dhcp4d.Lease - if err := json.Unmarshal(b, leases); err != nil { + if err := json.Unmarshal(b, &leases); err != nil { return err } h.SetLeases(leases) diff --git a/internal/dhcp4d/dhcp4d.go b/internal/dhcp4d/dhcp4d.go index c0d5153..c627d31 100644 --- a/internal/dhcp4d/dhcp4d.go +++ b/internal/dhcp4d/dhcp4d.go @@ -21,6 +21,10 @@ type Lease struct { Expiry time.Time `json:"expiry"` } +func (l *Lease) Expired(at time.Time) bool { + return !l.Expiry.IsZero() && at.After(l.Expiry) +} + type Handler struct { serverIP net.IP start net.IP // first IP address to hand out @@ -80,11 +84,11 @@ func (h *Handler) findLease() int { if len(h.leasesIP) < h.leaseRange { // TODO: hash the hwaddr like dnsmasq i := rand.Intn(h.leaseRange) - if l, ok := h.leasesIP[i]; !ok || now.After(l.Expiry) { + if l, ok := h.leasesIP[i]; !ok || l.Expired(now) { return i } for i := 0; i < h.leaseRange; i++ { - if l, ok := h.leasesIP[i]; !ok || now.After(l.Expiry) { + if l, ok := h.leasesIP[i]; !ok || l.Expired(now) { return i } } @@ -111,7 +115,7 @@ func (h *Handler) canLease(reqIP net.IP, hwaddr string) int { return leaseNum // lease already owned by requestor } - if h.timeNow().After(l.Expiry) { + if l.Expired(h.timeNow()) { return leaseNum // lease expired } @@ -174,8 +178,14 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d Hostname: string(options[dhcp4.OptionHostName]), } - // Release any old leases for this client if l, ok := h.leasesHW[lease.HardwareAddr]; ok { + if l.Expiry.IsZero() { + // Retain permanent lease properties + lease.Expiry = time.Time{} + lease.Hostname = l.Hostname + } + + // Release any old leases for this client delete(h.leasesIP, l.Num) } diff --git a/internal/dhcp4d/dhcp4d_test.go b/internal/dhcp4d/dhcp4d_test.go index 201f96b..17b4684 100644 --- a/internal/dhcp4d/dhcp4d_test.go +++ b/internal/dhcp4d/dhcp4d_test.go @@ -224,6 +224,43 @@ func TestPreviousLease(t *testing.T) { } } +func TestPermanentLease(t *testing.T) { + handler, cleanup := testHandler(t) + defer cleanup() + now := time.Now() + handler.timeNow = func() time.Time { return now } + + var ( + addr = net.IP{192, 168, 42, 23} + hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66} + ) + + handler.SetLeases([]*Lease{ + { + Num: 2, + Addr: addr, + HardwareAddr: hardwareAddr.String(), + }, + }) + + p := request(addr, hardwareAddr) + resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions()) + if got, want := resp.YIAddr().To4(), addr.To4(); !bytes.Equal(got, want) { + t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want) + } + + now = now.Add(3 * time.Hour) + + hardwareAddr[len(hardwareAddr)-1] = 0x77 + + p = request(addr, hardwareAddr) + resp = handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions()) + opts := resp.ParseOptions() + if got, want := dhcp4.MessageType(opts[dhcp4.OptionDHCPMessageType][0]), dhcp4.NAK; got != want { + t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want) + } +} + func TestExpiration(t *testing.T) { handler, cleanup := testHandler(t) defer cleanup()