Pull request 2130: 4923 gopacket dhcp vol.6
Updates #4923.
Squashed commit of the following:
commit 14ae8dc3680eae7d3ecb9e37a44c2e68221c5085
Merge: 280a4dbc7 713901c2a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Jan 31 13:52:52 2024 +0300
Merge branch 'master' into 4923-gopacket-dhcp-vol.6
commit 280a4dbc728ff67c7659f91734a74c87bf0bda43
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Jan 30 20:20:04 2024 +0300
dhcpsvc: imp docs
commit 310ed67b9bf22f88c4414095bfbfc1a29c6db6d5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Jan 30 18:51:49 2024 +0300
dhcpsvc: generalize
commit e4c2cae73a729be4db244d3042d93fcc9742bb34
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Jan 30 12:37:38 2024 +0300
dhcpsvc: imp code
commit 9a60d3529293ce1f0e8da70da05958f81e1d0092
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Jan 26 16:28:04 2024 +0300
dhcpsvc: imp code
commit 120c0472f3a3df2ebc0495a40936c8f94156db4b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Jan 25 20:44:09 2024 +0300
dhcpsvc: imp code, names, docs
commit a92f44c75279868d8e07fe7d468278025a245d13
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Jan 24 16:01:35 2024 +0300
dhcpsvc: imp code, docs
commit 18b3f237b7523f649b49563e852c298fe02fa8ae
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Jan 18 15:29:36 2024 +0300
dhcpsvc: add some lease-related methods
This commit is contained in:
parent
713901c2a0
commit
aa872dfe98
|
@ -19,6 +19,8 @@ type Config struct {
|
|||
// clients' hostnames.
|
||||
LocalDomainName string
|
||||
|
||||
// TODO(e.burkov): Add DB path.
|
||||
|
||||
// ICMPTimeout is the timeout for checking another DHCP server's presence.
|
||||
ICMPTimeout time.Duration
|
||||
|
||||
|
@ -68,12 +70,6 @@ func (conf *Config) Validate() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
||||
// validate returns an error in ic, if any.
|
||||
func (ic *InterfaceConfig) validate() (err error) {
|
||||
if ic == nil {
|
||||
|
|
|
@ -7,48 +7,14 @@ import (
|
|||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
// Interface is a DHCP service.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Separate HostByIP, MACByIP, IPByHost into a separate
|
||||
// interface. This is also valid for Enabled method.
|
||||
type Interface interface {
|
||||
agh.ServiceWithConfig[*Config]
|
||||
|
||||
|
@ -71,7 +37,8 @@ type Interface interface {
|
|||
// hostname, either set or generated.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// Leases returns all the active DHCP leases.
|
||||
// Leases returns all the active DHCP leases. The returned slice should be
|
||||
// a clone.
|
||||
//
|
||||
// TODO(e.burkov): Consider implementing iterating methods with appropriate
|
||||
// signatures instead of cloning the whole list.
|
||||
|
@ -91,6 +58,8 @@ type Interface interface {
|
|||
RemoveLease(l *Lease) (err error)
|
||||
|
||||
// Reset removes all the DHCP leases.
|
||||
//
|
||||
// TODO(e.burkov): If it's really needed?
|
||||
Reset() (err error)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package dhcpsvc
|
||||
|
||||
import "github.com/AdguardTeam/golibs/errors"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// errNilConfig is returned when a nil config met.
|
||||
|
@ -9,3 +13,9 @@ const (
|
|||
// errNoInterfaces is returned when no interfaces found in configuration.
|
||||
errNoInterfaces errors.Error = "no interfaces specified"
|
||||
)
|
||||
|
||||
// newMustErr returns an error that indicates that valName must be as must
|
||||
// describes.
|
||||
func newMustErr(valName, must string, val fmt.Stringer) (err error) {
|
||||
return fmt.Errorf("%s %s must %s", valName, val, must)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// netInterface is a common part of any network interface within the DHCP
|
||||
// server.
|
||||
//
|
||||
// TODO(e.burkov): Add other methods as [DHCPServer] evolves.
|
||||
type netInterface struct {
|
||||
// name is the name of the network interface.
|
||||
name string
|
||||
|
||||
// leases is a set of leases sorted by hardware address.
|
||||
leases []*Lease
|
||||
|
||||
// leaseTTL is the default Time-To-Live value for leases.
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// reset clears all the slices in iface for reuse.
|
||||
func (iface *netInterface) reset() {
|
||||
iface.leases = iface.leases[:0]
|
||||
}
|
||||
|
||||
// insertLease inserts the given lease into iface. It returns an error if the
|
||||
// lease can't be inserted.
|
||||
func (iface *netInterface) insertLease(l *Lease) (err error) {
|
||||
i, found := slices.BinarySearchFunc(iface.leases, l, compareLeaseMAC)
|
||||
if found {
|
||||
return fmt.Errorf("lease for mac %s already exists", l.HWAddr)
|
||||
}
|
||||
|
||||
iface.leases = slices.Insert(iface.leases, i, l)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Lease is a DHCP lease.
|
||||
//
|
||||
// TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
|
||||
// [websvc].
|
||||
//
|
||||
// TODO(e.burkov): Add validation method.
|
||||
type Lease struct {
|
||||
// IP is the IP address leased to the client.
|
||||
IP netip.Addr
|
||||
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time
|
||||
|
||||
// Hostname of the client.
|
||||
Hostname string
|
||||
|
||||
// HWAddr is the physical hardware address (MAC address).
|
||||
HWAddr net.HardwareAddr
|
||||
|
||||
// IsStatic defines if the lease is static.
|
||||
IsStatic bool
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of l.
|
||||
func (l *Lease) Clone() (clone *Lease) {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: slices.Clone(l.HWAddr),
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
// compareLeaseMAC compares two [Lease]s by hardware address.
|
||||
func compareLeaseMAC(a, b *Lease) (res int) {
|
||||
return bytes.Compare(a.HWAddr, b.HWAddr)
|
||||
}
|
|
@ -2,6 +2,10 @@ package dhcpsvc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
@ -15,18 +19,27 @@ type DHCPServer struct {
|
|||
// information about its clients.
|
||||
enabled *atomic.Bool
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP
|
||||
// clients' hostnames.
|
||||
// localTLD is the top-level domain name to use for resolving DHCP clients'
|
||||
// hostnames.
|
||||
localTLD string
|
||||
|
||||
// leasesMu protects the ipIndex and nameIndex fields against concurrent
|
||||
// access, as well as leaseHandlers within the interfaces.
|
||||
leasesMu *sync.RWMutex
|
||||
|
||||
// leaseByIP is a lookup shortcut for leases by their IP addresses.
|
||||
leaseByIP map[netip.Addr]*Lease
|
||||
|
||||
// leaseByName is a lookup shortcut for leases by their hostnames.
|
||||
//
|
||||
// TODO(e.burkov): Use a slice of leases with the same hostname?
|
||||
leaseByName map[string]*Lease
|
||||
|
||||
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
|
||||
interfaces4 []*iface4
|
||||
interfaces4 netInterfacesV4
|
||||
|
||||
// interfaces6 is the set of IPv6 interfaces sorted by interface name.
|
||||
interfaces6 []*iface6
|
||||
|
||||
// leases is the set of active DHCP leases.
|
||||
leases []*Lease
|
||||
interfaces6 netInterfacesV6
|
||||
|
||||
// icmpTimeout is the timeout for checking another DHCP server's presence.
|
||||
icmpTimeout time.Duration
|
||||
|
@ -42,26 +55,27 @@ func New(conf *Config) (srv *DHCPServer, err error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
ifaces4 := make([]*iface4, len(conf.Interfaces))
|
||||
ifaces6 := make([]*iface6, len(conf.Interfaces))
|
||||
// TODO(e.burkov): Add validations scoped to the network interfaces set.
|
||||
ifaces4 := make(netInterfacesV4, 0, len(conf.Interfaces))
|
||||
ifaces6 := make(netInterfacesV6, 0, len(conf.Interfaces))
|
||||
|
||||
ifaceNames := maps.Keys(conf.Interfaces)
|
||||
slices.Sort(ifaceNames)
|
||||
|
||||
var i4 *iface4
|
||||
var i6 *iface6
|
||||
var i4 *netInterfaceV4
|
||||
var i6 *netInterfaceV6
|
||||
|
||||
for _, ifaceName := range ifaceNames {
|
||||
iface := conf.Interfaces[ifaceName]
|
||||
|
||||
i4, err = newIface4(ifaceName, iface.IPv4)
|
||||
i4, err = newNetInterfaceV4(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)
|
||||
i6 = newNetInterfaceV6(ifaceName, iface.IPv6)
|
||||
if i6 != nil {
|
||||
ifaces6 = append(ifaces6, i6)
|
||||
}
|
||||
|
@ -70,13 +84,20 @@ func New(conf *Config) (srv *DHCPServer, err error) {
|
|||
enabled := &atomic.Bool{}
|
||||
enabled.Store(conf.Enabled)
|
||||
|
||||
return &DHCPServer{
|
||||
srv = &DHCPServer{
|
||||
enabled: enabled,
|
||||
localTLD: conf.LocalDomainName,
|
||||
leasesMu: &sync.RWMutex{},
|
||||
leaseByIP: map[netip.Addr]*Lease{},
|
||||
leaseByName: map[string]*Lease{},
|
||||
interfaces4: ifaces4,
|
||||
interfaces6: ifaces6,
|
||||
localTLD: conf.LocalDomainName,
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Load leases.
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// type check
|
||||
|
@ -91,10 +112,100 @@ func (srv *DHCPServer) Enabled() (ok bool) {
|
|||
|
||||
// Leases implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Leases() (leases []*Lease) {
|
||||
leases = make([]*Lease, 0, len(srv.leases))
|
||||
for _, lease := range srv.leases {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
for _, lease := range iface.leases {
|
||||
leases = append(leases, lease.Clone())
|
||||
}
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// HostByIP implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) HostByIP(ip netip.Addr) (host string) {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByIP[ip]; ok {
|
||||
return l.Hostname
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// MACByIP implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) MACByIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByIP[ip]; ok {
|
||||
return l.HWAddr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) IPByHost(host string) (ip netip.Addr) {
|
||||
lowered := strings.ToLower(host)
|
||||
|
||||
srv.leasesMu.RLock()
|
||||
defer srv.leasesMu.RUnlock()
|
||||
|
||||
if l, ok := srv.leaseByName[lowered]; ok {
|
||||
return l.IP
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// Reset implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Reset() (err error) {
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
iface.reset()
|
||||
}
|
||||
for _, iface := range srv.interfaces6 {
|
||||
iface.reset()
|
||||
}
|
||||
|
||||
maps.Clear(srv.leaseByIP)
|
||||
maps.Clear(srv.leaseByName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLease implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
|
||||
var ok bool
|
||||
var iface *netInterface
|
||||
|
||||
addr := l.IP
|
||||
|
||||
if addr.Is4() {
|
||||
iface, ok = srv.interfaces4.find(addr)
|
||||
} else {
|
||||
iface, ok = srv.interfaces6.find(addr)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("no interface for IP address %s", addr)
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
err = iface.insertLease(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leaseByIP[l.IP] = l
|
||||
srv.leaseByName[strings.ToLower(l.Hostname)] = l
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package dhcpsvc_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testLocalTLD is a common local TLD for tests.
|
||||
|
@ -113,3 +116,113 @@ func TestNew(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDHCPServer_index(t *testing.T) {
|
||||
srv, err := dhcpsvc.New(&dhcpsvc.Config{
|
||||
Enabled: true,
|
||||
LocalDomainName: testLocalTLD,
|
||||
Interfaces: map[string]*dhcpsvc.InterfaceConfig{
|
||||
"eth0": {
|
||||
IPv4: &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,
|
||||
},
|
||||
IPv6: &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
RangeStart: netip.MustParseAddr("2001:db8::1"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
},
|
||||
},
|
||||
"eth1": {
|
||||
IPv4: &dhcpsvc.IPv4Config{
|
||||
Enabled: true,
|
||||
GatewayIP: netip.MustParseAddr("172.16.0.1"),
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
RangeStart: netip.MustParseAddr("172.16.0.2"),
|
||||
RangeEnd: netip.MustParseAddr("172.16.0.255"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
},
|
||||
IPv6: &dhcpsvc.IPv6Config{
|
||||
Enabled: true,
|
||||
RangeStart: netip.MustParseAddr("2001:db9::1"),
|
||||
LeaseDuration: 1 * time.Hour,
|
||||
RAAllowSLAAC: true,
|
||||
RASLAACOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
host1 = "host1"
|
||||
host2 = "host2"
|
||||
host3 = "host3"
|
||||
host4 = "host4"
|
||||
host5 = "host5"
|
||||
)
|
||||
|
||||
ip1 := netip.MustParseAddr("192.168.0.2")
|
||||
ip2 := netip.MustParseAddr("192.168.0.3")
|
||||
ip3 := netip.MustParseAddr("172.16.0.3")
|
||||
ip4 := netip.MustParseAddr("172.16.0.4")
|
||||
|
||||
mac1 := net.HardwareAddr{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}
|
||||
mac2 := net.HardwareAddr{0x06, 0x05, 0x04, 0x03, 0x02, 0x01}
|
||||
mac3 := net.HardwareAddr{0x05, 0x04, 0x03, 0x02, 0x01, 0x00}
|
||||
|
||||
leases := []*dhcpsvc.Lease{{
|
||||
Hostname: host1,
|
||||
IP: ip1,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host2,
|
||||
IP: ip2,
|
||||
HWAddr: mac2,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host3,
|
||||
IP: ip3,
|
||||
HWAddr: mac3,
|
||||
IsStatic: true,
|
||||
}, {
|
||||
Hostname: host4,
|
||||
IP: ip4,
|
||||
HWAddr: mac1,
|
||||
IsStatic: true,
|
||||
}}
|
||||
for _, l := range leases {
|
||||
require.NoError(t, srv.AddLease(l))
|
||||
}
|
||||
|
||||
t.Run("ip_idx", func(t *testing.T) {
|
||||
assert.Equal(t, ip1, srv.IPByHost(host1))
|
||||
assert.Equal(t, ip2, srv.IPByHost(host2))
|
||||
assert.Equal(t, ip3, srv.IPByHost(host3))
|
||||
assert.Equal(t, ip4, srv.IPByHost(host4))
|
||||
assert.Equal(t, netip.Addr{}, srv.IPByHost(host5))
|
||||
})
|
||||
|
||||
t.Run("name_idx", func(t *testing.T) {
|
||||
assert.Equal(t, host1, srv.HostByIP(ip1))
|
||||
assert.Equal(t, host2, srv.HostByIP(ip2))
|
||||
assert.Equal(t, host3, srv.HostByIP(ip3))
|
||||
assert.Equal(t, host4, srv.HostByIP(ip4))
|
||||
assert.Equal(t, "", srv.HostByIP(netip.Addr{}))
|
||||
})
|
||||
|
||||
t.Run("mac_idx", func(t *testing.T) {
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip1))
|
||||
assert.Equal(t, mac2, srv.MACByIP(ip2))
|
||||
assert.Equal(t, mac3, srv.MACByIP(ip3))
|
||||
assert.Equal(t, mac1, srv.MACByIP(ip4))
|
||||
assert.Nil(t, srv.MACByIP(netip.Addr{}))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -64,69 +64,6 @@ func (conf *IPv4Config) validate() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
i = &iface4{
|
||||
name: name,
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
|
@ -318,3 +255,83 @@ func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
|
|||
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
||||
// netInterfaceV4 is a DHCP interface for IPv4 address family.
|
||||
type netInterfaceV4 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
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
}
|
||||
|
||||
// newNetInterfaceV4 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 newNetInterfaceV4(name string, conf *IPv4Config) (i *netInterfaceV4, 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)
|
||||
}
|
||||
|
||||
i = &netInterfaceV4{
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV4 []*netInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
@ -52,57 +53,6 @@ func (conf *IPv6Config) validate() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
i = &iface6{
|
||||
name: name,
|
||||
rangeStart: conf.RangeStart,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
|
@ -133,3 +83,79 @@ func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
|
|||
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
|
||||
return int(a.Code) - int(b.Code)
|
||||
}
|
||||
|
||||
// netInterfaceV6 is a DHCP interface for IPv6 address family.
|
||||
//
|
||||
// TODO(e.burkov): Add options.
|
||||
type netInterfaceV6 struct {
|
||||
// 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
|
||||
|
||||
// netInterface is embedded here to provide some common network interface
|
||||
// logic.
|
||||
netInterface
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// newNetInterfaceV6 creates a new DHCP interface for IPv6 address family with
|
||||
// the given configuration.
|
||||
//
|
||||
// TODO(e.burkov): Validate properly.
|
||||
func newNetInterfaceV6(name string, conf *IPv6Config) (i *netInterfaceV6) {
|
||||
if !conf.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
i = &netInterfaceV6{
|
||||
rangeStart: conf.RangeStart,
|
||||
netInterface: netInterface{
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type netInterfacesV6 []*netInterfaceV6
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces netInterfacesV6) 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 *netInterfaceV6) (contains bool) {
|
||||
return !iface.rangeStart.Less(ip) &&
|
||||
netip.PrefixFrom(iface.rangeStart, prefLen).Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &ifaces[i].netInterface, true
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue