Files
abrown38andClaude Opus 5.5 5e6c61cc8e Hidden interfaces, last-seen pings, activity by subnet
- 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>
2026-10-18 17:50:21 +01:00

85 lines
2.1 KiB
Go

package dhcpd
import (
"context"
"log/slog"
"net"
"net/netip"
"os"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// ICMPPinger pings with an unprivileged ICMP socket (Linux "ping sockets",
// allowed for the groups in net.ipv4.ping_group_range, which systemd opens
// to everyone). When the socket can't be opened, the check is skipped and
// Unavailable says why.
type ICMPPinger struct {
Log *slog.Logger
seq atomic.Uint32
once sync.Once
// Unavailable is why pings can't be sent ("" when they can).
Unavailable atomic.Value
}
// Ping reports whether a answers an echo request within timeout.
func (p *ICMPPinger) Ping(ctx context.Context, a netip.Addr, timeout time.Duration) bool {
conn, err := icmp.ListenPacket("udp4", "0.0.0.0")
if err != nil {
p.once.Do(func() {
msg := "can't send pings, so addresses aren't checked before they are offered and clients' last-seen times aren't known: " + err.Error() + " (check net.ipv4.ping_group_range)"
p.Unavailable.Store(msg)
if p.Log != nil {
p.Log.Warn(msg)
}
})
return false
}
defer conn.Close()
seq := int(p.seq.Add(1) & 0xffff)
msg := icmp.Message{Type: ipv4.ICMPTypeEcho, Body: &icmp.Echo{ID: os.Getpid() & 0xffff, Seq: seq, Data: []byte("steward")}}
b, err := msg.Marshal(nil)
if err != nil {
return false
}
dst := &net.UDPAddr{IP: a.AsSlice()}
if _, err := conn.WriteTo(b, dst); err != nil {
return false
}
deadline := time.Now().Add(timeout)
if d, ok := ctx.Deadline(); ok && d.Before(deadline) {
deadline = d
}
conn.SetReadDeadline(deadline)
buf := make([]byte, 1500)
for {
n, from, err := conn.ReadFrom(buf)
if err != nil {
return false
}
ua, ok := from.(*net.UDPAddr)
if !ok || !ua.IP.Equal(dst.IP) {
continue
}
m, err := icmp.ParseMessage(1, buf[:n])
if err != nil {
continue
}
if e, ok := m.Body.(*icmp.Echo); ok && m.Type == ipv4.ICMPTypeEchoReply && e.Seq == seq {
return true
}
}
}
// Problem says why pings can't be sent ("" when they can).
func (p *ICMPPinger) Problem() string {
if v, ok := p.Unavailable.Load().(string); ok {
return v
}
return ""
}