Subnets served per interface, pools, reservations, options at three levels, leases in SQLite saved before the ACK, ping and decline conflict checks, validated changes with history and undo, users and tokens, an audit log, a Kea importer, a sandboxed systemd unit and Debian packages. Tested with real udhcpc and dhcpcd clients in Docker (scripts/e2e.sh). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
package model
|
|
|
|
import (
|
|
"net/netip"
|
|
"time"
|
|
)
|
|
|
|
// Lease states.
|
|
const (
|
|
// LeaseActive: the client holds the address until End.
|
|
LeaseActive = "active"
|
|
// LeaseReleased: the client gave the address back. The row stays so
|
|
// the client gets the same address next time, if it is still free.
|
|
LeaseReleased = "released"
|
|
// LeaseExpired: the lease ran out (the state is computed: an active
|
|
// lease past its End).
|
|
LeaseExpired = "expired"
|
|
// LeaseDeclined: a client said the address is in use by someone else
|
|
// (DHCPDECLINE), or it answered a ping. It isn't handed out until End.
|
|
LeaseDeclined = "declined"
|
|
)
|
|
|
|
// Lease is an address handed out, or held back.
|
|
type Lease struct {
|
|
IP netip.Addr `json:"ip"`
|
|
SubnetID int64 `json:"subnet_id"`
|
|
MAC string `json:"mac"`
|
|
ClientID string `json:"client_id,omitempty"`
|
|
// Hostname is the name the client sent (option 12 or 81).
|
|
Hostname string `json:"hostname"`
|
|
State string `json:"state"`
|
|
Start time.Time `json:"start"`
|
|
End time.Time `json:"end"`
|
|
// Interface is where the client was last seen.
|
|
Interface string `json:"interface"`
|
|
Updated time.Time `json:"updated"`
|
|
}
|
|
|
|
// StateAt is the lease's state at a time (an active lease past its end
|
|
// has expired).
|
|
func (l *Lease) StateAt(now time.Time) string {
|
|
if l.State == LeaseActive && !now.Before(l.End) {
|
|
return LeaseExpired
|
|
}
|
|
return l.State
|
|
}
|
|
|
|
// ActiveAt reports whether the lease holds its address at a time.
|
|
func (l *Lease) ActiveAt(now time.Time) bool {
|
|
return l.State == LeaseActive && now.Before(l.End)
|
|
}
|
|
|
|
// HeldAt reports whether the address can't be handed out to another
|
|
// client at a time: an active or declined lease that hasn't ended.
|
|
func (l *Lease) HeldAt(now time.Time) bool {
|
|
return (l.State == LeaseActive || l.State == LeaseDeclined) && now.Before(l.End)
|
|
}
|
|
|
|
// Owns reports whether the lease belongs to a client: by client
|
|
// identifier when both have one, else by MAC address.
|
|
func (l *Lease) Owns(mac, clientID string) bool {
|
|
if l.ClientID != "" && clientID != "" {
|
|
return l.ClientID == clientID
|
|
}
|
|
return l.MAC != "" && l.MAC == mac
|
|
}
|