mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-08-04 15:28:58 +00:00
Pull request 2650: AGDNS-3863-gopacket-dhcp-vol.24
Updates #4923. Squashed commit of the following: commit 32b3580ed051c4d0b021fd0b141b2e25cd36b584 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue May 5 18:37:03 2026 +0300 dhcpsvc: fix docs commit cce786e4591d94373b6e2bcbeb51ee5ca1fa1764 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue May 5 15:44:53 2026 +0300 dhcpsvc: validate v6 messages
This commit is contained in:
parent
3eb42df776
commit
b60af4bf31
8 changed files with 314 additions and 44 deletions
|
|
@ -108,8 +108,10 @@ func (ic *InterfaceConfig) Validate() (err error) {
|
|||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return errors.Join(
|
||||
errors.Annotate(ic.IPv4.Validate(), "IPv4: %w"),
|
||||
errors.Annotate(ic.IPv6.Validate(), "IPv6: %w"),
|
||||
)
|
||||
errs := []error{
|
||||
errors.Annotate(ic.IPv4.Validate(), "ipv4: %w"),
|
||||
errors.Annotate(ic.IPv6.Validate(), "ipv6: %w"),
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,9 +109,6 @@ const (
|
|||
testAnotherRangeStartV6Str = "2001:db9::1"
|
||||
)
|
||||
|
||||
// testHWIface is the test MAC address of a test network interface.
|
||||
var testHWIface = net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
|
||||
var (
|
||||
// testIPv4Conf is a common valid IPv4 part of the interface configuration
|
||||
// for tests.
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ func newFrameData6(
|
|||
ether: etherLayer,
|
||||
ip: ipLayer,
|
||||
duid: duid,
|
||||
duidData: duid.Encode(),
|
||||
device: dev,
|
||||
localAddr: addr,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
|
|
@ -21,10 +22,6 @@ func (srv *DHCPServer) serveV6(
|
|||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv6: %w") }()
|
||||
|
||||
// TODO(e.burkov): Use the iface and fd parameters.
|
||||
_ = iface
|
||||
_ = fd
|
||||
|
||||
msg, ok := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
|
||||
if !ok {
|
||||
// TODO(e.burkov): Consider adding some debug information about the
|
||||
|
|
@ -36,29 +33,211 @@ func (srv *DHCPServer) serveV6(
|
|||
|
||||
// TODO(e.burkov): Handle duplicate TransactionID.
|
||||
|
||||
return srv.handleDHCPv6(ctx, msg.MsgType, msg)
|
||||
return iface.handleDHCPv6(ctx, msg.MsgType, fd, msg)
|
||||
}
|
||||
|
||||
// handleDHCPv6 handles the DHCPv6 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv6(
|
||||
_ context.Context,
|
||||
func (iface *dhcpInterfaceV6) handleDHCPv6(
|
||||
ctx context.Context,
|
||||
typ layers.DHCPv6MsgType,
|
||||
_ *layers.DHCPv6,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
switch typ {
|
||||
case
|
||||
layers.DHCPv6MsgTypeSolicit,
|
||||
layers.DHCPv6MsgTypeRequest,
|
||||
layers.DHCPv6MsgTypeConfirm,
|
||||
layers.DHCPv6MsgTypeRenew,
|
||||
layers.DHCPv6MsgTypeRebind,
|
||||
layers.DHCPv6MsgTypeInformationRequest,
|
||||
layers.DHCPv6MsgTypeRelease,
|
||||
layers.DHCPv6MsgTypeDecline:
|
||||
// TODO(e.burkov): Handle messages.
|
||||
case layers.DHCPv6MsgTypeSolicit:
|
||||
return iface.handleSolicit(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeRequest:
|
||||
return iface.handleRequest(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeConfirm:
|
||||
return iface.handleConfirm(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeRenew:
|
||||
return iface.handleRenew(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeRebind:
|
||||
return iface.handleRebind(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeInformationRequest:
|
||||
return iface.handleInfo(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeRelease:
|
||||
return iface.handleRelease(ctx, fd, req)
|
||||
case layers.DHCPv6MsgTypeDecline:
|
||||
return iface.handleDecline(ctx, fd, req)
|
||||
default:
|
||||
return fmt.Errorf("dhcpv6: request type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
return fmt.Errorf("dhcpv6: request type: %w: %d", errors.ErrBadEnumValue, typ)
|
||||
}
|
||||
}
|
||||
|
||||
// handleSolicit handles messages of type SOLICIT. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type SOLICIT. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleSolicit(
|
||||
ctx context.Context,
|
||||
_ *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDNoServer(req.Options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleRequest handles messages of type REQUEST. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type REQUEST. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleRequest(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDMatchingServer(req.Options, fd.duidData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConfirm handles messages of type CONFIRM. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type CONFIRM. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleConfirm(
|
||||
ctx context.Context,
|
||||
_ *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDNoServer(req.Options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type RENEW. req must not be nil and must be
|
||||
// a valid DHCPv6 message of type RENEW. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleRenew(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDMatchingServer(req.Options, fd.duidData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleRebind handles messages of type REBIND. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type REBIND. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleRebind(
|
||||
ctx context.Context,
|
||||
_ *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDNoServer(req.Options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleInfo handles messages of type INFORMATION-REQUEST. req must not be nil
|
||||
// and must be a valid DHCPv6 message of type INFORMATION-REQUEST. fd must be
|
||||
// valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleInfo(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
if srvID, ok := findOption6(req.Options, layers.DHCPv6OptServerID); ok {
|
||||
if !bytes.Equal(srvID, fd.duidData) {
|
||||
return fmt.Errorf(
|
||||
"dhcpv6: server id: got %v, want %v: %w",
|
||||
srvID,
|
||||
fd.duidData,
|
||||
errors.ErrNotEqual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := findOption6(req.Options, layers.DHCPv6OptIANA)
|
||||
if ok {
|
||||
return fmt.Errorf("dhcpv6: %s: ia option: %w", req.MsgType, errors.ErrUnexpectedValue)
|
||||
}
|
||||
|
||||
_, ok = findOption6(req.Options, layers.DHCPv6OptIATA)
|
||||
if ok {
|
||||
return fmt.Errorf("dhcpv6: %s: ia option: %w", req.MsgType, errors.ErrUnexpectedValue)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (iface *dhcpInterfaceV6) handleRelease(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDMatchingServer(req.Options, fd.duidData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleDecline handles messages of type DECLINE. req must not be nil and must
|
||||
// be a valid DHCPv6 message of type DECLINE. fd must be valid.
|
||||
//
|
||||
// TODO(e.burkov): Implement. This is a stub for now.
|
||||
func (iface *dhcpInterfaceV6) handleDecline(
|
||||
ctx context.Context,
|
||||
fd *frameData6,
|
||||
req *layers.DHCPv6,
|
||||
) (err error) {
|
||||
cliID, err := clientIDMatchingServer(req.Options, fd.duidData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: %s: %w", req.MsgType, err)
|
||||
}
|
||||
|
||||
l := iface.common.logger
|
||||
l.DebugContext(ctx, "handling message", "type", req.MsgType, "cli_id", cliID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,16 +128,16 @@ func (EmptyNetworkDevice) WritePacketData(_ []byte) (err error) {
|
|||
// frameData4 stores the Ethernet and IPv4 layers of the incoming packet, as
|
||||
// well as the network device that the packet was received from and its address.
|
||||
type frameData4 struct {
|
||||
// device is the network device that the packet was received from. It must
|
||||
// not be nil.
|
||||
device NetworkDevice
|
||||
|
||||
// ether is the Ethernet layer of the incoming packet. It must not be nil.
|
||||
ether *layers.Ethernet
|
||||
|
||||
// ip is the IPv4 layer of the incoming packet. It must not be nil.
|
||||
ip *layers.IPv4
|
||||
|
||||
// device is the network device that the packet was received from. It must
|
||||
// not be nil.
|
||||
device NetworkDevice
|
||||
|
||||
// localAddr is the local IP address that the packet was sent to. It must
|
||||
// be a valid IPv4 address assigned to the device.
|
||||
localAddr netip.Addr
|
||||
|
|
@ -146,16 +146,6 @@ type frameData4 struct {
|
|||
// frameData6 stores the Ethernet and IPv6 layers of the incoming packet, as
|
||||
// well as the network device that the packet was received from and its address.
|
||||
type frameData6 struct {
|
||||
// ether is the Ethernet layer of the incoming packet. It must not be nil.
|
||||
ether *layers.Ethernet
|
||||
|
||||
// ip is the IPv6 layer of the incoming packet. It must not be nil.
|
||||
ip *layers.IPv6
|
||||
|
||||
// duid is the DHCPv6 DUID constructed of the network device hardware
|
||||
// address. It must not be nil.
|
||||
duid *layers.DHCPv6DUID
|
||||
|
||||
// device is the network device that the packet was received from. It must
|
||||
// not be nil.
|
||||
device NetworkDevice
|
||||
|
|
@ -163,4 +153,19 @@ type frameData6 struct {
|
|||
// localAddr is the local IP address that the packet was sent to. It must
|
||||
// be a valid IPv6 address assigned to the device.
|
||||
localAddr netip.Addr
|
||||
|
||||
// duid is the DHCPv6 DUID constructed of the network device hardware
|
||||
// address. It must not be nil.
|
||||
duid *layers.DHCPv6DUID
|
||||
|
||||
// ether is the Ethernet layer of the incoming packet. It must not be nil.
|
||||
ether *layers.Ethernet
|
||||
|
||||
// ip is the IPv6 layer of the incoming packet. It must not be nil.
|
||||
ip *layers.IPv6
|
||||
|
||||
// duidData is the pre-encoded DUID-LL for this server interface. It is
|
||||
// used to match the Server Identifier option in incoming DHCPv6 messages.
|
||||
// It must not be nil.
|
||||
duidData []byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func (nd *testNetworkDevice) LinkType() (lt layers.LinkType) {
|
|||
|
||||
// newTestNetworkDeviceManager creates a network device manager for testing. It
|
||||
// requires that device opened have a deviceName. The device itself has a link
|
||||
// type [layers.LinkTypeEthernet] and a hardware address [testHWIface].
|
||||
// type [layers.LinkTypeEthernet] and a hardware address [testIfaceHWAddr].
|
||||
// Incoming packets are received from inCh and outgoing packets are sent to
|
||||
// outCh.
|
||||
func newTestNetworkDeviceManager(
|
||||
|
|
@ -125,7 +125,7 @@ func newTestNetworkDeviceManager(
|
|||
}
|
||||
|
||||
// newTestNetworkDevice creates a network device for testing. It has a link
|
||||
// type [layers.LinkTypeEthernet] and a hardware address [testHWIface].
|
||||
// type [layers.LinkTypeEthernet] and a hardware address [testIfaceHWAddr].
|
||||
// Incoming packets are received from inCh and outgoing packets are sent to
|
||||
// outCh.
|
||||
func newTestNetworkDevice(
|
||||
|
|
@ -169,7 +169,7 @@ func newTestNetworkDevice(
|
|||
}
|
||||
|
||||
onHardwareAddr := func() (hw net.HardwareAddr) {
|
||||
return testHWIface
|
||||
return testIfaceHWAddr
|
||||
}
|
||||
|
||||
onLinkType := func() (lt layers.LinkType) {
|
||||
|
|
|
|||
|
|
@ -208,3 +208,27 @@ func newServerDUID(mac net.HardwareAddr) (duid *layers.DHCPv6DUID) {
|
|||
LinkLayerAddress: mac,
|
||||
}
|
||||
}
|
||||
|
||||
// findOption6 returns the data of the first option with the given code in
|
||||
// opts. It returns nil and false if no such option is found.
|
||||
func findOption6(opts layers.DHCPv6Options, code layers.DHCPv6Opt) (data []byte, ok bool) {
|
||||
for _, opt := range opts {
|
||||
if opt.Code == code {
|
||||
return opt.Data, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// clientDUID6 returns the data of the Client Identifier option (option 1) of
|
||||
// msg.
|
||||
func clientDUID6(opts layers.DHCPv6Options) (duid []byte, ok bool) {
|
||||
return findOption6(opts, layers.DHCPv6OptClientID)
|
||||
}
|
||||
|
||||
// serverDUID6 returns the data of the Server Identifier option (option 2) of
|
||||
// msg.
|
||||
func serverDUID6(opts layers.DHCPv6Options) (duid []byte, ok bool) {
|
||||
return findOption6(opts, layers.DHCPv6OptServerID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
|
@ -156,7 +158,7 @@ type dhcpInterfaceV6 struct {
|
|||
|
||||
// newDHCPInterfaceV6 creates a new DHCP interface for IPv6 address family with
|
||||
// the given configuration. If the interface is disabled, it returns nil. conf
|
||||
// must be valid.
|
||||
// must be valid. hwAddr must not be empty.
|
||||
func (srv *DHCPServer) newDHCPInterfaceV6(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
|
|
@ -245,3 +247,63 @@ func (c *IPv6Config) options(ctx context.Context, l *slog.Logger) (imp, exp laye
|
|||
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
|
||||
return int(a.Code) - int(b.Code)
|
||||
}
|
||||
|
||||
// clientIDNoServer extracts the client identifier from opts and checks that
|
||||
// there is no server identifier. It returns an error if the client identifier
|
||||
// is not found or if the server identifier is found.
|
||||
func clientIDNoServer(opts layers.DHCPv6Options) (cliID *layers.DHCPv6DUID, err error) {
|
||||
_, ok := serverDUID6(opts)
|
||||
if ok {
|
||||
return nil, fmt.Errorf("dhcpv6: server id: %w", errors.ErrUnexpectedValue)
|
||||
}
|
||||
|
||||
cliIDData, ok := clientDUID6(opts)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dhcpv6: client id: %w", errors.ErrNoValue)
|
||||
}
|
||||
|
||||
cliID = &layers.DHCPv6DUID{}
|
||||
err = cliID.DecodeFromBytes(cliIDData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dhcpv6: client id: %w", err)
|
||||
}
|
||||
|
||||
return cliID, nil
|
||||
}
|
||||
|
||||
// clientIDMatchingServer extracts the client identifier from opts and checks
|
||||
// that the server identifier matches serverDUID. It returns an error if the
|
||||
// client identifier is not found, if the server identifier is not found, or if
|
||||
// the server identifier does not match serverDUID.
|
||||
func clientIDMatchingServer(
|
||||
opts layers.DHCPv6Options,
|
||||
serverDUID []byte,
|
||||
) (cliID *layers.DHCPv6DUID, err error) {
|
||||
srvID, ok := serverDUID6(opts)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dhcpv6: server id: %w", errors.ErrNoValue)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Add validate.EqualFunc.
|
||||
if !bytes.Equal(srvID, serverDUID) {
|
||||
return nil, fmt.Errorf(
|
||||
"dhcpv6: server id: got %v, want %v: %w",
|
||||
srvID,
|
||||
serverDUID,
|
||||
errors.ErrNotEqual,
|
||||
)
|
||||
}
|
||||
|
||||
cliIDData, ok := clientDUID6(opts)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dhcpv6: client id: %w", errors.ErrNoValue)
|
||||
}
|
||||
|
||||
cliID = &layers.DHCPv6DUID{}
|
||||
err = cliID.DecodeFromBytes(cliIDData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dhcpv6: client id: %w", err)
|
||||
}
|
||||
|
||||
return cliID, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue