Skip to content

Commit

Permalink
Pull request: 2704 local resolvers vol.1
Browse files Browse the repository at this point in the history
Merge in DNS/adguard-home from 2704-local-addresses-vol.1 to master

Updates AdguardTeam#2704.
Updates AdguardTeam#2829.
Updates AdguardTeam#2846.

Squashed commit of the following:

commit 9a49b3d
Author: Eugene Burkov <[email protected]>
Date:   Mon Mar 22 15:39:17 2021 +0300

    aghnet: imp docs and logging

commit 74f95a2
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 20:56:51 2021 +0300

    all: fix friday evening mistakes

commit 0e2066b
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 20:51:15 2021 +0300

    all: upd testify, imp code quality

commit 8237c50
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 20:19:29 2021 +0300

    aghnet: imp test naming

commit 14eb1e1
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 19:41:43 2021 +0300

    aghnet: isolate windows-specific functionality

commit d461ac8
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 14:50:05 2021 +0300

    aghnet: imp code quality

commit d0ee01c
Author: Eugene Burkov <[email protected]>
Date:   Fri Mar 19 11:59:10 2021 +0300

    all: mv funcs to agherr, mk system resolvers getter
  • Loading branch information
EugeneOne1 authored and heyxkhoa committed Mar 17, 2023
1 parent a54133b commit c79e60f
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 48 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ require (
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/cobra v1.1.3 // indirect
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/ti-mo/netfilter v0.4.0
github.com/u-root/u-root v7.0.0+incompatible
go.etcd.io/bbolt v1.3.5
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
Expand Down
18 changes: 18 additions & 0 deletions internal/agherr/agherr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package agherr
import (
"fmt"
"strings"

"github.com/AdguardTeam/golibs/log"
)

// Error is the constant error type.
Expand Down Expand Up @@ -95,6 +97,8 @@ type wrapper interface {
// }
//
// msg must contain the final ": %w" verb.
//
// TODO(a.garipov): Clearify the function usage.
func Annotate(msg string, errPtr *error, args ...interface{}) {
if errPtr == nil {
return
Expand All @@ -107,3 +111,17 @@ func Annotate(msg string, errPtr *error, args ...interface{}) {
*errPtr = fmt.Errorf(msg, args...)
}
}

// LogPanic is a convinient helper function to log a panic in a goroutine. It
// should not be used where proper error handling is required.
func LogPanic(prefix string) {
if v := recover(); v != nil {
if prefix != "" {
log.Error("%s: recovered from panic: %v", prefix, v)

return
}

log.Error("recovered from panic: %v", v)
}
}
38 changes: 38 additions & 0 deletions internal/agherr/agherr_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package agherr

import (
"bytes"
"errors"
"fmt"
"testing"

"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -120,3 +122,39 @@ func TestAnnotate(t *testing.T) {
assert.Equal(t, wantMsg, err.Error())
})
}

func TestLogPanic(t *testing.T) {
buf := &bytes.Buffer{}
aghtest.ReplaceLogWriter(t, buf)

t.Run("prefix", func(t *testing.T) {
const (
panicMsg = "spooky!"
prefix = "packagename"
errWithNoPrefix = "[error] recovered from panic: spooky!"
errWithPrefix = "[error] packagename: recovered from panic: spooky!"
)

panicFunc := func(prefix string) {
defer LogPanic(prefix)

panic(panicMsg)
}

panicFunc("")
assert.Contains(t, buf.String(), errWithNoPrefix)
buf.Reset()

panicFunc(prefix)
assert.Contains(t, buf.String(), errWithPrefix)
buf.Reset()
})

t.Run("don't_panic", func(t *testing.T) {
require.NotPanics(t, func() {
defer LogPanic("")
})

assert.Empty(t, buf.String())
})
}
22 changes: 22 additions & 0 deletions internal/aghnet/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,25 @@ func ErrorIsAddrInUse(err error) bool {

return errErrno == syscall.EADDRINUSE
}

// SplitHost is a wrapper for net.SplitHostPort for the cases when the hostport
// does not necessarily contain a port.
func SplitHost(hostport string) (host string, err error) {
host, _, err = net.SplitHostPort(hostport)
if err != nil {
// Check for the missing port error. If it is that error, just
// use the host as is.
//
// See the source code for net.SplitHostPort.
const missingPort = "missing port in address"

addrErr := &net.AddrError{}
if !errors.As(err, &addrErr) || addrErr.Err != missingPort {
return "", err
}

host = hostport
}

return host, nil
}
10 changes: 5 additions & 5 deletions internal/aghnet/net_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"regexp"
"strings"

"github.com/AdguardTeam/AdGuardHome/internal/util"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
)

