router7/internal/dhcp4d/dhcp4d_test.go

320 lines
9.5 KiB
Go
Raw Normal View History

2018-05-27 17:30:42 +02:00
package dhcp4d
import (
"bytes"
"io/ioutil"
2018-05-27 17:30:42 +02:00
"net"
"os"
"path/filepath"
2018-05-27 17:30:42 +02:00
"testing"
2018-06-09 15:04:31 +02:00
"time"
2018-05-27 17:30:42 +02:00
"github.com/krolaw/dhcp4"
)
2018-06-09 15:04:31 +02:00
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":[
{
"hardware_addr": "02:73:53:00:ca:fe",
"name": "uplink0"
},
{
"hardware_addr": "02:73:53:00:b0:0c",
"name": "lan0",
"addr": "192.168.42.1/24"
}
]
}
`
2018-06-09 15:04:31 +02:00
func testHandler(t *testing.T) (_ *Handler, cleanup func()) {
tmpdir, err := ioutil.TempDir("", "dhcp4dtest")
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmpdir, "interfaces.json"), []byte(goldenInterfaces), 0644); err != nil {
t.Fatal(err)
}
2018-06-09 15:04:31 +02:00
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()
2018-05-27 17:30:42 +02:00
var (
addr = net.IP{192, 168, 42, 23}
hardwareAddr = net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
hostname = "xps"
)
leasedCalled := false
handler.Leases = func(leases []*Lease) {
if got, want := len(leases), 1; got != want {
t.Fatalf("unexpected number of leases: got %d, want %d", got, want)
}
l := leases[0]
if got, want := l.Addr, addr; !bytes.Equal(got, want) {
t.Fatalf("unexpected lease.Addr: got %v, want %v", got, want)
}
if got, want := l.HardwareAddr, hardwareAddr.String(); got != want {
t.Fatalf("unexpected lease.HardwareAddr: got %q, want %q", got, want)
}
if got, want := l.Hostname, hostname; got != want {
t.Fatalf("unexpected lease.Hostname: got %q, want %q", got, want)
}
leasedCalled = true
}
p := dhcp4.RequestPacket(
dhcp4.Request,
hardwareAddr, // MAC address
addr, // requested IP address
[]byte{0xaa, 0xbb, 0xcc, 0xdd}, // transaction ID
false, // broadcast,
[]dhcp4.Option{
{
Code: dhcp4.OptionHostName,
Value: []byte(hostname),
},
},
)
handler.ServeDHCP(p, dhcp4.Request, p.ParseOptions())
if !leasedCalled {
t.Fatalf("leased callback not called")
}
}
func TestPreferredAddress(t *testing.T) {
2018-06-09 15:04:31 +02:00
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"
)
t.Run("no requested IP", func(t *testing.T) {
2018-06-09 15:04:31 +02:00
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)
}
})
t.Run("requested CIAddr", func(t *testing.T) {
2018-06-09 15:04:31 +02:00
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)
}
})
t.Run("requested option", func(t *testing.T) {
2018-06-09 15:04:31 +02:00
//p := request(net.IPv4zero, hardwareAddr)
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)
}
})
}
2018-06-09 15:04:31 +02:00
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