Pull request 2070: 4923 gopacket DHCP vol.4

Merge in DNS/adguard-home from 4923-gopacket-dhcp-vol.4 to master

Updates #4923.

Squashed commit of the following:

commit 4b87258c70ac98b2abb1ac95f7e916e244b3cd08
Merge: 61458864f 9b91a8740
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Nov 16 14:05:34 2023 +0300

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

commit 61458864f3df7a027e65060a5f0fb516cc7911a7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Nov 15 18:48:40 2023 +0300

    all: imp code

commit 506a0ab81e76beebb900f86580577563b471e4e2
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Nov 14 15:59:56 2023 +0300

    all: cleanup moving lease

commit 8d218b732662ac4308ed09d28c1bf9f65906d47c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Nov 13 18:13:39 2023 +0300

    all: rm old leases type
This commit is contained in:
Eugene Burkov 2023-11-16 14:14:40 +03:00
parent 9b91a87406
commit 8bb1aad739
17 changed files with 332 additions and 311 deletions

View file

@ -8,6 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
) )
@ -49,16 +50,16 @@ type ServerConfig struct {
// DHCPServer - DHCP server interface // DHCPServer - DHCP server interface
type DHCPServer interface { type DHCPServer interface {
// ResetLeases resets leases. // ResetLeases resets leases.
ResetLeases(leases []*Lease) (err error) ResetLeases(leases []*dhcpsvc.Lease) (err error)
// GetLeases returns deep clones of the current leases. // GetLeases returns deep clones of the current leases.
GetLeases(flags GetLeasesFlags) (leases []*Lease) GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease)
// AddStaticLease - add a static lease // AddStaticLease - add a static lease
AddStaticLease(l *Lease) (err error) AddStaticLease(l *dhcpsvc.Lease) (err error)
// RemoveStaticLease - remove a static lease // RemoveStaticLease - remove a static lease
RemoveStaticLease(l *Lease) (err error) RemoveStaticLease(l *dhcpsvc.Lease) (err error)
// UpdateStaticLease updates IP, hostname of the lease. // UpdateStaticLease updates IP, hostname of the lease.
UpdateStaticLease(l *Lease) (err error) UpdateStaticLease(l *dhcpsvc.Lease) (err error)
// FindMACbyIP returns a MAC address by the IP address of its lease, if // FindMACbyIP returns a MAC address by the IP address of its lease, if
// there is one. // there is one.
@ -81,7 +82,7 @@ type DHCPServer interface {
Start() (err error) Start() (err error)
// Stop - stop server // Stop - stop server
Stop() (err error) Stop() (err error)
getLeasesRef() []*Lease getLeasesRef() []*dhcpsvc.Lease
} }
// V4ServerConf - server configuration // V4ServerConf - server configuration

View file

@ -5,9 +5,13 @@ package dhcpd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"net/netip"
"os" "os"
"strings" "strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/v2/maybe" "github.com/google/renameio/v2/maybe"
@ -28,7 +32,60 @@ type dataLeases struct {
Version int `json:"version"` Version int `json:"version"`
// Leases is the list containing stored DHCP leases. // Leases is the list containing stored DHCP leases.
Leases []*Lease `json:"leases"` Leases []*dbLease `json:"leases"`
}
// dbLease is the structure of stored lease.
type dbLease struct {
Expiry string `json:"expires"`
IP netip.Addr `json:"ip"`
Hostname string `json:"hostname"`
HWAddr string `json:"mac"`
IsStatic bool `json:"static"`
}
// fromLease converts *dhcpsvc.Lease to *dbLease.
func fromLease(l *dhcpsvc.Lease) (dl *dbLease) {
var expiryStr string
if !l.IsStatic {
// The front-end is waiting for RFC 3999 format of the time value. It
// also shouldn't got an Expiry field for static leases.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2692.
expiryStr = l.Expiry.Format(time.RFC3339)
}
return &dbLease{
Expiry: expiryStr,
Hostname: l.Hostname,
HWAddr: l.HWAddr.String(),
IP: l.IP,
IsStatic: l.IsStatic,
}
}
// toLease converts *dbLease to *dhcpsvc.Lease.
func (dl *dbLease) toLease() (l *dhcpsvc.Lease, err error) {
mac, err := net.ParseMAC(dl.HWAddr)
if err != nil {
return nil, fmt.Errorf("parsing hardware address: %w", err)
}
expiry := time.Time{}
if !dl.IsStatic {
expiry, err = time.Parse(time.RFC3339, dl.Expiry)
if err != nil {
return nil, fmt.Errorf("parsing expiry time: %w", err)
}
}
return &dhcpsvc.Lease{
Expiry: expiry,
IP: dl.IP,
Hostname: dl.Hostname,
HWAddr: mac,
IsStatic: dl.IsStatic,
}, nil
} }
// dbLoad loads stored leases. // dbLoad loads stored leases.
@ -49,15 +106,22 @@ func (s *server) dbLoad() (err error) {
} }
leases := dl.Leases leases := dl.Leases
leases4 := []*dhcpsvc.Lease{}
leases4 := []*Lease{} leases6 := []*dhcpsvc.Lease{}
leases6 := []*Lease{}
for _, l := range leases { for _, l := range leases {
if l.IP.Is4() { var lease *dhcpsvc.Lease
leases4 = append(leases4, l) lease, err = l.toLease()
if err != nil {
log.Info("dhcp: invalid lease: %s", err)
continue
}
if lease.IP.Is4() {
leases4 = append(leases4, lease)
} else { } else {
leases6 = append(leases6, l) leases6 = append(leases6, lease)
} }
} }
@ -73,8 +137,12 @@ func (s *server) dbLoad() (err error) {
} }
} }
log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB", log.Info(
len(leases4), len(leases6), len(leases)) "dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
len(leases4),
len(leases6),
len(leases),
)
return nil return nil
} }
@ -83,24 +151,26 @@ func (s *server) dbLoad() (err error) {
func (s *server) dbStore() (err error) { func (s *server) dbStore() (err error) {
// Use an empty slice here as opposed to nil so that it doesn't write // Use an empty slice here as opposed to nil so that it doesn't write
// "null" into the database file if leases are empty. // "null" into the database file if leases are empty.
leases := []*Lease{} leases := []*dbLease{}
leases4 := s.srv4.getLeasesRef() for _, l := range s.srv4.getLeasesRef() {
leases = append(leases, leases4...) leases = append(leases, fromLease(l))
}
if s.srv6 != nil { if s.srv6 != nil {
leases6 := s.srv6.getLeasesRef() for _, l := range s.srv6.getLeasesRef() {
leases = append(leases, leases6...) leases = append(leases, fromLease(l))
}
} }
return writeDB(s.conf.dbFilePath, leases) return writeDB(s.conf.dbFilePath, leases)
} }
// writeDB writes leases to file at path. // writeDB writes leases to file at path.
func writeDB(path string, leases []*Lease) (err error) { func writeDB(path string, leases []*dbLease) (err error) {
defer func() { err = errors.Annotate(err, "writing db: %w") }() defer func() { err = errors.Annotate(err, "writing db: %w") }()
slices.SortFunc(leases, func(a, b *Lease) (res int) { slices.SortFunc(leases, func(a, b *dbLease) (res int) {
return strings.Compare(a.Hostname, b.Hostname) return strings.Compare(a.Hostname, b.Hostname)
}) })

View file

@ -2,7 +2,6 @@
package dhcpd package dhcpd
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@ -12,7 +11,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/timeutil" "github.com/AdguardTeam/golibs/timeutil"
"golang.org/x/exp/slices"
) )
const ( const (
@ -29,105 +27,6 @@ const (
defaultBackoff time.Duration = 500 * time.Millisecond defaultBackoff time.Duration = 500 * time.Millisecond
) )
// Lease contains the necessary information about a DHCP lease. It's used as is
// in the database, so don't change it until it's absolutely necessary, see
// [dataVersion].
//
// TODO(e.burkov): Unexport it and use [dhcpsvc.Lease].
type Lease struct {
// Expiry is the expiration time of the lease.
Expiry time.Time `json:"expires"`
// Hostname of the client.
Hostname string `json:"hostname"`
// HWAddr is the physical hardware address (MAC address).
HWAddr net.HardwareAddr `json:"mac"`
// IP is the IP address leased to the client.
IP netip.Addr `json:"ip"`
// IsStatic defines if the lease is static.
IsStatic bool `json:"static"`
}
// 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,
}
}
// IsBlocklisted returns true if the lease is blocklisted.
//
// TODO(a.garipov): Just make it a boolean field.
func (l *Lease) IsBlocklisted() (ok bool) {
if len(l.HWAddr) == 0 {
return false
}
for _, b := range l.HWAddr {
if b != 0 {
return false
}
}
return true
}
// MarshalJSON implements the json.Marshaler interface for Lease.
func (l Lease) MarshalJSON() ([]byte, error) {
var expiryStr string
if !l.IsStatic {
// The front-end is waiting for RFC 3999 format of the time
// value. It also shouldn't got an Expiry field for static
// leases.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2692.
expiryStr = l.Expiry.Format(time.RFC3339)
}
type lease Lease
return json.Marshal(&struct {
HWAddr string `json:"mac"`
Expiry string `json:"expires,omitempty"`
lease
}{
HWAddr: l.HWAddr.String(),
Expiry: expiryStr,
lease: lease(l),
})
}
// UnmarshalJSON implements the json.Unmarshaler interface for *Lease.
func (l *Lease) UnmarshalJSON(data []byte) (err error) {
type lease Lease
aux := struct {
*lease
HWAddr string `json:"mac"`
}{
lease: (*lease)(l),
}
if err = json.Unmarshal(data, &aux); err != nil {
return err
}
l.HWAddr, err = net.ParseMAC(aux.HWAddr)
if err != nil {
return fmt.Errorf("couldn't parse MAC address: %w", err)
}
return nil
}
// OnLeaseChangedT is a callback for lease changes. // OnLeaseChangedT is a callback for lease changes.
type OnLeaseChangedT func(flags int) type OnLeaseChangedT func(flags int)
@ -370,19 +269,7 @@ func (s *server) Stop() (err error) {
// Leases returns the list of active DHCP leases. // Leases returns the list of active DHCP leases.
func (s *server) Leases() (leases []*dhcpsvc.Lease) { func (s *server) Leases() (leases []*dhcpsvc.Lease) {
ls := append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...) return append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...)
leases = make([]*dhcpsvc.Lease, len(ls))
for i, l := range ls {
leases[i] = &dhcpsvc.Lease{
Expiry: l.Expiry,
Hostname: l.Hostname,
HWAddr: l.HWAddr,
IP: l.IP,
IsStatic: l.IsStatic,
}
}
return leases
} }
// MACByIP returns a MAC address by the IP address of its lease, if there is // MACByIP returns a MAC address by the IP address of its lease, if there is
@ -414,6 +301,6 @@ func (s *server) IPByHost(host string) (ip netip.Addr) {
} }
// AddStaticLease - add static v4 lease // AddStaticLease - add static v4 lease
func (s *server) AddStaticLease(l *Lease) error { func (s *server) AddStaticLease(l *dhcpsvc.Lease) error {
return s.srv4.AddStaticLease(l) return s.srv4.AddStaticLease(l)
} }

