kdhcp/iptools/pool.go

192 lines
4.1 KiB
Go
Raw Permalink Normal View History

2023-05-03 02:40:31 +00:00
package iptools
import (
2023-05-15 13:09:12 +00:00
"bytes"
2023-05-06 05:21:27 +00:00
"fmt"
2023-05-03 02:40:31 +00:00
"net/netip"
2023-05-06 05:21:27 +00:00
"sync"
2023-05-03 02:40:31 +00:00
"time"
2023-05-06 05:21:27 +00:00
2023-05-09 07:28:54 +00:00
"git.wntrmute.dev/kyle/goutils/log"
2023-05-15 13:09:12 +00:00
"git.wntrmute.dev/kyle/kdhcp/dhcp"
"git.wntrmute.dev/kyle/kdhcp/leases"
2023-05-03 02:40:31 +00:00
)
2023-05-06 05:21:27 +00:00
const DefaultExpiry = 168 * time.Hour
2023-05-03 02:40:31 +00:00
type Pool struct {
2023-05-15 13:09:12 +00:00
Name string `yaml:"name" json:"name"`
Expiry time.Duration `yaml:"expiry" json:"expiry"`
Available []*leases.Info `yaml:"available" json:"available"`
Active map[netip.Addr]*leases.Info `yaml:"active" json:"active"`
Limbo map[netip.Addr]*leases.Info `yaml:"limbo" json:"limbo"` // leases that are currently being offered
NoHostName bool `yaml:"no_hostname" json:"no_hostname"` // don't set the hostname
2023-05-06 05:21:27 +00:00
mtx *sync.Mutex
}
2023-05-15 13:09:12 +00:00
func (p *Pool) checkMaps() {
if p.Active == nil {
p.Active = map[netip.Addr]*leases.Info{}
}
if p.Limbo == nil {
p.Limbo = map[netip.Addr]*leases.Info{}
}
}
2023-05-06 05:21:27 +00:00
func (p *Pool) lock() {
2023-05-09 07:28:54 +00:00
if p.mtx == nil {
2023-05-06 05:21:27 +00:00
p.mtx = &sync.Mutex{}
}
p.mtx.Lock()
}
func (p *Pool) unlock() {
if p.mtx == nil {
panic("pool: attempted to unlock uninitialized mutex")
}
p.mtx.Unlock()
2023-05-03 02:40:31 +00:00
}
2023-05-06 05:21:27 +00:00
func NewPool(name string, r *Range) (*Pool, error) {
if r.Expiry == 0 {
r.Expiry = DefaultExpiry
}
2023-05-03 02:40:31 +00:00
p := &Pool{
2023-05-06 05:21:27 +00:00
Name: name,
Expiry: r.Expiry,
NoHostName: r.NoHostName,
Available: enumerateRange(name, r, true),
2023-05-15 13:09:12 +00:00
Limbo: map[netip.Addr]*leases.Info{},
2023-05-03 02:40:31 +00:00
}
return p, nil
}
2023-05-06 05:21:27 +00:00
func (p *Pool) sort() {
2023-05-15 13:09:12 +00:00
p.Available = leases.Sort(p.Available)
2023-05-03 02:40:31 +00:00
}
2023-05-06 05:21:27 +00:00
func (p *Pool) Sort() {
p.lock()
defer p.unlock()
p.sort()
}
2023-05-03 02:40:31 +00:00
func (p *Pool) IsAddressAvailable() bool {
return len(p.Available) > 0
}
2023-05-06 05:21:27 +00:00
// Peek returns the first available address from the pool and moves it
// from available to limbo. When the client is given the address, Accept
// should be called on the address to move it from limbo to active.
2023-05-15 13:09:12 +00:00
func (p *Pool) Peek(req *dhcp.Packet, t time.Time, waitFor time.Duration) *leases.Info {
2023-05-06 05:21:27 +00:00
p.lock()
defer p.unlock()
2023-05-15 13:09:12 +00:00
li := p.peek(req, t, waitFor)
2023-05-06 05:21:27 +00:00
return li
}
2023-05-15 13:09:12 +00:00
func (p *Pool) peek(req *dhcp.Packet, t time.Time, waitFor time.Duration) *leases.Info {
p.checkMaps()
for _, li := range p.Active {
if bytes.Equal(req.HardwareAddress, li.HardwareAddress) {
log.Debugf("returning existing lease to %x of %s", req.HardwareAddress, li.Addr)
return li
}
}
for _, li := range p.Limbo {
if bytes.Equal(req.HardwareAddress, li.HardwareAddress) {
log.Debugf("returning existing offer to %x of %s", req.HardwareAddress, li.Addr)
return li
}
}
2023-05-06 05:21:27 +00:00
if len(p.Available) == 0 {
return nil
}
2023-05-03 02:40:31 +00:00
lease := p.Available[0]
p.Available = p.Available[1:]
2023-05-15 13:09:12 +00:00
lease.HardwareAddress = req.HardwareAddress
lease.ResetExpiry(t, waitFor)
2023-05-03 02:40:31 +00:00
p.Limbo[lease.Addr] = lease
2023-05-06 05:21:27 +00:00
return lease
}
func (p *Pool) accept(addr netip.Addr) error {
2023-05-15 13:09:12 +00:00
p.checkMaps()
if active, ok := p.Active[addr]; ok {
return fmt.Errorf("limbo address %s is already active: %#v", addr, *active)
}
2023-05-06 05:21:27 +00:00
p.Active[addr] = p.Limbo[addr]
delete(p.Limbo, addr)
return nil
}
// Accept will move an address out limbo and mark it as active.
func (p *Pool) Accept(addr netip.Addr, t time.Time) error {
p.lock()
defer p.unlock()
p.update(t)
p.accept(addr)
return nil
2023-05-03 02:40:31 +00:00
}
// Update checks expirations on leases, moving any expired leases back
// to the available pool and removing any limbo leases that are expired.
2023-05-09 07:28:54 +00:00
func (p *Pool) Update(t time.Time) bool {
2023-05-06 05:21:27 +00:00
p.lock()
defer p.unlock()
2023-05-09 07:28:54 +00:00
return p.update(t)
2023-05-06 05:21:27 +00:00
}
2023-05-09 07:28:54 +00:00
func (p *Pool) update(t time.Time) bool {
2023-05-15 13:09:12 +00:00
p.checkMaps()
2023-05-09 07:28:54 +00:00
var updated bool
2023-05-06 05:21:27 +00:00
2023-05-03 02:40:31 +00:00
for k, v := range p.Active {
if v.IsExpired(t) {
2023-05-09 07:28:54 +00:00
updated = true
log.Infof("expiring active address %s for %x", v.Addr, v.HardwareAddress)
delete(p.Active, k)
2023-05-03 02:40:31 +00:00
v = v.Reset()
p.Available = append(p.Available, v)
}
}
for k, v := range p.Limbo {
if v.IsExpired(t) {
2023-05-09 07:28:54 +00:00
updated = true
log.Infof("expiring limbo address %s for %x", v.Addr, v.HardwareAddress)
2023-05-03 02:40:31 +00:00
delete(p.Limbo, k)
v = v.Reset()
p.Available = append(p.Available, v)
}
}
2023-05-09 07:28:54 +00:00
p.sort()
return updated
2023-05-03 02:40:31 +00:00
}
2023-05-15 13:09:12 +00:00
func (p *Pool) Renew(lease *leases.Info, t time.Time) {
p.checkMaps()
2023-05-09 07:28:54 +00:00
p.Active[lease.Addr].ResetExpiry(t, p.Expiry)
2023-05-03 02:40:31 +00:00
}