Skip to content

Commit

Permalink
Pull request: dhcpd: add purge, imp code
Browse files Browse the repository at this point in the history
Updates AdguardTeam#1691.

Squashed commit of the following:

commit 2ce6cc0
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 16 16:42:57 2021 +0300

    dhcp: imp code

commit 8f2bd70
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jun 16 16:27:06 2021 +0300

    dhcpd: add purge, imp code
  • Loading branch information
ainar-g committed Jun 16, 2021
1 parent ce36c95 commit 84e71e9
Show file tree
Hide file tree
Showing 19 changed files with 481 additions and 251 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to

### Added

- The ability to completely purge DHCP leases ([#1691]).
- The ability to set the timeout for querying the upstream servers ([#2280]).
- The ability to change group and user ID on startup on Unix ([#2763]).
- Experimental OpenBSD support for AMD64 and 64-bit ARM CPUs ([#2439]).
Expand Down Expand Up @@ -62,6 +63,7 @@ released by then.

- Go 1.15 support.

[#1691]: https://github.com/AdguardTeam/AdGuardHome/issues/1691
[#2280]: https://github.com/AdguardTeam/AdGuardHome/issues/2280
[#2439]: https://github.com/AdguardTeam/AdGuardHome/issues/2439
[#2441]: https://github.com/AdguardTeam/AdGuardHome/issues/2441
Expand Down
24 changes: 24 additions & 0 deletions internal/aghnet/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ import (
"golang.org/x/net/idna"
)

// CloneIP returns a clone of an IP address.
func CloneIP(ip net.IP) (clone net.IP) {
if ip != nil && len(ip) == 0 {
return net.IP{}
}

return append(clone, ip...)
}

// CloneMAC returns a clone of a MAC address.
func CloneMAC(mac net.HardwareAddr) (clone net.HardwareAddr) {
if mac != nil && len(mac) == 0 {
return net.HardwareAddr{}
}

return append(clone, mac...)
}

// IPFromAddr returns an IP address from addr. If addr is neither
// a *net.TCPAddr nor a *net.UDPAddr, it returns nil.
func IPFromAddr(addr net.Addr) (ip net.IP) {
Expand All @@ -31,6 +49,12 @@ func IsValidHostOuterRune(r rune) (ok bool) {
(r >= '0' && r <= '9')
}

// JoinHostPort is a convinient wrapper for net.JoinHostPort with port of type
// int.
func JoinHostPort(host string, port int) (hostport string) {
return net.JoinHostPort(host, strconv.Itoa(port))
}

// isValidHostRune returns true if r is a valid rune for a hostname label.
func isValidHostRune(r rune) (ok bool) {
return r == '-' || IsValidHostOuterRune(r)
Expand Down
28 changes: 28 additions & 0 deletions internal/aghnet/addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ import (
"github.com/stretchr/testify/require"
)

func TestCloneIP(t *testing.T) {
assert.Equal(t, net.IP(nil), CloneIP(nil))
assert.Equal(t, net.IP{}, CloneIP(net.IP{}))

ip := net.IP{1, 2, 3, 4}
clone := CloneIP(ip)
assert.Equal(t, ip, clone)
assert.NotSame(t, &ip[0], &clone[0])
}

func TestCloneMAC(t *testing.T) {
assert.Equal(t, net.HardwareAddr(nil), CloneMAC(nil))
assert.Equal(t, net.HardwareAddr{}, CloneMAC(net.HardwareAddr{}))

mac := net.HardwareAddr{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}
clone := CloneMAC(mac)
assert.Equal(t, mac, clone)
assert.NotSame(t, &mac[0], &clone[0])
}

func TestIPFromAddr(t *testing.T) {
ip := net.IP{1, 2, 3, 4}
assert.Equal(t, net.IP(nil), IPFromAddr(nil))
Expand Down Expand Up @@ -66,6 +86,14 @@ func TestValidateHardwareAddress(t *testing.T) {
}
}

func TestJoinHostPort(t *testing.T) {
assert.Equal(t, ":0", JoinHostPort("", 0))
assert.Equal(t, "host:12345", JoinHostPort("host", 12345))
assert.Equal(t, "1.2.3.4:12345", JoinHostPort("1.2.3.4", 12345))
assert.Equal(t, "[1234::5678]:12345", JoinHostPort("1234::5678", 12345))
assert.Equal(t, "[1234::5678%lo]:12345", JoinHostPort("1234::5678%lo", 12345))
}

func repeatStr(b *strings.Builder, s string, n int) {
for i := 0; i < n; i++ {
_, _ = b.WriteString(s)
Expand Down
6 changes: 0 additions & 6 deletions internal/aghnet/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,3 @@ func CollectAllIfacesAddrs() (addrs []string, err error) {

return addrs, nil
}

// JoinHostPort is a convinient wrapper for net.JoinHostPort with port of type
// int.
func JoinHostPort(host string, port int) (hostport string) {
return net.JoinHostPort(host, strconv.Itoa(port))
}
47 changes: 30 additions & 17 deletions internal/dhcpd/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package dhcpd

import (
"encoding/json"
"fmt"
"net"
"os"
"time"
Expand Down Expand Up @@ -31,7 +32,7 @@ func normalizeIP(ip net.IP) net.IP {
}

// Load lease table from DB
func (s *Server) dbLoad() {
func (s *Server) dbLoad() (err error) {
dynLeases := []*Lease{}
staticLeases := []*Lease{}
v6StaticLeases := []*Lease{}
Expand All @@ -40,17 +41,16 @@ func (s *Server) dbLoad() {
data, err := os.ReadFile(s.conf.DBFilePath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
log.Error("dhcp: can't read file %q: %v", s.conf.DBFilePath, err)
return fmt.Errorf("reading db: %w", err)
}

return
return nil
}

obj := []leaseJSON{}
err = json.Unmarshal(data, &obj)
if err != nil {
log.Error("dhcp: invalid DB: %v", err)
return
return fmt.Errorf("decoding db: %w", err)
}

numLeases := len(obj)
Expand Down Expand Up @@ -85,15 +85,23 @@ func (s *Server) dbLoad() {
}

leases4 := normalizeLeases(staticLeases, dynLeases)
s.srv4.ResetLeases(leases4)
err = s.srv4.ResetLeases(leases4)
if err != nil {
return fmt.Errorf("resetting dhcpv4 leases: %w", err)
}

leases6 := normalizeLeases(v6StaticLeases, v6DynLeases)
if s.srv6 != nil {
s.srv6.ResetLeases(leases6)
err = s.srv6.ResetLeases(leases6)
if err != nil {
return fmt.Errorf("resetting dhcpv6 leases: %w", err)
}
}

log.Info("dhcp: loaded leases v4:%d v6:%d total-read:%d from DB",
len(leases4), len(leases6), numLeases)

return nil
}

// Skip duplicate leases
Expand Down Expand Up @@ -124,20 +132,24 @@ func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease {
}

// Store lease table in DB
func (s *Server) dbStore() {
var leases []leaseJSON
func (s *Server) dbStore() (err error) {
// Use an empty slice here as opposed to nil so that it doesn't write
// "null" into the database file if leases are empty.
leases := []leaseJSON{}

leases4 := s.srv4.getLeasesRef()
for _, l := range leases4 {
if l.Expiry.Unix() == 0 {
continue
}

lease := leaseJSON{
HWAddr: l.HWAddr,
IP: l.IP,
Hostname: l.Hostname,
Expiry: l.Expiry.Unix(),
}

leases = append(leases, lease)
}

Expand All @@ -147,29 +159,30 @@ func (s *Server) dbStore() {
if l.Expiry.Unix() == 0 {
continue
}

lease := leaseJSON{
HWAddr: l.HWAddr,
IP: l.IP,
Hostname: l.Hostname,
Expiry: l.Expiry.Unix(),
}

leases = append(leases, lease)
}
}

data, err := json.Marshal(leases)
var data []byte
data, err = json.Marshal(leases)
if err != nil {
log.Error("json.Marshal: %v", err)
return
return fmt.Errorf("encoding db: %w", err)
}

err = maybe.WriteFile(s.conf.DBFilePath, data, 0o644)
if err != nil {
log.Error("dhcp: can't store lease table on disk: %v filename: %s",
err, s.conf.DBFilePath)

return
return fmt.Errorf("writing db: %w", err)
}

log.Info("dhcp: stored %d leases in DB", len(leases))
log.Info("dhcp: stored %d leases in db", len(leases))

return nil
}
Loading

0 comments on commit 84e71e9

Please sign in to comment.