View file

@ -9,6 +9,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -44,7 +45,7 @@ func TestDB(t *testing.T) {
s.srv6, err = v6Create(V6ServerConf{}) s.srv6, err = v6Create(V6ServerConf{})
require.NoError(t, err) require.NoError(t, err)
leases := []*Lease{{ leases := []*dhcpsvc.Lease{{
Expiry: time.Now().Add(time.Hour), Expiry: time.Now().Add(time.Hour),
Hostname: "static-1.local", Hostname: "static-1.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},

View file

@ -93,13 +93,13 @@ func leasesToStatic(leases []*dhcpsvc.Lease) (static []*leaseStatic) {
} }
// toLease converts leaseStatic to Lease or returns error. // toLease converts leaseStatic to Lease or returns error.
func (l *leaseStatic) toLease() (lease *Lease, err error) { func (l *leaseStatic) toLease() (lease *dhcpsvc.Lease, err error) {
addr, err := net.ParseMAC(l.HWAddr) addr, err := net.ParseMAC(l.HWAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't parse MAC address: %w", err) return nil, fmt.Errorf("couldn't parse MAC address: %w", err)
} }
return &Lease{ return &dhcpsvc.Lease{
HWAddr: addr, HWAddr: addr,
IP: l.IP, IP: l.IP,
Hostname: l.Hostname, Hostname: l.Hostname,
@ -593,7 +593,7 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) {
// parseLease parses a lease from r. If there is no error returns DHCPServer // parseLease parses a lease from r. If there is no error returns DHCPServer
// and *Lease. r must be non-nil. // and *Lease. r must be non-nil.
func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *Lease, err error) { func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) {
l := &leaseStatic{} l := &leaseStatic{}
err = json.NewDecoder(r).Decode(l) err = json.NewDecoder(r).Decode(l)
if err != nil { if err != nil {

View file

@ -2,6 +2,7 @@ package dhcpd
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/netip" "net/netip"
"os" "os"
@ -25,9 +26,9 @@ const (
dbFilename = "leases.db" dbFilename = "leases.db"
) )
// leaseJSON is the structure of stored lease. // leaseJSON is the structure of stored lease in a legacy database.
// //
// Deprecated: Use [Lease]. // Deprecated: Use [dbLease].
type leaseJSON struct { type leaseJSON struct {
HWAddr []byte `json:"mac"` HWAddr []byte `json:"mac"`
IP []byte `json:"ip"` IP []byte `json:"ip"`
@ -35,13 +36,28 @@ type leaseJSON struct {
Expiry int64 `json:"exp"` Expiry int64 `json:"exp"`
} }
func normalizeIP(ip net.IP) net.IP { // readOldDB reads the old database from the given path.
ip4 := ip.To4() func readOldDB(path string) (leases []*leaseJSON, err error) {
if ip4 != nil { // #nosec G304 -- Trust this path, since it's taken from the old file name
return ip4 // relative to the working directory and should generally be considered
// safe.
file, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
// Nothing to migrate.
return nil, nil
} else if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil, err
}
defer func() { err = errors.WithDeferred(err, file.Close()) }()
leases = []*leaseJSON{}
err = json.NewDecoder(file).Decode(&leases)
if err != nil {
return nil, fmt.Errorf("decoding old db: %w", err)
} }
return ip return leases, nil
} }
// migrateDB migrates stored leases if necessary. // migrateDB migrates stored leases if necessary.
@ -51,59 +67,50 @@ func migrateDB(conf *ServerConfig) (err error) {
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename) oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
dataDirPath := filepath.Join(conf.DataDir, dataFilename) dataDirPath := filepath.Join(conf.DataDir, dataFilename)
// #nosec G304 -- Trust this path, since it's taken from the old file name oldLeases, err := readOldDB(oldLeasesPath)
// relative to the working directory and should generally be considered if err != nil {
// safe. // Don't wrap the error since it's informative enough as is.
file, err := os.Open(oldLeasesPath) return err
if errors.Is(err, os.ErrNotExist) { } else if oldLeases == nil {
// Nothing to migrate. // Nothing to migrate.
return nil return nil
} else if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
} }
ljs := []leaseJSON{} leases := make([]*dbLease, 0, len(oldLeases))
err = json.NewDecoder(file).Decode(&ljs) for _, l := range oldLeases {
if err != nil { l.IP = normalizeIP(l.IP)
// Don't wrap the error since it's informative enough as is. ip, ok := netip.AddrFromSlice(l.IP)
return err
}
err = file.Close()
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
leases := []*Lease{}
for _, lj := range ljs {
lj.IP = normalizeIP(lj.IP)
ip, ok := netip.AddrFromSlice(lj.IP)
if !ok { if !ok {
log.Info("dhcp: invalid IP: %s", lj.IP) log.Info("dhcp: invalid IP: %s", l.IP)
continue continue
} }
lease := &Lease{ leases = append(leases, &dbLease{
Expiry: time.Unix(lj.Expiry, 0), Expiry: time.Unix(l.Expiry, 0).Format(time.RFC3339),
Hostname: lj.Hostname, Hostname: l.Hostname,
HWAddr: lj.HWAddr, HWAddr: net.HardwareAddr(l.HWAddr).String(),
IP: ip, IP: ip,
IsStatic: lj.Expiry == leaseExpireStatic, IsStatic: l.Expiry == leaseExpireStatic,
} })
leases = append(leases, lease)
} }
err = writeDB(dataDirPath, leases) err = writeDB(dataDirPath, leases)
if err != nil { if err != nil {
// Don't wrap the error since it's informative enough as is. // Don't wrap the error since an annotation deferred already.
return err return err
} }
return os.Remove(oldLeasesPath) return os.Remove(oldLeasesPath)
} }
// normalizeIP converts the given IP address to IPv4 if it's IPv4-mapped IPv6,
// or leaves it as is otherwise.
func normalizeIP(ip net.IP) (normalized net.IP) {
normalized = ip.To4()
if normalized != nil {
return normalized
}
return ip
}

View file

@ -2,7 +2,6 @@ package dhcpd
import ( import (
"encoding/json" "encoding/json"
"net"
"net/netip" "net/netip"
"os" "os"
"path/filepath" "path/filepath"
@ -27,16 +26,16 @@ func TestMigrateDB(t *testing.T) {
err := os.WriteFile(oldLeasesPath, []byte(testData), 0o644) err := os.WriteFile(oldLeasesPath, []byte(testData), 0o644)
require.NoError(t, err) require.NoError(t, err)
wantLeases := []*Lease{{ wantLeases := []*dbLease{{
Expiry: time.Time{}, Expiry: time.Unix(1, 0).Format(time.RFC3339),
Hostname: "test1", Hostname: "test1",
HWAddr: net.HardwareAddr{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, HWAddr: "11:22:33:44:55:66",
IP: netip.MustParseAddr("1.2.3.4"), IP: netip.MustParseAddr("1.2.3.4"),
IsStatic: true, IsStatic: true,
}, { }, {
Expiry: time.Unix(1231231231, 0), Expiry: time.Unix(1231231231, 0).Format(time.RFC3339),
Hostname: "test2", Hostname: "test2",
HWAddr: net.HardwareAddr{0x66, 0x55, 0x44, 0x33, 0x22, 0x11}, HWAddr: "66:55:44:33:22:11",
IP: netip.MustParseAddr("4.3.2.1"), IP: netip.MustParseAddr("4.3.2.1"),
IsStatic: false, IsStatic: false,
}} }}
@ -62,12 +61,12 @@ func TestMigrateDB(t *testing.T) {
leases := dl.Leases leases := dl.Leases
for i, wl := range wantLeases { for i, wantLease := range wantLeases {
assert.Equal(t, wl.Hostname, leases[i].Hostname) assert.Equal(t, wantLease.Hostname, leases[i].Hostname)
assert.Equal(t, wl.HWAddr, leases[i].HWAddr) assert.Equal(t, wantLease.HWAddr, leases[i].HWAddr)
assert.Equal(t, wl.IP, leases[i].IP) assert.Equal(t, wantLease.IP, leases[i].IP)
assert.Equal(t, wl.IsStatic, leases[i].IsStatic) assert.Equal(t, wantLease.IsStatic, leases[i].IsStatic)
require.True(t, wl.Expiry.Equal(leases[i].Expiry)) require.Equal(t, wantLease.Expiry, leases[i].Expiry)
} }
} }

View file

@ -7,6 +7,8 @@ package dhcpd
import ( import (
"net" "net"
"net/netip" "net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
) )
type winServer struct{} type winServer struct{}
@ -14,12 +16,12 @@ type winServer struct{}
// type check // type check
var _ DHCPServer = winServer{} var _ DHCPServer = winServer{}
func (winServer) ResetLeases(_ []*Lease) (err error) { return nil } func (winServer) ResetLeases(_ []*dhcpsvc.Lease) (err error) { return nil }
func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil } func (winServer) GetLeases(_ GetLeasesFlags) (leases []*dhcpsvc.Lease) { return nil }
func (winServer) getLeasesRef() []*Lease { return nil } func (winServer) getLeasesRef() []*dhcpsvc.Lease { return nil }
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil } func (winServer) AddStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil } func (winServer) RemoveStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
func (winServer) UpdateStaticLease(_ *Lease) (err error) { return nil } func (winServer) UpdateStaticLease(_ *dhcpsvc.Lease) (err error) { return nil }
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil } func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {} func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {} func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}

View file

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -38,7 +39,7 @@ type v4Server struct {
// have intersections with [implicitOpts]. // have intersections with [implicitOpts].
explicitOpts dhcpv4.Options explicitOpts dhcpv4.Options
// leasesLock protects leases, leaseHosts, and leasedOffsets. // leasesLock protects leases, hostsIndex, ipIndex, and leasedOffsets.
leasesLock sync.Mutex leasesLock sync.Mutex
// leasedOffsets contains offsets from conf.ipRange.start that have been // leasedOffsets contains offsets from conf.ipRange.start that have been
@ -46,13 +47,13 @@ type v4Server struct {
leasedOffsets *bitSet leasedOffsets *bitSet
// leases contains all dynamic and static leases. // leases contains all dynamic and static leases.
leases []*Lease leases []*dhcpsvc.Lease
// hostsIndex is the set of all hostnames of all known DHCP clients. // hostsIndex is the set of all hostnames of all known DHCP clients.
hostsIndex map[string]*Lease hostsIndex map[string]*dhcpsvc.Lease
// ipIndex is an index of leases by their IP addresses. // ipIndex is an index of leases by their IP addresses.
ipIndex map[netip.Addr]*Lease ipIndex map[netip.Addr]*dhcpsvc.Lease
} }
func (s *v4Server) enabled() (ok bool) { func (s *v4Server) enabled() (ok bool) {
@ -141,7 +142,7 @@ func (s *v4Server) IPByHost(host string) (ip netip.Addr) {
} }
// ResetLeases resets leases. // ResetLeases resets leases.
func (s *v4Server) ResetLeases(leases []*Lease) (err error) { func (s *v4Server) ResetLeases(leases []*dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }() defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
if s.conf == nil { if s.conf == nil {
@ -152,8 +153,8 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
s.leasedOffsets = newBitSet() s.leasedOffsets = newBitSet()
s.hostsIndex = make(map[string]*Lease, len(leases)) s.hostsIndex = make(map[string]*dhcpsvc.Lease, len(leases))
s.ipIndex = make(map[netip.Addr]*Lease, len(leases)) s.ipIndex = make(map[netip.Addr]*dhcpsvc.Lease, len(leases))
s.leases = nil s.leases = nil
for _, l := range leases { for _, l := range leases {
@ -173,14 +174,14 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
} }
// getLeasesRef returns the actual leases slice. For internal use only. // getLeasesRef returns the actual leases slice. For internal use only.
func (s *v4Server) getLeasesRef() []*Lease { func (s *v4Server) getLeasesRef() []*dhcpsvc.Lease {
return s.leases return s.leases
} }
// isBlocklisted returns true if this lease holds a blocklisted IP. // isBlocklisted returns true if this lease holds a blocklisted IP.
// //
// TODO(a.garipov): Make a method of *Lease? // TODO(a.garipov): Make a method of *Lease?
func (s *v4Server) isBlocklisted(l *Lease) (ok bool) { func (s *v4Server) isBlocklisted(l *dhcpsvc.Lease) (ok bool) {
if len(l.HWAddr) == 0 { if len(l.HWAddr) == 0 {
return false return false
} }
@ -196,11 +197,11 @@ func (s *v4Server) isBlocklisted(l *Lease) (ok bool) {
// 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
// use. // use.
func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) { func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease) {
// The function shouldn't return nil, because zero-length slice behaves // The function shouldn't return nil, because zero-length slice behaves
// differently in cases like marshalling. Our front-end also requires // differently in cases like marshalling. Our front-end also requires
// a non-nil value in the response. // a non-nil value in the response.
leases = []*Lease{} leases = []*dhcpsvc.Lease{}
getDynamic := flags&LeasesDynamic != 0 getDynamic := flags&LeasesDynamic != 0
getStatic := flags&LeasesStatic != 0 getStatic := flags&LeasesStatic != 0
@ -248,7 +249,7 @@ func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
const defaultHwAddrLen = 6 const defaultHwAddrLen = 6
// Add the specified IP to the black list for a time period // Add the specified IP to the black list for a time period
func (s *v4Server) blocklistLease(l *Lease) { func (s *v4Server) blocklistLease(l *dhcpsvc.Lease) {
l.HWAddr = make(net.HardwareAddr, defaultHwAddrLen) l.HWAddr = make(net.HardwareAddr, defaultHwAddrLen)
l.Hostname = "" l.Hostname = ""
l.Expiry = time.Now().Add(s.conf.leaseTime) l.Expiry = time.Now().Add(s.conf.leaseTime)
@ -284,7 +285,7 @@ func (s *v4Server) rmLeaseByIndex(i int) {
// Return error if a static lease is found // Return error if a static lease is found
// //
// TODO(s.chzhen): Refactor the code. // TODO(s.chzhen): Refactor the code.
func (s *v4Server) rmDynamicLease(lease *Lease) (err error) { func (s *v4Server) rmDynamicLease(lease *dhcpsvc.Lease) (err error) {
for i, l := range s.leases { for i, l := range s.leases {
isStatic := l.IsStatic isStatic := l.IsStatic
@ -320,7 +321,7 @@ const (
) )
// addLease adds a dynamic or static lease. // addLease adds a dynamic or static lease.
func (s *v4Server) addLease(l *Lease) (err error) { func (s *v4Server) addLease(l *dhcpsvc.Lease) (err error) {
r := s.conf.ipRange r := s.conf.ipRange
leaseIP := net.IP(l.IP.AsSlice()) leaseIP := net.IP(l.IP.AsSlice())
offset, inOffset := r.offset(leaseIP) offset, inOffset := r.offset(leaseIP)
@ -352,7 +353,7 @@ func (s *v4Server) addLease(l *Lease) (err error) {
} }
// rmLease removes a lease with the same properties. // rmLease removes a lease with the same properties.
func (s *v4Server) rmLease(lease *Lease) (err error) { func (s *v4Server) rmLease(lease *dhcpsvc.Lease) (err error) {
if len(s.leases) == 0 { if len(s.leases) == 0 {
return nil return nil
} }
@ -378,7 +379,7 @@ const ErrUnconfigured errors.Error = "server is unconfigured"
// AddStaticLease implements the DHCPServer interface for *v4Server. It is // AddStaticLease implements the DHCPServer interface for *v4Server. It is
// safe for concurrent use. // safe for concurrent use.
func (s *v4Server) AddStaticLease(l *Lease) (err error) { func (s *v4Server) AddStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }() defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
if s.conf == nil { if s.conf == nil {
@ -435,7 +436,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
} }
// UpdateStaticLease updates IP, hostname of the static lease. // UpdateStaticLease updates IP, hostname of the static lease.
func (s *v4Server) UpdateStaticLease(l *Lease) (err error) { func (s *v4Server) UpdateStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { defer func() {
if err != nil { if err != nil {
err = errors.Annotate(err, "dhcpv4: updating static lease: %w") err = errors.Annotate(err, "dhcpv4: updating static lease: %w")
@ -474,7 +475,7 @@ func (s *v4Server) UpdateStaticLease(l *Lease) (err error) {
} }
// validateStaticLease returns an error if the static lease is invalid. // validateStaticLease returns an error if the static lease is invalid.
func (s *v4Server) validateStaticLease(l *Lease) (err error) { func (s *v4Server) validateStaticLease(l *dhcpsvc.Lease) (err error) {
hostname, err := normalizeHostname(l.Hostname) hostname, err := normalizeHostname(l.Hostname)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
@ -511,7 +512,7 @@ func (s *v4Server) validateStaticLease(l *Lease) (err error) {
// updateStaticLease safe removes dynamic lease with the same properties and // updateStaticLease safe removes dynamic lease with the same properties and
// then adds a static lease l. // then adds a static lease l.
func (s *v4Server) updateStaticLease(l *Lease) (err error) { func (s *v4Server) updateStaticLease(l *dhcpsvc.Lease) (err error) {
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
@ -529,7 +530,7 @@ func (s *v4Server) updateStaticLease(l *Lease) (err error) {
} }
// RemoveStaticLease removes a static lease. It is safe for concurrent use. // RemoveStaticLease removes a static lease. It is safe for concurrent use.
func (s *v4Server) RemoveStaticLease(l *Lease) (err error) { func (s *v4Server) RemoveStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }() defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
if s.conf == nil { if s.conf == nil {
@ -606,7 +607,7 @@ func (s *v4Server) addrAvailable(target net.IP) (avail bool) {
} }
// findLease finds a lease by its MAC-address. // findLease finds a lease by its MAC-address.
func (s *v4Server) findLease(mac net.HardwareAddr) (l *Lease) { func (s *v4Server) findLease(mac net.HardwareAddr) (l *dhcpsvc.Lease) {
for _, l = range s.leases { for _, l = range s.leases {
if bytes.Equal(mac, l.HWAddr) { if bytes.Equal(mac, l.HWAddr) {
return l return l
@ -646,8 +647,8 @@ func (s *v4Server) findExpiredLease() int {
// reserveLease reserves a lease for a client by its MAC-address. It returns // reserveLease reserves a lease for a client by its MAC-address. It returns
// nil if it couldn't allocate a new lease. // nil if it couldn't allocate a new lease.
func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) { func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *dhcpsvc.Lease, err error) {
l = &Lease{HWAddr: slices.Clone(mac)} l = &dhcpsvc.Lease{HWAddr: slices.Clone(mac)}
nextIP := s.nextIP() nextIP := s.nextIP()
if nextIP == nil { if nextIP == nil {
@ -679,7 +680,7 @@ func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
// commitLease refreshes l's values. It takes the desired hostname into account // commitLease refreshes l's values. It takes the desired hostname into account
// when setting it into the lease, but generates a unique one if the provided // when setting it into the lease, but generates a unique one if the provided
// can't be used. // can't be used.
func (s *v4Server) commitLease(l *Lease, hostname string) { func (s *v4Server) commitLease(l *dhcpsvc.Lease, hostname string) {
prev := l.Hostname prev := l.Hostname
hostname = s.validHostnameForClient(hostname, l.IP) hostname = s.validHostnameForClient(hostname, l.IP)
@ -709,7 +710,7 @@ func (s *v4Server) commitLease(l *Lease, hostname string) {
// allocateLease allocates a new lease for the MAC address. If there are no IP // allocateLease allocates a new lease for the MAC address. If there are no IP
// addresses left, both l and err are nil. // addresses left, both l and err are nil.
func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) { func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *dhcpsvc.Lease, err error) {
for { for {
l, err = s.reserveLease(mac) l, err = s.reserveLease(mac)
if err != nil { if err != nil {
@ -728,7 +729,7 @@ func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) {
} }
// handleDiscover is the handler for the DHCP Discover request. // handleDiscover is the handler for the DHCP Discover request.
func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) { func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *dhcpsvc.Lease, err error) {
mac := req.ClientHWAddr mac := req.ClientHWAddr
defer s.conf.notify(LeaseChangedDBStore) defer s.conf.notify(LeaseChangedDBStore)
@ -787,7 +788,7 @@ func OptionFQDN(fqdn string) (opt dhcpv4.Option) {
// checkLease checks if the pair of mac and ip is already leased. The mismatch // checkLease checks if the pair of mac and ip is already leased. The mismatch
// is true when the existing lease has the same hardware address but differs in // is true when the existing lease has the same hardware address but differs in
// its IP address. // its IP address.
func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mismatch bool) { func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (l *dhcpsvc.Lease, mismatch bool) {
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()
@ -798,7 +799,7 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi
return nil, false return nil, false
} }
for _, l := range s.leases { for _, l = range s.leases {
if !bytes.Equal(l.HWAddr, mac) { if !bytes.Equal(l.HWAddr, mac) {
continue continue
} }
@ -823,7 +824,7 @@ func (s *v4Server) handleSelecting(
req *dhcpv4.DHCPv4, req *dhcpv4.DHCPv4,
reqIP net.IP, reqIP net.IP,
sid net.IP, sid net.IP,
) (l *Lease, needsReply bool) { ) (l *dhcpsvc.Lease, needsReply bool) {
// Client inserts the address of the selected server in server identifier, // Client inserts the address of the selected server in server identifier,
// ciaddr MUST be zero. // ciaddr MUST be zero.
mac := req.ClientHWAddr mac := req.ClientHWAddr
@ -857,7 +858,10 @@ func (s *v4Server) handleSelecting(
} }
// handleInitReboot handles the DHCPREQUEST generated during INIT-REBOOT state. // handleInitReboot handles the DHCPREQUEST generated during INIT-REBOOT state.
func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, needsReply bool) { func (s *v4Server) handleInitReboot(
req *dhcpv4.DHCPv4,
reqIP net.IP,
) (l *dhcpsvc.Lease, needsReply bool) {
mac := req.ClientHWAddr mac := req.ClientHWAddr
ip4 := reqIP.To4() ip4 := reqIP.To4()
@ -899,7 +903,7 @@ func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease,
// handleRenew handles the DHCPREQUEST generated during RENEWING or REBINDING // handleRenew handles the DHCPREQUEST generated during RENEWING or REBINDING
// state. // state.
func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *Lease, needsReply bool) { func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *dhcpsvc.Lease, needsReply bool) {
mac := req.ClientHWAddr mac := req.ClientHWAddr
// ciaddr MUST be filled in with client's IP address. // ciaddr MUST be filled in with client's IP address.
@ -926,7 +930,7 @@ func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *Lease, needsReply bool) {
// handleByRequestType handles the DHCPREQUEST according to the state during // handleByRequestType handles the DHCPREQUEST according to the state during
// which it's generated by client. // which it's generated by client.
func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) { func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *dhcpsvc.Lease, needsReply bool) {
reqIP, sid := req.RequestedIPAddress(), req.ServerIdentifier() reqIP, sid := req.RequestedIPAddress(), req.ServerIdentifier()
if sid != nil && !sid.IsUnspecified() { if sid != nil && !sid.IsUnspecified() {
@ -950,7 +954,7 @@ func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *Lease, needsR
// handleRequest is the handler for a DHCPREQUEST message. // handleRequest is the handler for a DHCPREQUEST message.
// //
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.2. // See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.2.
func (s *v4Server) handleRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) { func (s *v4Server) handleRequest(req, resp *dhcpv4.DHCPv4) (lease *dhcpsvc.Lease, needsReply bool) {
lease, needsReply = s.handleByRequestType(req) lease, needsReply = s.handleByRequestType(req)
if lease == nil { if lease == nil {
return nil, needsReply return nil, needsReply
@ -1043,7 +1047,7 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
} }
// findLeaseForIP returns a lease for provided ip and mac. // findLeaseForIP returns a lease for provided ip and mac.
func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *Lease) { func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *dhcpsvc.Lease) {
netIP, ok := netip.AddrFromSlice(ip) netIP, ok := netip.AddrFromSlice(ip)
if !ok { if !ok {
log.Info("dhcpv4: invalid IP: %s", ip) log.Info("dhcpv4: invalid IP: %s", ip)
@ -1106,7 +1110,11 @@ func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
} }
// messageHandler describes a DHCPv4 message handler function. // messageHandler describes a DHCPv4 message handler function.
type messageHandler func(s *v4Server, req, resp *dhcpv4.DHCPv4) (rCode int, l *Lease, err error) type messageHandler func(
s *v4Server,
req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4,
) (rCode int, l *dhcpsvc.Lease, err error)
// messageHandlers is a map of handlers for various messages with message types // messageHandlers is a map of handlers for various messages with message types
// keys. // keys.
@ -1115,7 +1123,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
s *v4Server, s *v4Server,
req *dhcpv4.DHCPv4, req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) { ) (rCode int, l *dhcpsvc.Lease, err error) {
l, err = s.handleDiscover(req, resp) l, err = s.handleDiscover(req, resp)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("handling discover: %s", err) return 0, nil, fmt.Errorf("handling discover: %s", err)
@ -1131,7 +1139,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
s *v4Server, s *v4Server,
req *dhcpv4.DHCPv4, req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) { ) (rCode int, l *dhcpsvc.Lease, err error) {
var toReply bool var toReply bool
l, toReply = s.handleRequest(req, resp) l, toReply = s.handleRequest(req, resp)
if l == nil { if l == nil {
@ -1149,7 +1157,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
s *v4Server, s *v4Server,
req *dhcpv4.DHCPv4, req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) { ) (rCode int, l *dhcpsvc.Lease, err error) {
err = s.handleDecline(req, resp) err = s.handleDecline(req, resp)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("handling decline: %s", err) return 0, nil, fmt.Errorf("handling decline: %s", err)
@ -1161,7 +1169,7 @@ var messageHandlers = map[dhcpv4.MessageType]messageHandler{
s *v4Server, s *v4Server,
req *dhcpv4.DHCPv4, req *dhcpv4.DHCPv4,
resp *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4,
) (rCode int, l *Lease, err error) { ) (rCode int, l *dhcpsvc.Lease, err error) {
err = s.handleRelease(req, resp) err = s.handleRelease(req, resp)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("handling release: %s", err) return 0, nil, fmt.Errorf("handling release: %s", err)
@ -1402,8 +1410,8 @@ func (s *v4Server) Stop() (err error) {
// Create DHCPv4 server // Create DHCPv4 server
func v4Create(conf *V4ServerConf) (srv *v4Server, err error) { func v4Create(conf *V4ServerConf) (srv *v4Server, err error) {
s := &v4Server{ s := &v4Server{
hostsIndex: map[string]*Lease{}, hostsIndex: map[string]*dhcpsvc.Lease{},
ipIndex: map[netip.Addr]*Lease{}, ipIndex: map[netip.Addr]*dhcpsvc.Lease{},
} }
err = conf.Validate() err = conf.Validate()

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
@ -67,7 +68,7 @@ func TestV4Server_leasing(t *testing.T) {
s := defaultSrv(t) s := defaultSrv(t)
t.Run("add_static", func(t *testing.T) { t.Run("add_static", func(t *testing.T) {
err := s.AddStaticLease(&Lease{ err := s.AddStaticLease(&dhcpsvc.Lease{
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,
@ -76,7 +77,7 @@ func TestV4Server_leasing(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("same_name", func(t *testing.T) { t.Run("same_name", func(t *testing.T) {
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&dhcpsvc.Lease{
Hostname: staticName, Hostname: staticName,
HWAddr: anotherMAC, HWAddr: anotherMAC,
IP: anotherIP, IP: anotherIP,
@ -90,7 +91,7 @@ func TestV4Server_leasing(t *testing.T) {
"dynamic leases for " + anotherIP.String() + "dynamic leases for " + anotherIP.String() +
" (" + staticMAC.String() + "): static lease already exists" " (" + staticMAC.String() + "): static lease already exists"
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&dhcpsvc.Lease{
Hostname: anotherName, Hostname: anotherName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: anotherIP, IP: anotherIP,
@ -104,7 +105,7 @@ func TestV4Server_leasing(t *testing.T) {
"dynamic leases for " + staticIP.String() + "dynamic leases for " + staticIP.String() +
" (" + anotherMAC.String() + "): static lease already exists" " (" + anotherMAC.String() + "): static lease already exists"
err = s.AddStaticLease(&Lease{ err = s.AddStaticLease(&dhcpsvc.Lease{
Hostname: anotherName, Hostname: anotherName,
HWAddr: anotherMAC, HWAddr: anotherMAC,
IP: staticIP, IP: staticIP,
@ -208,11 +209,11 @@ func TestV4Server_AddRemove_static(t *testing.T) {
require.Empty(t, ls) require.Empty(t, ls)
testCases := []struct { testCases := []struct {
lease *Lease lease *dhcpsvc.Lease
name string name string
wantErrMsg string wantErrMsg string
}{{ }{{
lease: &Lease{ lease: &dhcpsvc.Lease{
Hostname: "success.local", Hostname: "success.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -220,7 +221,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
name: "success", name: "success",
wantErrMsg: "", wantErrMsg: "",
}, { }, {
lease: &Lease{ lease: &dhcpsvc.Lease{
Hostname: "probably-router.local", Hostname: "probably-router.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: DefaultGatewayIP, IP: DefaultGatewayIP,
@ -229,7 +230,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
wantErrMsg: "dhcpv4: adding static lease: " + wantErrMsg: "dhcpv4: adding static lease: " +
`can't assign the gateway IP "192.168.10.1" to the lease`, `can't assign the gateway IP "192.168.10.1" to the lease`,
}, { }, {
lease: &Lease{ lease: &dhcpsvc.Lease{
Hostname: "ip6.local", Hostname: "ip6.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("ffff::1"), IP: netip.MustParseAddr("ffff::1"),
@ -238,7 +239,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
wantErrMsg: `dhcpv4: adding static lease: ` + wantErrMsg: `dhcpv4: adding static lease: ` +
`invalid IP "ffff::1": only IPv4 is supported`, `invalid IP "ffff::1": only IPv4 is supported`,
}, { }, {
lease: &Lease{ lease: &dhcpsvc.Lease{
Hostname: "bad-mac.local", Hostname: "bad-mac.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -247,7 +248,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` + wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` +
`bad mac address length 2, allowed: [6 8 20]`, `bad mac address length 2, allowed: [6 8 20]`,
}, { }, {
lease: &Lease{ lease: &dhcpsvc.Lease{
Hostname: "bad-lbl-.local", Hostname: "bad-lbl-.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -266,7 +267,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
return return
} }
err = s.RemoveStaticLease(&Lease{ err = s.RemoveStaticLease(&dhcpsvc.Lease{
IP: tc.lease.IP, IP: tc.lease.IP,
HWAddr: tc.lease.HWAddr, HWAddr: tc.lease.HWAddr,
}) })
@ -289,7 +290,7 @@ func TestV4_AddReplace(t *testing.T) {
s, ok := sIface.(*v4Server) s, ok := sIface.(*v4Server)
require.True(t, ok) require.True(t, ok)
dynLeases := []Lease{{ dynLeases := []dhcpsvc.Lease{{
Hostname: "dynamic-1.local", Hostname: "dynamic-1.local",
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -304,7 +305,7 @@ func TestV4_AddReplace(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
stLeases := []*Lease{{ stLeases := []*dhcpsvc.Lease{{
Hostname: "static-1.local", Hostname: "static-1.local",
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -513,7 +514,7 @@ func TestV4StaticLease_Get(t *testing.T) {
s.conf.dnsIPAddrs = []netip.Addr{dnsAddr} s.conf.dnsIPAddrs = []netip.Addr{dnsAddr}
s.implicitOpts.Update(dhcpv4.OptDNS(dnsAddr.AsSlice())) s.implicitOpts.Update(dhcpv4.OptDNS(dnsAddr.AsSlice()))
l := &Lease{ l := &dhcpsvc.Lease{
Hostname: "static-1.local", Hostname: "static-1.local",
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: netip.MustParseAddr("192.168.10.150"), IP: netip.MustParseAddr("192.168.10.150"),
@ -779,7 +780,7 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
s := &v4Server{ s := &v4Server{
leases: []*Lease{{ leases: []*dhcpsvc.Lease{{
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,
@ -791,11 +792,11 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
IP: anotherIP, IP: anotherIP,
}}, }},
} }
s.ipIndex = map[netip.Addr]*Lease{ s.ipIndex = map[netip.Addr]*dhcpsvc.Lease{
staticIP: s.leases[0], staticIP: s.leases[0],
anotherIP: s.leases[1], anotherIP: s.leases[1],
} }
s.hostsIndex = map[string]*Lease{ s.hostsIndex = map[string]*dhcpsvc.Lease{
staticName: s.leases[0], staticName: s.leases[0],
anotherName: s.leases[1], anotherName: s.leases[1],
} }
@ -845,7 +846,7 @@ func TestV4Server_handleDecline(t *testing.T) {
s4, ok := s.(*v4Server) s4, ok := s.(*v4Server)
require.True(t, ok) require.True(t, ok)
s4.leases = []*Lease{{ s4.leases = []*dhcpsvc.Lease{{
Hostname: dynamicName, Hostname: dynamicName,
HWAddr: dynamicMAC, HWAddr: dynamicMAC,
IP: dynamicIP, IP: dynamicIP,
@ -887,7 +888,7 @@ func TestV4Server_handleRelease(t *testing.T) {
s4, ok := s.(*v4Server) s4, ok := s.(*v4Server)
require.True(t, ok) require.True(t, ok)
s4.leases = []*Lease{{ s4.leases = []*dhcpsvc.Lease{{
Hostname: dynamicName, Hostname: dynamicName,
HWAddr: dynamicMAC, HWAddr: dynamicMAC,
IP: dynamicIP, IP: dynamicIP,

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -31,7 +32,7 @@ type v6Server struct {
sid dhcpv6.DUID sid dhcpv6.DUID
srv *server6.Server srv *server6.Server
leases []*Lease leases []*dhcpsvc.Lease
leasesLock sync.Mutex leasesLock sync.Mutex
ipAddrs [256]byte ipAddrs [256]byte
} }
@ -87,7 +88,7 @@ func (s *v6Server) IPByHost(host string) (ip netip.Addr) {
} }
// ResetLeases resets leases. // ResetLeases resets leases.
func (s *v6Server) ResetLeases(leases []*Lease) (err error) { func (s *v6Server) ResetLeases(leases []*dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
s.leasesLock.Lock() s.leasesLock.Lock()
@ -111,12 +112,14 @@ func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
// 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
// use. // use.
func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) { func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*dhcpsvc.Lease) {
// The function shouldn't return nil value because zero-length slice // The function shouldn't return nil value because zero-length slice
// behaves differently in cases like marshalling. Our front-end also // behaves differently in cases like marshalling. Our front-end also
// requires non-nil value in the response. // requires non-nil value in the response.
leases = []*Lease{} leases = []*dhcpsvc.Lease{}
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock()
for _, l := range s.leases { for _, l := range s.leases {
if l.IsStatic { if l.IsStatic {
if (flags & LeasesStatic) != 0 { if (flags & LeasesStatic) != 0 {
@ -128,12 +131,12 @@ func (s *v6Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
} }
} }
} }
s.leasesLock.Unlock()
return leases return leases
} }
// getLeasesRef returns the actual leases slice. For internal use only. // getLeasesRef returns the actual leases slice. For internal use only.
func (s *v6Server) getLeasesRef() []*Lease { func (s *v6Server) getLeasesRef() []*dhcpsvc.Lease {
return s.leases return s.leases
} }
@ -174,7 +177,7 @@ func (s *v6Server) leaseRemoveSwapByIndex(i int) {
// Remove a dynamic lease with the same properties // Remove a dynamic lease with the same properties
// Return error if a static lease is found // Return error if a static lease is found
func (s *v6Server) rmDynamicLease(lease *Lease) (err error) { func (s *v6Server) rmDynamicLease(lease *dhcpsvc.Lease) (err error) {
for i := 0; i < len(s.leases); i++ { for i := 0; i < len(s.leases); i++ {
l := s.leases[i] l := s.leases[i]
@ -204,7 +207,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) {
} }
// AddStaticLease adds a static lease. It is safe for concurrent use. // AddStaticLease adds a static lease. It is safe for concurrent use.
func (s *v6Server) AddStaticLease(l *Lease) (err error) { func (s *v6Server) AddStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
if !l.IP.Is6() { if !l.IP.Is6() {
@ -236,7 +239,7 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) {
} }
// UpdateStaticLease updates IP, hostname of the static lease. // UpdateStaticLease updates IP, hostname of the static lease.
func (s *v6Server) UpdateStaticLease(l *Lease) (err error) { func (s *v6Server) UpdateStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { defer func() {
if err != nil { if err != nil {
err = errors.Annotate(err, "dhcpv6: updating static lease: %w") err = errors.Annotate(err, "dhcpv6: updating static lease: %w")
@ -267,7 +270,7 @@ func (s *v6Server) UpdateStaticLease(l *Lease) (err error) {
} }
// RemoveStaticLease removes a static lease. It is safe for concurrent use. // RemoveStaticLease removes a static lease. It is safe for concurrent use.
func (s *v6Server) RemoveStaticLease(l *Lease) (err error) { func (s *v6Server) RemoveStaticLease(l *dhcpsvc.Lease) (err error) {
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
if !l.IP.Is6() { if !l.IP.Is6() {
@ -292,7 +295,7 @@ func (s *v6Server) RemoveStaticLease(l *Lease) (err error) {
} }
// Add a lease // Add a lease
func (s *v6Server) addLease(l *Lease) { func (s *v6Server) addLease(l *dhcpsvc.Lease) {
s.leases = append(s.leases, l) s.leases = append(s.leases, l)
ip := l.IP.As16() ip := l.IP.As16()
s.ipAddrs[ip[15]] = 1 s.ipAddrs[ip[15]] = 1
@ -300,7 +303,7 @@ func (s *v6Server) addLease(l *Lease) {
} }
// Remove a lease with the same properties // Remove a lease with the same properties
func (s *v6Server) rmLease(lease *Lease) (err error) { func (s *v6Server) rmLease(lease *dhcpsvc.Lease) (err error) {
for i, l := range s.leases { for i, l := range s.leases {
if l.IP == lease.IP { if l.IP == lease.IP {
if !bytes.Equal(l.HWAddr, lease.HWAddr) || if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
@ -318,7 +321,7 @@ func (s *v6Server) rmLease(lease *Lease) (err error) {
} }
// Find lease by MAC. // Find lease by MAC.
func (s *v6Server) findLease(mac net.HardwareAddr) (lease *Lease) { func (s *v6Server) findLease(mac net.HardwareAddr) (lease *dhcpsvc.Lease) {
for i := range s.leases { for i := range s.leases {
if bytes.Equal(mac, s.leases[i].HWAddr) { if bytes.Equal(mac, s.leases[i].HWAddr) {
return s.leases[i] return s.leases[i]
@ -356,8 +359,8 @@ func (s *v6Server) findFreeIP() net.IP {
} }
// Reserve lease for MAC // Reserve lease for MAC
func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease { func (s *v6Server) reserveLease(mac net.HardwareAddr) *dhcpsvc.Lease {
l := Lease{ l := dhcpsvc.Lease{
HWAddr: make([]byte, len(mac)), HWAddr: make([]byte, len(mac)),
} }
@ -390,7 +393,7 @@ func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease {
return &l return &l
} }
func (s *v6Server) commitDynamicLease(l *Lease) { func (s *v6Server) commitDynamicLease(l *dhcpsvc.Lease) {
l.Expiry = time.Now().Add(s.conf.leaseTime) l.Expiry = time.Now().Add(s.conf.leaseTime)
s.leasesLock.Lock() s.leasesLock.Lock()
@ -438,7 +441,7 @@ func (s *v6Server) checkSID(msg *dhcpv6.Message) error {
} }
// . IAAddress must be equal to the lease's IP // . IAAddress must be equal to the lease's IP
func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error { func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *dhcpsvc.Lease) error {
switch msg.Type() { switch msg.Type() {
case dhcpv6.MessageTypeRequest, case dhcpv6.MessageTypeRequest,
dhcpv6.MessageTypeConfirm, dhcpv6.MessageTypeConfirm,
@ -464,7 +467,7 @@ func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error {
} }
// Store lease in DB (if necessary) and return lease life time // Store lease in DB (if necessary) and return lease life time
func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration { func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *dhcpsvc.Lease) time.Duration {
lifetime := s.conf.leaseTime lifetime := s.conf.leaseTime
switch msg.Type() { switch msg.Type() {
@ -506,7 +509,7 @@ func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool {
return false return false
} }
var lease *Lease var lease *dhcpsvc.Lease
func() { func() {
s.leasesLock.Lock() s.leasesLock.Lock()
defer s.leasesLock.Unlock() defer s.leasesLock.Unlock()

View file

@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/iana" "github.com/insomniacslk/dhcp/iana"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -28,7 +29,7 @@ func TestV6_AddRemove_static(t *testing.T) {
require.Empty(t, s.GetLeases(LeasesStatic)) require.Empty(t, s.GetLeases(LeasesStatic))
// Add static lease. // Add static lease.
l := &Lease{ l := &dhcpsvc.Lease{
IP: netip.MustParseAddr("2001::1"), IP: netip.MustParseAddr("2001::1"),
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
} }
@ -47,7 +48,7 @@ func TestV6_AddRemove_static(t *testing.T) {
assert.True(t, ls[0].IsStatic) assert.True(t, ls[0].IsStatic)
// Try to remove non-existent static lease. // Try to remove non-existent static lease.
err = s.RemoveStaticLease(&Lease{ err = s.RemoveStaticLease(&dhcpsvc.Lease{
IP: netip.MustParseAddr("2001::2"), IP: netip.MustParseAddr("2001::2"),
HWAddr: l.HWAddr, HWAddr: l.HWAddr,
}) })
@ -72,7 +73,7 @@ func TestV6_AddReplace(t *testing.T) {
require.True(t, ok) require.True(t, ok)
// Add dynamic leases. // Add dynamic leases.
dynLeases := []*Lease{{ dynLeases := []*dhcpsvc.Lease{{
IP: netip.MustParseAddr("2001::1"), IP: netip.MustParseAddr("2001::1"),
HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
}, { }, {
@ -84,7 +85,7 @@ func TestV6_AddReplace(t *testing.T) {
s.addLease(l) s.addLease(l)
} }
stLeases := []*Lease{{ stLeases := []*dhcpsvc.Lease{{
IP: netip.MustParseAddr("2001::1"), IP: netip.MustParseAddr("2001::1"),
HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
}, { }, {
@ -126,7 +127,7 @@ func TestV6GetLease(t *testing.T) {
LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, LinkLayerAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
} }
l := &Lease{ l := &dhcpsvc.Lease{
IP: netip.MustParseAddr("2001::1"), IP: netip.MustParseAddr("2001::1"),
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
} }
@ -324,7 +325,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
s := &v6Server{ s := &v6Server{
leases: []*Lease{{ leases: []*dhcpsvc.Lease{{
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,
@ -337,7 +338,7 @@ func TestV6_FindMACbyIP(t *testing.T) {
}}, }},
} }
s.leases = []*Lease{{ s.leases = []*dhcpsvc.Lease{{
Hostname: staticName, Hostname: staticName,
HWAddr: staticMAC, HWAddr: staticMAC,
IP: staticIP, IP: staticIP,

View file

@ -43,7 +43,7 @@ func (conf *Config) Validate() (err error) {
case !conf.Enabled: case !conf.Enabled:
return nil return nil
case conf.ICMPTimeout < 0: case conf.ICMPTimeout < 0:
return fmt.Errorf("icmp timeout %s must be non-negative", conf.ICMPTimeout) return newMustErr("icmp timeout", "be non-negative", conf.ICMPTimeout)
} }
err = netutil.ValidateDomainName(conf.LocalDomainName) err = netutil.ValidateDomainName(conf.LocalDomainName)
@ -68,9 +68,9 @@ func (conf *Config) Validate() (err error) {
return nil return nil
} }
// mustBeErr returns an error that indicates that valName must be as must // newMustErr returns an error that indicates that valName must be as must
// describes. // describes.
func mustBeErr(valName, must string, val fmt.Stringer) (err error) { func newMustErr(valName, must string, val fmt.Stringer) (err error) {
return fmt.Errorf("%s %s must %s", valName, val, must) return fmt.Errorf("%s %s must %s", valName, val, must)
} }

View file

@ -10,13 +10,13 @@ import (
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh" "github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"golang.org/x/exp/slices"
) )
// Lease is a DHCP lease. // Lease is a DHCP lease.
// //
// TODO(e.burkov): Consider it to [agh], since it also may be needed in // TODO(e.burkov): Consider moving it to [agh], since it also may be needed in
// [websvc]. Also think of implementing iterating methods with appropriate // [websvc].
// signatures.
type Lease struct { type Lease struct {
// IP is the IP address leased to the client. // IP is the IP address leased to the client.
IP netip.Addr IP netip.Addr
@ -34,6 +34,21 @@ type Lease struct {
IsStatic bool 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,
}
}
type Interface interface { type Interface interface {
agh.ServiceWithConfig[*Config] agh.ServiceWithConfig[*Config]
@ -57,6 +72,9 @@ type Interface interface {
IPByHost(host string) (ip netip.Addr) IPByHost(host string) (ip netip.Addr)
// Leases returns all the active DHCP leases. // Leases returns all the active DHCP leases.
//
// TODO(e.burkov): Consider implementing iterating methods with appropriate
// signatures instead of cloning the whole list.
Leases() (ls []*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
@ -91,6 +109,9 @@ func (Empty) Shutdown(_ context.Context) (err error) { return nil }
// 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 }
// type check
var _ Interface = Empty{}
// Enabled implements the [Interface] interface for Empty. // Enabled implements the [Interface] interface for Empty.
func (Empty) Enabled() (ok bool) { return false } func (Empty) Enabled() (ok bool) { return false }
@ -103,9 +124,6 @@ func (Empty) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
// IPByHost implements the [Interface] interface for Empty. // IPByHost implements the [Interface] interface for Empty.
func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} } func (Empty) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
// type check
var _ Interface = Empty{}
// Leases implements the [Interface] interface for Empty. // Leases implements the [Interface] interface for Empty.
func (Empty) Leases() (leases []*Lease) { return nil } func (Empty) Leases() (leases []*Lease) { return nil }

View file

@ -25,6 +25,9 @@ type DHCPServer struct {
// interfaces6 is the set of IPv6 interfaces sorted by interface name. // interfaces6 is the set of IPv6 interfaces sorted by interface name.
interfaces6 []*iface6 interfaces6 []*iface6
// leases is the set of active DHCP leases.
leases []*Lease
// icmpTimeout is the timeout for checking another DHCP server's presence. // icmpTimeout is the timeout for checking another DHCP server's presence.
icmpTimeout time.Duration icmpTimeout time.Duration
} }
@ -75,3 +78,23 @@ func New(conf *Config) (srv *DHCPServer, err error) {
icmpTimeout: conf.ICMPTimeout, icmpTimeout: conf.ICMPTimeout,
}, nil }, nil
} }
// type check
//
// TODO(e.burkov): Uncomment when the [Interface] interface is implemented.
// var _ Interface = (*DHCPServer)(nil)
// Enabled implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) Enabled() (ok bool) {
return srv.enabled.Load()
}
// 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 {
leases = append(leases, lease.Clone())
}
return leases
}

View file

@ -45,15 +45,15 @@ func (conf *IPv4Config) validate() (err error) {
case !conf.Enabled: case !conf.Enabled:
return nil return nil
case !conf.GatewayIP.Is4(): case !conf.GatewayIP.Is4():
return mustBeErr("gateway ip", "be a valid ipv4", conf.GatewayIP) return newMustErr("gateway ip", "be a valid ipv4", conf.GatewayIP)
case !conf.SubnetMask.Is4(): case !conf.SubnetMask.Is4():
return mustBeErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask) return newMustErr("subnet mask", "be a valid ipv4 cidr mask", conf.SubnetMask)
case !conf.RangeStart.Is4(): case !conf.RangeStart.Is4():
return mustBeErr("range start", "be a valid ipv4", conf.RangeStart) return newMustErr("range start", "be a valid ipv4", conf.RangeStart)
case !conf.RangeEnd.Is4(): case !conf.RangeEnd.Is4():
return mustBeErr("range end", "be a valid ipv4", conf.RangeEnd) return newMustErr("range end", "be a valid ipv4", conf.RangeEnd)
case conf.LeaseDuration <= 0: case conf.LeaseDuration <= 0:
return mustBeErr("lease duration", "be less than %d", conf.LeaseDuration) return newMustErr("lease duration", "be less than %d", conf.LeaseDuration)
default: default:
return nil return nil
} }

View file

@ -314,7 +314,7 @@ func TestClientsAddExisting(t *testing.T) {
clients.dhcp = dhcpServer clients.dhcp = dhcpServer
err = dhcpServer.AddStaticLease(&dhcpd.Lease{ err = dhcpServer.AddStaticLease(&dhcpsvc.Lease{
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
IP: ip, IP: ip,
Hostname: "testhost", Hostname: "testhost",