Pull request 2014: 4923 gopacket dhcp vol.3
Merge in DNS/adguard-home from 4923-gopacket-dhcp-vol.3 to master
Updates #4923.
Squashed commit of the following:
commit 1a09c436e5666a515084cd5e76cfccd67991ae5e
Merge: 95bcf0720 c3f141a0a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Sep 28 19:38:57 2023 +0300
Merge branch 'master' into 4923-gopacket-dhcp-vol.3
commit 95bcf07206434fd451632e819926871ba8c14f08
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Sep 28 13:19:42 2023 +0300
dhcpsvc: fix interface to match legacy version
commit 5da513ce177319f19698c5a8e1d10affaaf5e85c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Sep 28 12:32:21 2023 +0300
dhcpsvc: make it build on 32bit
commit 37a935514b1cebdc817cdcd5ec3562baeafbc42d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Sep 27 19:39:35 2023 +0300
dhcpd: fix v6 as well
commit 03b5454b04c4fdb3fe928d661562883dc3e09d81
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Sep 27 19:34:17 2023 +0300
dhcpsvc: imp code, docs
commit 91a0e451f78fba64578cc541f7ba66579c31d388
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Sep 22 15:25:58 2023 +0300
dhcpsvc: imp filing
commit 57c91e1194caa00a69e62b6655b1b4e38b69b89f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Sep 22 15:23:02 2023 +0300
dhcpsvc: imp code
commit d86be56efbfc121c9fe2c5ecef992b4523e04d57
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Sep 14 12:24:39 2023 +0300
dhcpsvc: imp code, docs
commit c9ef29057e9e378779d1a7938ad13b6eebda8f50
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Sep 13 17:53:55 2023 +0300
dhcpsvc: add constructor, validations, tests
commit f2533ed64e4ef439603b9cdf9596f8b0c4a87cf1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Sep 12 23:05:42 2023 +0500
WIP
This commit is contained in:
parent
c3f141a0a8
commit
39aeaf8910
|
@ -148,6 +148,9 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.leasesLock.Lock()
|
||||||
|
defer s.leasesLock.Unlock()
|
||||||
|
|
||||||
s.leasedOffsets = newBitSet()
|
s.leasedOffsets = newBitSet()
|
||||||
s.hostsIndex = make(map[string]*Lease, len(leases))
|
s.hostsIndex = make(map[string]*Lease, len(leases))
|
||||||
s.ipIndex = make(map[netip.Addr]*Lease, len(leases))
|
s.ipIndex = make(map[netip.Addr]*Lease, len(leases))
|
||||||
|
@ -182,16 +185,13 @@ func (s *v4Server) isBlocklisted(l *Lease) (ok bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = true
|
|
||||||
for _, b := range l.HWAddr {
|
for _, b := range l.HWAddr {
|
||||||
if b != 0 {
|
if b != 0 {
|
||||||
ok = false
|
return false
|
||||||
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
|
// GetLeases returns the list of current DHCP leases. It is safe for concurrent
|
||||||
|
|
|
@ -90,6 +90,9 @@ func (s *v6Server) IPByHost(host string) (ip netip.Addr) {
|
||||||
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
||||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||||
|
|
||||||
|
s.leasesLock.Lock()
|
||||||
|
defer s.leasesLock.Unlock()
|
||||||
|
|
||||||
s.leases = nil
|
s.leases = nil
|
||||||
for _, l := range leases {
|
for _, l := range leases {
|
||||||
ip := net.IP(l.IP.AsSlice())
|
ip := net.IP(l.IP.AsSlice())
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package dhcpsvc
|
package dhcpsvc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/AdguardTeam/golibs/netutil"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the configuration for the DHCP service.
|
// Config is the configuration for the DHCP service.
|
||||||
|
@ -33,54 +35,58 @@ type InterfaceConfig struct {
|
||||||
IPv6 *IPv6Config
|
IPv6 *IPv6Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
// Validate returns an error in conf if any.
|
||||||
type IPv4Config struct {
|
func (conf *Config) Validate() (err error) {
|
||||||
// GatewayIP is the IPv4 address of the network's gateway. It is used as
|
switch {
|
||||||
// the default gateway for DHCP clients and also used in calculating the
|
case conf == nil:
|
||||||
// network-specific broadcast address.
|
return errNilConfig
|
||||||
GatewayIP netip.Addr
|
case !conf.Enabled:
|
||||||
|
return nil
|
||||||
|
case conf.ICMPTimeout < 0:
|
||||||
|
return fmt.Errorf("icmp timeout %s must be non-negative", conf.ICMPTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
|
err = netutil.ValidateDomainName(conf.LocalDomainName)
|
||||||
// IPv4 subnet mask (i.e. all 1s followed by all 0s).
|
if err != nil {
|
||||||
SubnetMask netip.Addr
|
// Don't wrap the error since it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
if len(conf.Interfaces) == 0 {
|
||||||
RangeStart netip.Addr
|
return errNoInterfaces
|
||||||
|
}
|
||||||
|
|
||||||
// RangeEnd is the last address in the range to assign to DHCP clients.
|
ifaces := maps.Keys(conf.Interfaces)
|
||||||
RangeEnd netip.Addr
|
slices.Sort(ifaces)
|
||||||
|
|
||||||
// Options is the list of DHCP options to send to DHCP clients.
|
for _, iface := range ifaces {
|
||||||
Options layers.DHCPOptions
|
if err = conf.Interfaces[iface].validate(); err != nil {
|
||||||
|
return fmt.Errorf("interface %q: %w", iface, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LeaseDuration is the TTL of a DHCP lease.
|
return nil
|
||||||
LeaseDuration time.Duration
|
|
||||||
|
|
||||||
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
|
|
||||||
// on the specific interface.
|
|
||||||
Enabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
// mustBeErr returns an error that indicates that valName must be as must
|
||||||
type IPv6Config struct {
|
// describes.
|
||||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
func mustBeErr(valName, must string, val fmt.Stringer) (err error) {
|
||||||
RangeStart netip.Addr
|
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||||
|
}
|
||||||
// Options is the list of DHCP options to send to DHCP clients.
|
|
||||||
Options layers.DHCPOptions
|
// validate returns an error in ic, if any.
|
||||||
|
func (ic *InterfaceConfig) validate() (err error) {
|
||||||
// LeaseDuration is the TTL of a DHCP lease.
|
if ic == nil {
|
||||||
LeaseDuration time.Duration
|
return errNilConfig
|
||||||
|
}
|
||||||
// RASlaacOnly defines whether the DHCP clients should only use SLAAC for
|
|
||||||
// address assignment.
|
if err = ic.IPv4.validate(); err != nil {
|
||||||
RASLAACOnly bool
|
return fmt.Errorf("ipv4: %w", err)
|
||||||
|
}
|
||||||
// RAAllowSlaac defines whether the DHCP clients may use SLAAC for address
|
|
||||||
// assignment.
|
if err = ic.IPv6.validate(); err != nil {
|
||||||
RAAllowSLAAC bool
|
return fmt.Errorf("ipv6: %w", err)
|
||||||
|
}
|
||||||
// Enabled is the state of the DHCPv6 service, whether it is enabled or not
|
|
||||||
// on the specific interface.
|
return nil
|
||||||
Enabled bool
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package dhcpsvc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_Validate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
conf *dhcpsvc.Config
|
||||||
|
wantErrMsg string
|
||||||
|
}{{
|
||||||
|
name: "nil_config",
|
||||||
|
conf: nil,
|
||||||
|
wantErrMsg: "config is nil",
|
||||||
|
}, {
|
||||||
|
name: "disabled",
|
||||||
|
conf: &dhcpsvc.Config{},
|
||||||
|
wantErrMsg: "",
|
||||||
|
}, {
|
||||||
|
name: "empty",
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
wantErrMsg: `bad domain name "": domain name is empty`,
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: nil,
|
||||||
|
},
|
||||||
|
name: "no_interfaces",
|
||||||
|
wantErrMsg: "no interfaces specified",
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: nil,
|
||||||
|
},
|
||||||
|
name: "no_interfaces",
|
||||||
|
wantErrMsg: "no interfaces specified",
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "nil_interface",
|
||||||
|
wantErrMsg: `interface "eth0": config is nil`,
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: nil,
|
||||||
|
IPv6: &dhcpsvc.IPv6Config{Enabled: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "nil_ipv4",
|
||||||
|
wantErrMsg: `interface "eth0": ipv4: config is nil`,
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: &dhcpsvc.IPv4Config{Enabled: false},
|
||||||
|
IPv6: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "nil_ipv6",
|
||||||
|
wantErrMsg: `interface "eth0": ipv6: config is nil`,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, tc.conf.Validate())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,16 +56,17 @@ type Interface interface {
|
||||||
// hostname, either set or generated.
|
// hostname, either set or generated.
|
||||||
IPByHost(host string) (ip netip.Addr)
|
IPByHost(host string) (ip netip.Addr)
|
||||||
|
|
||||||
// Leases returns all the DHCP leases.
|
// Leases returns all the active DHCP leases.
|
||||||
Leases() (leases []*Lease)
|
Leases() (ls []*Lease)
|
||||||
|
|
||||||
// AddLease adds a new DHCP lease. It returns an error if the lease is
|
// AddLease adds a new DHCP lease. It returns an error if the lease is
|
||||||
// invalid or already exists.
|
// invalid or already exists.
|
||||||
AddLease(l *Lease) (err error)
|
AddLease(l *Lease) (err error)
|
||||||
|
|
||||||
// EditLease changes an existing DHCP lease. It returns an error if there
|
// UpdateStaticLease changes an existing DHCP lease. It returns an error if
|
||||||
// is no lease equal to old or if new is invalid or already exists.
|
// there is no lease with such hardware addressor if new values are invalid
|
||||||
EditLease(old, new *Lease) (err error)
|
// or already exist.
|
||||||
|
UpdateStaticLease(l *Lease) (err error)
|
||||||
|
|
||||||
// RemoveLease removes an existing DHCP lease. It returns an error if there
|
// RemoveLease removes an existing DHCP lease. It returns an error if there
|
||||||
// is no lease equal to l.
|
// is no lease equal to l.
|
||||||
|
@ -79,7 +80,7 @@ type Interface interface {
|
||||||
type Empty struct{}
|
type Empty struct{}
|
||||||
|
|
||||||
// type check
|
// type check
|
||||||
var _ Interface = Empty{}
|
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
||||||
|
|
||||||
// Start implements the [Service] interface for Empty.
|
// Start implements the [Service] interface for Empty.
|
||||||
func (Empty) Start() (err error) { return nil }
|
func (Empty) Start() (err error) { return nil }
|
||||||
|
@ -87,8 +88,6 @@ func (Empty) Start() (err error) { return nil }
|
||||||
// Shutdown implements the [Service] interface for Empty.
|
// Shutdown implements the [Service] interface for Empty.
|
||||||
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
func (Empty) Shutdown(_ context.Context) (err error) { return nil }
|
||||||
|
|
||||||
var _ agh.ServiceWithConfig[*Config] = Empty{}
|
|
||||||
|
|
||||||
// Config implements the [ServiceWithConfig] interface for Empty.
|
// Config implements the [ServiceWithConfig] interface for Empty.
|
||||||
func (Empty) Config() (conf *Config) { return nil }
|
func (Empty) Config() (conf *Config) { return nil }
|
||||||
|
|
||||||
|
@ -113,8 +112,8 @@ func (Empty) Leases() (leases []*Lease) { return nil }
|
||||||
// AddLease implements the [Interface] interface for Empty.
|
// AddLease implements the [Interface] interface for Empty.
|
||||||
func (Empty) AddLease(_ *Lease) (err error) { return nil }
|
func (Empty) AddLease(_ *Lease) (err error) { return nil }
|
||||||
|
|
||||||
// EditLease implements the [Interface] interface for Empty.
|
// UpdateStaticLease implements the [Interface] interface for Empty.
|
||||||
func (Empty) EditLease(_, _ *Lease) (err error) { return nil }
|
func (Empty) UpdateStaticLease(_ *Lease) (err error) { return nil }
|
||||||
|
|
||||||
// RemoveLease implements the [Interface] interface for Empty.
|
// RemoveLease implements the [Interface] interface for Empty.
|
||||||
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
|
func (Empty) RemoveLease(_ *Lease) (err error) { return nil }
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import "github.com/AdguardTeam/golibs/errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// errNilConfig is returned when a nil config met.
|
||||||
|
errNilConfig errors.Error = "config is nil"
|
||||||
|
|
||||||
|
// errNoInterfaces is returned when no interfaces found in configuration.
|
||||||
|
errNoInterfaces errors.Error = "no interfaces specified"
|
||||||
|
)
|
|
@ -0,0 +1,98 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ipRange is an inclusive range of IP addresses. A zero range doesn't contain
|
||||||
|
// any IP addresses.
|
||||||
|
//
|
||||||
|
// It is safe for concurrent use.
|
||||||
|
type ipRange struct {
|
||||||
|
start netip.Addr
|
||||||
|
end netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxRangeLen is the maximum IP range length. The bitsets used in servers only
|
||||||
|
// accept uints, which can have the size of 32 bit.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov, e.burkov): Reconsider the value for IPv6.
|
||||||
|
const maxRangeLen = math.MaxUint32
|
||||||
|
|
||||||
|
// newIPRange creates a new IP address range. start must be less than end. The
|
||||||
|
// resulting range must not be greater than maxRangeLen.
|
||||||
|
func newIPRange(start, end netip.Addr) (r ipRange, err error) {
|
||||||
|
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
|
||||||
|
|
||||||
|
switch false {
|
||||||
|
case start.Is4() == end.Is4():
|
||||||
|
return ipRange{}, fmt.Errorf("%s and %s must be within the same address family", start, end)
|
||||||
|
case start.Less(end):
|
||||||
|
return ipRange{}, fmt.Errorf("start %s is greater than or equal to end %s", start, end)
|
||||||
|
default:
|
||||||
|
diff := (&big.Int{}).Sub(
|
||||||
|
(&big.Int{}).SetBytes(end.AsSlice()),
|
||||||
|
(&big.Int{}).SetBytes(start.AsSlice()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if !diff.IsUint64() || diff.Uint64() > maxRangeLen {
|
||||||
|
return ipRange{}, fmt.Errorf("range length must be within %d", uint32(maxRangeLen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipRange{
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains returns true if r contains ip.
|
||||||
|
func (r ipRange) contains(ip netip.Addr) (ok bool) {
|
||||||
|
// Assume that the end was checked to be within the same address family as
|
||||||
|
// the start during construction.
|
||||||
|
return r.start.Is4() == ip.Is4() && !ip.Less(r.start) && !r.end.Less(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipPredicate is a function that is called on every IP address in
|
||||||
|
// [ipRange.find].
|
||||||
|
type ipPredicate func(ip netip.Addr) (ok bool)
|
||||||
|
|
||||||
|
// find finds the first IP address in r for which p returns true. It returns an
|
||||||
|
// empty [netip.Addr] if there are no addresses that satisfy p.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use.
|
||||||
|
func (r ipRange) find(p ipPredicate) (ip netip.Addr) {
|
||||||
|
for ip = r.start; !r.end.Less(ip); ip = ip.Next() {
|
||||||
|
if p(ip) {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset returns the offset of ip from the beginning of r. It returns 0 and
|
||||||
|
// false if ip is not in r.
|
||||||
|
func (r ipRange) offset(ip netip.Addr) (offset uint64, ok bool) {
|
||||||
|
if !r.contains(ip) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
startData, ipData := r.start.As16(), ip.As16()
|
||||||
|
be := binary.BigEndian
|
||||||
|
|
||||||
|
// Assume that the range length was checked against maxRangeLen during
|
||||||
|
// construction.
|
||||||
|
return be.Uint64(ipData[8:]) - be.Uint64(startData[8:]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer interface for *ipRange.
|
||||||
|
func (r ipRange) String() (s string) {
|
||||||
|
return fmt.Sprintf("%s-%s", r.start, r.end)
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewIPRange(t *testing.T) {
|
||||||
|
start4 := netip.MustParseAddr("0.0.0.1")
|
||||||
|
end4 := netip.MustParseAddr("0.0.0.3")
|
||||||
|
start6 := netip.MustParseAddr("1::1")
|
||||||
|
end6 := netip.MustParseAddr("1::3")
|
||||||
|
end6Large := netip.MustParseAddr("2::3")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
start netip.Addr
|
||||||
|
end netip.Addr
|
||||||
|
name string
|
||||||
|
wantErrMsg string
|
||||||
|
}{{
|
||||||
|
start: start4,
|
||||||
|
end: end4,
|
||||||
|
name: "success_ipv4",
|
||||||
|
wantErrMsg: "",
|
||||||
|
}, {
|
||||||
|
start: start6,
|
||||||
|
end: end6,
|
||||||
|
name: "success_ipv6",
|
||||||
|
wantErrMsg: "",
|
||||||
|
}, {
|
||||||
|
start: end4,
|
||||||
|
end: start4,
|
||||||
|
name: "start_gt_end",
|
||||||
|
wantErrMsg: "invalid ip range: start 0.0.0.3 is greater than or equal to end 0.0.0.1",
|
||||||
|
}, {
|
||||||
|
start: start4,
|
||||||
|
end: start4,
|
||||||
|
name: "start_eq_end",
|
||||||
|
wantErrMsg: "invalid ip range: start 0.0.0.1 is greater than or equal to end 0.0.0.1",
|
||||||
|
}, {
|
||||||
|
start: start6,
|
||||||
|
end: end6Large,
|
||||||
|
name: "too_large",
|
||||||
|
wantErrMsg: "invalid ip range: range length must be within " +
|
||||||
|
strconv.FormatUint(maxRangeLen, 10),
|
||||||
|
}, {
|
||||||
|
start: start4,
|
||||||
|
end: end6,
|
||||||
|
name: "different_family",
|
||||||
|
wantErrMsg: "invalid ip range: 0.0.0.1 and 1::3 must be within the same address family",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := newIPRange(tc.start, tc.end)
|
||||||
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPRange_Contains(t *testing.T) {
|
||||||
|
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.3")
|
||||||
|
r, err := newIPRange(start, end)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
in netip.Addr
|
||||||
|
want assert.BoolAssertionFunc
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
in: start,
|
||||||
|
want: assert.True,
|
||||||
|
name: "start",
|
||||||
|
}, {
|
||||||
|
in: end,
|
||||||
|
want: assert.True,
|
||||||
|
name: "end",
|
||||||
|
}, {
|
||||||
|
in: start.Next(),
|
||||||
|
want: assert.True,
|
||||||
|
name: "within",
|
||||||
|
}, {
|
||||||
|
in: netip.MustParseAddr("0.0.0.0"),
|
||||||
|
want: assert.False,
|
||||||
|
name: "before",
|
||||||
|
}, {
|
||||||
|
in: netip.MustParseAddr("0.0.0.4"),
|
||||||
|
want: assert.False,
|
||||||
|
name: "after",
|
||||||
|
}, {
|
||||||
|
in: netip.MustParseAddr("::"),
|
||||||
|
want: assert.False,
|
||||||
|
name: "another_family",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tc.want(t, r.contains(tc.in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPRange_Find(t *testing.T) {
|
||||||
|
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.5")
|
||||||
|
r, err := newIPRange(start, end)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
num, ok := r.offset(end)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
predicate ipPredicate
|
||||||
|
want netip.Addr
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
predicate: func(ip netip.Addr) (ok bool) {
|
||||||
|
ipData := ip.AsSlice()
|
||||||
|
|
||||||
|
return ipData[len(ipData)-1]%2 == 0
|
||||||
|
},
|
||||||
|
want: netip.MustParseAddr("0.0.0.2"),
|
||||||
|
name: "even",
|
||||||
|
}, {
|
||||||
|
predicate: func(ip netip.Addr) (ok bool) {
|
||||||
|
ipData := ip.AsSlice()
|
||||||
|
|
||||||
|
return ipData[len(ipData)-1]%10 == 0
|
||||||
|
},
|
||||||
|
want: netip.Addr{},
|
||||||
|
name: "none",
|
||||||
|
}, {
|
||||||
|
predicate: func(ip netip.Addr) (ok bool) {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
want: start,
|
||||||
|
name: "first",
|
||||||
|
}, {
|
||||||
|
predicate: func(ip netip.Addr) (ok bool) {
|
||||||
|
off, _ := r.offset(ip)
|
||||||
|
|
||||||
|
return off == num
|
||||||
|
},
|
||||||
|
want: end,
|
||||||
|
name: "last",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := r.find(tc.predicate)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPRange_Offset(t *testing.T) {
|
||||||
|
start, end := netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.5")
|
||||||
|
r, err := newIPRange(start, end)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
in netip.Addr
|
||||||
|
name string
|
||||||
|
wantOffset uint64
|
||||||
|
wantOK bool
|
||||||
|
}{{
|
||||||
|
in: netip.MustParseAddr("0.0.0.2"),
|
||||||
|
name: "in",
|
||||||
|
wantOffset: 1,
|
||||||
|
wantOK: true,
|
||||||
|
}, {
|
||||||
|
in: start,
|
||||||
|
name: "in_start",
|
||||||
|
wantOffset: 0,
|
||||||
|
wantOK: true,
|
||||||
|
}, {
|
||||||
|
in: end,
|
||||||
|
name: "in_end",
|
||||||
|
wantOffset: 4,
|
||||||
|
wantOK: true,
|
||||||
|
}, {
|
||||||
|
in: netip.MustParseAddr("0.0.0.6"),
|
||||||
|
name: "out_after",
|
||||||
|
wantOffset: 0,
|
||||||
|
wantOK: false,
|
||||||
|
}, {
|
||||||
|
in: netip.MustParseAddr("0.0.0.0"),
|
||||||
|
name: "out_before",
|
||||||
|
wantOffset: 0,
|
||||||
|
wantOK: false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
offset, ok := r.offset(tc.in)
|
||||||
|
assert.Equal(t, tc.wantOffset, offset)
|
||||||
|
assert.Equal(t, tc.wantOK, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||||
|
type DHCPServer struct {
|
||||||
|
// enabled indicates whether the DHCP server is enabled and can provide
|
||||||
|
// information about its clients.
|
||||||
|
enabled *atomic.Bool
|
||||||
|
|
||||||
|
// localTLD is the top-level domain name to use for resolving DHCP
|
||||||
|
// clients' hostnames.
|
||||||
|
localTLD string
|
||||||
|
|
||||||
|
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
|
||||||
|
interfaces4 []*iface4
|
||||||
|
|
||||||
|
// interfaces6 is the set of IPv6 interfaces sorted by interface name.
|
||||||
|
interfaces6 []*iface6
|
||||||
|
|
||||||
|
// icmpTimeout is the timeout for checking another DHCP server's presence.
|
||||||
|
icmpTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new DHCP server with the given configuration. It returns an
|
||||||
|
// error if the given configuration can't be used.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Use.
|
||||||
|
func New(conf *Config) (srv *DHCPServer, err error) {
|
||||||
|
if !conf.Enabled {
|
||||||
|
// TODO(e.burkov): Perhaps return [Empty]?
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaces4 := make([]*iface4, len(conf.Interfaces))
|
||||||
|
ifaces6 := make([]*iface6, len(conf.Interfaces))
|
||||||
|
|
||||||
|
ifaceNames := maps.Keys(conf.Interfaces)
|
||||||
|
slices.Sort(ifaceNames)
|
||||||
|
|
||||||
|
var i4 *iface4
|
||||||
|
var i6 *iface6
|
||||||
|
|
||||||
|
for _, ifaceName := range ifaceNames {
|
||||||
|
iface := conf.Interfaces[ifaceName]
|
||||||
|
|
||||||
|
i4, err = newIface4(ifaceName, iface.IPv4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("interface %q: ipv4: %w", ifaceName, err)
|
||||||
|
} else if i4 != nil {
|
||||||
|
ifaces4 = append(ifaces4, i4)
|
||||||
|
}
|
||||||
|
|
||||||
|
i6 = newIface6(ifaceName, iface.IPv6)
|
||||||
|
if i6 != nil {
|
||||||
|
ifaces6 = append(ifaces6, i6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled := &atomic.Bool{}
|
||||||
|
enabled.Store(conf.Enabled)
|
||||||
|
|
||||||
|
return &DHCPServer{
|
||||||
|
enabled: enabled,
|
||||||
|
interfaces4: ifaces4,
|
||||||
|
interfaces6: ifaces6,
|
||||||
|
localTLD: conf.LocalDomainName,
|
||||||
|
icmpTimeout: conf.ICMPTimeout,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package dhcpsvc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||||
|
"github.com/AdguardTeam/golibs/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testLocalTLD is a common local TLD for tests.
|
||||||
|
const testLocalTLD = "local"
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
validIPv4Conf := &dhcpsvc.IPv4Config{
|
||||||
|
Enabled: true,
|
||||||
|
GatewayIP: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
|
RangeStart: netip.MustParseAddr("192.168.0.2"),
|
||||||
|
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||||
|
LeaseDuration: 1 * time.Hour,
|
||||||
|
}
|
||||||
|
gwInRangeConf := &dhcpsvc.IPv4Config{
|
||||||
|
Enabled: true,
|
||||||
|
GatewayIP: netip.MustParseAddr("192.168.0.100"),
|
||||||
|
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
|
RangeStart: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||||
|
LeaseDuration: 1 * time.Hour,
|
||||||
|
}
|
||||||
|
badStartConf := &dhcpsvc.IPv4Config{
|
||||||
|
Enabled: true,
|
||||||
|
GatewayIP: netip.MustParseAddr("192.168.0.1"),
|
||||||
|
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||||
|
RangeStart: netip.MustParseAddr("127.0.0.1"),
|
||||||
|
RangeEnd: netip.MustParseAddr("192.168.0.254"),
|
||||||
|
LeaseDuration: 1 * time.Hour,
|
||||||
|
}
|
||||||
|
|
||||||
|
validIPv6Conf := &dhcpsvc.IPv6Config{
|
||||||
|
Enabled: true,
|
||||||
|
RangeStart: netip.MustParseAddr("2001:db8::1"),
|
||||||
|
LeaseDuration: 1 * time.Hour,
|
||||||
|
RAAllowSLAAC: true,
|
||||||
|
RASLAACOnly: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
conf *dhcpsvc.Config
|
||||||
|
name string
|
||||||
|
wantErrMsg string
|
||||||
|
}{{
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: validIPv4Conf,
|
||||||
|
IPv6: validIPv6Conf,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "valid",
|
||||||
|
wantErrMsg: "",
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: &dhcpsvc.IPv4Config{Enabled: false},
|
||||||
|
IPv6: &dhcpsvc.IPv6Config{Enabled: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "disabled_interfaces",
|
||||||
|
wantErrMsg: "",
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: gwInRangeConf,
|
||||||
|
IPv6: validIPv6Conf,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "gateway_within_range",
|
||||||
|
wantErrMsg: `interface "eth0": ipv4: ` +
|
||||||
|
`gateway ip 192.168.0.100 in the ip range 192.168.0.1-192.168.0.254`,
|
||||||
|
}, {
|
||||||
|
conf: &dhcpsvc.Config{
|
||||||
|
Enabled: true,
|
||||||
|
LocalDomainName: testLocalTLD,
|
||||||
|
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||||
|
"eth0": {
|
||||||
|
IPv4: badStartConf,
|
||||||
|
IPv6: validIPv6Conf,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "bad_start",
|
||||||
|
wantErrMsg: `interface "eth0": ipv4: ` +
|
||||||
|
`range start 127.0.0.1 is not within 192.168.0.1/24`,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := dhcpsvc.New(tc.conf)
|
||||||
|
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||||
|
type IPv4Config struct {
|
||||||
|
// GatewayIP is the IPv4 address of the network's gateway. It is used as
|
||||||
|
// the default gateway for DHCP clients and also used in calculating the
|
||||||
|
// network-specific broadcast address.
|
||||||
|
GatewayIP netip.Addr
|
||||||
|
|
||||||
|
// SubnetMask is the IPv4 subnet mask of the network. It should be a valid
|
||||||
|
// IPv4 CIDR (i.e. all 1s followed by all 0s).
|
||||||
|
SubnetMask netip.Addr
|
||||||
|
|
||||||
|
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||||
|
RangeStart netip.Addr
|
||||||
|
|
||||||
|
// RangeEnd is the last address in the range to assign to DHCP clients.
|
||||||
|
RangeEnd netip.Addr
|
||||||
|
|
||||||
|
// Options is the list of DHCP options to send to DHCP clients.
|
||||||
|
Options layers.DHCPOptions
|
||||||
|
|
||||||
|
// LeaseDuration is the TTL of a DHCP lease.
|
||||||
|
LeaseDuration time.Duration
|
||||||
|
|
||||||
|
// Enabled is the state of the DHCPv4 service, whether it is enabled or not
|
||||||
|
// on the specific interface.
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate returns an error in conf if any.
|
||||||
|
func (conf *IPv4Config) validate() (err error) {
|
||||||
|
switch {
|
||||||
|
case conf == nil:
|
||||||
|
return errNilConfig
|
||||||
|
case !conf.Enabled:
|
||||||
|
return nil
|
||||||
|
case !conf.GatewayIP.Is4():
|
||||||
|
return mustBeErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
|
||||||
|
case !conf.SubnetMask.Is4():
|
||||||
|
return mustBeErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
|
||||||
|
case !conf.RangeStart.Is4():
|
||||||
|
return mustBeErr("range start", "be a valid ipv4", conf.RangeStart)
|
||||||
|
case !conf.RangeEnd.Is4():
|
||||||
|
return mustBeErr("range end", "be a valid ipv4", conf.RangeEnd)
|
||||||
|
case conf.LeaseDuration <= 0:
|
||||||
|
return mustBeErr("lease duration", "be less than %d", conf.LeaseDuration)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iface4 is a DHCP interface for IPv4 address family.
|
||||||
|
type iface4 struct {
|
||||||
|
// gateway is the IP address of the network gateway.
|
||||||
|
gateway netip.Addr
|
||||||
|
|
||||||
|
// subnet is the network subnet.
|
||||||
|
subnet netip.Prefix
|
||||||
|
|
||||||
|
// addrSpace is the IPv4 address space allocated for leasing.
|
||||||
|
addrSpace ipRange
|
||||||
|
|
||||||
|
// name is the name of the interface.
|
||||||
|
name string
|
||||||
|
|
||||||
|
// TODO(e.burkov): Add options.
|
||||||
|
|
||||||
|
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||||
|
leaseTTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// newIface4 creates a new DHCP interface for IPv4 address family with the given
|
||||||
|
// configuration. It returns an error if the given configuration can't be used.
|
||||||
|
func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
|
||||||
|
if !conf.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
maskLen, _ := net.IPMask(conf.SubnetMask.AsSlice()).Size()
|
||||||
|
subnet := netip.PrefixFrom(conf.GatewayIP, maskLen)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !subnet.Contains(conf.RangeStart):
|
||||||
|
return nil, fmt.Errorf("range start %s is not within %s", conf.RangeStart, subnet)
|
||||||
|
case !subnet.Contains(conf.RangeEnd):
|
||||||
|
return nil, fmt.Errorf("range end %s is not within %s", conf.RangeEnd, subnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrSpace, err := newIPRange(conf.RangeStart, conf.RangeEnd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if addrSpace.contains(conf.GatewayIP) {
|
||||||
|
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iface4{
|
||||||
|
name: name,
|
||||||
|
gateway: conf.GatewayIP,
|
||||||
|
subnet: subnet,
|
||||||
|
addrSpace: addrSpace,
|
||||||
|
leaseTTL: conf.LeaseDuration,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package dhcpsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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.
|
||||||
|
RangeStart netip.Addr
|
||||||
|
|
||||||
|
// Options is the list of DHCP options to send to DHCP clients.
|
||||||
|
Options layers.DHCPOptions
|
||||||
|
|
||||||
|
// LeaseDuration is the TTL of a DHCP lease.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate returns an error in conf if any.
|
||||||
|
func (conf *IPv6Config) validate() (err error) {
|
||||||
|
switch {
|
||||||
|
case conf == nil:
|
||||||
|
return errNilConfig
|
||||||
|
case !conf.Enabled:
|
||||||
|
return nil
|
||||||
|
case !conf.RangeStart.Is6():
|
||||||
|
return fmt.Errorf("range start %s should be a valid ipv6", conf.RangeStart)
|
||||||
|
case conf.LeaseDuration <= 0:
|
||||||
|
return fmt.Errorf("lease duration %s must be positive", conf.LeaseDuration)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iface6 is a DHCP interface for IPv6 address family.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Add options.
|
||||||
|
type iface6 struct {
|
||||||
|
// rangeStart is the first IP address in the range.
|
||||||
|
rangeStart netip.Addr
|
||||||
|
|
||||||
|
// name is the name of the interface.
|
||||||
|
name string
|
||||||
|
|
||||||
|
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||||
|
leaseTTL time.Duration
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// newIface6 creates a new DHCP interface for IPv6 address family with the given
|
||||||
|
// configuration.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): Validate properly.
|
||||||
|
func newIface6(name string, conf *IPv6Config) (i *iface6) {
|
||||||
|
if !conf.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iface6{
|
||||||
|
name: name,
|
||||||
|
rangeStart: conf.RangeStart,
|
||||||
|
leaseTTL: conf.LeaseDuration,
|
||||||
|
raSLAACOnly: conf.RASLAACOnly,
|
||||||
|
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue