mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-10-26 21:16:46 +09:00
Pull request 2741: AGDNS-3863-gopacket-dhcp-vol.34
Updates #4923.
Squashed commit of the following:
commit ac803e041ccc2d635031479a143fc1afd93921eb
Merge: 7c4263837 b0e930c6b
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 4 19:35:57 2026 +0300
Merge branch 'master' into AGDNS-3863-gopacket-dhcp-vol.34
commit 7c4263837388c51bdf33acabbca7ab0d7ae7d8ac
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 4 19:09:44 2026 +0300
dhcpsvc: don't consider zero iaid invalid
commit d883e64b91089dd0a2369e6e537b89c984c0d106
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 4 17:35:56 2026 +0300
dhcpsvc: imp maint
commit 947a9b34f9f0e07c6a895f55163757a85eb684b6
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Aug 4 15:35:14 2026 +0300
dhcpsvc: impl dhcpv6 release
This commit is contained in:
1 parent
b0e930c6bc
commit
369d476163
7 files changed
+249
-53
No files matched your search
@@ -393,27 +393,19 @@ func (db *testDatabase) Store(ctx context.Context, leases []*dhcpsvc.Lease) (err
|
||||
return db.onStore(ctx, leases)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// newTestDatabase creates a new *testDatabase for testing. The Load method
|
||||
// returns [testLeases], and the Store method panics.
|
||||
func newTestDatabase(tb testing.TB) (db *testDatabase) {
|
||||
tb.Helper()
|
||||
|
||||
db = &testDatabase{
|
||||
onLoad: func(ctx context.Context) (_ []*dhcpsvc.Lease, _ error) {
|
||||
panic(testutil.UnexpectedCall(ctx))
|
||||
return &testDatabase{
|
||||
onLoad: func(_ context.Context) (leases []*dhcpsvc.Lease, err error) {
|
||||
return testLeases, nil
|
||||
},
|
||||
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
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestDHCPServer_ServeEther4_discover(t *testing.T) {
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Database: newTestDatabase(t),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
@@ -105,7 +105,7 @@ func TestDHCPServer_ServeEther4_discoverExpired(t *testing.T) {
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Database: newTestDatabase(t),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
@@ -156,7 +156,7 @@ func TestDHCPServer_ServeEther4_release(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.NotContains(testutil.NewPanicT(t), leases, tc.want)
|
||||
@@ -262,7 +262,7 @@ func TestDHCPServer_ServeEther4_requestSelecting(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -311,7 +311,7 @@ func TestDHCPServer_ServeEther4_requestSelectingNoLease(t *testing.T) {
|
||||
flags: dhcpsvc.FlagsBroadcast,
|
||||
})
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV4)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
@@ -413,7 +413,7 @@ func TestDHCPServer_ServeEther4_requestInitReboot(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -513,7 +513,7 @@ func TestDHCPServer_ServeEther4_requestRenewSuccess(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -586,7 +586,7 @@ func TestDHCPServer_ServeEther4_requestRenewFail(t *testing.T) {
|
||||
}
|
||||
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Database: newTestDatabase(t),
|
||||
Interfaces: testIPv4InterfacesConf,
|
||||
NetworkDeviceManager: ndMgr,
|
||||
Enabled: true,
|
||||
@@ -641,7 +641,7 @@ func TestDHCPServer_ServeEther4_decline(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, wantLease)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
@@ -377,7 +378,8 @@ func (iface *dhcpInterfaceV6) handleInfo(
|
||||
// handleRelease handles messages of type RELEASE. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type RELEASE. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
// TODO(e.burkov): Verify all IA_NA options instead of handling only the first
|
||||
// one.
|
||||
func (iface *dhcpInterfaceV6) handleRelease(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
@@ -391,7 +393,43 @@ func (iface *dhcpInterfaceV6) handleRelease(
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
resp := &layers.DHCPv6{
|
||||
MsgType: layers.DHCPv6MsgTypeReply,
|
||||
TransactionID: req.TransactionID,
|
||||
}
|
||||
|
||||
iaid, ip := iface.ipForRelease(ctx, req)
|
||||
if ip == (netip.Addr{}) {
|
||||
resp.Options = iface.newUpdateRespOpts(fd, req, cliID, layers.DHCPv6Option{})
|
||||
|
||||
return respond6(fd, resp)
|
||||
}
|
||||
|
||||
key := macToKey(fd.ether.SrcMAC)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[key]
|
||||
if !hasLease || lease.IP != ip {
|
||||
respIANA := newIANAWithStatus(iaid, layers.DHCPv6StatusCodeNoBinding)
|
||||
resp.Options = iface.newUpdateRespOpts(fd, req, cliID, respIANA)
|
||||
|
||||
return respond6(fd, resp)
|
||||
}
|
||||
|
||||
err = iface.common.index.remove(ctx, lease, iface.common)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "removing lease", slogutil.KeyError, err)
|
||||
|
||||
respIANA := newIANAWithStatus(iaid, layers.DHCPv6StatusCodeNoBinding)
|
||||
resp.Options = iface.newUpdateRespOpts(fd, req, cliID, respIANA)
|
||||
} else {
|
||||
respIANA := &IANAOption{ID: iaid}
|
||||
resp.Options = iface.newUpdateRespOpts(fd, req, cliID, respIANA.Encode())
|
||||
}
|
||||
|
||||
return respond6(fd, resp)
|
||||
}
|
||||
|
||||
// handleDecline handles messages of type DECLINE. req must not be nil and must
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestDHCPServer_ServeEther6_solicit(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV6)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
@@ -185,7 +185,7 @@ func TestDHCPServer_ServeEther6_solicitRapidCommit(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -280,7 +280,7 @@ func TestDHCPServer_ServeEther6_request(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -361,7 +361,7 @@ func TestDHCPServer_ServeEther6_requestWithSolicit(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -469,7 +469,7 @@ func TestDHCPServer_ServeEther6_confirm(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV6)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
@@ -543,7 +543,7 @@ func TestDHCPServer_ServeEther6_renew(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -630,7 +630,7 @@ func TestDHCPServer_ServeEther6_rebind(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
req := testutil.RequireTypeAssert[*layers.DHCPv6](t, tc.in.Layer(layers.LayerTypeDHCPv6))
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Contains(t, leases, tc.want)
|
||||
@@ -699,10 +699,108 @@ func TestDHCPServer_ServeEther6_info(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)
|
||||
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_release(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
in gopacket.Packet
|
||||
want *dhcpsvc.Lease
|
||||
name string
|
||||
wantOpts layers.DHCPv6Options
|
||||
}{{
|
||||
in: newDHCPv6Release(t, testHWDynamic, testIPv6Dynamic),
|
||||
want: testLease6Dynamic,
|
||||
name: "success",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWDynamic),
|
||||
newOptIANAStatus(t, testIAID, layers.DHCPv6StatusCodeSuccess),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6Release(t, testHWStatic, testIPv6Static),
|
||||
want: testLease6Static,
|
||||
name: "success_static",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWStatic),
|
||||
newOptIANAStatus(t, testIAID, layers.DHCPv6StatusCodeSuccess),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6Release(t, testHWUnknown, testIPv6Unknown),
|
||||
want: nil,
|
||||
name: "no_binding",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWUnknown),
|
||||
newOptIANAStatus(t, testIAID, layers.DHCPv6StatusCodeNoBinding),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6Release(t, testHWDynamic, netip.Addr{}),
|
||||
want: nil,
|
||||
name: "no_iana",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWDynamic),
|
||||
newOptPreference(t, 0),
|
||||
newOptSolMaxRT(t, dhcpsvc.DefaultSolMaxRT),
|
||||
},
|
||||
}, {
|
||||
in: newDHCPv6Release(t, testHWDynamic, testIPv6Unknown),
|
||||
want: nil,
|
||||
name: "ip_mismatch",
|
||||
wantOpts: layers.DHCPv6Options{
|
||||
newOptServerDUID(t, testIfaceHWAddr),
|
||||
newOptClientDUID(t, testHWDynamic),
|
||||
newOptIANAStatus(t, testIAID, layers.DHCPv6StatusCodeNoBinding),
|
||||
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)
|
||||
|
||||
onStore := func(ctx context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.NotContains(t, leases, tc.want)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if tc.want != nil {
|
||||
db.onStore = onStore
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ndMgr, inCh, outCh := newTestNetworkDeviceManager(t, testIfaceAddrV6)
|
||||
startTestDHCPServer(t, &dhcpsvc.Config{
|
||||
@@ -1036,3 +1134,31 @@ func newDHCPv6Renew(tb testing.TB, mac net.HardwareAddr, reqIP netip.Addr) (pkt
|
||||
|
||||
return newTestPacket(tb, layers.LinkTypeEthernet, eth, ip, udp, dhcp)
|
||||
}
|
||||
|
||||
// newDHCPv6Release creates a new DHCPv6 RELEASE packet for testing.
|
||||
func newDHCPv6Release(tb testing.TB, mac net.HardwareAddr, reqIP netip.Addr) (pkt gopacket.Packet) {
|
||||
tb.Helper()
|
||||
|
||||
eth := newEthernetLayer(tb, mac, testIfaceHWAddr, layers.EthernetTypeIPv6)
|
||||
ip, udp := newIPv6UDPLayer(tb, netip.AddrPort{}, netip.AddrPort{})
|
||||
|
||||
dhcp := &layers.DHCPv6{
|
||||
MsgType: layers.DHCPv6MsgTypeRelease,
|
||||
HopCount: 0,
|
||||
// Don't specify link and peer addresses, as they are intended for relay
|
||||
// messages.
|
||||
LinkAddr: nil,
|
||||
PeerAddr: nil,
|
||||
TransactionID: testTransactionID,
|
||||
Options: layers.DHCPv6Options{
|
||||
newOptClientDUID(tb, mac),
|
||||
newOptServerDUID(tb, testIfaceHWAddr),
|
||||
},
|
||||
}
|
||||
|
||||
if reqIP.IsValid() && reqIP.Is6() {
|
||||
dhcp.Options = append(dhcp.Options, newOptIANA(tb, testIAID, reqIP, testLeaseTTL))
|
||||
}
|
||||
|
||||
return newTestPacket(tb, layers.LinkTypeEthernet, eth, ip, udp, dhcp)
|
||||
}
|
||||
@@ -97,11 +97,7 @@ func (idx *leaseIndex) add(l *Lease, iface *netInterface) (err error) {
|
||||
// remove removes l from idx and from iface. l must be valid, iface should
|
||||
// contain the same lease or the lease itself. It returns an error if the lease
|
||||
// not found.
|
||||
func (idx *leaseIndex) remove(
|
||||
ctx context.Context,
|
||||
l *Lease,
|
||||
iface *netInterface,
|
||||
) (err error) {
|
||||
func (idx *leaseIndex) remove(ctx context.Context, l *Lease, iface *netInterface) (err error) {
|
||||
loweredName := strings.ToLower(l.Hostname)
|
||||
|
||||
if _, ok := idx.byAddr[l.IP]; !ok {
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestDHCPServer_AddLease(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func TestDHCPServer_index(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Database: newTestDatabase(t),
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
@@ -244,7 +244,7 @@ func TestDHCPServer_UpdateStaticLease(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
@@ -319,7 +319,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
if tc.wantErrMsg == "" {
|
||||
db.onStore = onStore
|
||||
}
|
||||
@@ -340,7 +340,7 @@ func TestDHCPServer_RemoveLease(t *testing.T) {
|
||||
func TestDHCPServer_Reset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newTestDatabase(t, testLeases)
|
||||
db := newTestDatabase(t)
|
||||
db.onStore = func(_ context.Context, leases []*dhcpsvc.Lease) (err error) {
|
||||
assert.Empty(t, leases)
|
||||
|
||||
@@ -364,7 +364,7 @@ func TestServer_Leases(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := newTestDHCPServer(t, &dhcpsvc.Config{
|
||||
Database: newTestDatabase(t, testLeases),
|
||||
Database: newTestDatabase(t),
|
||||
Enabled: true,
|
||||
})
|
||||
|
||||
|
||||
+53
-9
@@ -403,6 +403,7 @@ func (iface *dhcpInterfaceV6) allocateForSolicit(
|
||||
) (lease *Lease, iaid uint32) {
|
||||
l := iface.common.logger
|
||||
key := macToKey(mac)
|
||||
now := iface.clock.Now()
|
||||
|
||||
for i, reqOpt := range req.Options {
|
||||
if reqOpt.Code != layers.DHCPv6OptIANA {
|
||||
@@ -417,12 +418,14 @@ func (iface *dhcpInterfaceV6) allocateForSolicit(
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Check if the IA actually requests any address.
|
||||
|
||||
var ok bool
|
||||
if lease, ok = iface.common.leases[key]; ok {
|
||||
return lease, iana.ID
|
||||
}
|
||||
|
||||
lease, err = iface.common.allocateLease(ctx, mac, key, iface.clock.Now())
|
||||
lease, err = iface.common.allocateLease(ctx, mac, key, now)
|
||||
if err != nil {
|
||||
l.DebugContext(ctx, "no address available", "iaid", iana.ID, slogutil.KeyError, err)
|
||||
|
||||
@@ -437,6 +440,41 @@ func (iface *dhcpInterfaceV6) allocateForSolicit(
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// ipForRelease returns the IAID and IP address requested for release in req.
|
||||
// It returns zero values if there is no IA_NA option or if all options are
|
||||
// malformed. req must be a valid DHCPv6 message of RELEASE type.
|
||||
func (iface *dhcpInterfaceV6) ipForRelease(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv6,
|
||||
) (iaid uint32, ip netip.Addr) {
|
||||
l := iface.common.logger
|
||||
|
||||
for i, reqOpt := range req.Options {
|
||||
if reqOpt.Code != layers.DHCPv6OptIANA {
|
||||
continue
|
||||
}
|
||||
|
||||
iana := &IANAOption{}
|
||||
err := iana.UnmarshalBinary(reqOpt.Data)
|
||||
if err != nil {
|
||||
l.DebugContext(ctx, "malformed ia_na", "idx", i, slogutil.KeyError, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
reqIP, hasReqIP := iana.requestedAddr()
|
||||
if !hasReqIP {
|
||||
l.DebugContext(ctx, "no ip in ia_na for release", "iaid", iana.ID)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return iana.ID, reqIP
|
||||
}
|
||||
|
||||
return 0, netip.Addr{}
|
||||
}
|
||||
|
||||
// firstIANA returns the first valid IA_NA option in req. It returns false if
|
||||
// there is no such option. req must not be nil.
|
||||
func (iface *dhcpInterfaceV6) firstIANA(
|
||||
@@ -502,10 +540,10 @@ func (iface *dhcpInterfaceV6) confirmAddrsOnLink(
|
||||
}
|
||||
|
||||
// newSolicitRespOpts returns the common option list for Advertise and
|
||||
// rapid-commit Reply responses to a Solicit request. Zero iaid creates an
|
||||
// rapid-commit Reply responses to a Solicit request. Nil lease creates an
|
||||
// option with Status Code NoAddrsAvail. rapidCommit defines whether the
|
||||
// response should include the Rapid Commit option. fd, req, cliID, and lease
|
||||
// must not be nil.
|
||||
// response should include the Rapid Commit option. fd, req, and cliID must not
|
||||
// be nil.
|
||||
func (iface *dhcpInterfaceV6) newSolicitRespOpts(
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
@@ -519,7 +557,7 @@ func (iface *dhcpInterfaceV6) newSolicitRespOpts(
|
||||
|
||||
// For Solicit without IA_NA options, respond with safe Advertise with no
|
||||
// IA_NA options and Status Code NoAddrsAvail.
|
||||
if iaid == 0 {
|
||||
if lease == nil {
|
||||
opts = append(opts, newStatusCodeOption(layers.DHCPv6StatusCodeNoAddrsAvail))
|
||||
} else {
|
||||
opts = append(opts, iface.iaNAFromLease(lease, iaid))
|
||||
@@ -541,7 +579,8 @@ func (iface *dhcpInterfaceV6) newSolicitRespOpts(
|
||||
|
||||
// newRequestRespOpts returns the common option list for Reply responses to a
|
||||
// Request message. fd, req, and cliID must not be nil. iana must be a valid
|
||||
// IA_NA option, or be empty if the response should not contain an IA_NA option.
|
||||
// IA_NA option, or have a zero code if the response should not contain an IA_NA
|
||||
// option.
|
||||
//
|
||||
// TODO(e.burkov): Keep the Reply option set aligned with the current Advertise
|
||||
// response shape until the wider DHCPv6 implementation is completed.
|
||||
@@ -595,11 +634,16 @@ func (iface *dhcpInterfaceV6) newConfirmRespOpts(
|
||||
return iface.appendRequestedOptions(opts, req)
|
||||
}
|
||||
|
||||
// newUpdateRespOpts returns the common option list for Reply responses to a
|
||||
// RENEW or REBIND messages. fd, req, and cliID must not be nil. iana must be
|
||||
// a valid IA_NA option, or empty if the response should not include one.
|
||||
// newUpdateRespOpts returns the common option list for Reply responses to
|
||||
// RENEW, REBIND, and RELEASE messages. fd, req, and cliID must not be nil.
|
||||
// iana must be a valid IA_NA option, or have a zero code if the response should
|
||||
// not include one.
|
||||
//
|
||||
// See RFC 9915 Section 18.3.4.
|
||||
//
|
||||
// TODO(e.burkov): DRY with other options builders
|
||||
//
|
||||
// TODO(e.burkov): Use internal types for options.
|
||||
func (iface *dhcpInterfaceV6) newUpdateRespOpts(
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
|
||||
Reference in new issue
Block a user