// hardwarePortInfo - information obtained using MacOS networksetup
Expand Down Expand Up @@ -47,7 +47,7 @@ func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
// it returns a map where the key is the interface name, and the value is the "hardware port"
// returns nil if it fails to parse the output
func getNetworkSetupHardwareReports() map[string]string {
_, out, err := util.RunCommand("networksetup", "-listallhardwareports")
_, out, err := aghos.RunCommand("networksetup", "-listallhardwareports")
if err != nil {
return nil
}
Expand All @@ -72,7 +72,7 @@ func getNetworkSetupHardwareReports() map[string]string {
func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
h := hardwarePortInfo{}

_, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort)
_, out, err := aghos.RunCommand("networksetup", "-getinfo", hardwarePort)
if err != nil {
return h, err
}
Expand Down Expand Up @@ -116,7 +116,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
args = append(args, dnsAddrs...)

// Setting DNS servers is necessary when configuring a static IP
code, _, err := util.RunCommand("networksetup", args...)
code, _, err := aghos.RunCommand("networksetup", args...)
if err != nil {
return err
}
Expand All @@ -125,7 +125,7 @@ func ifaceSetStaticIP(ifaceName string) (err error) {
}

// Actually configures hardware port to have static IP
code, _, err = util.RunCommand("networksetup", "-setmanual",
code, _, err = aghos.RunCommand("networksetup", "-setmanual",
portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
if err != nil {
return err
Expand Down
78 changes: 78 additions & 0 deletions internal/aghnet/systemresolvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package aghnet

import (
"time"

"github.com/AdguardTeam/AdGuardHome/internal/agherr"
"github.com/AdguardTeam/golibs/log"
)

// DefaultRefreshIvl is the default period of time between refreshing cached
// addresses.
// const DefaultRefreshIvl = 5 * time.Minute

// HostGenFunc is the signature for functions generating fake hostnames. The
// implementation must be safe for concurrent use.
type HostGenFunc func() (host string)

// unit is an alias for an existing map value.
type unit = struct{}

// SystemResolvers helps to work with local resolvers' addresses provided by OS.
type SystemResolvers interface {
// Get returns the slice of local resolvers' addresses.
// It should be safe for concurrent use.
Get() (rs []string)
// Refresh refreshes the local resolvers' addresses cache. It should be
// safe for concurrent use.
Refresh() (err error)
}

const (
// fakeDialErr is an error which dialFunc is expected to return.
fakeDialErr agherr.Error = "this error signals the successful dialFunc work"

// badAddrPassedErr is returned when dialFunc can't parse an IP address.
badAddrPassedErr agherr.Error = "the passed string is not a valid IP address"
)

// refreshWithTicker refreshes the cache of sr after each tick form tickCh.
func refreshWithTicker(sr SystemResolvers, tickCh <-chan time.Time) {
defer agherr.LogPanic("systemResolvers")

// TODO(e.burkov): Implement a functionality to stop ticker.
for range tickCh {
err := sr.Refresh()
if err != nil {
log.Error("systemResolvers: error in refreshing goroutine: %s", err)

continue
}

log.Debug("systemResolvers: local addresses cache is refreshed")
}
}

// NewSystemResolvers returns a SystemResolvers with the cache refresh rate
// defined by refreshIvl. It disables auto-resfreshing if refreshIvl is 0. If
// nil is passed for hostGenFunc, the default generator will be used.
func NewSystemResolvers(
refreshIvl time.Duration,
hostGenFunc HostGenFunc,
) (sr SystemResolvers, err error) {
sr = newSystemResolvers(refreshIvl, hostGenFunc)

// Fill cache.
err = sr.Refresh()
if err != nil {
return nil, err
}

if refreshIvl > 0 {
ticker := time.NewTicker(refreshIvl)

go refreshWithTicker(sr, ticker.C)
}

return sr, nil
}
96 changes: 96 additions & 0 deletions internal/aghnet/systemresolvers_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// +build !windows

package aghnet

import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/agherr"
)

// defaultHostGen is the default method of generating host for Refresh.
func defaultHostGen() (host string) {
// TODO(e.burkov): Use strings.Builder.
return fmt.Sprintf("test%d.org", time.Now().UnixNano())
}

// systemResolvers is a default implementation of SystemResolvers interface.
type systemResolvers struct {
resolver *net.Resolver
hostGenFunc HostGenFunc

// addrs is the map that contains cached local resolvers' addresses.
addrs map[string]unit
addrsLock sync.RWMutex
}

func (sr *systemResolvers) Refresh() (err error) {
defer agherr.Annotate("systemResolvers: %w", &err)

_, err = sr.resolver.LookupHost(context.Background(), sr.hostGenFunc())
dnserr := &net.DNSError{}
if errors.As(err, &dnserr) && dnserr.Err == fakeDialErr.Error() {
return nil
}

return err
}

func newSystemResolvers(refreshIvl time.Duration, hostGenFunc HostGenFunc) (sr SystemResolvers) {
if hostGenFunc == nil {
hostGenFunc = defaultHostGen
}
s := &systemResolvers{
resolver: &net.Resolver{
PreferGo: true,
},
hostGenFunc: hostGenFunc,
addrs: make(map[string]unit),
}
s.resolver.Dial = s.dialFunc

return s
}

// dialFunc gets the resolver's address and puts it into internal cache.
func (sr *systemResolvers) dialFunc(_ context.Context, _, address string) (_ net.Conn, err error) {
// Just validate the passed address is a valid IP.
var host string
host, err = SplitHost(address)
if err != nil {
// TODO(e.burkov): Maybe use a structured badAddrPassedErr to
// allow unwrapping of the real error.
return nil, fmt.Errorf("%s: %w", err, badAddrPassedErr)
}

if net.ParseIP(host) == nil {
return nil, fmt.Errorf("parsing %q: %w", host, badAddrPassedErr)
}

sr.addrsLock.Lock()
defer sr.addrsLock.Unlock()

sr.addrs[address] = unit{}

return nil, fakeDialErr
}

func (sr *systemResolvers) Get() (rs []string) {
sr.addrsLock.RLock()
defer sr.addrsLock.RUnlock()

addrs := sr.addrs
rs = make([]string, len(addrs))
var i int
for addr := range addrs {
rs[i] = addr
i++
}

return rs
}
Loading

0 comments on commit c79e60f

Please sign in to comment.