working offers

This commit is contained in:
2023-05-15 13:09:12 +00:00
parent f4fca2ffd0
commit cec46e68e8
29 changed files with 745 additions and 180 deletions

View File

@@ -3,8 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "iptools",
srcs = [
"hardware_address.go",
"lease_info.go",
"pool.go",
"range.go",
"tools.go",
@@ -12,6 +10,8 @@ go_library(
importpath = "git.wntrmute.dev/kyle/kdhcp/iptools",
visibility = ["//visibility:public"],
deps = [
"//dhcp",
"//leases",
"@dev_wntrmute_git_kyle_goutils//assert",
"@dev_wntrmute_git_kyle_goutils//log",
],
@@ -21,7 +21,6 @@ go_test(
name = "iptools_test",
size = "small",
srcs = [
"hardware_address_test.go",
"pool_test.go",
"range_test.go",
],

View File

@@ -1,41 +0,0 @@
package iptools
import (
"bytes"
"fmt"
"strconv"
"strings"
)
type HardwareAddress []byte
func (mac HardwareAddress) String() string {
marshalled := []string{}
for i := 0; i < len(mac); i++ {
marshalled = append(marshalled, fmt.Sprintf("%02x", []byte(mac[i:i+1])))
}
return strings.Join(marshalled, ":")
}
func (mac HardwareAddress) MarshalText() ([]byte, error) {
return []byte(mac.String()), nil
}
func (mac *HardwareAddress) UnmarshalText(b []byte) error {
rb := bytes.Split(b, []byte(":"))
for _, octet := range rb {
n, err := strconv.ParseUint(string(octet), 16, 8)
if err != nil {
return err
}
*mac = append(*mac, uint8(n))
}
return nil
}
func (mac HardwareAddress) Match(other HardwareAddress) bool {
return bytes.Equal(mac, other)
}

View File

@@ -1,31 +0,0 @@
package iptools
import (
"bytes"
"testing"
)
func TestHardwareMacMarshalling(t *testing.T) {
macString := "b8:27:eb:b6:a1:a7"
mac := HardwareAddress([]byte{0xb8, 0x27, 0xeb, 0xb6, 0xa1, 0xa7})
b, err := mac.MarshalText()
if err != nil {
t.Fatal(err)
}
s := string(b)
if s != macString {
t.Fatalf("have %s, want %s", s, macString)
}
mac2 := &HardwareAddress{}
err = mac2.UnmarshalText(b)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(*mac2, mac) {
t.Fatalf("have %x, want %x", *mac2, mac)
}
}

View File

@@ -1,73 +0,0 @@
package iptools
import (
"fmt"
"net/netip"
"sort"
"strings"
"time"
)
type LeaseInfo struct {
HostName string `yaml:"hostname"`
Addr netip.Addr `yaml:"addr"`
HardwareAddress HardwareAddress `yaml:"mac_addr"`
Expires time.Time `yaml:"expires"`
}
func (li *LeaseInfo) String() string {
return fmt.Sprintf("lease[hostname=%s addr=%s hw=%x expires=%s]", li.HostName, li.Addr, li.HardwareAddress, li.Expires)
}
type sortableLease []*LeaseInfo
func (a sortableLease) Len() int { return len(a) }
func (a sortableLease) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortableLease) Less(i, j int) bool { return a[i].Addr.Less(a[j].Addr) }
func (li *LeaseInfo) ResetExpiry(t time.Time, dur time.Duration) {
li.Expires = t.Add(dur)
}
func (li *LeaseInfo) IsExpired(t time.Time) bool {
return t.After(li.Expires)
}
func (li *LeaseInfo) Expire() {
li.Expires = time.Time{}
}
func SortLeases(leases []*LeaseInfo) []*LeaseInfo {
sortable := sortableLease(leases)
sort.Sort(sortable)
return []*LeaseInfo(sortable)
}
func (lease *LeaseInfo) Reset() *LeaseInfo {
lease.Expires = time.Time{}
lease.HardwareAddress = nil
return lease
}
func (lease LeaseInfo) Compare(other LeaseInfo) error {
susFields := []string{}
if lease.Addr != other.Addr {
susFields = append(susFields, fmt.Sprintf("address is %s, but is recorded as %s", lease.Addr, other.Addr))
}
if !lease.HardwareAddress.Match(other.HardwareAddress) {
susFields = append(susFields, fmt.Sprintf("hardware address is %s, but is recorded as %s", lease.HardwareAddress, other.HardwareAddress))
}
if lease.HostName != other.HostName {
susFields = append(susFields, fmt.Sprintf("hostname is %s, but is recorded as %s", lease.HostName, other.HostName))
}
if len(susFields) > 0 {
return fmt.Errorf("suspicious lease: %s", strings.Join(susFields, ";"))
}
return nil
}

View File

@@ -1,28 +1,39 @@
package iptools
import (
"bytes"
"fmt"
"net/netip"
"sync"
"time"
"git.wntrmute.dev/kyle/goutils/assert"
"git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/dhcp"
"git.wntrmute.dev/kyle/kdhcp/leases"
)
const DefaultExpiry = 168 * time.Hour
type Pool struct {
Name string `yaml:"name"`
Range *Range `yaml:"range"`
Expiry time.Duration `yaml:"expiry"`
Available []*LeaseInfo `yaml:"available"`
Active map[netip.Addr]*LeaseInfo `yaml:"active"`
Limbo map[netip.Addr]*LeaseInfo `yaml:"limbo"` // leases that are currently being offered
NoHostName bool `yaml:"no_hostname"` // don't set the hostname
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
mtx *sync.Mutex
}
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{}
}
}
func (p *Pool) lock() {
if p.mtx == nil {
p.mtx = &sync.Mutex{}
@@ -49,14 +60,14 @@ func NewPool(name string, r *Range) (*Pool, error) {
Expiry: r.Expiry,
NoHostName: r.NoHostName,
Available: enumerateRange(name, r, true),
Limbo: map[netip.Addr]*LeaseInfo{},
Limbo: map[netip.Addr]*leases.Info{},
}
return p, nil
}
func (p *Pool) sort() {
p.Available = SortLeases(p.Available)
p.Available = leases.Sort(p.Available)
}
func (p *Pool) Sort() {
@@ -73,30 +84,51 @@ func (p *Pool) IsAddressAvailable() bool {
// 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.
func (p *Pool) Peek(t time.Time, waitFor time.Duration) *LeaseInfo {
func (p *Pool) Peek(req *dhcp.Packet, t time.Time, waitFor time.Duration) *leases.Info {
p.lock()
defer p.unlock()
li := p.peek(t, waitFor)
li := p.peek(req, t, waitFor)
return li
}
func (p *Pool) peek(t time.Time, waitFor time.Duration) *LeaseInfo {
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
}
}
if len(p.Available) == 0 {
return nil
}
lease := p.Available[0]
p.Available = p.Available[1:]
lease.ResetExpiry(t, waitFor)
lease.HardwareAddress = req.HardwareAddress
lease.ResetExpiry(t, waitFor)
p.Limbo[lease.Addr] = lease
return lease
}
func (p *Pool) accept(addr netip.Addr) error {
assert.Bool(p.Active[addr] == nil, fmt.Sprintf("limbo address %s is already active: %#v", addr, *p.Active[addr]))
p.checkMaps()
if active, ok := p.Active[addr]; ok {
return fmt.Errorf("limbo address %s is already active: %#v", addr, *active)
}
p.Active[addr] = p.Limbo[addr]
delete(p.Limbo, addr)
@@ -124,6 +156,8 @@ func (p *Pool) Update(t time.Time) bool {
}
func (p *Pool) update(t time.Time) bool {
p.checkMaps()
var updated bool
for k, v := range p.Active {
@@ -150,6 +184,8 @@ func (p *Pool) update(t time.Time) bool {
return updated
}
func (p *Pool) Renew(lease *LeaseInfo, t time.Time) {
func (p *Pool) Renew(lease *leases.Info, t time.Time) {
p.checkMaps()
p.Active[lease.Addr].ResetExpiry(t, p.Expiry)
}

View File

@@ -11,11 +11,11 @@ const (
)
type Range struct {
Start netip.Addr `yaml:"start"`
End netip.Addr `yaml:"end"`
Network netip.Prefix `yaml:"network"`
Expiry time.Duration `yaml:"expiry"`
NoHostName bool `yaml:"no_hostname"` // don't set the hostname
Start netip.Addr `yaml:"start" json:"start"`
End netip.Addr `yaml:"end" json:"end"`
Network netip.Prefix `yaml:"network" json:"network"`
Expiry time.Duration `yaml:"expiry" json:"expiry"`
NoHostName bool `yaml:"no_hostname" json:"no_hostname"` // don't set the hostname
}
func (r *Range) Validate() error {

View File

@@ -2,9 +2,13 @@ package iptools
import (
"fmt"
"net"
"net/netip"
"git.wntrmute.dev/kyle/kdhcp/leases"
)
func enumerateRange(name string, r *Range, startFromOne bool) []*LeaseInfo {
func enumerateRange(name string, r *Range, startFromOne bool) []*leases.Info {
start := r.Start
cur := start
lenfmt := fmt.Sprintf("%%s%%0%dd", len(fmt.Sprintf("%d", r.numHosts())))
@@ -12,11 +16,11 @@ func enumerateRange(name string, r *Range, startFromOne bool) []*LeaseInfo {
if startFromOne {
i++
}
leases := []*LeaseInfo{}
lrange := []*leases.Info{}
for r.End.Compare(cur) >= 0 {
hostName := fmt.Sprintf(lenfmt, name, i)
leases = append(leases, &LeaseInfo{
lrange = append(lrange, &leases.Info{
HostName: hostName,
Addr: cur,
})
@@ -24,5 +28,18 @@ func enumerateRange(name string, r *Range, startFromOne bool) []*LeaseInfo {
cur = cur.Next()
}
return leases
return lrange
}
func NetIPtoAddr(ip net.IP) netip.Addr {
addr, ok := netip.AddrFromSlice(ip)
if !ok {
return netip.IPv4Unspecified()
}
if !addr.Is4() {
return netip.IPv4Unspecified()
}
return addr
}