Pull request 2584: 4923-gopacket-dhcp-vol.20

Updates #4923.

Squashed commit of the following:

commit 6efeea0845921a6ea20c43d5dd937294c271f800
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Feb 13 16:09:10 2026 +0300

    dhcpsvc: imp code

commit 50486ed5dbe3ad28a302ab382e5ad3eef4d9712e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Feb 12 17:56:34 2026 +0300

    dhcpsvc: close devices
This commit is contained in:
Eugene Burkov 2026-02-13 13:36:18 +00:00
parent 6abc092d6b
commit 81dcf43f2b
3 changed files with 62 additions and 9 deletions

View file

@ -2,6 +2,7 @@ package dhcpsvc
import (
"context"
"io"
"net/netip"
"github.com/AdguardTeam/golibs/errors"
@ -28,10 +29,10 @@ func (conf *NetworkDeviceConfig) Validate() (err error) {
}
// NetworkDeviceManager creates and manages network devices.
//
// TODO(e.burkov): Add device closing method.
type NetworkDeviceManager interface {
// Open opens a network device. conf must be valid.
//
// An attempt to open the same device multiple times may return an error.
Open(ctx context.Context, conf *NetworkDeviceConfig) (dev NetworkDevice, err error)
}
@ -59,6 +60,9 @@ func (EmptyNetworkDeviceManager) Open(
type NetworkDevice interface {
gopacket.PacketDataSource
// No methods of a device should be called after Close.
io.Closer
// Addresses returns all IP addresses assigned to the device.
Addresses() (ips []netip.Addr)
@ -82,6 +86,12 @@ func (EmptyNetworkDevice) ReadPacketData() (data []byte, ci gopacket.CaptureInfo
return nil, gopacket.CaptureInfo{}, nil
}
// Close implements the [io.Closer] interface for [EmptyNetworkDevice]. It
// always returns nil.
func (EmptyNetworkDevice) Close() (err error) {
return nil
}
// Addresses implements the [NetworkDevice] interface for [EmptyNetworkDevice].
// It always returns nil.
func (EmptyNetworkDevice) Addresses() (ips []netip.Addr) {

View file

@ -2,7 +2,9 @@ package dhcpsvc_test
import (
"context"
"io"
"net/netip"
"sync/atomic"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
@ -41,6 +43,7 @@ func (ndm *testNetworkDeviceManager) Open(
// TODO(e.burkov): Move to aghtest.
type testNetworkDevice struct {
onReadPacketData func() (data []byte, ci gopacket.CaptureInfo, err error)
onClose func() (err error)
onAddresses func() (ips []netip.Addr)
onLinkType func() (lt layers.LinkType)
onWritePacketData func(data []byte) (err error)
@ -55,6 +58,11 @@ func (nd *testNetworkDevice) ReadPacketData() (data []byte, ci gopacket.CaptureI
return nd.onReadPacketData()
}
// Close implements the [io.Closer] interface for *testNetworkDevice.
func (nd *testNetworkDevice) Close() (err error) {
return nd.onClose()
}
// Addresses implements the [dhcpsvc.NetworkDevice] interface for
// *testNetworkDevice.
func (nd *testNetworkDevice) Addresses() (ips []netip.Addr) {
@ -87,13 +95,19 @@ func newTestNetworkDeviceManager(
inCh = make(chan gopacket.Packet)
outCh = make(chan []byte)
isOpened := atomic.Bool{}
pt := testutil.PanicT{}
addrs := []netip.Addr{addr}
dev := &testNetworkDevice{
onReadPacketData: func() (data []byte, ci gopacket.CaptureInfo, err error) {
pkt, ok := testutil.RequireReceive(pt, inCh, testTimeout)
require.True(pt, ok)
require.Equal(pt, isOpened.Load(), ok)
if !ok {
return nil, gopacket.CaptureInfo{}, io.EOF
}
data = pkt.Data()
ci = gopacket.CaptureInfo{
@ -103,6 +117,12 @@ func newTestNetworkDeviceManager(
return data, ci, nil
},
onClose: func() (err error) {
isOpened.Store(false)
close(inCh)
return nil
},
onAddresses: func() (ips []netip.Addr) {
return addrs
},
@ -121,6 +141,7 @@ func newTestNetworkDeviceManager(
_ context.Context,
conf *dhcpsvc.NetworkDeviceConfig,
) (nd dhcpsvc.NetworkDevice, err error) {
isOpened.Store(true)
require.Equal(pt, deviceName, conf.Name)
return dev, nil

View file

@ -12,6 +12,7 @@ import (
"sync/atomic"
"time"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
)
@ -30,6 +31,12 @@ type DHCPServer struct {
// deviceManager is the manager of network devices.
deviceManager NetworkDeviceManager
// devices are the network devices opened in [DHCPServer.Start], mapped to
// their names. Those are closed in [DHCPServer.Shutdown].
//
// TODO(e.burkov): Consider storing those within interfaces.
devices container.KeyValues[string, NetworkDevice]
// localTLD is the top-level domain name to use for resolving DHCP clients'
// hostnames.
localTLD string
@ -137,9 +144,11 @@ func (srv *DHCPServer) Start(ctx context.Context) (err error) {
var errs []error
for _, iface := range srv.interfaces4 {
var nd NetworkDevice
nd, err = srv.deviceManager.Open(ctx, &NetworkDeviceConfig{
Name: iface.common.name,
netDevName := iface.common.name
var netDev NetworkDevice
netDev, err = srv.deviceManager.Open(ctx, &NetworkDeviceConfig{
Name: netDevName,
})
if err != nil {
errs = append(errs, err)
@ -147,7 +156,12 @@ func (srv *DHCPServer) Start(ctx context.Context) (err error) {
continue
}
go srv.serveEther4(context.WithoutCancel(ctx), iface, nd)
srv.devices = append(srv.devices, container.KeyValue[string, NetworkDevice]{
Key: netDevName,
Value: netDev,
})
go srv.serveEther4(context.WithoutCancel(ctx), iface, netDev)
}
// TODO(e.burkov): Serve EthernetTypeIPv6.
@ -159,9 +173,17 @@ func (srv *DHCPServer) Start(ctx context.Context) (err error) {
func (srv *DHCPServer) Shutdown(ctx context.Context) (err error) {
srv.logger.DebugContext(ctx, "shutting down dhcp server")
// TODO(e.burkov): Close the packet source.
var errs []error
for _, kv := range srv.devices {
netDevName, netDev := kv.Key, kv.Value
return nil
err = netDev.Close()
if err != nil {
errs = append(errs, fmt.Errorf("closing device %q: %w", netDevName, err))
}
}
return errors.Join(errs...)
}
// Enabled implements the [Interface] interface for *DHCPServer.