From bea39934bdab65b984e98d75ae69547b78ac55d9 Mon Sep 17 00:00:00 2001 From: Stanislav Chzhen Date: Thu, 23 Mar 2023 16:52:01 +0300 Subject: [PATCH] Pull request 1771: AG-20352-dhcpd-lease-netip-addr Merge in DNS/adguard-home from AG-20352-dhcpd-lease-netip-addr to master Squashed commit of the following: commit 4acd094e2d6ed972bac99cdb671670f6d8e61721 Merge: 51f61c19 df61741f Author: Stanislav Chzhen Date: Thu Mar 23 16:44:17 2023 +0300 Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr commit 51f61c193fdd31ee675be5598fc361228e407eb3 Author: Stanislav Chzhen Date: Wed Mar 22 18:25:30 2023 +0300 dhcpd: fix typo commit 2e64ad55475957925d2a3010c649e0adc5f18c4f Author: Stanislav Chzhen Date: Wed Mar 22 17:20:31 2023 +0300 dhcpd: add todo commit 668d4f62fd2c5b2e168025bf0b6bb36d7b617c80 Merge: 0020006e 306c1983 Author: Stanislav Chzhen Date: Wed Mar 22 15:09:38 2023 +0300 Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr commit 0020006e89f336dc58db1a2ca3ce90d2e7a5ca16 Author: Stanislav Chzhen Date: Wed Mar 22 15:08:58 2023 +0300 all: imp code commit 9a77f79869cdbde6de760734c0d8cf504e0464ef Author: Stanislav Chzhen Date: Mon Mar 20 18:13:35 2023 +0300 dhcpd: add todo commit 638c4ce2af72235bf065a6492d58f3f1b1e7644a Merge: c82b18f1 48431f8b Author: Stanislav Chzhen Date: Mon Mar 20 13:57:19 2023 +0300 Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr commit c82b18f1408d9e353aec517f4283e2f3eb260890 Author: Stanislav Chzhen Date: Mon Mar 20 13:56:31 2023 +0300 all: imp code commit 27e518120024103c292ac1cf134c6801fffc967e Author: Stanislav Chzhen Date: Fri Mar 17 12:14:02 2023 +0300 dhcpd: imp tests commit 8e919b0ceb0b20d1935587e717c7965cd8a33ad9 Author: Stanislav Chzhen Date: Fri Mar 17 11:02:50 2023 +0300 dhcpd: add tests commit 78ddefa73a255509af1c788147d6b1c332bb66ba Merge: c68e85c4 9f7a582d Author: Stanislav Chzhen Date: Thu Mar 16 14:15:24 2023 +0300 Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr commit c68e85c40947b6c83516424a2bd7af89b99447b4 Author: Stanislav Chzhen Date: Thu Mar 16 14:14:43 2023 +0300 all: add tests commit f338086309a68c4b71036fa14f757e39358702d4 Author: Stanislav Chzhen Date: Wed Mar 15 12:44:06 2023 +0300 all: dhcpd lease netip addr --- internal/aghnet/hostgen.go | 56 ++----- internal/aghnet/hostgen_test.go | 35 +---- internal/dhcpd/db.go | 12 +- internal/dhcpd/dhcpd.go | 13 +- internal/dhcpd/dhcpd_unix_test.go | 6 +- internal/dhcpd/http_unix.go | 29 ++-- internal/dhcpd/http_unix_test.go | 161 ++++++++++++++++++++ internal/dhcpd/iprange.go | 4 +- internal/dhcpd/v4_unix.go | 82 ++++++---- internal/dhcpd/v4_unix_test.go | 197 ++++++++++++++++++++++--- internal/dhcpd/v6_unix.go | 43 ++++-- internal/dhcpd/v6_unix_test.go | 93 ++++++++++-- internal/dnsforward/dns.go | 13 +- internal/dnsforward/dnsforward_test.go | 2 +- internal/home/clients.go | 11 +- internal/home/clients_test.go | 2 +- 16 files changed, 562 insertions(+), 197 deletions(-) create mode 100644 internal/dhcpd/http_unix_test.go diff --git a/internal/aghnet/hostgen.go b/internal/aghnet/hostgen.go index b1421a64..e4031f54 100644 --- a/internal/aghnet/hostgen.go +++ b/internal/aghnet/hostgen.go @@ -2,51 +2,13 @@ package aghnet import ( "fmt" - "net" - "strconv" + "net/netip" "strings" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/stringutil" ) -// The maximum lengths of generated hostnames for different IP versions. -const ( - ipv4HostnameMaxLen = len("192-168-100-100") - ipv6HostnameMaxLen = len("ff80-f076-0000-0000-0000-0000-0000-0010") -) - -// generateIPv4Hostname generates the hostname by IP address version 4. -func generateIPv4Hostname(ipv4 net.IP) (hostname string) { - hnData := make([]byte, 0, ipv4HostnameMaxLen) - for i, part := range ipv4 { - if i > 0 { - hnData = append(hnData, '-') - } - hnData = strconv.AppendUint(hnData, uint64(part), 10) - } - - return string(hnData) -} - -// generateIPv6Hostname generates the hostname by IP address version 6. -func generateIPv6Hostname(ipv6 net.IP) (hostname string) { - hnData := make([]byte, 0, ipv6HostnameMaxLen) - for i, partsNum := 0, net.IPv6len/2; i < partsNum; i++ { - if i > 0 { - hnData = append(hnData, '-') - } - for _, val := range ipv6[i*2 : i*2+2] { - if val < 10 { - hnData = append(hnData, '0') - } - hnData = strconv.AppendUint(hnData, uint64(val), 16) - } - } - - return string(hnData) -} - // GenerateHostname generates the hostname from ip. In case of using IPv4 the // result should be like: // @@ -57,12 +19,20 @@ func generateIPv6Hostname(ipv6 net.IP) (hostname string) { // ff80-f076-0000-0000-0000-0000-0000-0010 // // ip must be either an IPv4 or an IPv6. -func GenerateHostname(ip net.IP) (hostname string) { - if ipv4 := ip.To4(); ipv4 != nil { - return generateIPv4Hostname(ipv4) +func GenerateHostname(ip netip.Addr) (hostname string) { + if !ip.IsValid() { + // TODO(s.chzhen): Get rid of it. + panic("aghnet generate hostname: invalid ip") } - return generateIPv6Hostname(ip) + ip = ip.Unmap() + hostname = ip.StringExpanded() + + if ip.Is4() { + return strings.Replace(hostname, ".", "-", -1) + } + + return strings.Replace(hostname, ":", "-", -1) } // NewDomainNameSet returns nil and error, if list has duplicate or empty diff --git a/internal/aghnet/hostgen_test.go b/internal/aghnet/hostgen_test.go index d37e556b..5896720d 100644 --- a/internal/aghnet/hostgen_test.go +++ b/internal/aghnet/hostgen_test.go @@ -1,7 +1,7 @@ package aghnet import ( - "net" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -12,19 +12,19 @@ func TestGenerateHostName(t *testing.T) { testCases := []struct { name string want string - ip net.IP + ip netip.Addr }{{ name: "good_ipv4", want: "127-0-0-1", - ip: net.IP{127, 0, 0, 1}, + ip: netip.MustParseAddr("127.0.0.1"), }, { name: "good_ipv6", want: "fe00-0000-0000-0000-0000-0000-0000-0001", - ip: net.ParseIP("fe00::1"), + ip: netip.MustParseAddr("fe00::1"), }, { name: "4to6", want: "1-2-3-4", - ip: net.ParseIP("::ffff:1.2.3.4"), + ip: netip.MustParseAddr("::ffff:1.2.3.4"), }} for _, tc := range testCases { @@ -36,29 +36,6 @@ func TestGenerateHostName(t *testing.T) { }) t.Run("invalid", func(t *testing.T) { - testCases := []struct { - name string - ip net.IP - }{{ - name: "bad_ipv4", - ip: net.IP{127, 0, 0, 1, 0}, - }, { - name: "bad_ipv6", - ip: net.IP{ - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, - }, - }, { - name: "nil", - ip: nil, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Panics(t, func() { GenerateHostname(tc.ip) }) - }) - } + assert.Panics(t, func() { GenerateHostname(netip.Addr{}) }) }) } diff --git a/internal/dhcpd/db.go b/internal/dhcpd/db.go index 91a83447..c6a7b8ab 100644 --- a/internal/dhcpd/db.go +++ b/internal/dhcpd/db.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net" + "net/netip" "os" "time" @@ -32,6 +33,8 @@ func normalizeIP(ip net.IP) net.IP { } // Load lease table from DB +// +// TODO(s.chzhen): Decrease complexity. func (s *server) dbLoad() (err error) { dynLeases := []*Lease{} staticLeases := []*Lease{} @@ -57,14 +60,15 @@ func (s *server) dbLoad() (err error) { for i := range obj { obj[i].IP = normalizeIP(obj[i].IP) - if !(len(obj[i].IP) == 4 || len(obj[i].IP) == 16) { + ip, ok := netip.AddrFromSlice(obj[i].IP) + if !ok { log.Info("dhcp: invalid IP: %s", obj[i].IP) continue } lease := Lease{ HWAddr: obj[i].HWAddr, - IP: obj[i].IP, + IP: ip, Hostname: obj[i].Hostname, Expiry: time.Unix(obj[i].Expiry, 0), } @@ -145,7 +149,7 @@ func (s *server) dbStore() (err error) { lease := leaseJSON{ HWAddr: l.HWAddr, - IP: l.IP, + IP: l.IP.AsSlice(), Hostname: l.Hostname, Expiry: l.Expiry.Unix(), } @@ -162,7 +166,7 @@ func (s *server) dbStore() (err error) { lease := leaseJSON{ HWAddr: l.HWAddr, - IP: l.IP, + IP: l.IP.AsSlice(), Hostname: l.Hostname, Expiry: l.Expiry.Unix(), } diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index ef7fe19f..07a73a48 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -41,13 +41,16 @@ type Lease struct { // of 1 means that this is a static lease. Expiry time.Time `json:"expires"` - Hostname string `json:"hostname"` - HWAddr net.HardwareAddr `json:"mac"` + // 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. // - // TODO(a.garipov): Migrate leases.db and use netip.Addr. - IP net.IP `json:"ip"` + // TODO(a.garipov): Migrate leases.db. + IP netip.Addr `json:"ip"` } // Clone returns a deep copy of l. @@ -60,7 +63,7 @@ func (l *Lease) Clone() (clone *Lease) { Expiry: l.Expiry, Hostname: l.Hostname, HWAddr: slices.Clone(l.HWAddr), - IP: slices.Clone(l.IP), + IP: l.IP, } } diff --git a/internal/dhcpd/dhcpd_unix_test.go b/internal/dhcpd/dhcpd_unix_test.go index 0bf516cb..9e52f62a 100644 --- a/internal/dhcpd/dhcpd_unix_test.go +++ b/internal/dhcpd/dhcpd_unix_test.go @@ -48,11 +48,11 @@ func TestDB(t *testing.T) { Expiry: time.Now().Add(time.Hour), Hostname: "static-1.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 100}, + IP: netip.MustParseAddr("192.168.10.100"), }, { Hostname: "static-2.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB}, - IP: net.IP{192, 168, 10, 101}, + IP: netip.MustParseAddr("192.168.10.101"), }} srv4, ok := s.srv4.(*v4Server) @@ -96,7 +96,7 @@ func TestNormalizeLeases(t *testing.T) { staticLeases := []*Lease{{ HWAddr: net.HardwareAddr{1, 2, 3, 4}, - IP: net.IP{0, 2, 3, 4}, + IP: netip.MustParseAddr("0.2.3.4"), }, { HWAddr: net.HardwareAddr{2, 2, 3, 4}, }} diff --git a/internal/dhcpd/http_unix.go b/internal/dhcpd/http_unix.go index 2e7ef57d..4ef8cc04 100644 --- a/internal/dhcpd/http_unix.go +++ b/internal/dhcpd/http_unix.go @@ -496,18 +496,18 @@ func (s *server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request return } - if l.IP == nil { + if !l.IP.IsValid() { aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP") return } + l.IP = l.IP.Unmap() + var srv DHCPServer - if ip4 := l.IP.To4(); ip4 != nil { - l.IP = ip4 + if l.IP.Is4() { srv = s.srv4 } else { - l.IP = l.IP.To16() srv = s.srv6 } @@ -528,27 +528,22 @@ func (s *server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ return } - if l.IP == nil { + if !l.IP.IsValid() { aghhttp.Error(r, w, http.StatusBadRequest, "invalid IP") return } - ip4 := l.IP.To4() + l.IP = l.IP.Unmap() - if ip4 == nil { - l.IP = l.IP.To16() - - err = s.srv6.RemoveStaticLease(l) - if err != nil { - aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) - } - - return + var srv DHCPServer + if l.IP.Is4() { + srv = s.srv4 + } else { + srv = s.srv6 } - l.IP = ip4 - err = s.srv4.RemoveStaticLease(l) + err = srv.RemoveStaticLease(l) if err != nil { aghhttp.Error(r, w, http.StatusBadRequest, "%s", err) diff --git a/internal/dhcpd/http_unix_test.go b/internal/dhcpd/http_unix_test.go new file mode 100644 index 00000000..0204a7e6 --- /dev/null +++ b/internal/dhcpd/http_unix_test.go @@ -0,0 +1,161 @@ +//go:build darwin || freebsd || linux || openbsd + +package dhcpd + +import ( + "bytes" + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "net/netip" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestServer_handleDHCPStatus(t *testing.T) { + const staticName = "static-client" + + staticIP := netip.MustParseAddr("192.168.10.10") + staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + staticLease := &Lease{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: staticMAC, + IP: staticIP, + } + + s, err := Create(&ServerConfig{ + Enabled: true, + Conf4: *defaultV4ServerConf(), + WorkDir: t.TempDir(), + DBFilePath: dbFilename, + ConfigModified: func() {}, + }) + require.NoError(t, err) + + // checkStatus is a helper that asserts the response of + // [*server.handleDHCPStatus]. + checkStatus := func(t *testing.T, want *dhcpStatusResponse) { + w := httptest.NewRecorder() + var req *http.Request + req, err = http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + + b := &bytes.Buffer{} + err = json.NewEncoder(b).Encode(&want) + require.NoError(t, err) + + s.handleDHCPStatus(w, req) + assert.Equal(t, http.StatusOK, w.Code) + + assert.JSONEq(t, b.String(), w.Body.String()) + } + + // defaultResponse is a helper that returs the response with default + // configuration. + defaultResponse := func() *dhcpStatusResponse { + conf4 := defaultV4ServerConf() + conf4.LeaseDuration = 86400 + + resp := &dhcpStatusResponse{ + V4: *conf4, + V6: V6ServerConf{}, + Leases: []*Lease{}, + StaticLeases: []*Lease{}, + Enabled: true, + } + + return resp + } + + ok := t.Run("status", func(t *testing.T) { + resp := defaultResponse() + + checkStatus(t, resp) + }) + require.True(t, ok) + + ok = t.Run("add_static_lease", func(t *testing.T) { + w := httptest.NewRecorder() + + b := &bytes.Buffer{} + err = json.NewEncoder(b).Encode(staticLease) + require.NoError(t, err) + + var r *http.Request + r, err = http.NewRequest(http.MethodPost, "", b) + require.NoError(t, err) + + s.handleDHCPAddStaticLease(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + resp := defaultResponse() + resp.StaticLeases = []*Lease{staticLease} + + checkStatus(t, resp) + }) + require.True(t, ok) + + ok = t.Run("add_invalid_lease", func(t *testing.T) { + w := httptest.NewRecorder() + + b := &bytes.Buffer{} + + err = json.NewEncoder(b).Encode(&Lease{}) + require.NoError(t, err) + + var r *http.Request + r, err = http.NewRequest(http.MethodPost, "", b) + require.NoError(t, err) + + s.handleDHCPAddStaticLease(w, r) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + require.True(t, ok) + + ok = t.Run("remove_static_lease", func(t *testing.T) { + w := httptest.NewRecorder() + + b := &bytes.Buffer{} + err = json.NewEncoder(b).Encode(staticLease) + require.NoError(t, err) + + var r *http.Request + r, err = http.NewRequest(http.MethodPost, "", b) + require.NoError(t, err) + + s.handleDHCPRemoveStaticLease(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + resp := defaultResponse() + + checkStatus(t, resp) + }) + require.True(t, ok) + + ok = t.Run("set_config", func(t *testing.T) { + w := httptest.NewRecorder() + + resp := defaultResponse() + resp.Enabled = false + + b := &bytes.Buffer{} + err = json.NewEncoder(b).Encode(&resp) + require.NoError(t, err) + + var r *http.Request + r, err = http.NewRequest(http.MethodPost, "", b) + require.NoError(t, err) + + s.handleDHCPSetConfig(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + checkStatus(t, resp) + }) + require.True(t, ok) +} diff --git a/internal/dhcpd/iprange.go b/internal/dhcpd/iprange.go index 6cf7d01f..0f401277 100644 --- a/internal/dhcpd/iprange.go +++ b/internal/dhcpd/iprange.go @@ -16,6 +16,8 @@ import ( // // TODO(a.garipov): Perhaps create an optimized version with uint32 for IPv4 // ranges? Or use one of uint128 packages? +// +// TODO(e.burkov): Use netip.Addr. type ipRange struct { start *big.Int end *big.Int @@ -27,8 +29,6 @@ const maxRangeLen = math.MaxUint32 // newIPRange creates a new IP address range. start must be less than end. The // resulting range must not be greater than maxRangeLen. -// -// TODO(e.burkov): Use netip.Addr. func newIPRange(start, end net.IP) (r *ipRange, err error) { defer func() { err = errors.Annotate(err, "invalid ip range: %w") }() diff --git a/internal/dhcpd/v4_unix.go b/internal/dhcpd/v4_unix.go index 63ae46d9..73947580 100644 --- a/internal/dhcpd/v4_unix.go +++ b/internal/dhcpd/v4_unix.go @@ -98,7 +98,7 @@ func normalizeHostname(hostname string) (norm string, err error) { // validHostnameForClient accepts the hostname sent by the client and its IP and // returns either a normalized version of that hostname, or a new hostname // generated from the IP address, or an empty string. -func (s *v4Server) validHostnameForClient(cliHostname string, ip net.IP) (hostname string) { +func (s *v4Server) validHostnameForClient(cliHostname string, ip netip.Addr) (hostname string) { hostname, err := normalizeHostname(cliHostname) if err != nil { log.Info("dhcpv4: %s", err) @@ -211,9 +211,8 @@ func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { return nil } - netIP := ip.AsSlice() for _, l := range s.leases { - if l.IP.Equal(netIP) { + if l.IP == ip { if l.Expiry.After(now) || l.IsStatic() { return l.HWAddr } @@ -247,7 +246,8 @@ func (s *v4Server) rmLeaseByIndex(i int) { s.leases = append(s.leases[:i], s.leases[i+1:]...) r := s.conf.ipRange - offset, ok := r.offset(l.IP) + leaseIP := net.IP(l.IP.AsSlice()) + offset, ok := r.offset(leaseIP) if ok { s.leasedOffsets.set(offset, false) } @@ -263,7 +263,7 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) { for i, l := range s.leases { isStatic := l.IsStatic() - if bytes.Equal(l.HWAddr, lease.HWAddr) || l.IP.Equal(lease.IP) { + if bytes.Equal(l.HWAddr, lease.HWAddr) || l.IP == lease.IP { if isStatic { return errors.Error("static lease already exists") } @@ -291,13 +291,13 @@ const ErrDupHostname = errors.Error("hostname is not unique") // addLease adds a dynamic or static lease. func (s *v4Server) addLease(l *Lease) (err error) { r := s.conf.ipRange - offset, inOffset := r.offset(l.IP) + leaseIP := net.IP(l.IP.AsSlice()) + offset, inOffset := r.offset(leaseIP) if l.IsStatic() { // TODO(a.garipov, d.seregin): Subnet can be nil when dhcp server is // disabled. - addr := netip.AddrFrom4(*(*[4]byte)(l.IP.To4())) - if sn := s.conf.subnet; !sn.Contains(addr) { + if sn := s.conf.subnet; !sn.Contains(l.IP) { return fmt.Errorf("subnet %s does not contain the ip %q", sn, l.IP) } } else if !inOffset { @@ -325,7 +325,7 @@ func (s *v4Server) rmLease(lease *Lease) (err error) { } for i, l := range s.leases { - if l.IP.Equal(lease.IP) { + if l.IP == lease.IP { if !bytes.Equal(l.HWAddr, lease.HWAddr) || l.Hostname != lease.Hostname { return fmt.Errorf("lease for ip %s is different: %+v", lease.IP, l) } @@ -352,10 +352,11 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { return ErrUnconfigured } - ip := l.IP.To4() - if ip == nil { + l.IP = l.IP.Unmap() + + if !l.IP.Is4() { return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP) - } else if gwIP := s.conf.GatewayIP; gwIP == netip.AddrFrom4(*(*[4]byte)(ip)) { + } else if gwIP := s.conf.GatewayIP; gwIP == l.IP { return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP) } @@ -396,7 +397,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { if err != nil { err = fmt.Errorf( "removing dynamic leases for %s (%s): %w", - ip, + l.IP, l.HWAddr, err, ) @@ -406,7 +407,7 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) { err = s.addLease(l) if err != nil { - err = fmt.Errorf("adding static lease for %s (%s): %w", ip, l.HWAddr, err) + err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err) return } @@ -429,7 +430,7 @@ func (s *v4Server) RemoveStaticLease(l *Lease) (err error) { return ErrUnconfigured } - if len(l.IP) != 4 { + if !l.IP.Is4() { return fmt.Errorf("invalid IP") } @@ -542,8 +543,8 @@ func (s *v4Server) findExpiredLease() int { func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) { l = &Lease{HWAddr: slices.Clone(mac)} - l.IP = s.nextIP() - if l.IP == nil { + nextIP := s.nextIP() + if nextIP == nil { i := s.findExpiredLease() if i < 0 { return nil, nil @@ -554,6 +555,13 @@ func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) { return s.leases[i], nil } + netIP, ok := netip.AddrFromSlice(nextIP) + if !ok { + return nil, errors.Error("invalid ip") + } + + l.IP = netIP + err = s.addLease(l) if err != nil { return nil, err @@ -603,7 +611,8 @@ func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) { return nil, nil } - if s.addrAvailable(l.IP) { + leaseIP := l.IP.AsSlice() + if s.addrAvailable(leaseIP) { return l, nil } @@ -623,8 +632,9 @@ func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error l = s.findLease(mac) if l != nil { reqIP := req.RequestedIPAddress() - if len(reqIP) != 0 && !reqIP.Equal(l.IP) { - log.Debug("dhcpv4: different RequestedIP: %s != %s", reqIP, l.IP) + leaseIP := net.IP(l.IP.AsSlice()) + if len(reqIP) != 0 && !reqIP.Equal(leaseIP) { + log.Debug("dhcpv4: different RequestedIP: %s != %s", reqIP, leaseIP) } resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer)) @@ -674,12 +684,19 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi s.leasesLock.Lock() defer s.leasesLock.Unlock() + netIP, ok := netip.AddrFromSlice(ip) + if !ok { + log.Info("check lease: invalid IP: %s", ip) + + return nil, false + } + for _, l := range s.leases { if !bytes.Equal(l.HWAddr, mac) { continue } - if l.IP.Equal(ip) { + if l.IP == netIP { return l, false } @@ -878,9 +895,16 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) { reqIP = req.ClientIPAddr } + netIP, ok := netip.AddrFromSlice(reqIP) + if !ok { + log.Info("dhcpv4: invalid IP: %s", reqIP) + + return nil + } + var oldLease *Lease for _, l := range s.leases { - if bytes.Equal(l.HWAddr, mac) && l.IP.Equal(reqIP) { + if bytes.Equal(l.HWAddr, mac) && l.IP == netIP { oldLease = l break @@ -920,8 +944,7 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) { log.Info("dhcpv4: changed ip from %s to %s for %s", reqIP, newLease.IP, mac) - resp.YourIPAddr = make([]byte, 4) - copy(resp.YourIPAddr, newLease.IP) + resp.YourIPAddr = net.IP(newLease.IP.AsSlice()) resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) @@ -944,8 +967,15 @@ func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) { s.leasesLock.Lock() defer s.leasesLock.Unlock() + netIP, ok := netip.AddrFromSlice(reqIP) + if !ok { + log.Info("dhcpv4: invalid IP: %s", reqIP) + + return nil + } + for _, l := range s.leases { - if !bytes.Equal(l.HWAddr, mac) || !l.IP.Equal(reqIP) { + if !bytes.Equal(l.HWAddr, mac) || l.IP != netIP { continue } @@ -1018,7 +1048,7 @@ func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int { } if l != nil { - resp.YourIPAddr = slices.Clone(l.IP) + resp.YourIPAddr = net.IP(l.IP.AsSlice()) } s.updateOptions(req, resp) diff --git a/internal/dhcpd/v4_unix_test.go b/internal/dhcpd/v4_unix_test.go index 8c1255e1..474f4caf 100644 --- a/internal/dhcpd/v4_unix_test.go +++ b/internal/dhcpd/v4_unix_test.go @@ -62,7 +62,7 @@ func TestV4Server_leasing(t *testing.T) { anotherName = "another-client" ) - staticIP := net.IP{192, 168, 10, 10} + staticIP := netip.MustParseAddr("192.168.10.10") anotherIP := DefaultRangeStart staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} @@ -83,7 +83,7 @@ func TestV4Server_leasing(t *testing.T) { Expiry: time.Unix(leaseExpireStatic, 0), Hostname: staticName, HWAddr: anotherMAC, - IP: anotherIP.AsSlice(), + IP: anotherIP, }) assert.ErrorIs(t, err, ErrDupHostname) }) @@ -97,7 +97,7 @@ func TestV4Server_leasing(t *testing.T) { Expiry: time.Unix(leaseExpireStatic, 0), Hostname: anotherName, HWAddr: staticMAC, - IP: anotherIP.AsSlice(), + IP: anotherIP, }) testutil.AssertErrorMsg(t, wantErrMsg, err) }) @@ -124,13 +124,14 @@ func TestV4Server_leasing(t *testing.T) { discoverAnOffer := func( t *testing.T, name string, - ip net.IP, + netIP netip.Addr, mac net.HardwareAddr, ) (resp *dhcpv4.DHCPv4) { testutil.CleanupAndRequireSuccess(t, func() (err error) { return s.ResetLeases(s.GetLeases(LeasesStatic)) }) + ip := net.IP(netIP.AsSlice()) req, err := dhcpv4.NewDiscovery( mac, dhcpv4.WithOption(dhcpv4.OptHostName(name)), @@ -151,7 +152,7 @@ func TestV4Server_leasing(t *testing.T) { } t.Run("same_name", func(t *testing.T) { - resp := discoverAnOffer(t, staticName, anotherIP.AsSlice(), anotherMAC) + resp := discoverAnOffer(t, staticName, anotherIP, anotherMAC) req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption( dhcpv4.OptHostName(staticName), @@ -161,11 +162,15 @@ func TestV4Server_leasing(t *testing.T) { res := s4.handle(req, resp) require.Positive(t, res) - assert.Equal(t, aghnet.GenerateHostname(resp.YourIPAddr), resp.HostName()) + var netIP netip.Addr + netIP, ok = netip.AddrFromSlice(resp.YourIPAddr) + require.True(t, ok) + + assert.Equal(t, aghnet.GenerateHostname(netIP), resp.HostName()) }) t.Run("same_mac", func(t *testing.T) { - resp := discoverAnOffer(t, anotherName, anotherIP.AsSlice(), staticMAC) + resp := discoverAnOffer(t, anotherName, anotherIP, staticMAC) req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption( dhcpv4.OptHostName(anotherName), @@ -179,7 +184,8 @@ func TestV4Server_leasing(t *testing.T) { require.Len(t, fqdnOptData, 3+len(staticName)) assert.Equal(t, []uint8(staticName), fqdnOptData[3:]) - assert.Equal(t, staticIP, resp.YourIPAddr) + ip := net.IP(staticIP.AsSlice()) + assert.Equal(t, ip, resp.YourIPAddr) }) t.Run("same_ip", func(t *testing.T) { @@ -212,7 +218,7 @@ func TestV4Server_AddRemove_static(t *testing.T) { lease: &Lease{ Hostname: "success.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), }, name: "success", wantErrMsg: "", @@ -220,7 +226,7 @@ func TestV4Server_AddRemove_static(t *testing.T) { lease: &Lease{ Hostname: "probably-router.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: DefaultGatewayIP.AsSlice(), + IP: DefaultGatewayIP, }, name: "with_gateway_ip", wantErrMsg: "dhcpv4: adding static lease: " + @@ -229,7 +235,7 @@ func TestV4Server_AddRemove_static(t *testing.T) { lease: &Lease{ Hostname: "ip6.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.ParseIP("ffff::1"), + IP: netip.MustParseAddr("ffff::1"), }, name: "ipv6", wantErrMsg: `dhcpv4: adding static lease: ` + @@ -238,7 +244,7 @@ func TestV4Server_AddRemove_static(t *testing.T) { lease: &Lease{ Hostname: "bad-mac.local", HWAddr: net.HardwareAddr{0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), }, name: "bad_mac", wantErrMsg: `dhcpv4: adding static lease: bad mac address "aa:aa": ` + @@ -247,7 +253,7 @@ func TestV4Server_AddRemove_static(t *testing.T) { lease: &Lease{ Hostname: "bad-lbl-.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), }, name: "bad_hostname", wantErrMsg: `dhcpv4: adding static lease: validating hostname: ` + @@ -289,11 +295,11 @@ func TestV4_AddReplace(t *testing.T) { dynLeases := []Lease{{ Hostname: "dynamic-1.local", HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), }, { Hostname: "dynamic-2.local", HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 151}, + IP: netip.MustParseAddr("192.168.10.151"), }} for i := range dynLeases { @@ -304,11 +310,11 @@ func TestV4_AddReplace(t *testing.T) { stLeases := []*Lease{{ Hostname: "static-1.local", HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), }, { Hostname: "static-2.local", HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 152}, + IP: netip.MustParseAddr("192.168.10.152"), }} for _, l := range stLeases { @@ -320,7 +326,7 @@ func TestV4_AddReplace(t *testing.T) { require.Len(t, ls, 2) for i, l := range ls { - assert.True(t, stLeases[i].IP.Equal(l.IP)) + assert.Equal(t, stLeases[i].IP, l.IP) assert.Equal(t, stLeases[i].HWAddr, l.HWAddr) assert.True(t, l.IsStatic()) } @@ -513,7 +519,7 @@ func TestV4StaticLease_Get(t *testing.T) { l := &Lease{ Hostname: "static-1.local", HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, - IP: net.IP{192, 168, 10, 150}, + IP: netip.MustParseAddr("192.168.10.150"), } err := s.AddStaticLease(l) require.NoError(t, err) @@ -539,7 +545,9 @@ func TestV4StaticLease_Get(t *testing.T) { t.Run("offer", func(t *testing.T) { assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) assert.Equal(t, mac, resp.ClientHWAddr) - assert.True(t, l.IP.Equal(resp.YourIPAddr)) + + ip := net.IP(l.IP.AsSlice()) + assert.True(t, ip.Equal(resp.YourIPAddr)) assert.True(t, resp.Router()[0].Equal(s.conf.GatewayIP.AsSlice())) assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice())) @@ -564,7 +572,9 @@ func TestV4StaticLease_Get(t *testing.T) { t.Run("ack", func(t *testing.T) { assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) assert.Equal(t, mac, resp.ClientHWAddr) - assert.True(t, l.IP.Equal(resp.YourIPAddr)) + + ip := net.IP(l.IP.AsSlice()) + assert.True(t, ip.Equal(resp.YourIPAddr)) assert.True(t, resp.Router()[0].Equal(s.conf.GatewayIP.AsSlice())) assert.True(t, resp.ServerIdentifier().Equal(s.conf.GatewayIP.AsSlice())) @@ -583,7 +593,7 @@ func TestV4StaticLease_Get(t *testing.T) { ls := s.GetLeases(LeasesStatic) require.Len(t, ls, 1) - assert.True(t, l.IP.Equal(ls[0].IP)) + assert.Equal(t, l.IP, ls[0].IP) assert.Equal(t, mac, ls[0].HWAddr) }) } @@ -681,7 +691,8 @@ func TestV4DynamicLease_Get(t *testing.T) { ls := s.GetLeases(LeasesDynamic) require.Len(t, ls, 1) - assert.True(t, net.IP{192, 168, 10, 100}.Equal(ls[0].IP)) + ip := netip.MustParseAddr("192.168.10.100") + assert.Equal(t, ip, ls[0].IP) assert.Equal(t, mac, ls[0].HWAddr) }) } @@ -862,3 +873,143 @@ func TestV4Server_Send(t *testing.T) { assert.True(t, resp.IsBroadcast()) }) } + +func TestV4Server_FindMACbyIP(t *testing.T) { + const ( + staticName = "static-client" + anotherName = "another-client" + ) + + staticIP := netip.MustParseAddr("192.168.10.10") + staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + anotherIP := netip.MustParseAddr("192.168.100.100") + anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} + + s := &v4Server{ + leases: []*Lease{{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: staticMAC, + IP: staticIP, + }, { + Expiry: time.Unix(10, 0), + Hostname: anotherName, + HWAddr: anotherMAC, + IP: anotherIP, + }}, + } + + testCases := []struct { + want net.HardwareAddr + ip netip.Addr + name string + }{{ + name: "basic", + ip: staticIP, + want: staticMAC, + }, { + name: "not_found", + ip: netip.MustParseAddr("1.2.3.4"), + want: nil, + }, { + name: "expired", + ip: anotherIP, + want: nil, + }, { + name: "v6", + ip: netip.MustParseAddr("ffff::1"), + want: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mac := s.FindMACbyIP(tc.ip) + + require.Equal(t, tc.want, mac) + }) + } +} + +func TestV4Server_handleDecline(t *testing.T) { + const ( + dynamicName = "dynamic-client" + anotherName = "another-client" + ) + + dynamicIP := netip.MustParseAddr("192.168.10.200") + dynamicMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + s := defaultSrv(t) + + s4, ok := s.(*v4Server) + require.True(t, ok) + + s4.leases = []*Lease{{ + Hostname: dynamicName, + HWAddr: dynamicMAC, + IP: dynamicIP, + }} + + req, err := dhcpv4.New( + dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(net.IP(dynamicIP.AsSlice()))), + ) + require.NoError(t, err) + + req.ClientIPAddr = net.IP(dynamicIP.AsSlice()) + req.ClientHWAddr = dynamicMAC + + resp := &dhcpv4.DHCPv4{} + err = s4.handleDecline(req, resp) + require.NoError(t, err) + + wantResp := &dhcpv4.DHCPv4{ + YourIPAddr: net.IP(s4.conf.RangeStart.AsSlice()), + Options: dhcpv4.OptionsFromList( + dhcpv4.OptMessageType(dhcpv4.MessageTypeAck), + ), + } + + require.Equal(t, wantResp, resp) +} + +func TestV4Server_handleRelease(t *testing.T) { + const ( + dynamicName = "dymamic-client" + anotherName = "another-client" + ) + + dynamicIP := netip.MustParseAddr("192.168.10.200") + dynamicMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + s := defaultSrv(t) + + s4, ok := s.(*v4Server) + require.True(t, ok) + + s4.leases = []*Lease{{ + Hostname: dynamicName, + HWAddr: dynamicMAC, + IP: dynamicIP, + }} + + req, err := dhcpv4.New( + dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(net.IP(dynamicIP.AsSlice()))), + ) + require.NoError(t, err) + + req.ClientIPAddr = net.IP(dynamicIP.AsSlice()) + req.ClientHWAddr = dynamicMAC + + resp := &dhcpv4.DHCPv4{} + err = s4.handleRelease(req, resp) + require.NoError(t, err) + + wantResp := &dhcpv4.DHCPv4{ + Options: dhcpv4.OptionsFromList( + dhcpv4.OptMessageType(dhcpv4.MessageTypeAck), + ), + } + + require.Equal(t, wantResp, resp) +} diff --git a/internal/dhcpd/v6_unix.go b/internal/dhcpd/v6_unix.go index d69d47c2..177abfb3 100644 --- a/internal/dhcpd/v6_unix.go +++ b/internal/dhcpd/v6_unix.go @@ -61,13 +61,13 @@ func ip6InRange(start, ip net.IP) bool { // ResetLeases resets leases. func (s *v6Server) ResetLeases(leases []*Lease) (err error) { - defer func() { err = errors.Annotate(err, "dhcpv4: %w") }() + defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() s.leases = nil for _, l := range leases { - + ip := net.IP(l.IP.AsSlice()) if l.Expiry.Unix() != leaseExpireStatic && - !ip6InRange(s.conf.ipStart, l.IP) { + !ip6InRange(s.conf.ipStart, ip) { log.Debug("dhcpv6: skipping a lease with IP %v: not within current IP range", l.IP) @@ -119,9 +119,8 @@ func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { return nil } - netIP := ip.AsSlice() for _, l := range s.leases { - if l.IP.Equal(netIP) { + if l.IP == ip { if l.Expiry.After(now) || l.IsStatic() { return l.HWAddr } @@ -133,7 +132,8 @@ func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) { // Remove (swap) lease by index func (s *v6Server) leaseRemoveSwapByIndex(i int) { - s.ipAddrs[s.leases[i].IP[15]] = 0 + leaseIP := s.leases[i].IP.As16() + s.ipAddrs[leaseIP[15]] = 0 log.Debug("dhcpv6: removed lease %s", s.leases[i].HWAddr) n := len(s.leases) @@ -162,7 +162,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) { l = s.leases[i] } - if net.IP.Equal(l.IP, lease.IP) { + if l.IP == lease.IP { if l.Expiry.Unix() == leaseExpireStatic { return fmt.Errorf("static lease already exists") } @@ -178,7 +178,7 @@ func (s *v6Server) rmDynamicLease(lease *Lease) (err error) { func (s *v6Server) AddStaticLease(l *Lease) (err error) { defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() - if len(l.IP) != net.IPv6len { + if !l.IP.Is6() { return fmt.Errorf("invalid IP") } @@ -210,7 +210,7 @@ func (s *v6Server) AddStaticLease(l *Lease) (err error) { func (s *v6Server) RemoveStaticLease(l *Lease) (err error) { defer func() { err = errors.Annotate(err, "dhcpv6: %w") }() - if len(l.IP) != 16 { + if !l.IP.Is6() { return fmt.Errorf("invalid IP") } @@ -234,14 +234,15 @@ func (s *v6Server) RemoveStaticLease(l *Lease) (err error) { // Add a lease func (s *v6Server) addLease(l *Lease) { s.leases = append(s.leases, l) - s.ipAddrs[l.IP[15]] = 1 + ip := l.IP.As16() + s.ipAddrs[ip[15]] = 1 log.Debug("dhcpv6: added lease %s <-> %s", l.IP, l.HWAddr) } // Remove a lease with the same properties func (s *v6Server) rmLease(lease *Lease) (err error) { for i, l := range s.leases { - if net.IP.Equal(l.IP, lease.IP) { + if l.IP == lease.IP { if !bytes.Equal(l.HWAddr, lease.HWAddr) || l.Hostname != lease.Hostname { return fmt.Errorf("lease not found") @@ -308,18 +309,27 @@ func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease { s.leasesLock.Lock() defer s.leasesLock.Unlock() - copy(l.IP, s.conf.ipStart) - l.IP = s.findFreeIP() - if l.IP == nil { + ip := s.findFreeIP() + if ip == nil { i := s.findExpiredLease() if i < 0 { return nil } + copy(s.leases[i].HWAddr, mac) + return s.leases[i] } + netIP, ok := netip.AddrFromSlice(ip) + if !ok { + return nil + } + + l.IP = netIP + s.addLease(&l) + return &l } @@ -388,7 +398,8 @@ func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error { return fmt.Errorf("no IANA.Addr option in %s", msg.Type().String()) } - if !oiaAddr.IPv6Addr.Equal(lease.IP) { + leaseIP := net.IP(lease.IP.AsSlice()) + if !oiaAddr.IPv6Addr.Equal(leaseIP) { return fmt.Errorf("invalid IANA.Addr option in %s", msg.Type().String()) } } @@ -475,7 +486,7 @@ func (s *v6Server) process(msg *dhcpv6.Message, req, resp dhcpv6.DHCPv6) bool { copy(oia.IaId[:], []byte(valueIAID)) } oiaAddr := &dhcpv6.OptIAAddress{ - IPv6Addr: lease.IP, + IPv6Addr: net.IP(lease.IP.AsSlice()), PreferredLifetime: lifetime, ValidLifetime: lifetime, } diff --git a/internal/dhcpd/v6_unix_test.go b/internal/dhcpd/v6_unix_test.go index 201f62a6..7a588054 100644 --- a/internal/dhcpd/v6_unix_test.go +++ b/internal/dhcpd/v6_unix_test.go @@ -4,7 +4,9 @@ package dhcpd import ( "net" + "net/netip" "testing" + "time" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/iana" @@ -27,7 +29,7 @@ func TestV6_AddRemove_static(t *testing.T) { // Add static lease. l := &Lease{ - IP: net.ParseIP("2001::1"), + IP: netip.MustParseAddr("2001::1"), HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, } err = s.AddStaticLease(l) @@ -46,7 +48,7 @@ func TestV6_AddRemove_static(t *testing.T) { // Try to remove non-existent static lease. err = s.RemoveStaticLease(&Lease{ - IP: net.ParseIP("2001::2"), + IP: netip.MustParseAddr("2001::2"), HWAddr: l.HWAddr, }) require.Error(t, err) @@ -71,10 +73,10 @@ func TestV6_AddReplace(t *testing.T) { // Add dynamic leases. dynLeases := []*Lease{{ - IP: net.ParseIP("2001::1"), + IP: netip.MustParseAddr("2001::1"), HWAddr: net.HardwareAddr{0x11, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, }, { - IP: net.ParseIP("2001::2"), + IP: netip.MustParseAddr("2001::2"), HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, }} @@ -83,10 +85,10 @@ func TestV6_AddReplace(t *testing.T) { } stLeases := []*Lease{{ - IP: net.ParseIP("2001::1"), + IP: netip.MustParseAddr("2001::1"), HWAddr: net.HardwareAddr{0x33, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, }, { - IP: net.ParseIP("2001::3"), + IP: netip.MustParseAddr("2001::3"), HWAddr: net.HardwareAddr{0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, }} @@ -99,7 +101,7 @@ func TestV6_AddReplace(t *testing.T) { require.Len(t, ls, 2) for i, l := range ls { - assert.True(t, stLeases[i].IP.Equal(l.IP)) + assert.Equal(t, stLeases[i].IP, l.IP) assert.Equal(t, stLeases[i].HWAddr, l.HWAddr) assert.EqualValues(t, leaseExpireStatic, l.Expiry.Unix()) } @@ -126,7 +128,7 @@ func TestV6GetLease(t *testing.T) { } l := &Lease{ - IP: net.ParseIP("2001::1"), + IP: netip.MustParseAddr("2001::1"), HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, } err = s.AddStaticLease(l) @@ -158,7 +160,8 @@ func TestV6GetLease(t *testing.T) { oia = resp.Options.OneIANA() oiaAddr = oia.Options.OneAddress() - assert.Equal(t, l.IP, oiaAddr.IPv6Addr) + ip := net.IP(l.IP.AsSlice()) + assert.Equal(t, ip, oiaAddr.IPv6Addr) assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) }) @@ -182,7 +185,8 @@ func TestV6GetLease(t *testing.T) { oia = resp.Options.OneIANA() oiaAddr = oia.Options.OneAddress() - assert.Equal(t, l.IP, oiaAddr.IPv6Addr) + ip := net.IP(l.IP.AsSlice()) + assert.Equal(t, ip, oiaAddr.IPv6Addr) assert.Equal(t, s.conf.leaseTime.Seconds(), oiaAddr.ValidLifetime.Seconds()) }) @@ -308,3 +312,72 @@ func TestIP6InRange(t *testing.T) { }) } } + +func TestV6_FindMACbyIP(t *testing.T) { + const ( + staticName = "static-client" + anotherName = "another-client" + ) + + staticIP := netip.MustParseAddr("2001::1") + staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} + + anotherIP := netip.MustParseAddr("2001::100") + anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB} + + s := &v6Server{ + leases: []*Lease{{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: staticMAC, + IP: staticIP, + }, { + Expiry: time.Unix(10, 0), + Hostname: anotherName, + HWAddr: anotherMAC, + IP: anotherIP, + }}, + } + + s.leases = []*Lease{{ + Expiry: time.Unix(leaseExpireStatic, 0), + Hostname: staticName, + HWAddr: staticMAC, + IP: staticIP, + }, { + Expiry: time.Unix(10, 0), + Hostname: anotherName, + HWAddr: anotherMAC, + IP: anotherIP, + }} + + testCases := []struct { + want net.HardwareAddr + ip netip.Addr + name string + }{{ + name: "basic", + ip: staticIP, + want: staticMAC, + }, { + name: "not_found", + ip: netip.MustParseAddr("ffff::1"), + want: nil, + }, { + name: "expired", + ip: anotherIP, + want: nil, + }, { + name: "v4", + ip: netip.MustParseAddr("1.2.3.4"), + want: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mac := s.FindMACbyIP(tc.ip) + + require.Equal(t, tc.want, mac) + }) + } +} diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index cc02bb9c..e48a30ca 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -243,17 +243,16 @@ func (s *Server) onDHCPLeaseChanged(flags int) { lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix) // Assume that we only process IPv4 now. - // - // TODO(a.garipov): Remove once we switch to netip.Addr more fully. - ip, err := netutil.IPToAddr(l.IP, netutil.AddrFamilyIPv4) - if err != nil { - log.Debug("dnsforward: skipping invalid ip %v from dhcp: %s", l.IP, err) + if !l.IP.Is4() { + log.Debug("dnsforward: skipping invalid ip from dhcp: bad ipv4 net.IP %v", l.IP) continue } - ipToHost[ip] = lowhost - hostToIP[lowhost] = ip + leaseIP := l.IP + + ipToHost[leaseIP] = lowhost + hostToIP[lowhost] = leaseIP } s.setTableHostToIP(hostToIP) diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index d0ef9fa2..bb4b28c6 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -1073,7 +1073,7 @@ var testDHCP = &dhcpd.MockInterface{ OnEnabled: func() (ok bool) { return true }, OnLeases: func(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { return []*dhcpd.Lease{{ - IP: net.IP{192, 168, 12, 34}, + IP: netip.MustParseAddr("192.168.12.34"), HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, Hostname: "myhost", }} diff --git a/internal/home/clients.go b/internal/home/clients.go index 923f71ad..48b95baf 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -19,7 +19,6 @@ import ( "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" "golang.org/x/exp/maps" "golang.org/x/exp/slices" @@ -859,15 +858,7 @@ func (clients *clientsContainer) updateFromDHCP(add bool) { continue } - // TODO(a.garipov): Remove once we switch to netip.Addr more fully. - ipAddr, err := netutil.IPToAddrNoMapped(l.IP) - if err != nil { - log.Error("clients: bad client ip %v from dhcp: %s", l.IP, err) - - continue - } - - ok := clients.addHostLocked(ipAddr, l.Hostname, ClientSourceDHCP) + ok := clients.addHostLocked(l.IP, l.Hostname, ClientSourceDHCP) if ok { n++ } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index ae58fa35..1c08348e 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -275,7 +275,7 @@ func TestClientsAddExisting(t *testing.T) { t.Skip("skipping dhcp test on windows") } - ip := net.IP{1, 2, 3, 4} + ip := netip.MustParseAddr("1.2.3.4") // First, init a DHCP server with a single static lease. config := &dhcpd.ServerConfig{