Files
AdGuardHome/internal/dhcpsvc/v6.go
Eugene Burkov d4ee146f6b Pull request #2422: 4923 gopacket dhcp vol.10
Merge in DNS/adguard-home from 4923-gopacket-dhcp-vol.10 to master

Squashed commit of the following:

commit ef2106a35aadecf8340314535ec232f91ff44b50
Merge: f14e68781 f69da062e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 9 16:13:52 2025 +0300

    Merge branch 'master' into 4923-gopacket-dhcp-vol.10

commit f14e687815eb66a7c161dfaa3655c41fded643a8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 6 18:36:32 2025 +0300

    dhcpsvc: imp code, docs

commit c94b428d6fd277a840476f02820bd065cde17d6c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 5 13:21:37 2025 +0300

    dhcpsvc: imp code

commit 57eaea08e572fea41295d373b547f614a0c099ac
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 4 19:48:03 2025 +0300

    dhcpsvc: add methods

commit b32218848cf2a90ecfc9cc6cf12c2579bcb1a2df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 30 15:57:53 2025 +0300

    dhcpsvc: add handlers

commit 615f1248160351f3bcdb040fb867d32630979d40
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 14 16:10:01 2025 +0300

    dhcpsvc: multiplex dhcpv4
2025-06-10 12:28:51 +03:00

181 lines
5.0 KiB
Go

package dhcpsvc
import (
"context"
"fmt"
"log/slog"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/google/gopacket/layers"
)
// IPv6Config is the interface-specific configuration for DHCPv6.
type IPv6Config struct {
// RangeStart is the first address in the range to assign to DHCP clients.
// It should be a valid IPv6 address.
RangeStart netip.Addr
// Options is the list of explicit DHCP options to send to clients. The
// options with zero length are treated as deletions of the corresponding
// options, either implicit or explicit.
Options layers.DHCPv6Options
// LeaseDuration is the TTL of a DHCP lease. It should be positive.
LeaseDuration time.Duration
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
// address assignment.
RASLAACOnly bool
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
// assignment.
RAAllowSLAAC bool
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
// on the specific interface.
Enabled bool
}
// type check
var _ validate.Interface = (*IPv6Config)(nil)
// Validate implements the [validate.Interface] interface for *IPv6Config.
func (c *IPv6Config) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
} else if !c.Enabled {
return nil
}
var errs []error
if !c.RangeStart.Is6() {
err = fmt.Errorf("range start %s should be a valid ipv6", c.RangeStart)
errs = append(errs, err)
}
if c.LeaseDuration <= 0 {
err = fmt.Errorf("lease duration %s must be positive", c.LeaseDuration)
errs = append(errs, err)
}
return errors.Join(errs...)
}
// dhcpInterfaceV6 is a DHCP interface for IPv6 address family.
type dhcpInterfaceV6 struct {
// common is the common part of any network interface within the DHCP
// server.
common *netInterface
// rangeStart is the first IP address in the range.
rangeStart netip.Addr
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
// initialized with default values. It must not have intersections with
// explicitOpts.
implicitOpts layers.DHCPv6Options
// explicitOpts are the user-configured options. It must not have
// intersections with implicitOpts.
explicitOpts layers.DHCPv6Options
// raSLAACOnly defines if DHCP should send ICMPv6.RA packets without MO
// flags.
raSLAACOnly bool
// raAllowSLAAC defines if DHCP should send ICMPv6.RA packets with MO flags.
raAllowSLAAC bool
}
// 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.
func newDHCPInterfaceV6(
ctx context.Context,
l *slog.Logger,
name string,
conf *IPv6Config,
) (iface *dhcpInterfaceV6) {
if !conf.Enabled {
l.DebugContext(ctx, "disabled")
return nil
}
iface = &dhcpInterfaceV6{
rangeStart: conf.RangeStart,
common: &netInterface{
logger: l,
leases: map[macKey]*Lease{},
name: name,
leaseTTL: conf.LeaseDuration,
},
raSLAACOnly: conf.RASLAACOnly,
raAllowSLAAC: conf.RAAllowSLAAC,
}
iface.implicitOpts, iface.explicitOpts = conf.options(ctx, l)
return iface
}
// dhcpInterfacesV6 is a slice of network interfaces of IPv6 address family.
type dhcpInterfacesV6 []*dhcpInterfaceV6
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces dhcpInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool) {
// prefLen is the length of prefix to match ip against.
//
// TODO(e.burkov): DHCPv6 inherits the weird behavior of legacy
// implementation where the allocated range constrained by the first address
// and the first address with last byte set to 0xff. Proper prefixes should
// be used instead.
const prefLen = netutil.IPv6BitLen - 8
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV6) (contains bool) {
return !ip.Less(iface.rangeStart) &&
netip.PrefixFrom(iface.rangeStart, prefLen).Contains(ip)
})
if i < 0 {
return nil, false
}
return ifaces[i].common, true
}
// options returns the implicit and explicit options for the interface. The two
// lists are disjoint and the implicit options are initialized with default
// values.
//
// TODO(e.burkov): Add implicit options according to RFC.
func (c *IPv6Config) options(ctx context.Context, l *slog.Logger) (imp, exp layers.DHCPv6Options) {
// Set default values of host configuration parameters listed in RFC 8415.
imp = layers.DHCPv6Options{}
slices.SortFunc(imp, compareV6OptionCodes)
// Set values for explicitly configured options.
for _, e := range c.Options {
i, found := slices.BinarySearchFunc(imp, e, compareV6OptionCodes)
if found {
imp = slices.Delete(imp, i, i+1)
}
exp = append(exp, e)
}
l.DebugContext(ctx, "options", "implicit", imp, "explicit", exp)
return imp, exp
}
// compareV6OptionCodes compares option codes of a and b.
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
return int(a.Code) - int(b.Code)
}