working offers
This commit is contained in:
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user