Address order: sequential, reverse or random, per subnet and pool
- Which never-used pool address a new client gets: sequential (the lowest, as before), reverse or random, in the settings, a subnet or a single pool (FIRST-LAST:ORDER). When no never-used address is left, the one free the longest, whatever the order; returning clients keep theirs. - A subnet's pools are used in the order listed, or one at random weighted by its never-used addresses. Pools now keep the order they are given in (no more sorting by address). - CLI --address-order and --pool-order; --add-pool of an existing range changes its order in place. Web UI: per-pool order and a move-up button, pool and address order on the subnet and settings pages. - Kea's allocator: random is imported. Schema migration 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
29fc991ce1
commit
8ee430276c
29 files changed
+653
-79
No files matched your search
@@ -0,0 +1,117 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user