dhcp4d: implement lease expiration
This commit is contained in:
parent
ea476bbb04
commit
f9c3c23b16
@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type Lease struct {
|
||||
Num int
|
||||
Addr net.IP
|
||||
HardwareAddr string
|
||||
Hostname string
|
||||
@ -29,6 +30,8 @@ type Handler struct {
|
||||
leasesHW map[string]*Lease
|
||||
leasesIP map[int]*Lease
|
||||
|
||||
timeNow func() time.Time
|
||||
|
||||
// Leases is called whenever a new lease is handed out
|
||||
Leases func([]*Lease)
|
||||
}
|
||||
@ -57,24 +60,24 @@ func NewHandler(dir string) (*Handler, error) {
|
||||
dhcp4.OptionDomainName: []byte("lan"),
|
||||
dhcp4.OptionDomainSearch: []byte{0x03, 'l', 'a', 'n', 0x00},
|
||||
},
|
||||
timeNow: time.Now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) findLease() int {
|
||||
now := h.timeNow()
|
||||
if len(h.leasesIP) < h.leaseRange {
|
||||
// Hand out a free lease
|
||||
// TODO: hash the hwaddr like dnsmasq
|
||||
i := rand.Intn(h.leaseRange)
|
||||
if _, ok := h.leasesIP[i]; !ok {
|
||||
if l, ok := h.leasesIP[i]; !ok || now.After(l.Expiry) {
|
||||
return i
|
||||
}
|
||||
for i := 0; i < h.leaseRange; i++ {
|
||||
if _, ok := h.leasesIP[i]; !ok {
|
||||
if l, ok := h.leasesIP[i]; !ok || now.After(l.Expiry) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: expire the oldest lease
|
||||
return -1
|
||||
}
|
||||
|
||||
@ -88,16 +91,24 @@ func (h *Handler) canLease(reqIP net.IP, hwaddr string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
if l, exists := h.leasesIP[leaseNum]; exists && l.HardwareAddr != hwaddr {
|
||||
return -1 // lease already in use
|
||||
l, ok := h.leasesIP[leaseNum]
|
||||
if !ok {
|
||||
return leaseNum // lease available
|
||||
}
|
||||
|
||||
return leaseNum
|
||||
if l.HardwareAddr == hwaddr {
|
||||
return leaseNum // lease already owned by requestor
|
||||
}
|
||||
|
||||
if h.timeNow().After(l.Expiry) {
|
||||
return leaseNum // lease expired
|
||||
}
|
||||
|
||||
return -1 // lease unavailable
|
||||
}
|
||||
|
||||
// TODO: is ServeDHCP always run from the same goroutine, or do we need locking?
|
||||
func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) dhcp4.Packet {
|
||||
log.Printf("got DHCP packet: %+v, msgType: %v, options: %v", p, msgType, options)
|
||||
reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress])
|
||||
if reqIP == nil {
|
||||
reqIP = net.IP(p.CIAddr())
|
||||
@ -105,26 +116,28 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
|
||||
switch msgType {
|
||||
case dhcp4.Discover:
|
||||
// Find previous lease for this HardwareAddr, if any
|
||||
// hwAddr := p.CHAddr().String()
|
||||
// if lease, ok := h.leases[hwAddr]; ok {
|
||||
|
||||
// }
|
||||
|
||||
free := -1
|
||||
hwAddr := p.CHAddr().String()
|
||||
|
||||
// try to offer the requested IP, if any and available
|
||||
if !bytes.Equal(reqIP.To4(), net.IPv4zero) {
|
||||
free = h.canLease(reqIP, p.CHAddr().String())
|
||||
log.Printf("canLease: %v", free)
|
||||
free = h.canLease(reqIP, hwAddr)
|
||||
}
|
||||
|
||||
// offer previous lease for this HardwareAddr, if any
|
||||
if lease, ok := h.leasesHW[hwAddr]; ok {
|
||||
free = lease.Num
|
||||
}
|
||||
|
||||
if free == -1 {
|
||||
free = h.findLease()
|
||||
}
|
||||
|
||||
if free == -1 {
|
||||
log.Printf("Cannot reply with DHCPOFFER: no more leases available")
|
||||
return nil // no free leases
|
||||
}
|
||||
|
||||
log.Printf("start = %v, free = %v", h.start, free)
|
||||
return dhcp4.ReplyPacket(p,
|
||||
dhcp4.Offer,
|
||||
h.serverIP,
|
||||
@ -143,11 +156,18 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
}
|
||||
|
||||
lease := &Lease{
|
||||
Num: leaseNum,
|
||||
Addr: reqIP,
|
||||
HardwareAddr: p.CHAddr().String(),
|
||||
Expiry: time.Now().Add(h.leasePeriod),
|
||||
Hostname: string(options[dhcp4.OptionHostName]),
|
||||
}
|
||||
|
||||
// Release any old leases for this client
|
||||
if l, ok := h.leasesHW[lease.HardwareAddr]; ok {
|
||||
delete(h.leasesIP, l.Num)
|
||||
}
|
||||
|
||||
h.leasesIP[leaseNum] = lease
|
||||
h.leasesHW[lease.HardwareAddr] = lease
|
||||
if h.Leases != nil {
|
||||
@ -161,7 +181,5 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
|
||||
h.options.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
|
||||
}
|
||||
// 1970/01/01 01:00:04 got DHCP packet: [1 1 6 0 142 216 238 39 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 164 76 200 233 19 71 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99 130 83 99 53 1 3 54 4 192 168 42 1 50 4 192 168 42 33 12 3 120 112 115 55 18 1 28 2 3 15 6 119 12 44 47 26 121 42 121 249 33 252 42 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], msgType: Request, options: map[OptionDHCPMessageType:[3] OptionServerIdentifier:[192 168 42 1] OptionHostName:[120 112 115] OptionParameterRequestList:[1 28 2 3 15 6 119 12 44 47 26 121 42 121 249 33 252 42] OptionRequestedIPAddress:[192 168 42 33]]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -7,10 +7,30 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/krolaw/dhcp4"
|
||||
)
|
||||
|
||||
func packet(mt dhcp4.MessageType, addr net.IP, hwaddr net.HardwareAddr, opts []dhcp4.Option) dhcp4.Packet {
|
||||
return dhcp4.RequestPacket(
|
||||
mt,
|
||||
hwaddr, // MAC address
|
||||
addr, // requested IP address
|
||||
[]byte{0xaa, 0xbb, 0xcc, 0xdd}, // transaction ID
|
||||
false, // broadcast,
|
||||
opts,
|
||||
)
|
||||
}
|
||||
|
||||
func request(addr net.IP, hwaddr net.HardwareAddr, opts ...dhcp4.Option) dhcp4.Packet {
|
||||
return packet(dhcp4.Request, addr, hwaddr, opts)
|
||||
}
|
||||
|
||||
func discover(addr net.IP, hwaddr net.HardwareAddr, opts ...dhcp4.Option) dhcp4.Packet {
|
||||
return packet(dhcp4.Discover, addr, hwaddr, opts)
|
||||
}
|
||||
|
||||
const goldenInterfaces = `
|
||||
{
|
||||
"interfaces":[
|
||||
@ -27,24 +47,29 @@ const goldenInterfaces = `
|
||||
}
|
||||
`
|
||||
|
||||
func TestLease(t *testing.T) {
|
||||
func testHandler(t *testing.T) (_ *Handler, cleanup func()) {
|
||||
tmpdir, err := ioutil.TempDir("", "dhcp4dtest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpdir, "interfaces.json"), []byte(goldenInterfaces), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler, err := NewHandler(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return handler, func() { os.RemoveAll(tmpdir) }
|
||||
}
|
||||
|
||||
func TestLease(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
var (
|
||||
addr = net.IP{192, 168, 42, 23}
|
||||
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
hostname = "xps"
|
||||
)
|
||||
handler, err := NewHandler(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
leasedCalled := false
|
||||
handler.Leases = func(leases []*Lease) {
|
||||
if got, want := len(leases), 1; got != want {
|
||||
@ -82,38 +107,17 @@ func TestLease(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPreferredAddress(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "dhcp4dtest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpdir, "interfaces.json"), []byte(goldenInterfaces), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
var (
|
||||
addr = net.IP{192, 168, 42, 23}
|
||||
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
hostname = "xps"
|
||||
)
|
||||
handler, err := NewHandler(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("no requested IP", func(t *testing.T) {
|
||||
p := dhcp4.RequestPacket(
|
||||
dhcp4.Discover,
|
||||
hardwareAddr, // MAC address
|
||||
net.IPv4zero, // requested IP address
|
||||
[]byte{0xaa, 0xbb, 0xcc, 0xdd}, // transaction ID
|
||||
false, // broadcast,
|
||||
[]dhcp4.Option{
|
||||
{
|
||||
Code: dhcp4.OptionHostName,
|
||||
Value: []byte(hostname),
|
||||
},
|
||||
},
|
||||
)
|
||||
p := request(net.IPv4zero, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Discover, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr.To4(); bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPOFFER for wrong IP: got %v, want %v", got, want)
|
||||
@ -121,19 +125,7 @@ func TestPreferredAddress(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("requested CIAddr", func(t *testing.T) {
|
||||
p := dhcp4.RequestPacket(
|
||||
dhcp4.Discover,
|
||||
hardwareAddr, // MAC address
|
||||
addr, // requested IP address
|
||||
[]byte{0xaa, 0xbb, 0xcc, 0xdd}, // transaction ID
|
||||
false, // broadcast,
|
||||
[]dhcp4.Option{
|
||||
{
|
||||
Code: dhcp4.OptionHostName,
|
||||
Value: []byte(hostname),
|
||||
},
|
||||
},
|
||||
)
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Discover, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPOFFER for wrong IP: got %v, want %v", got, want)
|
||||
@ -141,6 +133,7 @@ func TestPreferredAddress(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("requested option", func(t *testing.T) {
|
||||
//p := request(net.IPv4zero, hardwareAddr)
|
||||
p := dhcp4.RequestPacket(
|
||||
dhcp4.Discover,
|
||||
hardwareAddr, // MAC address
|
||||
@ -164,3 +157,163 @@ func TestPreferredAddress(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPoolBoundaries(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
var (
|
||||
addr = net.IP{192, 168, 42, 23}
|
||||
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
)
|
||||
|
||||
for _, last := range []byte{1, 202} {
|
||||
addr[len(addr)-1] = last
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
opts := resp.ParseOptions()
|
||||
if got, want := dhcp4.MessageType(opts[dhcp4.OptionDHCPMessageType][0]), dhcp4.NAK; got != want {
|
||||
t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPreviousLease(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
var (
|
||||
addr1 = net.IP{192, 168, 42, 23}
|
||||
addr2 = net.IP{192, 168, 42, 42}
|
||||
hardwareAddr1 = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
hardwareAddr2 = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x77}
|
||||
)
|
||||
|
||||
p := request(addr1, hardwareAddr1)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr1.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
p = request(addr1, hardwareAddr2)
|
||||
resp = handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
opts := resp.ParseOptions()
|
||||
if got, want := dhcp4.MessageType(opts[dhcp4.OptionDHCPMessageType][0]), dhcp4.NAK; got != want {
|
||||
t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
p = discover(net.IPv4zero, hardwareAddr1)
|
||||
resp = handler.ServeDHCP(p, dhcp4.Discover, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr1.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPOFFER for wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Free addr1 by requesting addr2
|
||||
p = request(addr2, hardwareAddr1)
|
||||
resp = handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr2.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Verify addr1 is now available to other clients
|
||||
p = request(addr1, hardwareAddr2)
|
||||
resp = handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr1.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpiration(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
now := time.Now()
|
||||
handler.timeNow = func() time.Time { return now }
|
||||
|
||||
var (
|
||||
addr = net.IP{192, 168, 42, 23}
|
||||
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
)
|
||||
|
||||
t.Run("allocate entire pool", func(t *testing.T) {
|
||||
// 1 is the DHCP server,
|
||||
for i := 1; i < 1+200; i++ {
|
||||
addr[len(addr)-1] = byte(1 + (i % 254)) // avoid .0 (net) and .255 (broadcast)
|
||||
hardwareAddr[len(hardwareAddr)-1] = addr[len(addr)-1]
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("re-allocate", func(t *testing.T) {
|
||||
// 1 is the DHCP server,
|
||||
for i := 1; i < 1+200; i++ {
|
||||
addr[len(addr)-1] = byte(1 + (i % 254)) // avoid .0 (net) and .255 (broadcast)
|
||||
hardwareAddr[len(hardwareAddr)-1] = addr[len(addr)-1]
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("full", func(t *testing.T) {
|
||||
// 1 is the DHCP server,
|
||||
for i := 1; i < 1+200; i++ {
|
||||
addr[len(addr)-1] = byte(1 + (i % 254)) // avoid .0 (net) and .255 (broadcast)
|
||||
hardwareAddr[len(hardwareAddr)-1] = addr[len(addr)-1] - 1
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
opts := resp.ParseOptions()
|
||||
if got, want := dhcp4.MessageType(opts[dhcp4.OptionDHCPMessageType][0]), dhcp4.NAK; got != want {
|
||||
t.Errorf("DHCPREQUEST resulted in unexpected message type: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
hardwareAddr[len(hardwareAddr)-1] = 0
|
||||
p := discover(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Discover, p.ParseOptions())
|
||||
if resp != nil {
|
||||
t.Errorf("DHCPDISCOVER(%v) resulted in unexpected offer of %v", addr, resp.YIAddr())
|
||||
}
|
||||
})
|
||||
|
||||
now = now.Add(3 * time.Hour)
|
||||
|
||||
t.Run("re-allocate after expiration", func(t *testing.T) {
|
||||
// 1 is the DHCP server,
|
||||
for i := 1; i < 1+200; i++ {
|
||||
addr[len(addr)-1] = byte(1 + (i % 254)) // avoid .0 (net) and .255 (broadcast)
|
||||
p := request(addr, hardwareAddr)
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if got, want := resp.YIAddr().To4(), addr.To4(); !bytes.Equal(got, want) {
|
||||
t.Errorf("DHCPREQUEST resulted in wrong IP: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerID(t *testing.T) {
|
||||
handler, cleanup := testHandler(t)
|
||||
defer cleanup()
|
||||
|
||||
var (
|
||||
addr = net.IP{192, 168, 42, 23}
|
||||
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
|
||||
)
|
||||
|
||||
p := request(addr, hardwareAddr, dhcp4.Option{
|
||||
Code: dhcp4.OptionServerIdentifier,
|
||||
Value: net.IP{192, 168, 1, 1},
|
||||
})
|
||||
resp := handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
|
||||
if resp != nil {
|
||||
t.Errorf("DHCPDISCOVER(%v) resulted in unexpected offer of %v", addr, resp.YIAddr())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test persistent storage
|
||||
|
Loading…
x
Reference in New Issue
Block a user