- interfaces.exclude in steward.yaml (names or shell patterns) leaves interfaces out of GET /interfaces, the web UI's picker and 'steward interface list'. Subnets on them are still served. - With the ping check on, active leases are pinged every minute; a client that answers gets a last-seen time (leases.last_seen, migration 4). Not answering never takes a client offline. - The activity log filters by subnet (--subnet, the Activity page, GET /events?subnet=). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.3 KiB
Go
70 lines
2.3 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"`
|
|
// LastSeen is when the client last answered a ping (nil: never).
|
|
// Clients that block pings stay online while their lease lasts.
|
|
LastSeen *time.Time `json:"last_seen,omitempty"`
|
|
}
|
|
|
|
// 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
|
|
}
|