package dhcpd import ( "net/netip" "time" "steward/internal/model" ) // neverUsed picks a pool address that was never handed out (or whose // lease was deleted), in the subnet's order (s.mu held): the pools listed // or at random, and inside a pool sequential, reverse or random // (docs/subnets.md, "Address order"). func (s *Server) neverUsed(cfg *config, own map[netip.Addr]bool, sub *subnet, cl client, now time.Time) (netip.Addr, bool) { ok := func(ip netip.Addr) bool { return s.leases[ip] == nil && s.usable(cfg, own, sub, ip, cl, now) } order := func(p model.Pool) string { switch { case p.Order != "": return p.Order case sub.AddressOrder != "": return sub.AddressOrder } return cfg.settings.EffectiveAddressOrder() } if sub.PoolOrder != model.PoolsRandom { for _, p := range sub.Pools { if ip, found := s.pickIn(p, order(p), ok); found { return ip, true } } return netip.Addr{}, false } // A pool at random, weighted by its never-used addresses. counts := make([]int, len(sub.Pools)) total := 0 for i, p := range sub.Pools { counts[i] = countIn(p, ok) total += counts[i] } if total == 0 { return netip.Addr{}, false } n := s.randN(total) for i, p := range sub.Pools { if n < counts[i] { return s.pickIn(p, order(p), ok) } n -= counts[i] } return netip.Addr{}, false } // pickIn picks an address of the pool that ok accepts, in an order. func (s *Server) pickIn(p model.Pool, order string, ok func(netip.Addr) bool) (netip.Addr, bool) { var found netip.Addr switch order { case model.OrderRandom: n := countIn(p, ok) if n == 0 { return netip.Addr{}, false } k := s.randN(n) eachIn(p, false, func(ip netip.Addr) bool { if !ok(ip) { return false } if k == 0 { found = ip return true } k-- return false }) default: eachIn(p, order == model.OrderReverse, func(ip netip.Addr) bool { if ok(ip) { found = ip return true } return false }) } return found, found.IsValid() } // countIn counts the pool's addresses that ok accepts. func countIn(p model.Pool, ok func(netip.Addr) bool) int { n := 0 eachIn(p, false, func(ip netip.Addr) bool { if ok(ip) { n++ } return false }) return n } // eachIn calls fn for the pool's addresses, lowest first (highest first // when reverse), until it returns true. func eachIn(p model.Pool, reverse bool, fn func(netip.Addr) bool) { if !p.Start.Is4() || !p.End.Is4() || p.End.Less(p.Start) { return } lo, hi := model.U32(p.Start), model.U32(p.End) if reverse { for v := hi; ; v-- { if fn(model.FromU32(v)) || v == lo { return } } } for v := lo; ; v++ { if fn(model.FromU32(v)) || v == hi { return } } }