package dhcpd import ( "context" "net/netip" "sync" "time" "steward/internal/model" ) // How often active leases' addresses are pinged for their last-seen time, // and how many pings run at once. const ( seenEvery = time.Minute seenWorkers = 16 ) // watchSeen pings the active leases' addresses every seenEvery. func (s *Server) watchSeen(ctx context.Context) { t := time.NewTicker(seenEvery) defer t.Stop() for { s.CheckSeen(ctx) select { case <-ctx.Done(): return case <-t.C: } } } // CheckSeen pings every active lease's address once (when the ping check // is on), and records when each that answered was seen. A client that // doesn't answer keeps its last time, or none: many block pings, and stay // online while their lease lasts. func (s *Server) CheckSeen(ctx context.Context) { cfg := s.cfg.Load() if s.pinger == nil || !cfg.settings.PingCheck { return } now := s.now() var active []model.Lease s.mu.Lock() for _, l := range s.leases { if l.ActiveAt(now) { active = append(active, *l) } } s.mu.Unlock() timeout := cfg.settings.EffectivePingTimeout() var ( mu sync.Mutex seen = map[netip.Addr]time.Time{} wg sync.WaitGroup sem = make(chan struct{}, seenWorkers) ) for _, l := range active { if ctx.Err() != nil { break } sem <- struct{}{} wg.Add(1) go func() { defer func() { <-sem; wg.Done() }() if s.pinger.Ping(ctx, l.IP, timeout) { mu.Lock() seen[l.IP] = s.now() mu.Unlock() } }() } wg.Wait() if len(seen) == 0 { return } s.mu.Lock() defer s.mu.Unlock() for _, was := range active { at, ok := seen[was.IP] if !ok { continue } // The address may have gone to another client meanwhile. l := s.leases[was.IP] if l == nil || l.MAC != was.MAC || l.ClientID != was.ClientID || !l.ActiveAt(at) { delete(seen, was.IP) continue } l.LastSeen = &at } if err := s.store.SetLastSeen(ctx, seen); err != nil { s.warnOnce("last seen", time.Hour, "saving clients' last-seen times failed", "err", err) } }