| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- // Copyright 2018-present the CoreDHCP Authors. All rights reserved
- // This source code is licensed under the MIT license found in the
- // LICENSE file in the root directory of this source tree.
- package file
- import (
- "net"
- "net/netip"
- "os"
- "testing"
- "time"
- "github.com/insomniacslk/dhcp/dhcpv4"
- "github.com/insomniacslk/dhcp/dhcpv6"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func TestLoadDHCPv4Records(t *testing.T) {
- t.Run("valid leases", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // fill temp file with valid lease lines (mixed case) and some comments
- _, err = tmp.WriteString(`00:11:22:33:44:aa 192.0.2.100
- 11:BB:33:DD:55:FF 192.0.2.101 # arbitrary spaces and trailing comment
- # this is a simple comment
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- records, err := LoadDHCPv4Records(tmp.Name())
- if !assert.NoError(t, err) {
- return
- }
- if assert.Equal(t, 2, len(records)) {
- if assert.Contains(t, records, "00:11:22:33:44:aa") {
- assert.Equal(t, netip.MustParseAddr("192.0.2.100"), records["00:11:22:33:44:aa"])
- }
- if assert.Contains(t, records, "11:bb:33:dd:55:ff") {
- assert.Equal(t, netip.MustParseAddr("192.0.2.101"), records["11:bb:33:dd:55:ff"])
- }
- }
- })
- t.Run("missing field should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with too few fields
- _, err = tmp.WriteString("foo\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("invalid MAC address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with invalid MAC address to trigger an error
- _, err = tmp.WriteString("abcd 192.0.2.102\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("invalid IP address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with invalid IPv4 address to trigger an error
- _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("duplicate MAC address are allowed", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add lines with duplicate MAC addresses to check for no error
- _, err = tmp.WriteString(`aa:11:11:11:11:11 1.2.3.4
- AA:11:11:11:11:11 5.6.7.8
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.NoError(t, err)
- })
- t.Run("duplicate IP address are allowed", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with duplicate IPv4 addresses to check for no error
- _, err = tmp.WriteString(`11:11:11:11:11:11 1.2.3.4
- 22:22:22:22:22:22 1.2.3.4
- 33:33:33:33:33:33 1.2.3.4
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.NoError(t, err)
- })
- t.Run("lease with IPv6 address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with IPv6 address instead to trigger an error
- _, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv4Records(tmp.Name())
- assert.Error(t, err)
- })
- }
- func TestLoadDHCPv6Records(t *testing.T) {
- t.Run("valid leases", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // fill temp file with valid lease lines and some comments
- _, err = tmp.WriteString(`00:11:22:33:44:aa 2001:db8::10:1
- 11:BB:33:DD:55:FF 2001:db8::10:2 # arbitrary spaces and trailing comment
- # this is a simple comment
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- records, err := LoadDHCPv6Records(tmp.Name())
- if !assert.NoError(t, err) {
- return
- }
- if assert.Equal(t, 2, len(records)) {
- if assert.Contains(t, records, "00:11:22:33:44:aa") {
- assert.Equal(t, netip.MustParseAddr("2001:db8::10:1"), records["00:11:22:33:44:aa"])
- }
- if assert.Contains(t, records, "11:bb:33:dd:55:ff") {
- assert.Equal(t, netip.MustParseAddr("2001:db8::10:2"), records["11:bb:33:dd:55:ff"])
- }
- }
- })
- t.Run("missing field should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with too few fields
- _, err = tmp.WriteString("foo\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("invalid MAC address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with invalid MAC address to trigger an error
- _, err = tmp.WriteString("abcd 2001:db8::10:3\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("invalid IP address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with invalid MAC address to trigger an error
- _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.Error(t, err)
- })
- t.Run("duplicate MAC address are allowed", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add lines with duplicate MAC addresses to trigger an error
- _, err = tmp.WriteString(`aa:11:11:11:11:11 2001:db8::10:1
- AA:11:11:11:11:11 2001:db8::10:2
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.NoError(t, err)
- })
- t.Run("duplicate IP address are allowed", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add lines with duplicate IPv6 addresses to trigger an error
- _, err = tmp.WriteString(`11:11:11:11:11:11 2001:db8::10:1
- 22:22:22:22:22:22 2001:db8::10:1
- 33:33:33:33:33:33 2001:db8::10:1
- `)
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.NoError(t, err)
- })
- t.Run("lease with IPv4 address should raise error", func(t *testing.T) {
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- require.NoError(t, os.Remove(tmp.Name()))
- }()
- // add line with IPv4 address instead to trigger an error
- _, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
- require.NoError(t, err)
- require.NoError(t, tmp.Close())
- _, err = LoadDHCPv6Records(tmp.Name())
- assert.Error(t, err)
- })
- }
- func TestHandler4(t *testing.T) {
- t.Run("unknown MAC", func(t *testing.T) {
- // prepare DHCPv4 request
- mac := "aa:11:22:33:44:55"
- claddr, _ := net.ParseMAC(mac)
- req := &dhcpv4.DHCPv4{
- ClientHWAddr: claddr,
- }
- resp := &dhcpv4.DHCPv4{}
- assert.Nil(t, resp.ClientIPAddr)
- // if we handle this DHCP request, nothing should change since the lease is
- // unknown
- result, stop := Handler4(req, resp)
- assert.Same(t, result, resp)
- assert.False(t, stop)
- assert.Nil(t, result.YourIPAddr)
- })
- t.Run("known MAC", func(t *testing.T) {
- // prepare DHCPv4 request
- mac := "aa:11:22:33:44:55"
- claddr, _ := net.ParseMAC(mac)
- req := &dhcpv4.DHCPv4{
- ClientHWAddr: claddr,
- }
- resp := &dhcpv4.DHCPv4{}
- assert.Nil(t, resp.ClientIPAddr)
- // add lease for the MAC in the lease map
- clIPAddr := netip.MustParseAddr("192.0.2.100")
- StaticRecords = map[string]netip.Addr{
- mac: clIPAddr,
- }
- // if we handle this DHCP request, the YourIPAddr field should be set
- // in the result
- result, stop := Handler4(req, resp)
- assert.Same(t, result, resp)
- assert.True(t, stop)
- assert.Equal(t, net.IP(clIPAddr.AsSlice()), result.YourIPAddr)
- // cleanup
- StaticRecords = make(map[string]netip.Addr)
- })
- }
- func TestHandler6(t *testing.T) {
- t.Run("unknown MAC", func(t *testing.T) {
- // prepare DHCPv6 request
- mac := "aa:11:22:33:44:55"
- claddr, _ := net.ParseMAC(mac)
- req, err := dhcpv6.NewSolicit(claddr)
- require.NoError(t, err)
- resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
- require.NoError(t, err)
- assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
- // if we handle this DHCP request, nothing should change since the lease is
- // unknown
- result, stop := Handler6(req, resp)
- assert.False(t, stop)
- assert.Equal(t, 0, len(result.GetOption(dhcpv6.OptionIANA)))
- })
- t.Run("known MAC", func(t *testing.T) {
- // prepare DHCPv6 request
- mac := "aa:11:22:33:44:55"
- claddr, _ := net.ParseMAC(mac)
- req, err := dhcpv6.NewSolicit(claddr)
- require.NoError(t, err)
- resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
- require.NoError(t, err)
- assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
- // add lease for the MAC in the lease map
- clIPAddr := netip.MustParseAddr("2001:db8::10:1")
- StaticRecords = map[string]netip.Addr{
- mac: clIPAddr,
- }
- // if we handle this DHCP request, there should be a specific IANA option
- // set in the resulting response
- result, stop := Handler6(req, resp)
- assert.False(t, stop)
- if assert.Equal(t, 1, len(result.GetOption(dhcpv6.OptionIANA))) {
- opt := result.GetOneOption(dhcpv6.OptionIANA)
- assert.Contains(t, opt.String(), "IP=2001:db8::10:1")
- }
- // cleanup
- StaticRecords = make(map[string]netip.Addr)
- })
- }
- func TestSetupFile(t *testing.T) {
- // too few arguments
- _, _, err := setupFile(false)
- assert.Error(t, err)
- // empty file name
- _, _, err = setupFile(false, "")
- assert.Error(t, err)
- // trigger error in LoadDHCPv*Records
- _, _, err = setupFile(false, "/foo/bar")
- assert.Error(t, err)
- _, _, err = setupFile(true, "/foo/bar")
- assert.Error(t, err)
- // setup temp leases file
- tmp, err := os.CreateTemp("", "test_plugin_file")
- require.NoError(t, err)
- defer func() {
- tmp.Close()
- os.Remove(tmp.Name())
- }()
- t.Run("typical case", func(t *testing.T) {
- _, err = tmp.WriteString("aa:11:22:33:44:55 2001:db8::10:1\n")
- require.NoError(t, err)
- _, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
- require.NoError(t, err)
- assert.Equal(t, 0, len(StaticRecords))
- // leases should show up in StaticRecords
- _, _, err = setupFile(true, tmp.Name())
- if assert.NoError(t, err) {
- assert.Equal(t, 2, len(StaticRecords))
- assert.Equal(t, StaticRecords["aa:11:22:33:44:55"], netip.MustParseAddr("2001:db8::10:1"))
- assert.Equal(t, StaticRecords["11:22:33:44:55:66"], netip.MustParseAddr("2001:db8::10:2"))
- }
- })
- t.Run("autorefresh enabled", func(t *testing.T) {
- _, _, err = setupFile(true, tmp.Name(), autoRefreshArg)
- if assert.NoError(t, err) {
- assert.Equal(t, 2, len(StaticRecords))
- }
- // we add more leases to the file
- // this should trigger an event to refresh the leases database
- // without calling setupFile again.
- // Note that the IPv6 address is uppercase (allowed but not best practice)
- _, err = tmp.WriteString("22:33:44:55:66:77 2001:DB8::10:3\n")
- require.NoError(t, err)
- // since the event is processed asynchronously, give it a little time
- time.Sleep(time.Millisecond * 100)
- // an additional record should show up in the database
- // but we should respect the locking first
- recLock.RLock()
- defer recLock.RUnlock()
- assert.Equal(t, 3, len(StaticRecords))
- assert.Equal(t, StaticRecords["22:33:44:55:66:77"], netip.MustParseAddr("2001:db8::10:3"))
- })
- }
|