dhcpsvc: imp code, tests

This commit is contained in:
Eugene Burkov 2024-07-09 15:28:52 +03:00
parent 22e37e587e
commit 083da36713
8 changed files with 227 additions and 219 deletions

View File

@ -66,6 +66,8 @@ func (conf *Config) Validate() (err error) {
errs = append(errs, err) errs = append(errs, err)
} }
// This is a best-effort check for the file accessibility. The file will be
// checked again when it is opened later.
if _, err = os.Stat(conf.DBFilePath); err != nil && !errors.Is(err, os.ErrNotExist) { if _, err = os.Stat(conf.DBFilePath); err != nil && !errors.Is(err, os.ErrNotExist) {
errs = append(errs, fmt.Errorf("db file path %q: %w", conf.DBFilePath, err)) errs = append(errs, fmt.Errorf("db file path %q: %w", conf.DBFilePath, err))
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/fs"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@ -19,6 +20,9 @@ import (
// dataVersion is the current version of the stored DHCP leases structure. // dataVersion is the current version of the stored DHCP leases structure.
const dataVersion = 1 const dataVersion = 1
// databasePerm is the permissions for the database file.
const databasePerm fs.FileMode = 0o640
// dataLeases is the structure of the stored DHCP leases. // dataLeases is the structure of the stored DHCP leases.
type dataLeases struct { type dataLeases struct {
// Leases is the list containing stored DHCP leases. // Leases is the list containing stored DHCP leases.
@ -43,8 +47,8 @@ func (dl *dbLease) compareNames(other *dbLease) (res int) {
return strings.Compare(dl.Hostname, other.Hostname) return strings.Compare(dl.Hostname, other.Hostname)
} }
// fromLease converts *Lease to *dbLease. // toDBLease converts *Lease to *dbLease.
func fromLease(l *Lease) (dl *dbLease) { func toDBLease(l *Lease) (dl *dbLease) {
var expiryStr string var expiryStr string
if !l.IsStatic { if !l.IsStatic {
// The front-end is waiting for RFC 3999 format of the time value. It // The front-end is waiting for RFC 3999 format of the time value. It
@ -63,8 +67,8 @@ func fromLease(l *Lease) (dl *dbLease) {
} }
} }
// toLease converts dl to *Lease. // toInternal converts dl to *Lease.
func (dl *dbLease) toLease() (l *Lease, err error) { func (dl *dbLease) toInternal() (l *Lease, err error) {
mac, err := net.ParseMAC(dl.HWAddr) mac, err := net.ParseMAC(dl.HWAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing hardware address: %w", err) return nil, fmt.Errorf("parsing hardware address: %w", err)
@ -117,47 +121,38 @@ func (srv *DHCPServer) dbLoad(ctx context.Context) (err error) {
// addDBLeases adds leases to the server. // addDBLeases adds leases to the server.
func (srv *DHCPServer) addDBLeases(ctx context.Context, leases []*dbLease) { func (srv *DHCPServer) addDBLeases(ctx context.Context, leases []*dbLease) {
const logMsg = "loading lease"
var v4, v6 uint var v4, v6 uint
for i, l := range leases { for i, l := range leases {
var lease *Lease lease, err := l.toInternal()
lease, err := l.toLease()
if err != nil { if err != nil {
srv.logger.DebugContext(ctx, logMsg, "idx", i, slogutil.KeyError, err) srv.logger.WarnContext(ctx, "converting lease", "idx", i, slogutil.KeyError, err)
continue continue
} }
addr := l.IP iface, err := srv.ifaceForAddr(l.IP)
iface, err := srv.ifaceForAddr(addr)
if err != nil { if err != nil {
srv.logger.DebugContext(ctx, logMsg, "idx", i, slogutil.KeyError, err) srv.logger.WarnContext(ctx, "searching lease iface", "idx", i, slogutil.KeyError, err)
continue continue
} }
err = srv.leases.add(lease, iface) err = srv.leases.add(lease, iface)
if err != nil { if err != nil {
srv.logger.DebugContext(ctx, logMsg, "idx", i, slogutil.KeyError, err) srv.logger.WarnContext(ctx, "adding lease", "idx", i, slogutil.KeyError, err)
continue continue
} }
if lease.IP.Is4() { if l.IP.Is4() {
v4++ v4++
} else { } else {
v6++ v6++
} }
} }
srv.logger.InfoContext( // TODO(e.burkov): Group by interface.
ctx, srv.logger.InfoContext(ctx, "loaded leases", "v4", v4, "v6", v6, "total", len(leases))
"loaded leases",
"v4", v4,
"v6", v6,
"total", len(leases),
)
} }
// writeDB writes leases to the database file. It expects the // writeDB writes leases to the database file. It expects the
@ -166,13 +161,13 @@ func (srv *DHCPServer) dbStore(ctx context.Context) (err error) {
defer func() { err = errors.Annotate(err, "writing db: %w") }() defer func() { err = errors.Annotate(err, "writing db: %w") }()
dl := &dataLeases{ dl := &dataLeases{
// Avoid writing "null" into the database file if there are no leases. // Avoid writing null into the database file if there are no leases.
Leases: make([]*dbLease, 0, srv.leases.len()), Leases: make([]*dbLease, 0, srv.leases.len()),
Version: dataVersion, Version: dataVersion,
} }
srv.leases.rangeLeases(func(l *Lease) (cont bool) { srv.leases.rangeLeases(func(l *Lease) (cont bool) {
lease := fromLease(l) lease := toDBLease(l)
i, _ := slices.BinarySearchFunc(dl.Leases, lease, (*dbLease).compareNames) i, _ := slices.BinarySearchFunc(dl.Leases, lease, (*dbLease).compareNames)
dl.Leases = slices.Insert(dl.Leases, i, lease) dl.Leases = slices.Insert(dl.Leases, i, lease)
@ -185,7 +180,7 @@ func (srv *DHCPServer) dbStore(ctx context.Context) (err error) {
return err return err
} }
err = maybe.WriteFile(srv.dbFilePath, buf, 0o644) err = maybe.WriteFile(srv.dbFilePath, buf, databasePerm)
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is. // Don't wrap the error since it's informative enough as is.
return err return err

View File

@ -0,0 +1,4 @@
package dhcpsvc
// DatabasePerm is the permissions for the test database file.
const DatabasePerm = databasePerm

View File

@ -1,55 +0,0 @@
package dhcpsvc_test
import (
"net/netip"
"path/filepath"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServer_loadDatabase(t *testing.T) {
leasesPath := filepath.Join("testdata", t.Name(), "leases.json")
ipv4Conf := &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("192.168.0.2"),
RangeEnd: netip.MustParseAddr("192.168.0.254"),
LeaseDuration: 1 * time.Hour,
}
conf := &dhcpsvc.Config{
Enabled: true,
LocalDomainName: "local",
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
IPv4: ipv4Conf,
IPv6: &dhcpsvc.IPv6Config{Enabled: false},
},
},
DBFilePath: leasesPath,
Logger: discardLog,
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, conf)
require.NoError(t, err)
expiry, err := time.Parse(time.RFC3339, "2042-01-02T03:04:05Z")
require.NoError(t, err)
wantLeases := []*dhcpsvc.Lease{{
Expiry: expiry,
IP: netip.MustParseAddr("192.168.0.3"),
Hostname: "example.host",
HWAddr: mustParseMAC(t, "AA:AA:AA:AA:AA:AA"),
IsStatic: false,
}}
assert.Equal(t, wantLeases, srv.Leases())
}

View File

@ -0,0 +1,66 @@
package dhcpsvc_test
import (
"net"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/stretchr/testify/require"
)
// testLocalTLD is a common local TLD for tests.
const testLocalTLD = "local"
// testTimeout is a common timeout for tests and contexts.
const testTimeout time.Duration = 10 * time.Second
// discardLog is a logger to discard test output.
var discardLog = slogutil.NewDiscardLogger()
// testInterfaceConf is a common set of interface configurations for tests.
var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("192.168.0.2"),
RangeEnd: netip.MustParseAddr("192.168.0.254"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db8::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
"eth1": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("172.16.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("172.16.0.2"),
RangeEnd: netip.MustParseAddr("172.16.0.255"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db9::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
}
// mustParseMAC parses a hardware address from s and requires no errors.
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
require.NoError(t, err)
return mac
}

View File

@ -29,8 +29,9 @@ type DHCPServer struct {
// dbFilePath is the path to the database file containing the DHCP leases. // dbFilePath is the path to the database file containing the DHCP leases.
// //
// TODO(e.burkov): Perhaps, extract leases and database into a separate // TODO(e.burkov): Consider extracting the database logic into a separate
// type. // interface to prevent packages that only need lease data from depending on
// the entire server and to simplify testing.
dbFilePath string dbFilePath string
// leasesMu protects the leases index as well as leases in the interfaces. // leasesMu protects the leases index as well as leases in the interfaces.

View File

@ -2,7 +2,6 @@ package dhcpsvc_test
import ( import (
"io/fs" "io/fs"
"net"
"net/netip" "net/netip"
"os" "os"
"path/filepath" "path/filepath"
@ -11,84 +10,33 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// testLocalTLD is a common local TLD for tests. // testdata is a filesystem containing data for tests.
const testLocalTLD = "local" var testdata = os.DirFS("testdata")
// testTimeout is a common timeout for tests and contexts. // newTempDB copies the leases database file located in the testdata FS, under
const testTimeout time.Duration = 10 * time.Second // tb.Name()/leases.db, to a temporary directory and returns the path to the
// copied file.
func newTempDB(tb testing.TB) (dst string) {
tb.Helper()
// discardLog is a logger to discard test output. const filename = "leases.json"
var discardLog = slogutil.NewDiscardLogger()
// copyDB copies the leases database file located in the testdata directory, data, err := fs.ReadFile(testdata, filepath.Join(tb.Name(), filename))
// under tb.Name() directory, to a temporary directory and returns the path to
// the copied file.
func copyDB(tb testing.TB) (dst string) {
testdata := os.DirFS("testdata")
data, err := fs.ReadFile(testdata, filepath.Join(tb.Name(), "leases.json"))
require.NoError(tb, err) require.NoError(tb, err)
dst = filepath.Join(tb.TempDir(), "leases.json") dst = filepath.Join(tb.TempDir(), filename)
err = os.WriteFile(dst, data, 0o644) err = os.WriteFile(dst, data, dhcpsvc.DatabasePerm)
require.NoError(tb, err) require.NoError(tb, err)
return dst return dst
} }
// testInterfaceConf is a common set of interface configurations for tests.
var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
"eth0": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("192.168.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("192.168.0.2"),
RangeEnd: netip.MustParseAddr("192.168.0.254"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db8::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
"eth1": {
IPv4: &dhcpsvc.IPv4Config{
Enabled: true,
GatewayIP: netip.MustParseAddr("172.16.0.1"),
SubnetMask: netip.MustParseAddr("255.255.255.0"),
RangeStart: netip.MustParseAddr("172.16.0.2"),
RangeEnd: netip.MustParseAddr("172.16.0.255"),
LeaseDuration: 1 * time.Hour,
},
IPv6: &dhcpsvc.IPv6Config{
Enabled: true,
RangeStart: netip.MustParseAddr("2001:db9::1"),
LeaseDuration: 1 * time.Hour,
RAAllowSLAAC: true,
RASLAACOnly: true,
},
},
}
// mustParseMAC parses a hardware address from s and requires no errors.
func mustParseMAC(t require.TestingT, s string) (mac net.HardwareAddr) {
mac, err := net.ParseMAC(s)
require.NoError(t, err)
return mac
}
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
validIPv4Conf := &dhcpsvc.IPv4Config{ validIPv4Conf := &dhcpsvc.IPv4Config{
Enabled: true, Enabled: true,
@ -217,23 +165,25 @@ func TestDHCPServer_AddLease(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
const ( const (
host1 = "host1" existHost = "host1"
host2 = "host2" newHost = "host2"
host3 = "host3" ipv6Host = "host3"
) )
ip1 := netip.MustParseAddr("192.168.0.2") var (
ip2 := netip.MustParseAddr("192.168.0.3") existIP = netip.MustParseAddr("192.168.0.2")
ip3 := netip.MustParseAddr("2001:db8::2") newIP = netip.MustParseAddr("192.168.0.3")
newIPv6 = netip.MustParseAddr("2001:db8::2")
mac1 := mustParseMAC(t, "01:02:03:04:05:06") existMAC = mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "06:05:04:03:02:01") newMAC = mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "02:03:04:05:06:07") ipv6MAC = mustParseMAC(t, "02:03:04:05:06:07")
)
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{ require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
Hostname: host1, Hostname: existHost,
IP: ip1, IP: existIP,
HWAddr: mac1, HWAddr: existMAC,
IsStatic: true, IsStatic: true,
})) }))
@ -244,61 +194,61 @@ func TestDHCPServer_AddLease(t *testing.T) {
}{{ }{{
name: "outside_range", name: "outside_range",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host2, Hostname: newHost,
IP: netip.MustParseAddr("1.2.3.4"), IP: netip.MustParseAddr("1.2.3.4"),
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "adding lease: no interface for ip 1.2.3.4", wantErrMsg: "adding lease: no interface for ip 1.2.3.4",
}, { }, {
name: "duplicate_ip", name: "duplicate_ip",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host2, Hostname: newHost,
IP: ip1, IP: existIP,
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "adding lease: lease for ip " + ip1.String() + wantErrMsg: "adding lease: lease for ip " + existIP.String() +
" already exists", " already exists",
}, { }, {
name: "duplicate_hostname", name: "duplicate_hostname",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host1, Hostname: existHost,
IP: ip2, IP: newIP,
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "adding lease: lease for hostname " + host1 + wantErrMsg: "adding lease: lease for hostname " + existHost +
" already exists", " already exists",
}, { }, {
name: "duplicate_hostname_case", name: "duplicate_hostname_case",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: strings.ToUpper(host1), Hostname: strings.ToUpper(existHost),
IP: ip2, IP: newIP,
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "adding lease: lease for hostname " + wantErrMsg: "adding lease: lease for hostname " +
strings.ToUpper(host1) + " already exists", strings.ToUpper(existHost) + " already exists",
}, { }, {
name: "duplicate_mac", name: "duplicate_mac",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host2, Hostname: newHost,
IP: ip2, IP: newIP,
HWAddr: mac1, HWAddr: existMAC,
}, },
wantErrMsg: "adding lease: lease for mac " + mac1.String() + wantErrMsg: "adding lease: lease for mac " + existMAC.String() +
" already exists", " already exists",
}, { }, {
name: "valid", name: "valid",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host2, Hostname: newHost,
IP: ip2, IP: newIP,
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "valid_v6", name: "valid_v6",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host3, Hostname: ipv6Host,
IP: ip3, IP: newIPv6,
HWAddr: mac3, HWAddr: ipv6MAC,
}, },
wantErrMsg: "", wantErrMsg: "",
}} }}
@ -316,7 +266,7 @@ func TestDHCPServer_AddLease(t *testing.T) {
func TestDHCPServer_index(t *testing.T) { func TestDHCPServer_index(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout) ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := copyDB(t) leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{ srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true, Enabled: true,
Logger: discardLog, Logger: discardLog,
@ -334,21 +284,23 @@ func TestDHCPServer_index(t *testing.T) {
host5 = "host5" host5 = "host5"
) )
ip1 := netip.MustParseAddr("192.168.0.2") var (
ip2 := netip.MustParseAddr("192.168.0.3") ip1 = netip.MustParseAddr("192.168.0.2")
ip3 := netip.MustParseAddr("172.16.0.3") ip2 = netip.MustParseAddr("192.168.0.3")
ip4 := netip.MustParseAddr("172.16.0.4") ip3 = netip.MustParseAddr("172.16.0.3")
ip4 = netip.MustParseAddr("172.16.0.4")
mac1 := mustParseMAC(t, "01:02:03:04:05:06") mac1 = mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "06:05:04:03:02:01") mac2 = mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "02:03:04:05:06:07") mac3 = mustParseMAC(t, "02:03:04:05:06:07")
)
t.Run("ip_idx", func(t *testing.T) { t.Run("ip_idx", func(t *testing.T) {
assert.Equal(t, ip1, srv.IPByHost(host1)) assert.Equal(t, ip1, srv.IPByHost(host1))
assert.Equal(t, ip2, srv.IPByHost(host2)) assert.Equal(t, ip2, srv.IPByHost(host2))
assert.Equal(t, ip3, srv.IPByHost(host3)) assert.Equal(t, ip3, srv.IPByHost(host3))
assert.Equal(t, ip4, srv.IPByHost(host4)) assert.Equal(t, ip4, srv.IPByHost(host4))
assert.Equal(t, netip.Addr{}, srv.IPByHost(host5)) assert.Zero(t, srv.IPByHost(host5))
}) })
t.Run("name_idx", func(t *testing.T) { t.Run("name_idx", func(t *testing.T) {
@ -356,7 +308,7 @@ func TestDHCPServer_index(t *testing.T) {
assert.Equal(t, host2, srv.HostByIP(ip2)) assert.Equal(t, host2, srv.HostByIP(ip2))
assert.Equal(t, host3, srv.HostByIP(ip3)) assert.Equal(t, host3, srv.HostByIP(ip3))
assert.Equal(t, host4, srv.HostByIP(ip4)) assert.Equal(t, host4, srv.HostByIP(ip4))
assert.Equal(t, "", srv.HostByIP(netip.Addr{})) assert.Zero(t, srv.HostByIP(netip.Addr{}))
}) })
t.Run("mac_idx", func(t *testing.T) { t.Run("mac_idx", func(t *testing.T) {
@ -364,14 +316,14 @@ func TestDHCPServer_index(t *testing.T) {
assert.Equal(t, mac2, srv.MACByIP(ip2)) assert.Equal(t, mac2, srv.MACByIP(ip2))
assert.Equal(t, mac3, srv.MACByIP(ip3)) assert.Equal(t, mac3, srv.MACByIP(ip3))
assert.Equal(t, mac1, srv.MACByIP(ip4)) assert.Equal(t, mac1, srv.MACByIP(ip4))
assert.Nil(t, srv.MACByIP(netip.Addr{})) assert.Zero(t, srv.MACByIP(netip.Addr{}))
}) })
} }
func TestDHCPServer_UpdateStaticLease(t *testing.T) { func TestDHCPServer_UpdateStaticLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout) ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := copyDB(t) leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{ srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true, Enabled: true,
Logger: discardLog, Logger: discardLog,
@ -390,14 +342,16 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
host6 = "host6" host6 = "host6"
) )
ip1 := netip.MustParseAddr("192.168.0.2") var (
ip2 := netip.MustParseAddr("192.168.0.3") ip1 = netip.MustParseAddr("192.168.0.2")
ip3 := netip.MustParseAddr("192.168.0.4") ip2 = netip.MustParseAddr("192.168.0.3")
ip4 := netip.MustParseAddr("2001:db8::3") ip3 = netip.MustParseAddr("192.168.0.4")
ip4 = netip.MustParseAddr("2001:db8::3")
mac1 := mustParseMAC(t, "01:02:03:04:05:06") mac1 = mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "06:05:04:03:02:01") mac2 = mustParseMAC(t, "06:05:04:03:02:01")
mac3 := mustParseMAC(t, "06:05:04:03:02:02") mac3 = mustParseMAC(t, "06:05:04:03:02:02")
)
testCases := []struct { testCases := []struct {
name string name string
@ -476,7 +430,7 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
func TestDHCPServer_RemoveLease(t *testing.T) { func TestDHCPServer_RemoveLease(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout) ctx := testutil.ContextWithTimeout(t, testTimeout)
leasesPath := copyDB(t) leasesPath := newTempDB(t)
srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{ srv, err := dhcpsvc.New(ctx, &dhcpsvc.Config{
Enabled: true, Enabled: true,
Logger: discardLog, Logger: discardLog,
@ -492,13 +446,15 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
host3 = "host3" host3 = "host3"
) )
ip1 := netip.MustParseAddr("192.168.0.2") var (
ip2 := netip.MustParseAddr("192.168.0.3") existIP = netip.MustParseAddr("192.168.0.2")
ip3 := netip.MustParseAddr("2001:db8::2") newIP = netip.MustParseAddr("192.168.0.3")
newIPv6 = netip.MustParseAddr("2001:db8::2")
mac1 := mustParseMAC(t, "01:02:03:04:05:06") existMAC = mustParseMAC(t, "01:02:03:04:05:06")
mac2 := mustParseMAC(t, "02:03:04:05:06:07") newMAC = mustParseMAC(t, "02:03:04:05:06:07")
mac3 := mustParseMAC(t, "06:05:04:03:02:01") ipv6MAC = mustParseMAC(t, "06:05:04:03:02:01")
)
testCases := []struct { testCases := []struct {
name string name string
@ -508,40 +464,40 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
name: "not_found_mac", name: "not_found_mac",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host1, Hostname: host1,
IP: ip1, IP: existIP,
HWAddr: mac2, HWAddr: newMAC,
}, },
wantErrMsg: "removing lease: no lease for mac " + mac2.String(), wantErrMsg: "removing lease: no lease for mac " + newMAC.String(),
}, { }, {
name: "not_found_ip", name: "not_found_ip",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host1, Hostname: host1,
IP: ip2, IP: newIP,
HWAddr: mac1, HWAddr: existMAC,
}, },
wantErrMsg: "removing lease: no lease for ip " + ip2.String(), wantErrMsg: "removing lease: no lease for ip " + newIP.String(),
}, { }, {
name: "not_found_host", name: "not_found_host",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host2, Hostname: host2,
IP: ip1, IP: existIP,
HWAddr: mac1, HWAddr: existMAC,
}, },
wantErrMsg: "removing lease: no lease for hostname " + host2, wantErrMsg: "removing lease: no lease for hostname " + host2,
}, { }, {
name: "valid", name: "valid",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host1, Hostname: host1,
IP: ip1, IP: existIP,
HWAddr: mac1, HWAddr: existMAC,
}, },
wantErrMsg: "", wantErrMsg: "",
}, { }, {
name: "valid_v6", name: "valid_v6",
lease: &dhcpsvc.Lease{ lease: &dhcpsvc.Lease{
Hostname: host3, Hostname: host3,
IP: ip3, IP: newIPv6,
HWAddr: mac3, HWAddr: ipv6MAC,
}, },
wantErrMsg: "", wantErrMsg: "",
}} }}
@ -557,7 +513,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
} }
func TestDHCPServer_Reset(t *testing.T) { func TestDHCPServer_Reset(t *testing.T) {
leasesPath := copyDB(t) leasesPath := newTempDB(t)
conf := &dhcpsvc.Config{ conf := &dhcpsvc.Config{
Enabled: true, Enabled: true,
Logger: discardLog, Logger: discardLog,
@ -579,3 +535,37 @@ func TestDHCPServer_Reset(t *testing.T) {
assert.FileExists(t, leasesPath) assert.FileExists(t, leasesPath)
assert.Empty(t, srv.Leases()) assert.Empty(t, srv.Leases())
} }
func TestServer_Leases(t *testing.T) {
leasesPath := newTempDB(t)
conf := &dhcpsvc.Config{
Enabled: true,
Logger: discardLog,
LocalDomainName: testLocalTLD,
Interfaces: testInterfaceConf,
DBFilePath: leasesPath,
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
srv, err := dhcpsvc.New(ctx, conf)
require.NoError(t, err)
expiry, err := time.Parse(time.RFC3339, "2042-01-02T03:04:05Z")
require.NoError(t, err)
wantLeases := []*dhcpsvc.Lease{{
Expiry: expiry,
IP: netip.MustParseAddr("192.168.0.3"),
Hostname: "example.host",
HWAddr: mustParseMAC(t, "AA:AA:AA:AA:AA:AA"),
IsStatic: false,
}, {
Expiry: time.Time{},
IP: netip.MustParseAddr("192.168.0.4"),
Hostname: "example.static.host",
HWAddr: mustParseMAC(t, "BB:BB:BB:BB:BB:BB"),
IsStatic: true,
}}
assert.Equal(t, wantLeases, srv.Leases())
}

View File

@ -5,6 +5,11 @@
"hostname": "example.host", "hostname": "example.host",
"mac": "AA:AA:AA:AA:AA:AA", "mac": "AA:AA:AA:AA:AA:AA",
"static": false "static": false
}, {
"ip": "192.168.0.4",
"hostname": "example.static.host",
"mac": "BB:BB:BB:BB:BB:BB",
"static": true
}], }],
"version": 1 "version": 1
} }