From 2014da4ca32e3b83281357ac5a13ae336f0ffca6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Mar 2022 17:38:16 +0100 Subject: [PATCH] dhcp4d: display active devices based on LastACK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This has the advantage that it also works for static DHCP leases, provided the device obtains a DHCP lease at all (and isn’t configured with a static IP address, like the shelly motion sensors for example). --- cmd/dhcp4d/dhcp4d.go | 32 +++++++++++++++++++++----------- internal/dhcp4d/dhcp4d.go | 35 +++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index ae705a4..f78b19b 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -92,6 +92,9 @@ var ( } return dur.Truncate(1 * time.Second).String() }, + "zero": func(t time.Time) bool { + return t.IsZero() + }, }).Parse(` @@ -150,7 +153,7 @@ form { Hostname MAC address Vendor -Expiry +Last ACK {{ range $idx, $l := . }} @@ -166,27 +169,31 @@ form { {{$l.HardwareAddr}} {{$l.Vendor}} - -{{ if $l.Expired }} -{{ since $l.Expiry }} -expired -{{ else }} -{{ if $l.Static }} -static -{{ else }} -{{ timefmt $l.Expiry }} + +{{ if (not (zero $l.LastACK)) }} +{{ timefmt $l.LastACK }} +{{ if $l.Active }} active {{ end }} +{{ if $l.Expired }} +expired +{{ end }} {{ end }} {{ end }} {{ end }} +

Static Leases

{{ template "table" .StaticLeases }} +
+ +

Dynamic Leases

+ {{ template "table" .DynamicLeases }}
+ `)) @@ -341,17 +348,20 @@ func newSrv(permDir string) (*srv, error) { Vendor string Expired bool Static bool + Active bool } leasesMu.Lock() defer leasesMu.Unlock() static := make([]tmplLease, 0, len(leases)) dynamic := make([]tmplLease, 0, len(leases)) + now := time.Now() tl := func(l *dhcp4d.Lease) tmplLease { return tmplLease{ Lease: *l, Vendor: ouiDB.Lookup(l.HardwareAddr[:8]), - Expired: l.Expired(time.Now()), + Expired: l.Expired(now), + Active: l.Active(now), Static: l.Expiry.IsZero(), } } diff --git a/internal/dhcp4d/dhcp4d.go b/internal/dhcp4d/dhcp4d.go index fec3496..4eaf1de 100644 --- a/internal/dhcp4d/dhcp4d.go +++ b/internal/dhcp4d/dhcp4d.go @@ -43,12 +43,17 @@ type Lease struct { Hostname string `json:"hostname"` HostnameOverride string `json:"hostname_override"` Expiry time.Time `json:"expiry"` + LastACK time.Time `json:"last_ack"` } func (l *Lease) Expired(at time.Time) bool { return !l.Expiry.IsZero() && at.After(l.Expiry) } +func (l *Lease) Active(at time.Time) bool { + return !l.LastACK.IsZero() && at.Before(l.LastACK.Add(leasePeriod)) +} + type Handler struct { serverIP net.IP start net.IP // first IP address to hand out @@ -90,18 +95,14 @@ func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.Pac copy(start, serverIP) start[len(start)-1] += 1 return &Handler{ - rawConn: conn, - iface: iface, - leasesHW: make(map[string]int), - leasesIP: make(map[int]*Lease), - serverIP: serverIP, - start: start, - leaseRange: 230, - // Apple recommends a DHCP lease time of 1 hour in - // https://support.apple.com/de-ch/HT202068, - // so if 20 minutes ever causes any trouble, - // we should try increasing it to 1 hour. - LeasePeriod: 20 * time.Minute, + rawConn: conn, + iface: iface, + leasesHW: make(map[string]int), + leasesIP: make(map[int]*Lease), + serverIP: serverIP, + start: start, + leaseRange: 230, + LeasePeriod: leasePeriod, options: dhcp4.Options{ dhcp4.OptionSubnetMask: []byte{255, 255, 255, 0}, dhcp4.OptionRouter: []byte(serverIP), @@ -113,6 +114,12 @@ func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.Pac }, nil } +// Apple recommends a DHCP lease time of 1 hour in +// https://support.apple.com/de-ch/HT202068, +// so if 20 minutes ever causes any trouble, +// we should try increasing it to 1 hour. +const leasePeriod = 20 * time.Minute + // SetLeases overwrites the leases database with the specified leases, typically // loaded from persistent storage. There is no locking, so SetLeases must be // called before Serve. @@ -122,6 +129,9 @@ func (h *Handler) SetLeases(leases []*Lease) { h.leasesHW = make(map[string]int) h.leasesIP = make(map[int]*Lease) for _, l := range leases { + if l.LastACK.IsZero() { + l.LastACK = l.Expiry + } h.leasesHW[l.HardwareAddr] = l.Num h.leasesIP[l.Num] = l } @@ -334,6 +344,7 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d HardwareAddr: hwAddr, Expiry: h.timeNow().Add(h.leasePeriodForDevice(hwAddr)), Hostname: string(options[dhcp4.OptionHostName]), + LastACK: h.timeNow(), } copy(lease.Addr, reqIP.To4())