mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-08-04 15:28:58 +00:00
Pull request 2727: AGDNS-3863-gopacket-dhcp-vol.30
Updates #4923.
Squashed commit of the following:
commit 26f8b000f83888c873ae3bf959636d4e95c58f01
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Thu Jul 23 16:37:14 2026 +0300
dhcpsvc: imp code
commit 76d4f8997d
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Jul 21 14:25:14 2026 +0300
dhcpsvc: imp tests
This commit is contained in:
parent
f078919e22
commit
599cfa9eb5
31 changed files with 840 additions and 927 deletions
|
|
@ -7,11 +7,16 @@ import (
|
|||
// Database is the interface for storing DHCP leases.
|
||||
type Database interface {
|
||||
// Load loads leases from the database. If err is not nil, leases must be
|
||||
// nil. It must be safe for concurrent use.
|
||||
// nil. Leases must not be modified.
|
||||
//
|
||||
// Implementations must document the sorting order, if the one is defined.
|
||||
// It must be safe for concurrent use.
|
||||
Load(ctx context.Context) (leases []*Lease, err error)
|
||||
|
||||
// Store stores leases to the database. leases must be valid. It must be
|
||||
// safe for concurrent use.
|
||||
// Store stores leases to the database. leases must not be nil and all its
|
||||
// elements must be valid. leases must not be modified until Store returns.
|
||||
//
|
||||
// It must be safe for concurrent use.
|
||||
Store(ctx context.Context, leases []*Lease) (err error)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,9 @@ package dhcpsvc_test
|
|||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -33,9 +30,6 @@ const testLocalTLD = "local"
|
|||
// testIfaceName is the name of the test network interface.
|
||||
const testIfaceName = "iface0"
|
||||
|
||||
// testDBLeasesFilename is the common name of a leases database file for tests.
|
||||
const testDBLeasesFilename = "leases.json"
|
||||
|
||||
// testTimeout is a common timeout for tests and contexts.
|
||||
const testTimeout = 10 * time.Second
|
||||
|
||||
|
|
@ -45,9 +39,6 @@ const testLeaseTTL = 24 * time.Hour
|
|||
// testLogger is a common logger for tests.
|
||||
var testLogger = slogutil.NewDiscardLogger()
|
||||
|
||||
// testdata is a filesystem containing data for tests.
|
||||
var testdata = os.DirFS("testdata")
|
||||
|
||||
// testCurrentTime is the fixed time returned by [testClock] to ensure
|
||||
// reproducible tests.
|
||||
var testCurrentTime = time.Date(2025, 1, 1, 1, 1, 1, 0, time.UTC)
|
||||
|
|
@ -59,6 +50,40 @@ var testClock = &faketime.Clock{
|
|||
},
|
||||
}
|
||||
|
||||
// Lease hostnames for test cases.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
// testLease4HostnameUnknown is the test hostname for an unknown DHCPv4
|
||||
// lease.
|
||||
testLease4HostnameUnknown = "unknown4"
|
||||
|
||||
// testLease4HostnameStatic is the test hostname for a static DHCPv4 lease.
|
||||
testLease4HostnameStatic = "static4"
|
||||
|
||||
// testLease4HostnameDynamic is the test hostname for a dynamic DHCPv4
|
||||
// lease.
|
||||
testLease4HostnameDynamic = "dynamic4"
|
||||
|
||||
// testLease4HostnameExpired is the test hostname for an expired DHCPv4
|
||||
// lease.
|
||||
testLease4HostnameExpired = "expired4"
|
||||
|
||||
// testLease6HostnameUnknown is the test hostname for an unknown DHCPv6
|
||||
// lease.
|
||||
testLease6HostnameUnknown = "unknown6"
|
||||
|
||||
// testLease6HostnameStatic is the test hostname for a static DHCPv6 lease.
|
||||
testLease6HostnameStatic = "static6"
|
||||
|
||||
// testLease6HostnameDynamic is the test hostname for a dynamic DHCPv6 lease.
|
||||
testLease6HostnameDynamic = "dynamic6"
|
||||
|
||||
// testLease6HostnameExpired is the test hostname for an expired DHCPv6
|
||||
// lease.
|
||||
testLease6HostnameExpired = "expired6"
|
||||
)
|
||||
|
||||
const (
|
||||
// testGatewayIPv4Str is the string representation of the gateway IPv4
|
||||
// address used in tests.
|
||||
|
|
@ -173,13 +198,13 @@ var testInterfaceConf = map[string]*dhcpsvc.InterfaceConfig{
|
|||
SubnetMask: netip.MustParseAddr(testAnotherSubnetMaskV4Str),
|
||||
RangeStart: netip.MustParseAddr(testAnotherRangeStartV4Str),
|
||||
RangeEnd: netip.MustParseAddr(testAnotherRangeEndV4Str),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
LeaseDuration: testLeaseTTL,
|
||||
},
|
||||
IPv6: &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
Clock: timeutil.SystemClock{},
|
||||
RangeStart: netip.MustParseAddr(testAnotherRangeStartV6Str),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
LeaseDuration: testLeaseTTL,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
},
|
||||
|
|
@ -206,18 +231,126 @@ var (
|
|||
testHWAnother = net.HardwareAddr{0x4, 0x5, 0x6, 0x7, 0x8, 0x9}
|
||||
)
|
||||
|
||||
// IPv4 addresses for tests.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
// testIPv4Unknown is the test IP address for an unknown client.
|
||||
testIPv4Unknown = netip.MustParseAddr("192.0.2.142")
|
||||
|
||||
// testIPv4Static is the test IP address for a known static lease.
|
||||
testIPv4Static = netip.MustParseAddr("192.0.2.101")
|
||||
|
||||
// testIPv4Dynamic is the test IP address for a known dynamic lease.
|
||||
testIPv4Dynamic = netip.MustParseAddr("192.0.2.102")
|
||||
|
||||
// testIPv4Expired is the test IP address for a known expired lease.
|
||||
testIPv4Expired = netip.MustParseAddr("192.0.2.103")
|
||||
|
||||
// testIPv4OtherSubnet is the test IP address for a client on another
|
||||
// subnet.
|
||||
testIPv4OtherSubnet = netip.MustParseAddr(testAnotherGatewayIPv4Str)
|
||||
|
||||
// testIPv4RelayAgent is the test IP address of the relay agent.
|
||||
testIPv4RelayAgent = netip.MustParseAddr("10.0.0.1")
|
||||
)
|
||||
|
||||
// IPv6 addresses for tests.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
// testIPv6Unknown is the test IP address for an unknown client.
|
||||
testIPv6Unknown = netip.MustParseAddr("2001:db8::64")
|
||||
|
||||
// testIPv6Dynamic is the test IP address for a known dynamic lease.
|
||||
testIPv6Dynamic = netip.MustParseAddr("2001:db8::66")
|
||||
|
||||
// testIPv6Expired is the test IP address for a known expired lease.
|
||||
testIPv6Expired = netip.MustParseAddr("2001:db8::67")
|
||||
|
||||
// testIPv6Static is the test IP address for a known static lease.
|
||||
testIPv6Static = netip.MustParseAddr("2001:db8::65")
|
||||
)
|
||||
|
||||
// Time-related variables for test cases.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
// testExpiryDynamicLease is the test expiry time for a dynamic lease.
|
||||
testExpiryDynamicLease = time.Date(2025, 1, 1, 10, 1, 1, 0, time.UTC)
|
||||
// testExpiryDynamicLease is the test expiry time for a dynamic lease, not
|
||||
// yet expired according to [testClock].
|
||||
testExpiryDynamicLease = testCurrentTime.Add(testLeaseTTL)
|
||||
|
||||
// testTTLDynamicLease is the test TTL for the dynamic lease.
|
||||
testTTLDynamicLease = testExpiryDynamicLease.Sub(testCurrentTime)
|
||||
// testExpiryExpiredLease is the test expiry time for an expired lease
|
||||
// according to [testClock].
|
||||
testExpiryExpiredLease = testCurrentTime.Add(-time.Hour)
|
||||
)
|
||||
|
||||
// TODO(e.burkov): Add a default lease expiry time, according to
|
||||
// [testCurrentTime].
|
||||
var (
|
||||
// testLease4Dynamic is a common valid dynamic DHCPv4 lease for tests.
|
||||
testLease4Dynamic = &dhcpsvc.Lease{
|
||||
IP: testIPv4Dynamic,
|
||||
HWAddr: testHWDynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: testLease4HostnameDynamic,
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
// testLease4Static is a common valid static DHCPv4 lease for tests.
|
||||
testLease4Static = &dhcpsvc.Lease{
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWStatic,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IsStatic: true,
|
||||
}
|
||||
|
||||
// testLease4Expired is a common expired DHCPv4 lease for tests.
|
||||
testLease4Expired = &dhcpsvc.Lease{
|
||||
IP: testIPv4Expired,
|
||||
HWAddr: testHWExpired,
|
||||
Expiry: testExpiryExpiredLease,
|
||||
Hostname: testLease4HostnameExpired,
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
// testLease6Dynamic is a common valid dynamic DHCPv6 lease for tests.
|
||||
testLease6Dynamic = &dhcpsvc.Lease{
|
||||
IP: testIPv6Dynamic,
|
||||
HWAddr: testHWDynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: testLease6HostnameDynamic,
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
// testLease6Static is a common valid static DHCPv6 lease for tests.
|
||||
testLease6Static = &dhcpsvc.Lease{
|
||||
IP: testIPv6Static,
|
||||
HWAddr: testHWStatic,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease6HostnameStatic,
|
||||
IsStatic: true,
|
||||
}
|
||||
|
||||
// testLease6Expired is a common expired DHCPv6 lease for tests.
|
||||
testLease6Expired = &dhcpsvc.Lease{
|
||||
IP: testIPv6Expired,
|
||||
HWAddr: testHWExpired,
|
||||
Expiry: testExpiryExpiredLease,
|
||||
Hostname: testLease6HostnameExpired,
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
// testLeases4 is a common set of leases for tests, containing only IPv4
|
||||
// leases.
|
||||
testLeases4 = []*dhcpsvc.Lease{testLease4Dynamic, testLease4Expired, testLease4Static}
|
||||
|
||||
// testLeases6 is a common set of leases for tests, containing only IPv6
|
||||
// leases.
|
||||
testLeases6 = []*dhcpsvc.Lease{testLease6Dynamic, testLease6Expired, testLease6Static}
|
||||
|
||||
// testLeases is a set of leases for tests, containing both IPv4 and IPv6
|
||||
// leases.
|
||||
testLeases = slices.Concat(testLeases4, testLeases6)
|
||||
)
|
||||
|
||||
// fullLayersStack4 is the complete stack of layers expected to appear in the
|
||||
|
|
@ -240,6 +373,8 @@ var fullLayersStack6 = []gopacket.LayerType{
|
|||
|
||||
// testDatabase is a mock implementation of the [dhcpsvc.Database] interface for
|
||||
// tests.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving to aghtest.
|
||||
type testDatabase struct {
|
||||
onLoad func(ctx context.Context) (leases []*dhcpsvc.Lease, err error)
|
||||
onStore func(ctx context.Context, leases []*dhcpsvc.Lease) (err error)
|
||||
|
|
@ -258,45 +393,27 @@ func (db *testDatabase) Store(ctx context.Context, leases []*dhcpsvc.Lease) (err
|
|||
return db.onStore(ctx, leases)
|
||||
}
|
||||
|
||||
// newTestDatabase copies the leases database file located in the testdata FS,
|
||||
// under tb.Name()/leases.json, to a temporary directory and constructs a
|
||||
// [*testDatabase] that properly loads the leases from that file.
|
||||
//
|
||||
// TODO(e.burkov): Move leases from testdata to the literals in tests, and
|
||||
// improve this helper.
|
||||
func newTestDatabase(tb testing.TB) (db *testDatabase) {
|
||||
// newTestDatabase creates a new *testDatabase for testing. If initial is not
|
||||
// nil, db.Load is set to return it. By default, db.Store panics on any call.
|
||||
func newTestDatabase(tb testing.TB, initial []*dhcpsvc.Lease) (db *testDatabase) {
|
||||
tb.Helper()
|
||||
|
||||
data, err := fs.ReadFile(testdata, path.Join(tb.Name(), testDBLeasesFilename))
|
||||
require.NoError(tb, err)
|
||||
|
||||
dst := filepath.Join(tb.TempDir(), testDBLeasesFilename)
|
||||
require.NoError(tb, os.WriteFile(dst, data, dhcpsvc.JSONDatabasePerm))
|
||||
|
||||
jsonDB := dhcpsvc.NewJSONDatabase(&dhcpsvc.JSONDatabaseConfig{
|
||||
Logger: testLogger,
|
||||
FilePath: dst,
|
||||
})
|
||||
|
||||
db = newPanicDatabase(tb)
|
||||
db.onLoad = jsonDB.Load
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// newPanicDatabase returns a *testDatabase that panics on any call to its
|
||||
// methods.
|
||||
func newPanicDatabase(tb testing.TB) (db *testDatabase) {
|
||||
tb.Helper()
|
||||
|
||||
return &testDatabase{
|
||||
onLoad: func(ctx context.Context) (leases []*dhcpsvc.Lease, err error) {
|
||||
db = &testDatabase{
|
||||
onLoad: func(ctx context.Context) (_ []*dhcpsvc.Lease, _ error) {
|
||||
panic(testutil.UnexpectedCall(ctx))
|
||||
},
|
||||
onStore: func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
onStore: func(ctx context.Context, leases []*dhcpsvc.Lease) (_ error) {
|
||||
panic(testutil.UnexpectedCall(ctx, leases))
|
||||
},
|
||||
}
|
||||
|
||||
if initial != nil {
|
||||
db.onLoad = func(ctx context.Context) (leases []*dhcpsvc.Lease, err error) {
|
||||
return initial, nil
|
||||
}
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// newTestDHCPServer creates a new DHCPServer for testing. It uses the default
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
|
@ -303,7 +304,17 @@ func (iface *dhcpInterfaceV4) handleInitReboot(
|
|||
return
|
||||
}
|
||||
|
||||
iface.updateAndRespond(ctx, l, req, lease, fd, idOpt)
|
||||
lease.Hostname = cmp.Or(hostname4(req), lease.Hostname)
|
||||
|
||||
err := iface.updateLease(ctx, lease)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "init-reboot request failed", slogutil.KeyError, err)
|
||||
iface.respondNAK(ctx, req, fd, idOpt)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.respondACK(ctx, req, fd, lease, idOpt)
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type DHCPREQUEST in RENEWING or REBINDING
|
||||
|
|
@ -342,7 +353,18 @@ func (iface *dhcpInterfaceV4) handleRenew(
|
|||
return
|
||||
}
|
||||
|
||||
iface.updateAndRespond(ctx, l, req, lease, fd, idOpt)
|
||||
lease.Hostname = cmp.Or(hostname4(req), lease.Hostname)
|
||||
|
||||
err := iface.updateLease(ctx, lease)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "renew request failed", slogutil.KeyError, err)
|
||||
|
||||
iface.respondNAK(ctx, req, fd, idOpt)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.respondACK(ctx, req, fd, lease, idOpt)
|
||||
}
|
||||
|
||||
// handleDecline handles messages of type DHCPDECLINE. req must be a
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
|
|
@ -27,48 +26,11 @@ var testIPv4InterfacesConf = map[string]*dhcpsvc.InterfaceConfig{
|
|||
},
|
||||
}
|
||||
|
||||
// Lease hostnames for test cases.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
// testLease4HostnameStatic is the test hostname for a static DHCPv4 lease.
|
||||
testLease4HostnameStatic = "static4"
|
||||
|
||||
// testLease4HostnameDynamic is the test hostname for a dynamic DHCPv4
|
||||
// lease.
|
||||
testLease4HostnameDynamic = "dynamic4"
|
||||
|
||||
// testLease4HostnameExpired is the test hostname for an expired DHCPv4
|
||||
// lease.
|
||||
testLease4HostnameExpired = "expired4"
|
||||
)
|
||||
|
||||
// testXid is a common transaction ID for DHCPv4 tests.
|
||||
//
|
||||
// TODO(e.burkov): Generate unique IDs when they will be actually used.
|
||||
const testXid = 1
|
||||
|
||||
// IP addresses for test cases.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
// testIPv4Unknown is the test IP address for an unknown client.
|
||||
testIPv4Unknown = netip.MustParseAddr("192.0.2.142")
|
||||
|
||||
// testIPv4Static is the test IP address for a known static lease.
|
||||
testIPv4Static = netip.MustParseAddr("192.0.2.101")
|
||||
|
||||
// testIPv4Dynamic is the test IP address for a known dynamic lease.
|
||||
testIPv4Dynamic = netip.MustParseAddr("192.0.2.102")
|
||||
|
||||
// testIPv4OtherSubnet is the test IP address for a client on another
|
||||
// subnet.
|
||||
testIPv4OtherSubnet = netip.MustParseAddr(testAnotherGatewayIPv4Str)
|
||||
|
||||
// testIPv4RelayAgent is the test IP address of the relay agent.
|
||||
testIPv4RelayAgent = netip.MustParseAddr("10.0.0.1")
|
||||
)
|
||||
|
||||
func TestDHCPServer_ServeEther4_discover(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
@ -99,7 +61,7 @@ func TestDHCPServer_ServeEther4_discover(t *testing.T) {
|
|||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeOffer),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
newOptLeaseTime(t, testTTLDynamicLease),
|
||||
newOptLeaseTime(t, testLeaseTTL),
|
||||
newOptHostname(t, testLease4HostnameDynamic),
|
||||
},
|
||||
}, {
|
||||
|
|
@ -116,14 +78,12 @@ func TestDHCPServer_ServeEther4_discover(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv4](t, tc.in.Layer(layers.LayerTypeDHCPv4))
|
||||
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
|
|
@ -145,7 +105,7 @@ func TestDHCPServer_ServeEther4_discoverExpired(t *testing.T) {
|
|||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t),
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
|
|
@ -164,8 +124,6 @@ func TestDHCPServer_ServeEther4_discoverExpired(t *testing.T) {
|
|||
func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipMismatch := testIPv4Dynamic.Next().Next()
|
||||
|
||||
testCases := []struct {
|
||||
req gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
|
|
@ -175,7 +133,7 @@ func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
|||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Dynamic,
|
||||
HWAddr: testHWDynamic,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: testLease4HostnameDynamic,
|
||||
IsStatic: false,
|
||||
},
|
||||
|
|
@ -185,7 +143,7 @@ func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
|||
want: nil,
|
||||
name: "not_found",
|
||||
}, {
|
||||
req: newDHCPRELEASE(t, testHWAnother, ipMismatch),
|
||||
req: newDHCPRELEASE(t, testHWDynamic, testIPv4Unknown),
|
||||
want: nil,
|
||||
name: "mismatch_ip",
|
||||
}, {
|
||||
|
|
@ -195,11 +153,11 @@ func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.NotContains(testutil.NewPanicT(t), leases, tc.want)
|
||||
|
||||
|
|
@ -223,7 +181,6 @@ func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Increase maintainability index.
|
||||
func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
@ -246,7 +203,7 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
|||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Conf.RangeStart,
|
||||
HWAddr: testHWUnknown,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "",
|
||||
IsStatic: false,
|
||||
},
|
||||
|
|
@ -269,22 +226,6 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
|||
want: nil,
|
||||
name: "wrong_server_id",
|
||||
wantOpts: nil,
|
||||
}, {
|
||||
discover: nil,
|
||||
request: newDHCPREQUEST(t, &dhcpRequestConfig{
|
||||
options: layers.DHCPOptions{
|
||||
newOptRequestIP(t, testIPv4Conf.RangeEnd.Next()),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
},
|
||||
clientHWAddr: testHWUnknown,
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
}),
|
||||
want: nil,
|
||||
name: "no_lease",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeNak),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
},
|
||||
}, {
|
||||
discover: newDHCPDISCOVER(t, testHWStatic),
|
||||
request: newDHCPREQUEST(t, &dhcpRequestConfig{
|
||||
|
|
@ -318,11 +259,11 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
|
|
@ -342,12 +283,10 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
|||
Enabled: true,
|
||||
})
|
||||
|
||||
if tc.discover != nil {
|
||||
testutil.RequireSend(t, inCh, tc.discover, testTimeout)
|
||||
testutil.RequireSend(t, inCh, tc.discover, testTimeout)
|
||||
|
||||
_, ok := testutil.RequireReceive(t, outCh, testTimeout)
|
||||
require.True(t, ok)
|
||||
}
|
||||
_, ok := testutil.RequireReceive(t, outCh, testTimeout)
|
||||
require.True(t, ok)
|
||||
|
||||
if tc.wantOpts == nil {
|
||||
dev.onWritePacketData = unexpectedWritePacketData
|
||||
|
|
@ -360,6 +299,38 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_ServeEther4_requestSelectingNoLease(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
request := newDHCPREQUEST(t, &dhcpRequestConfig{
|
||||
options: layers.DHCPOptions{
|
||||
newOptRequestIP(t, testIPv4Conf.RangeEnd.Next()),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
},
|
||||
clientHWAddr: testHWUnknown,
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
})
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
Logger: testLogger,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
testutil.RequireSend(t, inCh, request, testTimeout)
|
||||
|
||||
assertValidResponse4(t, dhcpv4FromPacket(t, request), outCh, layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeNak),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Test expired.
|
||||
func TestDHCPServer_ServeEther4_requestInitReboot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
@ -374,13 +345,7 @@ func TestDHCPServer_ServeEther4_requestInitReboot(t *testing.T) {
|
|||
clientHWAddr: testHWStatic,
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
}),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWStatic,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
want: testLease4Static,
|
||||
name: "success",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeAck),
|
||||
|
|
@ -445,11 +410,11 @@ func TestDHCPServer_ServeEther4_requestInitReboot(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
|
|
@ -493,18 +458,12 @@ func TestDHCPServer_ServeEther4_requestRenewSuccess(t *testing.T) {
|
|||
clientIP: testIPv4Dynamic,
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
}),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Dynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "dynamic4",
|
||||
HWAddr: testHWDynamic,
|
||||
IsStatic: false,
|
||||
},
|
||||
want: testLease4Dynamic,
|
||||
name: "success",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeAck),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
newOptLeaseTime(t, testTTLDynamicLease),
|
||||
newOptLeaseTime(t, testLeaseTTL),
|
||||
newOptHostname(t, testLease4HostnameDynamic),
|
||||
},
|
||||
}, {
|
||||
|
|
@ -513,13 +472,7 @@ func TestDHCPServer_ServeEther4_requestRenewSuccess(t *testing.T) {
|
|||
clientIP: testIPv4Static,
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
}),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Static,
|
||||
Expiry: time.Time{},
|
||||
Hostname: "static4",
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
want: testLease4Static,
|
||||
name: "static",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeAck),
|
||||
|
|
@ -533,18 +486,12 @@ func TestDHCPServer_ServeEther4_requestRenewSuccess(t *testing.T) {
|
|||
clientIP: testIPv4Dynamic,
|
||||
relayAgentIP: testIPv4RelayAgent,
|
||||
}),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Dynamic,
|
||||
HWAddr: testHWDynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "dynamic4",
|
||||
IsStatic: false,
|
||||
},
|
||||
want: testLease4Dynamic,
|
||||
name: "relay_agent",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeAck),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
newOptLeaseTime(t, testTTLDynamicLease),
|
||||
newOptLeaseTime(t, testLeaseTTL),
|
||||
newOptHostname(t, testLease4HostnameDynamic),
|
||||
},
|
||||
}, {
|
||||
|
|
@ -552,28 +499,22 @@ func TestDHCPServer_ServeEther4_requestRenewSuccess(t *testing.T) {
|
|||
clientHWAddr: testHWDynamic,
|
||||
clientIP: testIPv4Dynamic,
|
||||
}),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv4Dynamic,
|
||||
HWAddr: testHWDynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "dynamic4",
|
||||
IsStatic: false,
|
||||
},
|
||||
want: testLease4Dynamic,
|
||||
name: "ciaddr_unicast",
|
||||
wantOpts: layers.DHCPOptions{
|
||||
newOptMessageType(t, layers.DHCPMsgTypeAck),
|
||||
newOptServerID(t, testIfaceAddrV4),
|
||||
newOptLeaseTime(t, testTTLDynamicLease),
|
||||
newOptLeaseTime(t, testLeaseTTL),
|
||||
newOptHostname(t, testLease4HostnameDynamic),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
|
|
@ -636,8 +577,6 @@ func TestDHCPServer_ServeEther4_requestRenewFail(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
@ -647,7 +586,7 @@ func TestDHCPServer_ServeEther4_requestRenewFail(t *testing.T) {
|
|||
}
|
||||
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
|
|
@ -690,12 +629,10 @@ func TestDHCPServer_ServeEther4_decline(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := newTestDatabase(t)
|
||||
|
||||
// The lease should be blocked.
|
||||
wantLease := &dhcpsvc.Lease{
|
||||
IP: tc.want,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "",
|
||||
HWAddr: dhcpsvc.BlockedHardwareAddr,
|
||||
IsStatic: false,
|
||||
|
|
@ -704,6 +641,8 @@ func TestDHCPServer_ServeEther4_decline(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, wantLease)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
|
|
@ -37,35 +36,15 @@ const testIAID = 1
|
|||
// TODO(e.burkov): Generate unique IDs when they will be actually used.
|
||||
var testTransactionID = []byte{0x01, 0x02, 0x03}
|
||||
|
||||
// IP addresses for test cases.
|
||||
//
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
// testIPv6Unknown is the test IP address for an unknown client.
|
||||
testIPv6Unknown = netip.MustParseAddr("2001:db8::64")
|
||||
|
||||
// testIPv6Dynamic is the test IP address for a known dynamic lease.
|
||||
testIPv6Dynamic = netip.MustParseAddr("2001:db8::66")
|
||||
|
||||
// testIPv6Expired is the test IP address for a known expired lease.
|
||||
testIPv6Expired = netip.MustParseAddr("2001:db8::67")
|
||||
|
||||
// testIPv6Static is the test IP address for a known static lease.
|
||||
testIPv6Static = netip.MustParseAddr("2001:db8::65")
|
||||
)
|
||||
|
||||
// TODO(e.burkov): Increase maintainability index.
|
||||
func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
in gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
name string
|
||||
wantOpts layers.DHCPv6Options
|
||||
}{{
|
||||
in: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, false),
|
||||
want: nil,
|
||||
name: "new",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
|
|
@ -76,7 +55,6 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
},
|
||||
}, {
|
||||
in: newDHCPv6SOLICIT(t, testHWStatic, testIPv6Static, false),
|
||||
want: nil,
|
||||
name: "existing_static",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
|
|
@ -87,7 +65,6 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
},
|
||||
}, {
|
||||
in: newDHCPv6SOLICIT(t, testHWDynamic, testIPv6Dynamic, false),
|
||||
want: nil,
|
||||
name: "existing_dynamic",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
|
|
@ -98,7 +75,6 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
},
|
||||
}, {
|
||||
in: newDHCPv6SOLICIT(t, testHWExpired, testIPv6Expired, false),
|
||||
want: nil,
|
||||
name: "existing_expired",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
|
|
@ -107,16 +83,50 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV6)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Interfaces: testIPv6InterfacesConf,
|
||||
Logger: testLogger,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
testutil.RequireSend(t, inCh, tc.in, testTimeout)
|
||||
|
||||
assertValidResponse6(t, req, outCh, tc.wantOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_ServeEther6_solicitRapidCommit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
in gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
name string
|
||||
wantOpts layers.DHCPv6Options
|
||||
}{{
|
||||
in: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "new_rapid_commit",
|
||||
name: "new",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
|
|
@ -126,15 +136,9 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
layers.NewDHCPv6Option(layers.DHCPv6OptRapidCommit, []byte{}),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6SOLICIT(t, testHWStatic, testIPv6Static, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Static,
|
||||
Expiry: time.Time{},
|
||||
Hostname: "static6",
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
name: "existing_rapid_commit",
|
||||
in: newDHCPv6SOLICIT(t, testHWStatic, testIPv6Static, true),
|
||||
want: testLease6Static,
|
||||
name: "existing",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWStatic),
|
||||
|
|
@ -144,15 +148,9 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
layers.NewDHCPv6Option(layers.DHCPv6OptRapidCommit, []byte{}),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6SOLICIT(t, testHWDynamic, testIPv6Dynamic, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Dynamic,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: "dynamic6",
|
||||
HWAddr: testHWDynamic,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "existing_dynamic_rapid_commit",
|
||||
in: newDHCPv6SOLICIT(t, testHWDynamic, testIPv6Dynamic, true),
|
||||
want: testLease6Dynamic,
|
||||
name: "existing_dynamic",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWDynamic),
|
||||
|
|
@ -165,12 +163,12 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
in: newDHCPv6SOLICIT(t, testHWExpired, testIPv6Expired, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Expired,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Hostname: "expired6",
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: testLease6HostnameExpired,
|
||||
HWAddr: testHWExpired,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "existing_expired_rapid_commit",
|
||||
name: "existing_expired",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWExpired),
|
||||
|
|
@ -184,16 +182,17 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if tc.want != nil {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
|
@ -216,8 +215,6 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
|||
|
||||
// TODO(e.burkov): Add tests for REQUEST causing errors. This would require a
|
||||
// custom implementation of the address checker at least.
|
||||
//
|
||||
// TODO(e.burkov): Increase maintainability index.
|
||||
func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
@ -225,16 +222,14 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
|||
|
||||
testCases := []struct {
|
||||
in gopacket.Packet
|
||||
solicit gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
name string
|
||||
wantOpts layers.DHCPv6Options
|
||||
}{{
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
solicit: nil,
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
|
|
@ -248,10 +243,9 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
|||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, notOnLinkAddr),
|
||||
solicit: nil,
|
||||
want: nil,
|
||||
name: "not_on_link",
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, notOnLinkAddr),
|
||||
want: nil,
|
||||
name: "not_on_link",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
|
|
@ -260,15 +254,8 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
|||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWStatic, testIPv6Static),
|
||||
solicit: nil,
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Static,
|
||||
Expiry: time.Time{},
|
||||
Hostname: "static6",
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
in: newDHCPv6REQUEST(t, testHWStatic, testIPv6Static),
|
||||
want: testLease6Static,
|
||||
name: "existing_static",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
|
|
@ -278,62 +265,25 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
|||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, netip.Addr{}),
|
||||
solicit: nil,
|
||||
want: nil,
|
||||
name: "no_iana",
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, netip.Addr{}),
|
||||
want: nil,
|
||||
name: "no_iana",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
solicit: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, false),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "preceding_solicit",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptIANA(t, testIAID, testIPv6Conf.RangeStart),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
solicit: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testClock.Now().Add(testLeaseTTL),
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "preceding_solicit_rapid_commit",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptIANA(t, testIAID, testIPv6Conf.RangeStart),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
db := newTestDatabase(t)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
|
|
@ -353,13 +303,92 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
|||
Enabled: true,
|
||||
})
|
||||
|
||||
if tc.solicit != nil {
|
||||
testutil.RequireSend(t, inCh, tc.solicit, testTimeout)
|
||||
testutil.RequireSend(t, inCh, tc.in, testTimeout)
|
||||
|
||||
_, ok := testutil.RequireReceive(t, outCh, testTimeout)
|
||||
require.True(t, ok)
|
||||
assertValidResponse6(t, req, outCh, tc.wantOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_ServeEther6_requestWithSolicit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
in gopacket.Packet
|
||||
solicit gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
name string
|
||||
wantOpts layers.DHCPv6Options
|
||||
}{{
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
solicit: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, false),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "preceding_solicit",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptIANA(t, testIAID, testIPv6Conf.RangeStart),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6REQUEST(t, testHWUnknown, testIPv6Unknown),
|
||||
solicit: newDHCPv6SOLICIT(t, testHWUnknown, testIPv6Unknown, true),
|
||||
want: &dhcpsvc.Lease{
|
||||
IP: testIPv6Conf.RangeStart,
|
||||
Expiry: testExpiryDynamicLease,
|
||||
Hostname: aghnet.GenerateHostname(testIPv6Conf.RangeStart),
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: false,
|
||||
},
|
||||
name: "preceding_solicit_rapid_commit",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptIANA(t, testIAID, testIPv6Conf.RangeStart),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if tc.want != nil {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV6)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Interfaces: testIPv6InterfacesConf,
|
||||
Logger: testLogger,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
testutil.RequireSend(t, inCh, tc.solicit, testTimeout)
|
||||
|
||||
_, ok := testutil.RequireReceive(t, outCh, testTimeout)
|
||||
require.True(t, ok)
|
||||
|
||||
testutil.RequireSend(t, inCh, tc.in, testTimeout)
|
||||
|
||||
assertValidResponse6(t, req, outCh, tc.wantOpts)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -62,7 +63,7 @@ func (jl *jsonLease) compareNames(other *jsonLease) (res int) {
|
|||
return strings.Compare(jl.Hostname, other.Hostname)
|
||||
}
|
||||
|
||||
// toJSONLease converts *Lease to *jsonLease.
|
||||
// toJSONLease converts *Lease to *jsonLease. l must not be nil.
|
||||
func toJSONLease(l *Lease) (jl *jsonLease) {
|
||||
var expiryStr string
|
||||
if !l.IsStatic {
|
||||
|
|
@ -208,7 +209,12 @@ func (db *JSONDatabase) Store(ctx context.Context, leases []*Lease) (err error)
|
|||
dl.Leases = slices.Insert(dl.Leases, i, lease)
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(dl)
|
||||
// TODO(e.burkov): Consider pooling buffers.
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
err = enc.Encode(dl)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
|
|
@ -217,7 +223,7 @@ func (db *JSONDatabase) Store(ctx context.Context, leases []*Lease) (err error)
|
|||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
err = maybe.WriteFile(db.filePath, buf, jsonDatabasePerm)
|
||||
err = maybe.WriteFile(db.filePath, buf.Bytes(), jsonDatabasePerm)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
|
|
|
|||
130
internal/dhcpsvc/jsondatabase_test.go
Normal file
130
internal/dhcpsvc/jsondatabase_test.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// dbFilePath returns the path to the database file for the given test.
|
||||
func dbFilePath(tb testing.TB) (p string) {
|
||||
return filepath.Join("testdata", tb.Name()+".json")
|
||||
}
|
||||
|
||||
func TestJSONDatabase_Load(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErrMsg string
|
||||
want []*dhcpsvc.Lease
|
||||
}{{
|
||||
name: "success",
|
||||
wantErrMsg: "",
|
||||
want: testLeases,
|
||||
}, {
|
||||
name: "no_file",
|
||||
wantErrMsg: "",
|
||||
want: nil,
|
||||
}, {
|
||||
name: "empty",
|
||||
wantErrMsg: "",
|
||||
want: nil,
|
||||
}, {
|
||||
want: nil,
|
||||
name: "bad_format",
|
||||
wantErrMsg: "loading db: decoding db: json: cannot unmarshal array " +
|
||||
"into Go value of type dhcpsvc.jsonLeasesData",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := dhcpsvc.NewJSONDatabase(&dhcpsvc.JSONDatabaseConfig{
|
||||
Logger: testLogger,
|
||||
FilePath: dbFilePath(t),
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
leases, err := db.Load(ctx)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
// Use unordered comparison since the order of leases is undefined.
|
||||
assert.ElementsMatch(t, tc.want, leases)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONDatabase_Store(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const dbFileName = "leases.json"
|
||||
|
||||
dirToRemove := t.TempDir()
|
||||
require.NoError(t, os.RemoveAll(dirToRemove))
|
||||
|
||||
testCases := []struct {
|
||||
wantErr error
|
||||
name string
|
||||
path string
|
||||
in []*dhcpsvc.Lease
|
||||
}{{
|
||||
wantErr: nil,
|
||||
name: "success",
|
||||
path: filepath.Join(t.TempDir(), dbFileName),
|
||||
in: testLeases,
|
||||
}, {
|
||||
wantErr: nil,
|
||||
name: "nil",
|
||||
path: filepath.Join(t.TempDir(), dbFileName),
|
||||
in: nil,
|
||||
}, {
|
||||
wantErr: nil,
|
||||
name: "empty",
|
||||
path: filepath.Join(t.TempDir(), dbFileName),
|
||||
in: []*dhcpsvc.Lease{},
|
||||
}, {
|
||||
wantErr: fs.ErrNotExist,
|
||||
name: "bad_dir",
|
||||
path: filepath.Join(dirToRemove, dbFileName),
|
||||
in: testLeases,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
db := dhcpsvc.NewJSONDatabase(&dhcpsvc.JSONDatabaseConfig{
|
||||
Logger: testLogger,
|
||||
FilePath: tc.path,
|
||||
})
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
err := db.Store(ctx, tc.in)
|
||||
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
if tc.wantErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.FileExists(t, tc.path)
|
||||
|
||||
content, err := os.ReadFile(tc.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
wantContent, err := os.ReadFile(dbFilePath(t))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, wantContent, content)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ func (idx *leaseIndex) addDBLeases(
|
|||
continue
|
||||
}
|
||||
|
||||
err = idx.add(l, iface)
|
||||
err = idx.add(l.Clone(), iface)
|
||||
if err != nil {
|
||||
logger.WarnContext(ctx, "adding lease", "idx", i, slogutil.KeyError, err)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,12 @@ package dhcpsvc_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -19,171 +16,135 @@ import (
|
|||
func TestDHCPServer_AddLease(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// TODO(e.burkov): Use a mock.
|
||||
leasesPath := filepath.Join(t.TempDir(), "leases.json")
|
||||
db := dhcpsvc.NewJSONDatabase(&dhcpsvc.JSONDatabaseConfig{
|
||||
Logger: testLogger,
|
||||
FilePath: leasesPath,
|
||||
})
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
existHost = "host1"
|
||||
newHost = "host2"
|
||||
ipv6Host = "host3"
|
||||
)
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
existIP = netip.MustParseAddr("192.0.2.2")
|
||||
newIP = netip.MustParseAddr("192.0.2.3")
|
||||
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
||||
|
||||
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
||||
newMAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
||||
ipv6MAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
||||
)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
require.NoError(t, srv.AddLease(ctx, &dhcpsvc.Lease{
|
||||
Hostname: existHost,
|
||||
IP: existIP,
|
||||
HWAddr: existMAC,
|
||||
IsStatic: true,
|
||||
}))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
want *dhcpsvc.Lease
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "outside_range",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: newHost,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
IP: netip.MustParseAddr("1.2.3.4"),
|
||||
HWAddr: newMAC,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "adding lease: no interface for ip 1.2.3.4",
|
||||
}, {
|
||||
name: "duplicate_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: newHost,
|
||||
IP: existIP,
|
||||
HWAddr: newMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for ip " + existIP.String() +
|
||||
wantErrMsg: "adding lease: lease for ip " + testIPv4Static.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: existHost,
|
||||
IP: newIP,
|
||||
HWAddr: newMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IP: testIPv4Unknown,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for hostname " + existHost +
|
||||
wantErrMsg: "adding lease: lease for hostname " + testLease4HostnameStatic +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname_case",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: strings.ToUpper(existHost),
|
||||
IP: newIP,
|
||||
HWAddr: newMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: strings.ToUpper(testLease4HostnameStatic),
|
||||
IP: testIPv4Unknown,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for hostname " +
|
||||
strings.ToUpper(existHost) + " already exists",
|
||||
strings.ToUpper(testLease4HostnameStatic) + " already exists",
|
||||
}, {
|
||||
name: "duplicate_mac",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: newHost,
|
||||
IP: newIP,
|
||||
HWAddr: existMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
IP: testIPv4Unknown,
|
||||
HWAddr: testHWStatic,
|
||||
},
|
||||
wantErrMsg: "adding lease: lease for mac " + existMAC.String() +
|
||||
wantErrMsg: "adding lease: lease for mac " + testHWStatic.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: newHost,
|
||||
IP: newIP,
|
||||
HWAddr: newMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
IP: testIPv4Unknown,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: ipv6Host,
|
||||
IP: newIPv6,
|
||||
HWAddr: ipv6MAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease6HostnameUnknown,
|
||||
IP: testIPv6Unknown,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.AddLease(ctx, tc.lease))
|
||||
t.Parallel()
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
|
||||
err := srv.AddLease(ctx, tc.want)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, srv.Leases())
|
||||
}
|
||||
|
||||
func TestDHCPServer_index(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// TODO(e.burkov): Use a mock.
|
||||
db := newTestDatabase(t)
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
host4 = "host4"
|
||||
host5 = "host5"
|
||||
)
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
ip1 = netip.MustParseAddr("192.0.2.2")
|
||||
ip2 = netip.MustParseAddr("192.0.2.3")
|
||||
ip3 = netip.MustParseAddr("198.51.100.3")
|
||||
ip4 = netip.MustParseAddr("198.51.100.4")
|
||||
|
||||
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
||||
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
||||
mac3 = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
||||
)
|
||||
|
||||
t.Run("ip_idx", func(t *testing.T) {
|
||||
assert.Equal(t, ip1, srv.IPByHost(host1))
|
||||
assert.Equal(t, ip2, srv.IPByHost(host2))
|
||||
assert.Equal(t, ip3, srv.IPByHost(host3))
|
||||
assert.Equal(t, ip4, srv.IPByHost(host4))
|
||||
assert.Zero(t, srv.IPByHost(host5))
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, testIPv4Static, srv.IPByHost(testLease4HostnameStatic))
|
||||
assert.Equal(t, testIPv4Dynamic, srv.IPByHost(testLease4HostnameDynamic))
|
||||
// TODO(e.burkov): Consider treating expired leases as non-existent.
|
||||
assert.Equal(t, testIPv4Expired, srv.IPByHost(testLease4HostnameExpired))
|
||||
assert.Zero(t, srv.IPByHost(testLease4HostnameUnknown))
|
||||
})
|
||||
|
||||
t.Run("name_idx", func(t *testing.T) {
|
||||
assert.Equal(t, host1, srv.HostByIP(ip1))
|
||||
assert.Equal(t, host2, srv.HostByIP(ip2))
|
||||
assert.Equal(t, host3, srv.HostByIP(ip3))
|
||||
assert.Equal(t, host4, srv.HostByIP(ip4))
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, testLease4HostnameStatic, srv.HostByIP(testIPv4Static))
|
||||
assert.Equal(t, testLease4HostnameDynamic, srv.HostByIP(testIPv4Dynamic))
|
||||
assert.Equal(t, testLease4HostnameExpired, srv.HostByIP(testIPv4Expired))
|
||||
assert.Zero(t, srv.HostByIP(testIPv4Unknown))
|
||||
assert.Zero(t, srv.HostByIP(netip.Addr{}))
|
||||
})
|
||||
|
||||
t.Run("mac_idx", func(t *testing.T) {
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip1))
|
||||
assert.Equal(t, mac2, srv.MACByIP(ip2))
|
||||
assert.Equal(t, mac3, srv.MACByIP(ip3))
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip4))
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, testHWStatic, srv.MACByIP(testIPv4Static))
|
||||
assert.Equal(t, testHWDynamic, srv.MACByIP(testIPv4Dynamic))
|
||||
assert.Equal(t, testHWExpired, srv.MACByIP(testIPv4Expired))
|
||||
assert.Zero(t, srv.MACByIP(testIPv4Unknown))
|
||||
assert.Zero(t, srv.MACByIP(netip.Addr{}))
|
||||
})
|
||||
}
|
||||
|
|
@ -191,39 +152,6 @@ func TestDHCPServer_index(t *testing.T) {
|
|||
func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// TODO(e.burkov): Use a mock.
|
||||
db := newTestDatabase(t)
|
||||
db.onStore = func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
host4 = "host4"
|
||||
host5 = "host5"
|
||||
host6 = "host6"
|
||||
)
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
ip1 = netip.MustParseAddr("192.0.2.2")
|
||||
ip2 = netip.MustParseAddr("192.0.2.3")
|
||||
ip3 = netip.MustParseAddr("192.0.2.4")
|
||||
ip4 = netip.MustParseAddr("2001:db8::2")
|
||||
|
||||
mac1 = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
||||
mac2 = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
||||
mac3 = errors.Must(net.ParseMAC("06:05:04:03:02:02"))
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
|
|
@ -231,67 +159,99 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
|||
}{{
|
||||
name: "outside_range",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: netip.MustParseAddr("1.2.3.4"),
|
||||
HWAddr: mac1,
|
||||
IP: testIPv4Conf.RangeEnd.Next(),
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameStatic,
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "updating static lease: no interface for ip 1.2.3.4",
|
||||
wantErrMsg: "updating static lease: no interface for ip " +
|
||||
testIPv4Conf.RangeEnd.Next().String(),
|
||||
}, {
|
||||
name: "not_found",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac2,
|
||||
IP: testIPv4Unknown,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
HWAddr: testHWUnknown,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "updating static lease: no lease for mac " + mac2.String(),
|
||||
wantErrMsg: "updating static lease: no lease for mac " + testHWUnknown.String(),
|
||||
}, {
|
||||
name: "duplicate_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: ip2,
|
||||
HWAddr: mac1,
|
||||
IP: testIPv4Dynamic,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameStatic,
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for ip " + ip2.String() +
|
||||
wantErrMsg: "updating static lease: lease for ip " + testIPv4Dynamic.String() +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IP: testIPv4Unknown,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameDynamic,
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for hostname " + host2 +
|
||||
wantErrMsg: "updating static lease: lease for hostname " + testLease4HostnameDynamic +
|
||||
" already exists",
|
||||
}, {
|
||||
name: "duplicate_hostname_case",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: strings.ToUpper(host2),
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IP: testIPv4Unknown,
|
||||
Expiry: time.Time{},
|
||||
Hostname: strings.ToUpper(testLease4HostnameDynamic),
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "updating static lease: lease for hostname " +
|
||||
strings.ToUpper(host2) + " already exists",
|
||||
strings.ToUpper(testLease4HostnameDynamic) + " already exists",
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac1,
|
||||
IP: testIPv4Unknown,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease4HostnameStatic,
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host6,
|
||||
IP: ip4,
|
||||
HWAddr: mac3,
|
||||
IP: testIPv6Unknown,
|
||||
Expiry: time.Time{},
|
||||
Hostname: testLease6HostnameUnknown,
|
||||
HWAddr: testHWStatic,
|
||||
IsStatic: true,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// TODO(e.burkov): Make parallel.
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.lease)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.UpdateStaticLease(ctx, tc.lease))
|
||||
})
|
||||
|
|
@ -301,95 +261,85 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
|||
func TestDHCPServer_RemoveLease(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// TODO(e.burkov): Use a mock.
|
||||
db := newTestDatabase(t)
|
||||
db.onStore = func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
)
|
||||
|
||||
// NOTE: Keep in sync with testdata.
|
||||
var (
|
||||
existIP = netip.MustParseAddr("192.0.2.2")
|
||||
newIP = netip.MustParseAddr("192.0.2.3")
|
||||
newIPv6 = netip.MustParseAddr("2001:db8::2")
|
||||
|
||||
existMAC = errors.Must(net.ParseMAC("01:02:03:04:05:06"))
|
||||
newMAC = errors.Must(net.ParseMAC("02:03:04:05:06:07"))
|
||||
ipv6MAC = errors.Must(net.ParseMAC("06:05:04:03:02:01"))
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
lease *dhcpsvc.Lease
|
||||
want *dhcpsvc.Lease
|
||||
wantErrMsg string
|
||||
}{{
|
||||
name: "not_found_mac",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: existIP,
|
||||
HWAddr: newMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWUnknown,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for mac " + newMAC.String(),
|
||||
wantErrMsg: "removing lease: no lease for mac " + testHWUnknown.String(),
|
||||
}, {
|
||||
name: "not_found_ip",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: newIP,
|
||||
HWAddr: existMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IP: testIPv4Unknown,
|
||||
HWAddr: testHWStatic,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for ip " + newIP.String(),
|
||||
wantErrMsg: "removing lease: no lease for ip " + testIPv4Unknown.String(),
|
||||
}, {
|
||||
name: "not_found_host",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host2,
|
||||
IP: existIP,
|
||||
HWAddr: existMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameUnknown,
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWStatic,
|
||||
},
|
||||
wantErrMsg: "removing lease: no lease for hostname " + host2,
|
||||
wantErrMsg: "removing lease: no lease for hostname " + testLease4HostnameUnknown,
|
||||
}, {
|
||||
name: "valid",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host1,
|
||||
IP: existIP,
|
||||
HWAddr: existMAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease4HostnameStatic,
|
||||
IP: testIPv4Static,
|
||||
HWAddr: testHWStatic,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "valid_v6",
|
||||
lease: &dhcpsvc.Lease{
|
||||
Hostname: host3,
|
||||
IP: newIPv6,
|
||||
HWAddr: ipv6MAC,
|
||||
want: &dhcpsvc.Lease{
|
||||
Hostname: testLease6HostnameStatic,
|
||||
IP: testIPv6Static,
|
||||
HWAddr: testHWStatic,
|
||||
},
|
||||
wantErrMsg: "",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.NotContains(t, leases, tc.want)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, srv.RemoveLease(ctx, tc.lease))
|
||||
|
||||
err := srv.RemoveLease(ctx, tc.want)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
})
|
||||
}
|
||||
|
||||
assert.Empty(t, srv.Leases())
|
||||
}
|
||||
|
||||
func TestDHCPServer_Reset(t *testing.T) {
|
||||
// TODO(e.burkov): Use a mock.
|
||||
db := newTestDatabase(t)
|
||||
db.onStore = func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db.onStore = func(_ context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Empty(t, leases)
|
||||
|
||||
return nil
|
||||
|
|
@ -400,9 +350,7 @@ func TestDHCPServer_Reset(t *testing.T) {
|
|||
Enabled: true,
|
||||
})
|
||||
|
||||
const leasesNum = 4
|
||||
|
||||
require.Len(t, srv.Leases(), leasesNum)
|
||||
require.ElementsMatch(t, srv.Leases(), testLeases)
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
require.NoError(t, srv.Reset(ctx))
|
||||
|
|
@ -411,28 +359,12 @@ func TestDHCPServer_Reset(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServer_Leases(t *testing.T) {
|
||||
// TODO(e.burkov): Use a mock.
|
||||
db := newTestDatabase(t)
|
||||
t.Parallel()
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: db,
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
expiry, err := time.Parse(time.RFC3339, "2042-01-02T03:04:05Z")
|
||||
require.NoError(t, err)
|
||||
|
||||
wantLeases := []*dhcpsvc.Lease{{
|
||||
Expiry: expiry,
|
||||
IP: netip.MustParseAddr("192.0.2.3"),
|
||||
Hostname: "example.host",
|
||||
HWAddr: errors.Must(net.ParseMAC("AA:AA:AA:AA:AA:AA")),
|
||||
IsStatic: false,
|
||||
}, {
|
||||
Expiry: time.Time{},
|
||||
IP: netip.MustParseAddr("192.0.2.4"),
|
||||
Hostname: "example.static.host",
|
||||
HWAddr: errors.Must(net.ParseMAC("BB:BB:BB:BB:BB:BB")),
|
||||
IsStatic: true,
|
||||
}}
|
||||
assert.ElementsMatch(t, wantLeases, srv.Leases())
|
||||
assert.ElementsMatch(t, testLeases, srv.Leases())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.2",
|
||||
"hostname": "host1",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::2",
|
||||
"hostname": "host3",
|
||||
"mac": "06:05:04:03:02:01",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.2",
|
||||
"hostname": "host1",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.3",
|
||||
"hostname": "host2",
|
||||
"mac": "06:05:04:03:02:01",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::2",
|
||||
"hostname": "host3",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::3",
|
||||
"hostname": "host4",
|
||||
"mac": "06:05:04:03:02:02",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "mismatch",
|
||||
"mac": "04:05:06:07:08:09",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T01:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T01:01:00Z",
|
||||
"ip": "192.0.2.100",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "mismatch",
|
||||
"mac": "04:05:06:07:08:09",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T01:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T01:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T01:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "2001:db8::66",
|
||||
"hostname": "dynamic6",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T01:01:00Z",
|
||||
"ip": "2001:db8::67",
|
||||
"hostname": "expired6",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::65",
|
||||
"hostname": "static6",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-01T10:01:01Z",
|
||||
"ip": "2001:db8::66",
|
||||
"hostname": "dynamic6",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T01:01:00Z",
|
||||
"ip": "2001:db8::67",
|
||||
"hostname": "expired6",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::65",
|
||||
"hostname": "static6",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.2",
|
||||
"hostname": "host1",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.3",
|
||||
"hostname": "host2",
|
||||
"mac": "01:02:03:04:05:07",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::2",
|
||||
"hostname": "host4",
|
||||
"mac": "06:05:04:03:02:02",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.2",
|
||||
"hostname": "host1",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.3",
|
||||
"hostname": "host2",
|
||||
"mac": "06:05:04:03:02:01",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "198.51.100.3",
|
||||
"hostname": "host3",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "198.51.100.4",
|
||||
"hostname": "host4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
1
internal/dhcpsvc/testdata/TestJSONDatabase_Load/bad_format.json
vendored
Normal file
1
internal/dhcpsvc/testdata/TestJSONDatabase_Load/bad_format.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
4
internal/dhcpsvc/testdata/TestJSONDatabase_Load/empty.json
vendored
Normal file
4
internal/dhcpsvc/testdata/TestJSONDatabase_Load/empty.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leases": [],
|
||||
"version": 1
|
||||
}
|
||||
47
internal/dhcpsvc/testdata/TestJSONDatabase_Load/success.json
vendored
Normal file
47
internal/dhcpsvc/testdata/TestJSONDatabase_Load/success.json
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-02T01:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T00:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::65",
|
||||
"hostname": "static6",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-02T01:01:01Z",
|
||||
"ip": "2001:db8::66",
|
||||
"hostname": "dynamic6",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T00:01:01Z",
|
||||
"ip": "2001:db8::67",
|
||||
"hostname": "expired6",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
4
internal/dhcpsvc/testdata/TestJSONDatabase_Store/empty.json
vendored
Normal file
4
internal/dhcpsvc/testdata/TestJSONDatabase_Store/empty.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leases": [],
|
||||
"version": 1
|
||||
}
|
||||
4
internal/dhcpsvc/testdata/TestJSONDatabase_Store/nil.json
vendored
Normal file
4
internal/dhcpsvc/testdata/TestJSONDatabase_Store/nil.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leases": [],
|
||||
"version": 1
|
||||
}
|
||||
47
internal/dhcpsvc/testdata/TestJSONDatabase_Store/success.json
vendored
Normal file
47
internal/dhcpsvc/testdata/TestJSONDatabase_Store/success.json
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2025-01-02T01:01:01Z",
|
||||
"ip": "192.0.2.102",
|
||||
"hostname": "dynamic4",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-02T01:01:01Z",
|
||||
"ip": "2001:db8::66",
|
||||
"hostname": "dynamic6",
|
||||
"mac": "02:03:04:05:06:07",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T00:01:01Z",
|
||||
"ip": "192.0.2.103",
|
||||
"hostname": "expired4",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "2025-01-01T00:01:01Z",
|
||||
"ip": "2001:db8::67",
|
||||
"hostname": "expired6",
|
||||
"mac": "03:04:05:06:07:08",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "192.0.2.101",
|
||||
"hostname": "static4",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"expires": "",
|
||||
"ip": "2001:db8::65",
|
||||
"hostname": "static6",
|
||||
"mac": "01:02:03:04:05:06",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"leases": [
|
||||
{
|
||||
"expires": "2042-01-02T03:04:05Z",
|
||||
"ip": "192.0.2.3",
|
||||
"hostname": "example.host",
|
||||
"mac": "AA:AA:AA:AA:AA:AA",
|
||||
"static": false
|
||||
},
|
||||
{
|
||||
"ip": "192.0.2.4",
|
||||
"hostname": "example.static.host",
|
||||
"mac": "BB:BB:BB:BB:BB:BB",
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
|
@ -11,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/AdguardTeam/golibs/validate"
|
||||
"github.com/gopacket/gopacket/layers"
|
||||
|
|
@ -226,7 +224,7 @@ func (srv *DHCPServer) newDHCPInterfaceV4(
|
|||
// updateLease updates lease in the database. lease must be valid and not
|
||||
// expired.
|
||||
//
|
||||
// TODO(e.burkov): Consider simplifying this wrapping.
|
||||
// TODO(e.burkov): Consider simplifying the wrapping.
|
||||
func (iface *dhcpInterfaceV4) updateLease(ctx context.Context, lease *Lease) (err error) {
|
||||
return iface.common.index.update(ctx, lease, iface.common)
|
||||
}
|
||||
|
|
@ -343,7 +341,7 @@ type dhcpInterfacesV4 []*dhcpInterfaceV4
|
|||
// returns false if there is no such interface. ip must be valid.
|
||||
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
return iface.common.addrSpace.contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
|
|
@ -352,31 +350,6 @@ func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok boo
|
|||
return ifaces[i].common, true
|
||||
}
|
||||
|
||||
// updateAndRespond updates the lease and sends a DHCPACK or DHCPNAK response to
|
||||
// the client according to the update result. idOpt is an expected to be the
|
||||
// value of the DHCP option Client Identifier, nil if not present. req must be
|
||||
// a DHCPREQUEST message, lease, and l must not be nil, fd must be valid.
|
||||
func (iface *dhcpInterfaceV4) updateAndRespond(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
req *layers.DHCPv4,
|
||||
lease *Lease,
|
||||
fd *frameData4,
|
||||
idOpt []byte,
|
||||
) {
|
||||
lease.Hostname = cmp.Or(hostname4(req), lease.Hostname)
|
||||
|
||||
err := iface.updateLease(ctx, lease)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "init-reboot request failed", slogutil.KeyError, err)
|
||||
iface.respondNAK(ctx, req, fd, idOpt)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.respondACK(ctx, req, fd, lease, idOpt)
|
||||
}
|
||||
|
||||
// FlagsBroadcast is the DHCPv4 message flags field with the broadcast bit set.
|
||||
const FlagsBroadcast uint16 = 1 << 15
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue