dhcp4d: offer requested address if available

This commit is contained in:
Michael Stapelberg 2018-06-07 08:39:47 +02:00
parent 0152ef3601
commit 3561ec3708
2 changed files with 122 additions and 22 deletions

View File

@ -2,6 +2,7 @@
package dhcp4d
import (
"bytes"
"log"
"math/rand"
"net"
@ -62,6 +63,7 @@ func NewHandler(dir string) (*Handler, error) {
func (h *Handler) findLease() int {
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 {
return i
@ -72,13 +74,35 @@ func (h *Handler) findLease() int {
}
}
}
// Re-use the oldest lease
// TODO: expire the oldest lease
return -1
}
func (h *Handler) canLease(reqIP net.IP, hwaddr string) int {
if len(reqIP) != 4 || reqIP.Equal(net.IPv4zero) {
return -1
}
leaseNum := dhcp4.IPRange(h.start, reqIP) - 1
if leaseNum < 0 || leaseNum >= h.leaseRange {
return -1
}
if l, exists := h.leasesIP[leaseNum]; exists && l.HardwareAddr != hwaddr {
return -1 // lease already in use
}
return leaseNum
}
// 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())
}
switch msgType {
case dhcp4.Discover:
// Find previous lease for this HardwareAddr, if any
@ -86,11 +110,20 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
// if lease, ok := h.leases[hwAddr]; ok {
// }
free := h.findLease()
free := -1
if !bytes.Equal(reqIP.To4(), net.IPv4zero) {
free = h.canLease(reqIP, p.CHAddr().String())
log.Printf("canLease: %v", free)
}
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,
@ -104,33 +137,16 @@ func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d
return nil // message not for this dhcp server
}
nak := dhcp4.ReplyPacket(p, dhcp4.NAK, h.serverIP, nil, 0, nil)
reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress])
if reqIP == nil {
reqIP = net.IP(p.CIAddr())
}
if len(reqIP) != 4 || reqIP.Equal(net.IPv4zero) {
leaseNum := h.canLease(reqIP, p.CHAddr().String())
if leaseNum == -1 {
return nak
}
leaseNum := dhcp4.IPRange(h.start, reqIP) - 1
if leaseNum < 0 || leaseNum >= h.leaseRange {
return nak
}
if l, exists := h.leasesIP[leaseNum]; exists && l.HardwareAddr != p.CHAddr().String() {
return nak // lease already in use
}
var hostname string
if b, ok := options[dhcp4.OptionHostName]; ok {
hostname = string(b)
}
lease := &Lease{
Addr: reqIP,
HardwareAddr: p.CHAddr().String(),
Expiry: time.Now().Add(h.leasePeriod),
Hostname: hostname,
Hostname: string(options[dhcp4.OptionHostName]),
}
h.leasesIP[leaseNum] = lease
h.leasesHW[lease.HardwareAddr] = lease

View File

@ -80,3 +80,87 @@ func TestLease(t *testing.T) {
t.Fatalf("leased callback not called")
}
}
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)
}
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),
},
},
)
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)
}
})
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),
},
},
)
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)
}
})
t.Run("requested option", 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),
},
{
Code: dhcp4.OptionRequestedIPAddress,
Value: addr,
},
},
)
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)
}
})
}