Pull request: 2704 local addresses vol.3
Merge in DNS/adguard-home from 2704-local-addresses-vol.3 to master Updates #2704. Updates #2829. Updates #2928. Squashed commit of the following: commit 8c42355c0093a3ac6951f79a5211e7891800f93a Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 18:07:41 2021 +0300 dnsforward: rm errors pkg commit 7594a21a620239951039454dd5686a872e6f41a8 Merge: 830b0834908452f8Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 18:00:03 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.3 commit 830b0834090510096061fed20b600195ab3773b8 Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Apr 7 17:47:51 2021 +0300 dnsforward: reduce local upstream timeout commit 493e81d9e8bacdc690f88af29a38d211b9733c7e Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 19:11:00 2021 +0300 client: private_upstream test commit a0194ac28f15114578359b8c2460cd9af621e912 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 18:36:23 2021 +0300 all: expand api, fix conflicts commit 0f4e06836fed958391aa597c8b02453564980ca3 Merge: 89cf93ad8746005dAuthor: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 18:35:04 2021 +0300 Merge branch 'master' into 2704-local-addresses-vol.3 commit 89cf93ad4f26c2bf4f1b18ecaa4d3a1e169f9b06 Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 18:02:40 2021 +0300 client: add local ptr upstreams to upstream test commit e6dd869dddd4888474d625cbb005bad6390e4760 Author: Ildar Kamalov <ik@adguard.com> Date: Tue Apr 6 15:24:22 2021 +0300 client: add private DNS form commit b858057b9a957a416117f22b8bd0025f90e8c758 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 13:05:28 2021 +0300 aghstrings: mk cloning correct commit 8009ba60a6a7d6ceb7b6483a29f4e68d533af243 Author: Eugene Burkov <e.burkov@adguard.com> Date: Tue Apr 6 12:37:46 2021 +0300 aghstrings: fix lil bug commit 0dd19f2e7cc7c0de21517c37abd8336a907e1c0d Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:45:01 2021 +0300 all: log changes commit eb5558d96fffa6e7bca7e14d3740d26e47382e23 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:18:53 2021 +0300 dnsforward: keep the style commit d6d5fcbde40a633129c0e04887b81cf0b1ce6875 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 20:02:52 2021 +0300 dnsforward: disable redundant filtering for local ptr commit 4f864c32027d10db9bcb4a264d2338df8c20afac Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 17:53:17 2021 +0300 dnsforward: imp tests commit 7848e6f2341868f8ba0bb839956a0b7444cf02ca Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Apr 5 14:52:12 2021 +0300 all: imp code commit 19ac30653800eebf8aaee499f65560ae2d458a5a Author: Eugene Burkov <e.burkov@adguard.com> Date: Sun Apr 4 16:28:05 2021 +0300 all: mv more logic to aghstrings commit fac892ec5f0d2e30d6d64def0609267bbae4a202 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:23:23 2021 +0300 dnsforward: use filepath commit 05a3aeef1181b914788d14c7519287d467ab301f Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:17:54 2021 +0300 aghstrings: introduce the pkg commit f24e1b63d6e1bf266a4ed063f46f86d7abf65663 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 20:01:23 2021 +0300 all: imp code commit 0217a0ebb341f99a90c9b68013bebf6ff73d08ae Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Apr 2 18:04:13 2021 +0300 openapi: log changes ... and 3 more commits
This commit is contained in:
@@ -46,7 +46,8 @@ func (e *manyError) Error() (msg string) {
|
||||
b := &strings.Builder{}
|
||||
|
||||
// Ignore errors, since strings.(*Buffer).Write never returns
|
||||
// errors.
|
||||
// errors. We don't use aghstrings.WriteToBuilder here since
|
||||
// this package should be importable for any other.
|
||||
_, _ = fmt.Fprintf(b, "%s: %s (hidden: %s", e.message, e.underlying[0], e.underlying[1])
|
||||
for _, u := range e.underlying[2:] {
|
||||
// See comment above.
|
||||
|
||||
@@ -31,7 +31,11 @@ type multiAddrExchanger struct {
|
||||
|
||||
// NewMultiAddrExchanger creates an Exchanger instance from passed addresses.
|
||||
// It returns an error if any of addrs failed to become an upstream.
|
||||
func NewMultiAddrExchanger(addrs []string, timeout time.Duration) (e Exchanger, err error) {
|
||||
func NewMultiAddrExchanger(
|
||||
addrs []string,
|
||||
bootstraps []string,
|
||||
timeout time.Duration,
|
||||
) (e Exchanger, err error) {
|
||||
defer agherr.Annotate("exchanger: %w", &err)
|
||||
|
||||
if len(addrs) == 0 {
|
||||
@@ -41,7 +45,10 @@ func NewMultiAddrExchanger(addrs []string, timeout time.Duration) (e Exchanger,
|
||||
var ups []upstream.Upstream = make([]upstream.Upstream, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
var u upstream.Upstream
|
||||
u, err = upstream.AddressToUpstream(addr, upstream.Options{Timeout: timeout})
|
||||
u, err = upstream.AddressToUpstream(addr, upstream.Options{
|
||||
Bootstrap: bootstraps,
|
||||
Timeout: timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,19 +15,19 @@ func TestNewMultiAddrExchanger(t *testing.T) {
|
||||
var err error
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{}, 0)
|
||||
e, err = NewMultiAddrExchanger([]string{}, nil, 0)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, e)
|
||||
})
|
||||
|
||||
t.Run("successful", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{"www.example.com"}, 0)
|
||||
e, err = NewMultiAddrExchanger([]string{"www.example.com"}, nil, 0)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, e)
|
||||
})
|
||||
|
||||
t.Run("unsuccessful", func(t *testing.T) {
|
||||
e, err = NewMultiAddrExchanger([]string{"invalid-proto://www.example.com"}, 0)
|
||||
e, err = NewMultiAddrExchanger([]string{"invalid-proto://www.example.com"}, nil, 0)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, e)
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
@@ -355,30 +356,30 @@ const (
|
||||
// (PTR) record lookups. This is the modified version of ReverseAddr from
|
||||
// github.com/miekg/dns package with no error among returned values.
|
||||
func ReverseAddr(ip net.IP) (arpa string) {
|
||||
const dot = "."
|
||||
|
||||
var strLen int
|
||||
var suffix string
|
||||
// Don't handle errors in implementations since strings.WriteString
|
||||
// never returns non-nil errors.
|
||||
var writeByte func(val byte)
|
||||
b := &strings.Builder{}
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
strLen, suffix = arpaV4MaxLen, arpaV4Suffix[1:]
|
||||
ip = ip4
|
||||
writeByte = func(val byte) {
|
||||
_, _ = b.WriteString(strconv.Itoa(int(val)))
|
||||
_, _ = b.WriteRune('.')
|
||||
aghstrings.WriteToBuilder(b, strconv.Itoa(int(val)), dot)
|
||||
}
|
||||
|
||||
} else if ip6 := ip.To16(); ip6 != nil {
|
||||
strLen, suffix = arpaV6MaxLen, arpaV6Suffix[1:]
|
||||
ip = ip6
|
||||
writeByte = func(val byte) {
|
||||
lByte, rByte := val&0xF, val>>4
|
||||
|
||||
_, _ = b.WriteString(strconv.FormatUint(uint64(lByte), 16))
|
||||
_, _ = b.WriteRune('.')
|
||||
_, _ = b.WriteString(strconv.FormatUint(uint64(rByte), 16))
|
||||
_, _ = b.WriteRune('.')
|
||||
aghstrings.WriteToBuilder(
|
||||
b,
|
||||
strconv.FormatUint(uint64(val&0xF), 16),
|
||||
dot,
|
||||
strconv.FormatUint(uint64(val>>4), 16),
|
||||
dot,
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -389,7 +390,38 @@ func ReverseAddr(ip net.IP) (arpa string) {
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
writeByte(ip[i])
|
||||
}
|
||||
_, _ = b.WriteString(suffix)
|
||||
aghstrings.WriteToBuilder(b, suffix)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// CollectAllIfacesAddrs returns the slice of all network interfaces IP
|
||||
// addresses without port number.
|
||||
func CollectAllIfacesAddrs() (addrs []string, err error) {
|
||||
var ifaces []net.Interface
|
||||
ifaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting network interfaces: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
var ifaceAddrs []net.Addr
|
||||
ifaceAddrs, err = iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
cidr := addr.String()
|
||||
var ip net.IP
|
||||
ip, _, err = net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing cidr: %w", err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
71
internal/aghstrings/strings.go
Normal file
71
internal/aghstrings/strings.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Package aghstrings contains utilities dealing with strings.
|
||||
package aghstrings
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CloneSliceOrEmpty returns the copy of a or empty strings slice if a is nil.
|
||||
func CloneSliceOrEmpty(a []string) (b []string) {
|
||||
return append([]string{}, a...)
|
||||
}
|
||||
|
||||
// CloneSlice returns the exact copy of a.
|
||||
func CloneSlice(a []string) (b []string) {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CloneSliceOrEmpty(a)
|
||||
}
|
||||
|
||||
// InSlice checks if string is in the slice of strings.
|
||||
func InSlice(strs []string, str string) (ok bool) {
|
||||
for _, s := range strs {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SplitNext splits string by a byte and returns the first chunk skipping empty
|
||||
// ones. Whitespaces are trimmed.
|
||||
func SplitNext(s *string, sep rune) (chunk string) {
|
||||
if s == nil {
|
||||
return chunk
|
||||
}
|
||||
|
||||
i := strings.IndexByte(*s, byte(sep))
|
||||
if i == -1 {
|
||||
chunk = *s
|
||||
*s = ""
|
||||
|
||||
return strings.TrimSpace(chunk)
|
||||
}
|
||||
|
||||
chunk = (*s)[:i]
|
||||
*s = (*s)[i+1:]
|
||||
var j int
|
||||
var r rune
|
||||
for j, r = range *s {
|
||||
if r != sep {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
*s = (*s)[j:]
|
||||
|
||||
return strings.TrimSpace(chunk)
|
||||
}
|
||||
|
||||
// WriteToBuilder is a convenient wrapper for strings.(*Builder).WriteString
|
||||
// that deals with multiple strings and ignores errors that are guaranteed to be
|
||||
// nil.
|
||||
func WriteToBuilder(b *strings.Builder, strs ...string) {
|
||||
// TODO(e.burkov): Recover from panic?
|
||||
for _, s := range strs {
|
||||
_, _ = b.WriteString(s)
|
||||
}
|
||||
}
|
||||
114
internal/aghstrings/strings_test.go
Normal file
114
internal/aghstrings/strings_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package aghstrings
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCloneSlice_family(t *testing.T) {
|
||||
a := []string{"1", "2", "3"}
|
||||
|
||||
t.Run("cloneslice_simple", func(t *testing.T) {
|
||||
assert.Equal(t, a, CloneSlice(a))
|
||||
})
|
||||
|
||||
t.Run("cloneslice_nil", func(t *testing.T) {
|
||||
assert.Nil(t, CloneSlice(nil))
|
||||
})
|
||||
|
||||
t.Run("cloneslice_empty", func(t *testing.T) {
|
||||
assert.Equal(t, []string{}, CloneSlice([]string{}))
|
||||
})
|
||||
|
||||
t.Run("clonesliceorempty_nil", func(t *testing.T) {
|
||||
assert.Equal(t, []string{}, CloneSliceOrEmpty(nil))
|
||||
})
|
||||
|
||||
t.Run("clonesliceorempty_empty", func(t *testing.T) {
|
||||
assert.Equal(t, []string{}, CloneSliceOrEmpty([]string{}))
|
||||
})
|
||||
|
||||
t.Run("clonesliceorempty_sameness", func(t *testing.T) {
|
||||
assert.Equal(t, CloneSlice(a), CloneSliceOrEmpty(a))
|
||||
})
|
||||
}
|
||||
|
||||
func TestInSlice(t *testing.T) {
|
||||
simpleStrs := []string{"1", "2", "3"}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
str string
|
||||
strs []string
|
||||
want bool
|
||||
}{{
|
||||
name: "yes",
|
||||
str: "2",
|
||||
strs: simpleStrs,
|
||||
want: true,
|
||||
}, {
|
||||
name: "no",
|
||||
str: "4",
|
||||
strs: simpleStrs,
|
||||
want: false,
|
||||
}, {
|
||||
name: "nil",
|
||||
str: "any",
|
||||
strs: nil,
|
||||
want: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, InSlice(tc.strs, tc.str))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitNext(t *testing.T) {
|
||||
t.Run("ordinary", func(t *testing.T) {
|
||||
s := " a,b , c "
|
||||
require.Equal(t, "a", SplitNext(&s, ','))
|
||||
require.Equal(t, "b", SplitNext(&s, ','))
|
||||
require.Equal(t, "c", SplitNext(&s, ','))
|
||||
|
||||
assert.Empty(t, s)
|
||||
})
|
||||
|
||||
t.Run("nil_source", func(t *testing.T) {
|
||||
assert.Equal(t, "", SplitNext(nil, 's'))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteToBuilder(t *testing.T) {
|
||||
b := &strings.Builder{}
|
||||
|
||||
t.Run("single", func(t *testing.T) {
|
||||
assert.NotPanics(t, func() { WriteToBuilder(b, t.Name()) })
|
||||
assert.Equal(t, t.Name(), b.String())
|
||||
})
|
||||
|
||||
b.Reset()
|
||||
t.Run("several", func(t *testing.T) {
|
||||
const (
|
||||
_1 = "one"
|
||||
_2 = "two"
|
||||
_123 = _1 + _2
|
||||
)
|
||||
assert.NotPanics(t, func() { WriteToBuilder(b, _1, _2) })
|
||||
assert.Equal(t, _123, b.String())
|
||||
})
|
||||
|
||||
b.Reset()
|
||||
t.Run("nothing", func(t *testing.T) {
|
||||
assert.NotPanics(t, func() { WriteToBuilder(b) })
|
||||
assert.Equal(t, "", b.String())
|
||||
})
|
||||
|
||||
t.Run("nil_builder", func(t *testing.T) {
|
||||
assert.Panics(t, func() { WriteToBuilder(nil, "a") })
|
||||
})
|
||||
}
|
||||
@@ -11,10 +11,10 @@ type Exchanger struct {
|
||||
}
|
||||
|
||||
// Exchange implements aghnet.Exchanger interface for *Exchanger.
|
||||
func (lr *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
if lr.Ups == nil {
|
||||
lr.Ups = &TestErrUpstream{}
|
||||
func (e *Exchanger) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
if e.Ups == nil {
|
||||
e.Ups = &TestErrUpstream{}
|
||||
}
|
||||
|
||||
return lr.Ups.Exchange(req)
|
||||
return e.Ups.Exchange(req)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ type Config struct {
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"`
|
||||
|
||||
// CustomResolver is the resolver used by DNSFilter.
|
||||
CustomResolver Resolver
|
||||
CustomResolver Resolver `yaml:"-"`
|
||||
}
|
||||
|
||||
// LookupStats store stats collected during safebrowsing or parental checks
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -181,26 +182,21 @@ func hostnameToHashes(host string) map[[32]byte]string {
|
||||
// convert hash array to string
|
||||
func (c *sbCtx) getQuestion() string {
|
||||
b := &strings.Builder{}
|
||||
encoder := hex.NewEncoder(b)
|
||||
|
||||
for hash := range c.hashToHost {
|
||||
// Ignore errors, since strings.(*Buffer).Write never returns
|
||||
// errors.
|
||||
//
|
||||
// TODO(e.burkov, a.garipov): Find out and document why exactly
|
||||
// this slice.
|
||||
_, _ = encoder.Write(hash[0:2])
|
||||
_, _ = b.WriteRune('.')
|
||||
aghstrings.WriteToBuilder(b, hex.EncodeToString(hash[0:2]), ".")
|
||||
}
|
||||
|
||||
if c.svc == "SafeBrowsing" {
|
||||
// See comment above.
|
||||
_, _ = b.WriteString(sbTXTSuffix)
|
||||
aghstrings.WriteToBuilder(b, sbTXTSuffix)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// See comment above.
|
||||
_, _ = b.WriteString(pcTXTSuffix)
|
||||
aghstrings.WriteToBuilder(b, pcTXTSuffix)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
@@ -36,16 +37,15 @@ func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []strin
|
||||
return err
|
||||
}
|
||||
|
||||
buf := strings.Builder{}
|
||||
b := &strings.Builder{}
|
||||
for _, s := range blockedHosts {
|
||||
buf.WriteString(s)
|
||||
buf.WriteString("\n")
|
||||
aghstrings.WriteToBuilder(b, s, "\n")
|
||||
}
|
||||
|
||||
listArray := []filterlist.RuleList{}
|
||||
list := &filterlist.StringRuleList{
|
||||
ID: int(0),
|
||||
RulesText: buf.String(),
|
||||
RulesText: b.String(),
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
listArray = append(listArray, list)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -149,6 +149,13 @@ type ServerConfig struct {
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
|
||||
|
||||
// ResolveClients signals if the RDNS should resolve clients' addresses.
|
||||
ResolveClients bool
|
||||
|
||||
// LocalPTRResolvers is a slice of addresses to be used as upstreams for
|
||||
// resolving PTR queries for local addresses.
|
||||
LocalPTRResolvers []string
|
||||
}
|
||||
|
||||
// if any of ServerConfig values are zero, then default values from below are used
|
||||
@@ -274,7 +281,7 @@ func (s *Server) prepareUpstreamSettings() error {
|
||||
}
|
||||
d := string(data)
|
||||
for len(d) != 0 {
|
||||
s := util.SplitNext(&d, '\n')
|
||||
s := aghstrings.SplitNext(&d, '\n')
|
||||
upstreams = append(upstreams, s)
|
||||
}
|
||||
log.Debug("dns: using %d upstream servers from file %s", len(upstreams), s.conf.UpstreamDNSFileName)
|
||||
|
||||
@@ -293,6 +293,14 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||
// Do not perform unreversing ever again.
|
||||
ctx.unreversedReqIP = ip
|
||||
|
||||
// Disable redundant filtering.
|
||||
filterSetts := s.getClientRequestFilteringSettings(ctx)
|
||||
filterSetts.ParentalEnabled = false
|
||||
filterSetts.SafeBrowsingEnabled = false
|
||||
filterSetts.SafeSearchEnabled = false
|
||||
filterSetts.ServicesRules = nil
|
||||
ctx.setts = filterSetts
|
||||
|
||||
// Nothing to restrict.
|
||||
return resultCodeSuccess
|
||||
}
|
||||
@@ -405,15 +413,19 @@ func processFilteringBeforeRequest(ctx *dnsContext) (rc resultCode) {
|
||||
var err error
|
||||
ctx.protectionEnabled = s.conf.ProtectionEnabled && s.dnsFilter != nil
|
||||
if ctx.protectionEnabled {
|
||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||
if ctx.setts == nil {
|
||||
ctx.setts = s.getClientRequestFilteringSettings(ctx)
|
||||
}
|
||||
ctx.result, err = s.filterDNSRequest(ctx)
|
||||
}
|
||||
s.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
ctx.err = err
|
||||
|
||||
return resultCodeError
|
||||
}
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,13 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
@@ -92,7 +95,6 @@ type DNSCreateParams struct {
|
||||
QueryLog querylog.QueryLog
|
||||
DHCPServer dhcpd.ServerInterface
|
||||
SubnetDetector *aghnet.SubnetDetector
|
||||
LocalResolvers aghnet.Exchanger
|
||||
AutohostTLD string
|
||||
}
|
||||
|
||||
@@ -127,7 +129,6 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
stats: p.Stats,
|
||||
queryLog: p.QueryLog,
|
||||
subnetDetector: p.SubnetDetector,
|
||||
localResolvers: p.LocalResolvers,
|
||||
autohostSuffix: autohostSuffix,
|
||||
}
|
||||
|
||||
@@ -176,15 +177,23 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||
s.RLock()
|
||||
sc := s.conf.FilteringConfig
|
||||
*c = sc
|
||||
c.RatelimitWhitelist = stringArrayDup(sc.RatelimitWhitelist)
|
||||
c.BootstrapDNS = stringArrayDup(sc.BootstrapDNS)
|
||||
c.AllowedClients = stringArrayDup(sc.AllowedClients)
|
||||
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
||||
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
||||
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
||||
c.RatelimitWhitelist = aghstrings.CloneSlice(sc.RatelimitWhitelist)
|
||||
c.BootstrapDNS = aghstrings.CloneSlice(sc.BootstrapDNS)
|
||||
c.AllowedClients = aghstrings.CloneSlice(sc.AllowedClients)
|
||||
c.DisallowedClients = aghstrings.CloneSlice(sc.DisallowedClients)
|
||||
c.BlockedHosts = aghstrings.CloneSlice(sc.BlockedHosts)
|
||||
c.UpstreamDNS = aghstrings.CloneSlice(sc.UpstreamDNS)
|
||||
s.RUnlock()
|
||||
}
|
||||
|
||||
// RDNSSettings returns the copy of actual RDNS configuration.
|
||||
func (s *Server) RDNSSettings() (localPTRResolvers []string, resolveClients bool) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return aghstrings.CloneSlice(s.conf.LocalPTRResolvers), s.conf.ResolveClients
|
||||
}
|
||||
|
||||
// Resolve - get IP addresses by host name from an upstream server.
|
||||
// No request/response filtering is performed.
|
||||
// Query log and Stats are not updated.
|
||||
@@ -195,24 +204,73 @@ func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
||||
return s.internalProxy.LookupIPAddr(host)
|
||||
}
|
||||
|
||||
// Exchange - send DNS request to an upstream server and receive response
|
||||
// No request/response filtering is performed.
|
||||
// Query log and Stats are not updated.
|
||||
// This method may be called before Start().
|
||||
func (s *Server) Exchange(req *dns.Msg) (*dns.Msg, error) {
|
||||
// RDNSExchanger is a resolver for clients' addresses.
|
||||
type RDNSExchanger interface {
|
||||
// Exchange tries to resolve the ip in a suitable way, e.g. either as
|
||||
// local or as external.
|
||||
Exchange(ip net.IP) (host string, err error)
|
||||
}
|
||||
|
||||
const (
|
||||
// rDNSEmptyAnswerErr is returned by Exchange method when the answer
|
||||
// section of respond is empty.
|
||||
rDNSEmptyAnswerErr agherr.Error = "the answer section is empty"
|
||||
|
||||
// rDNSNotPTRErr is returned by Exchange method when the response is not
|
||||
// of PTR type.
|
||||
rDNSNotPTRErr agherr.Error = "the response is not a ptr"
|
||||
)
|
||||
|
||||
// Exchange implements the RDNSExchanger interface for *Server.
|
||||
func (s *Server) Exchange(ip net.IP) (host string, err error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
ctx := &proxy.DNSContext{
|
||||
Proto: "udp",
|
||||
Req: req,
|
||||
StartTime: time.Now(),
|
||||
if !s.conf.ResolveClients {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{{
|
||||
Name: arpa,
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
var resp *dns.Msg
|
||||
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||
resp, err = s.localResolvers.Exchange(req)
|
||||
} else {
|
||||
ctx := &proxy.DNSContext{
|
||||
Proto: "udp",
|
||||
Req: req,
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
err = s.internalProxy.Resolve(ctx)
|
||||
|
||||
resp = ctx.Res
|
||||
}
|
||||
err := s.internalProxy.Resolve(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
return ctx.Res, nil
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
return "", fmt.Errorf("lookup for %q: %w", arpa, rDNSEmptyAnswerErr)
|
||||
}
|
||||
|
||||
ptr, ok := resp.Answer[0].(*dns.PTR)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("type checking: %w", rDNSNotPTRErr)
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
||||
}
|
||||
|
||||
// Start starts the DNS server.
|
||||
@@ -231,6 +289,110 @@ func (s *Server) startLocked() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// defaultLocalTimeout is the default timeout for resolving addresses from
|
||||
// locally-served networks. It is assumed that local resolvers should work much
|
||||
// faster than ordinary upstreams.
|
||||
const defaultLocalTimeout = 1 * time.Second
|
||||
|
||||
// collectDNSIPAddrs returns the slice of IP addresses without port number which
|
||||
// we are listening on. For internal use only.
|
||||
func (s *Server) collectDNSIPAddrs() (addrs []string, err error) {
|
||||
addrs = make([]string, len(s.conf.TCPListenAddrs)+len(s.conf.UDPListenAddrs))
|
||||
var i int
|
||||
var ip net.IP
|
||||
for _, addr := range s.conf.TCPListenAddrs {
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip = addr.IP; ip.IsUnspecified() {
|
||||
return aghnet.CollectAllIfacesAddrs()
|
||||
}
|
||||
|
||||
addrs[i] = ip.String()
|
||||
i++
|
||||
}
|
||||
for _, addr := range s.conf.UDPListenAddrs {
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip = addr.IP; ip.IsUnspecified() {
|
||||
return aghnet.CollectAllIfacesAddrs()
|
||||
}
|
||||
|
||||
addrs[i] = ip.String()
|
||||
i++
|
||||
}
|
||||
|
||||
return addrs[:i], nil
|
||||
}
|
||||
|
||||
// stringSetSubtract subtracts b from a interpreted as sets.
|
||||
func stringSetSubtract(a, b []string) (c []string) {
|
||||
// unit is an object to be used as value in set.
|
||||
type unit = struct{}
|
||||
|
||||
cSet := make(map[string]unit)
|
||||
for _, k := range a {
|
||||
cSet[k] = unit{}
|
||||
}
|
||||
|
||||
for _, k := range b {
|
||||
delete(cSet, k)
|
||||
}
|
||||
|
||||
c = make([]string, len(cSet))
|
||||
i := 0
|
||||
for k := range cSet {
|
||||
c[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// setupResolvers initializes the resolvers for local addresses. For internal
|
||||
// use only.
|
||||
func (s *Server) setupResolvers(localAddrs []string) (err error) {
|
||||
bootstraps := s.conf.BootstrapDNS
|
||||
if len(localAddrs) == 0 {
|
||||
var sysRes aghnet.SystemResolvers
|
||||
// TODO(e.burkov): Enable the refresher after the actual
|
||||
// implementation passes the public testing.
|
||||
sysRes, err = aghnet.NewSystemResolvers(0, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localAddrs = sysRes.Get()
|
||||
bootstraps = nil
|
||||
}
|
||||
log.Debug("upstreams to resolve PTR for local addresses: %v", localAddrs)
|
||||
|
||||
var ourAddrs []string
|
||||
ourAddrs, err = s.collectDNSIPAddrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(e.burkov): The approach of subtracting sets of strings
|
||||
// is not really applicable here since in case of listening on
|
||||
// all network interfaces we should check the whole interface's
|
||||
// network to cut off all the loopback addresses as well.
|
||||
localAddrs = stringSetSubtract(localAddrs, ourAddrs)
|
||||
|
||||
if s.localResolvers, err = aghnet.NewMultiAddrExchanger(
|
||||
localAddrs,
|
||||
bootstraps,
|
||||
defaultLocalTimeout,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare the object
|
||||
func (s *Server) Prepare(config *ServerConfig) error {
|
||||
// Initialize the server configuration
|
||||
@@ -305,6 +467,12 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
// Create the main DNS proxy instance
|
||||
// --
|
||||
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
||||
|
||||
err = s.setupResolvers(s.conf.LocalPTRResolvers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up resolvers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
@@ -73,7 +74,6 @@ func createTestServer(t *testing.T, filterConf *dnsfilter.Config, forwardConf Se
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DNSFilter: f,
|
||||
SubnetDetector: snd,
|
||||
LocalResolvers: &aghtest.Exchanger{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -82,6 +82,11 @@ func createTestServer(t *testing.T, filterConf *dnsfilter.Config, forwardConf Se
|
||||
err = s.Prepare(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.localResolvers = &aghtest.Exchanger{}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -728,7 +733,6 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters),
|
||||
SubnetDetector: snd,
|
||||
LocalResolvers: &aghtest.Exchanger{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -866,7 +870,6 @@ func TestRewrite(t *testing.T) {
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DNSFilter: f,
|
||||
SubnetDetector: snd,
|
||||
LocalResolvers: &aghtest.Exchanger{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1029,7 +1032,6 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
DNSFilter: dnsfilter.New(&dnsfilter.Config{}, nil),
|
||||
DHCPServer: &testDHCP{},
|
||||
SubnetDetector: snd,
|
||||
LocalResolvers: &aghtest.Exchanger{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1094,7 +1096,6 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DNSFilter: dnsfilter.New(&c, nil),
|
||||
SubnetDetector: snd,
|
||||
LocalResolvers: &aghtest.Exchanger{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1164,3 +1165,100 @@ func TestNewServer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_Exchange(t *testing.T) {
|
||||
extUpstream := &aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
|
||||
},
|
||||
}
|
||||
locUpstream := &aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
||||
"2.1.168.192.in-addr.arpa.": {},
|
||||
},
|
||||
}
|
||||
upstreamErr := agherr.Error("upstream error")
|
||||
errUpstream := &aghtest.TestErrUpstream{
|
||||
Err: upstreamErr,
|
||||
}
|
||||
nonPtrUpstream := &aghtest.TestBlockUpstream{
|
||||
Hostname: "some-host",
|
||||
Block: true,
|
||||
}
|
||||
|
||||
dns := NewCustomServer(&proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{extUpstream},
|
||||
},
|
||||
},
|
||||
})
|
||||
dns.conf.ResolveClients = true
|
||||
|
||||
var err error
|
||||
dns.subnetDetector, err = aghnet.NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
localIP := net.IP{192, 168, 1, 1}
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
wantErr error
|
||||
locUpstream upstream.Upstream
|
||||
req net.IP
|
||||
}{{
|
||||
name: "external_good",
|
||||
want: "one.one.one.one",
|
||||
wantErr: nil,
|
||||
locUpstream: nil,
|
||||
req: net.IP{1, 1, 1, 1},
|
||||
}, {
|
||||
name: "local_good",
|
||||
want: "local.domain",
|
||||
wantErr: nil,
|
||||
locUpstream: locUpstream,
|
||||
req: localIP,
|
||||
}, {
|
||||
name: "upstream_error",
|
||||
want: "",
|
||||
wantErr: upstreamErr,
|
||||
locUpstream: errUpstream,
|
||||
req: localIP,
|
||||
}, {
|
||||
name: "empty_answer_error",
|
||||
want: "",
|
||||
wantErr: rDNSEmptyAnswerErr,
|
||||
locUpstream: locUpstream,
|
||||
req: net.IP{192, 168, 1, 2},
|
||||
}, {
|
||||
name: "not_ptr_error",
|
||||
want: "",
|
||||
wantErr: rDNSNotPTRErr,
|
||||
locUpstream: nonPtrUpstream,
|
||||
req: localIP,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dns.localResolvers = &aghtest.Exchanger{
|
||||
Ups: tc.locUpstream,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, eerr := dns.Exchange(tc.req)
|
||||
|
||||
require.ErrorIs(t, eerr, tc.wantErr)
|
||||
assert.Equal(t, tc.want, host)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("resolving_disabled", func(t *testing.T) {
|
||||
dns.conf.ResolveClients = false
|
||||
for _, tc := range testCases {
|
||||
host, eerr := dns.Exchange(tc.req)
|
||||
|
||||
require.NoError(t, eerr)
|
||||
assert.Empty(t, host)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,15 +42,15 @@ func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *dnsfilter.F
|
||||
return &setts
|
||||
}
|
||||
|
||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request
|
||||
// was filtered.
|
||||
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was
|
||||
// filtered.
|
||||
func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
d := ctx.proxyCtx
|
||||
// TODO(e.burkov): Consistently use req instead of d.Req since it is
|
||||
// declared.
|
||||
req := d.Req
|
||||
host := strings.TrimSuffix(req.Question[0].Name, ".")
|
||||
res, err := s.dnsFilter.CheckHost(host, d.Req.Question[0].Qtype, ctx.setts)
|
||||
res, err := s.dnsFilter.CheckHost(host, req.Question[0].Qtype, ctx.setts)
|
||||
if err != nil {
|
||||
// Return immediately if there's an error
|
||||
return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err)
|
||||
@@ -63,8 +63,8 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
// Resolve the new canonical name, not the original host
|
||||
// name. The original question is readded in
|
||||
// processFilteringAfterResponse.
|
||||
ctx.origQuestion = d.Req.Question[0]
|
||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
ctx.origQuestion = req.Question[0]
|
||||
req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
} else if res.Reason == dnsfilter.RewrittenAutoHosts && len(res.ReverseHosts) != 0 {
|
||||
resp := s.makeResponse(req)
|
||||
for _, h := range res.ReverseHosts {
|
||||
@@ -84,7 +84,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
}
|
||||
|
||||
d.Res = resp
|
||||
} else if res.Reason == dnsfilter.Rewritten || res.Reason == dnsfilter.RewrittenAutoHosts {
|
||||
} else if res.Reason.In(dnsfilter.Rewritten, dnsfilter.RewrittenAutoHosts) {
|
||||
resp := s.makeResponse(req)
|
||||
|
||||
name := host
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -27,59 +28,67 @@ type dnsConfig struct {
|
||||
UpstreamsFile *string `json:"upstream_dns_file"`
|
||||
Bootstraps *[]string `json:"bootstrap_dns"`
|
||||
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
RateLimit *uint32 `json:"ratelimit"`
|
||||
BlockingMode *string `json:"blocking_mode"`
|
||||
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||
UpstreamMode *string `json:"upstream_mode"`
|
||||
CacheSize *uint32 `json:"cache_size"`
|
||||
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
||||
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
RateLimit *uint32 `json:"ratelimit"`
|
||||
BlockingMode *string `json:"blocking_mode"`
|
||||
BlockingIPv4 net.IP `json:"blocking_ipv4"`
|
||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled *bool `json:"edns_cs_enabled"`
|
||||
DNSSECEnabled *bool `json:"dnssec_enabled"`
|
||||
DisableIPv6 *bool `json:"disable_ipv6"`
|
||||
UpstreamMode *string `json:"upstream_mode"`
|
||||
CacheSize *uint32 `json:"cache_size"`
|
||||
CacheMinTTL *uint32 `json:"cache_ttl_min"`
|
||||
CacheMaxTTL *uint32 `json:"cache_ttl_max"`
|
||||
ResolveClients *bool `json:"resolve_clients"`
|
||||
LocalPTRUpstreams *[]string `json:"local_ptr_upstreams"`
|
||||
}
|
||||
|
||||
func (s *Server) getDNSConfig() dnsConfig {
|
||||
s.RLock()
|
||||
upstreams := stringArrayDup(s.conf.UpstreamDNS)
|
||||
defer s.RUnlock()
|
||||
|
||||
upstreams := aghstrings.CloneSliceOrEmpty(s.conf.UpstreamDNS)
|
||||
upstreamFile := s.conf.UpstreamDNSFileName
|
||||
bootstraps := stringArrayDup(s.conf.BootstrapDNS)
|
||||
bootstraps := aghstrings.CloneSliceOrEmpty(s.conf.BootstrapDNS)
|
||||
protectionEnabled := s.conf.ProtectionEnabled
|
||||
blockingMode := s.conf.BlockingMode
|
||||
BlockingIPv4 := s.conf.BlockingIPv4
|
||||
BlockingIPv6 := s.conf.BlockingIPv6
|
||||
Ratelimit := s.conf.Ratelimit
|
||||
EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||
EnableDNSSEC := s.conf.EnableDNSSEC
|
||||
AAAADisabled := s.conf.AAAADisabled
|
||||
CacheSize := s.conf.CacheSize
|
||||
CacheMinTTL := s.conf.CacheMinTTL
|
||||
CacheMaxTTL := s.conf.CacheMaxTTL
|
||||
blockingIPv4 := s.conf.BlockingIPv4
|
||||
blockingIPv6 := s.conf.BlockingIPv6
|
||||
ratelimit := s.conf.Ratelimit
|
||||
enableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||
enableDNSSEC := s.conf.EnableDNSSEC
|
||||
aaaaDisabled := s.conf.AAAADisabled
|
||||
cacheSize := s.conf.CacheSize
|
||||
cacheMinTTL := s.conf.CacheMinTTL
|
||||
cacheMaxTTL := s.conf.CacheMaxTTL
|
||||
resolveClients := s.conf.ResolveClients
|
||||
localPTRUpstreams := aghstrings.CloneSliceOrEmpty(s.conf.LocalPTRResolvers)
|
||||
var upstreamMode string
|
||||
if s.conf.FastestAddr {
|
||||
upstreamMode = "fastest_addr"
|
||||
} else if s.conf.AllServers {
|
||||
upstreamMode = "parallel"
|
||||
}
|
||||
s.RUnlock()
|
||||
|
||||
return dnsConfig{
|
||||
Upstreams: &upstreams,
|
||||
UpstreamsFile: &upstreamFile,
|
||||
Bootstraps: &bootstraps,
|
||||
ProtectionEnabled: &protectionEnabled,
|
||||
BlockingMode: &blockingMode,
|
||||
BlockingIPv4: BlockingIPv4,
|
||||
BlockingIPv6: BlockingIPv6,
|
||||
RateLimit: &Ratelimit,
|
||||
EDNSCSEnabled: &EnableEDNSClientSubnet,
|
||||
DNSSECEnabled: &EnableDNSSEC,
|
||||
DisableIPv6: &AAAADisabled,
|
||||
CacheSize: &CacheSize,
|
||||
CacheMinTTL: &CacheMinTTL,
|
||||
CacheMaxTTL: &CacheMaxTTL,
|
||||
BlockingIPv4: blockingIPv4,
|
||||
BlockingIPv6: blockingIPv6,
|
||||
RateLimit: &ratelimit,
|
||||
EDNSCSEnabled: &enableEDNSClientSubnet,
|
||||
DNSSECEnabled: &enableDNSSEC,
|
||||
DisableIPv6: &aaaaDisabled,
|
||||
CacheSize: &cacheSize,
|
||||
CacheMinTTL: &cacheMinTTL,
|
||||
CacheMaxTTL: &cacheMaxTTL,
|
||||
UpstreamMode: &upstreamMode,
|
||||
ResolveClients: &resolveClients,
|
||||
LocalPTRUpstreams: &localPTRUpstreams,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +236,11 @@ func (s *Server) setConfigRestartable(dc dnsConfig) (restart bool) {
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.LocalPTRUpstreams != nil {
|
||||
s.conf.LocalPTRResolvers = *dc.LocalPTRUpstreams
|
||||
restart = true
|
||||
}
|
||||
|
||||
if dc.UpstreamsFile != nil {
|
||||
s.conf.UpstreamDNSFileName = *dc.UpstreamsFile
|
||||
restart = true
|
||||
@@ -294,15 +308,24 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) {
|
||||
s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr"
|
||||
}
|
||||
|
||||
if dc.ResolveClients != nil {
|
||||
s.conf.ResolveClients = *dc.ResolveClients
|
||||
}
|
||||
|
||||
return s.setConfigRestartable(dc)
|
||||
}
|
||||
|
||||
// upstreamJSON is a request body for handleTestUpstreamDNS endpoint.
|
||||
type upstreamJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
||||
Upstreams []string `json:"upstream_dns"`
|
||||
BootstrapDNS []string `json:"bootstrap_dns"`
|
||||
PrivateUpstreams []string `json:"private_upstream"`
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified
|
||||
// ValidateUpstreams validates each upstream and returns an error if any
|
||||
// upstream is invalid or if there are no default upstreams specified.
|
||||
//
|
||||
// TODO(e.burkov): Move into aghnet or even into dnsproxy.
|
||||
func ValidateUpstreams(upstreams []string) (err error) {
|
||||
// No need to validate comments
|
||||
upstreams = filterOutComments(upstreams)
|
||||
@@ -428,52 +451,76 @@ func checkPlainDNS(upstream string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
req := upstreamJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||
return
|
||||
// excFunc is a signature of function to check if upstream exchanges correctly.
|
||||
type excFunc func(u upstream.Upstream) (err error)
|
||||
|
||||
// checkDNSUpstreamExc checks if the DNS upstream exchanges correctly.
|
||||
func checkDNSUpstreamExc(u upstream.Upstream) (err error) {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: "google-public-dns-a.google.com.",
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
var reply *dns.Msg
|
||||
reply, err = u.Exchange(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
}
|
||||
|
||||
for _, host := range req.Upstreams {
|
||||
err = checkDNS(host, req.BootstrapDNS)
|
||||
if err != nil {
|
||||
log.Info("%v", err)
|
||||
result[host] = err.Error()
|
||||
} else {
|
||||
result[host] = "OK"
|
||||
if len(reply.Answer) != 1 {
|
||||
return fmt.Errorf("wrong response")
|
||||
}
|
||||
|
||||
if t, ok := reply.Answer[0].(*dns.A); ok {
|
||||
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
||||
return fmt.Errorf("wrong response")
|
||||
}
|
||||
}
|
||||
|
||||
jsonVal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDNS(input string, bootstrap []string) error {
|
||||
// checkPrivateUpstreamExc checks if the upstream for resolving private
|
||||
// addresses exchanges correctly.
|
||||
func checkPrivateUpstreamExc(u upstream.Upstream) (err error) {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: "1.0.0.127.in-addr.arpa.",
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
if _, err = u.Exchange(req); err != nil {
|
||||
return fmt.Errorf("couldn't communicate with upstream: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDNS(input string, bootstrap []string, ef excFunc) (err error) {
|
||||
if !isUpstream(input) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// separate upstream from domains list
|
||||
input, useDefault, err := separateUpstream(input)
|
||||
if err != nil {
|
||||
// Separate upstream from domains list.
|
||||
var useDefault bool
|
||||
if input, useDefault, err = separateUpstream(input); err != nil {
|
||||
return fmt.Errorf("wrong upstream format: %w", err)
|
||||
}
|
||||
|
||||
// No need to check this DNS server
|
||||
// No need to check this DNS server.
|
||||
if !useDefault {
|
||||
return nil
|
||||
}
|
||||
@@ -486,35 +533,80 @@ func checkDNS(input string, bootstrap []string) error {
|
||||
bootstrap = defaultBootstrap
|
||||
}
|
||||
|
||||
log.Debug("checking if dns %s works...", input)
|
||||
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
|
||||
log.Debug("checking if dns server %q works...", input)
|
||||
var u upstream.Upstream
|
||||
u, err = upstream.AddressToUpstream(input, upstream.Options{
|
||||
Bootstrap: bootstrap,
|
||||
Timeout: DefaultTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to choose upstream for %s: %w", input, err)
|
||||
return fmt.Errorf("failed to choose upstream for %q: %w", input, err)
|
||||
}
|
||||
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = []dns.Question{
|
||||
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err := u.Exchange(&req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with dns server %s: %w", input, err)
|
||||
}
|
||||
if len(reply.Answer) != 1 {
|
||||
return fmt.Errorf("dns server %s returned wrong answer", input)
|
||||
}
|
||||
if t, ok := reply.Answer[0].(*dns.A); ok {
|
||||
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
||||
return fmt.Errorf("dns server %s returned wrong answer: %v", input, t.A)
|
||||
}
|
||||
if err = ef(u); err != nil {
|
||||
return fmt.Errorf("upstream %q fails to exchange: %w", input, err)
|
||||
}
|
||||
|
||||
log.Debug("dns %s works OK", input)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
req := &upstreamJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
bootstraps := req.BootstrapDNS
|
||||
|
||||
for _, host := range req.Upstreams {
|
||||
err = checkDNS(host, bootstraps, checkDNSUpstreamExc)
|
||||
if err != nil {
|
||||
log.Info("%v", err)
|
||||
result[host] = err.Error()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
result[host] = "OK"
|
||||
}
|
||||
|
||||
for _, host := range req.PrivateUpstreams {
|
||||
err = checkDNS(host, bootstraps, checkPrivateUpstreamExc)
|
||||
if err != nil {
|
||||
log.Info("%v", err)
|
||||
// TODO(e.burkov): If passed upstream have already
|
||||
// written an error above, we rewriting the error for
|
||||
// it. These cases should be handled properly instead.
|
||||
result[host] = err.Error()
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
result[host] = "OK"
|
||||
}
|
||||
|
||||
jsonVal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Control flow:
|
||||
// web
|
||||
// -> dnsforward.handleDOH -> dnsforward.ServeHTTP
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
@@ -13,6 +16,22 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func loadTestData(t *testing.T, casesFileName string, cases interface{}) {
|
||||
t.Helper()
|
||||
|
||||
var f *os.File
|
||||
f, err := os.Open(filepath.Join("testdata", casesFileName))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, f.Close())
|
||||
})
|
||||
|
||||
err = json.NewDecoder(f).Decode(cases)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
const jsonExt = ".json"
|
||||
|
||||
func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
filterConf := &dnsfilter.Config{
|
||||
SafeBrowsingEnabled: true,
|
||||
@@ -42,36 +61,38 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
conf func() ServerConfig
|
||||
want string
|
||||
name string
|
||||
}{{
|
||||
name: "all_right",
|
||||
conf: func() ServerConfig {
|
||||
return defaultConf
|
||||
},
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
name: "all_right",
|
||||
}, {
|
||||
name: "fastest_addr",
|
||||
conf: func() ServerConfig {
|
||||
conf := defaultConf
|
||||
conf.FastestAddr = true
|
||||
|
||||
return conf
|
||||
},
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
name: "fastest_addr",
|
||||
}, {
|
||||
name: "parallel",
|
||||
conf: func() ServerConfig {
|
||||
conf := defaultConf
|
||||
conf.AllServers = true
|
||||
|
||||
return conf
|
||||
},
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
name: "parallel",
|
||||
}}
|
||||
|
||||
var data map[string]json.RawMessage
|
||||
loadTestData(t, t.Name()+jsonExt, &data)
|
||||
|
||||
for _, tc := range testCases {
|
||||
caseWant, ok := data[tc.name]
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(w.Body.Reset)
|
||||
|
||||
@@ -79,7 +100,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
s.handleGetConfig(w, nil)
|
||||
|
||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, tc.want, w.Body.String())
|
||||
assert.JSONEq(t, string(caseWant), w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -108,97 +129,81 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||
|
||||
err := s.Start()
|
||||
assert.Nil(t, err)
|
||||
defer func() {
|
||||
t.Cleanup(func() {
|
||||
assert.Nil(t, s.Stop())
|
||||
}()
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n"
|
||||
testCases := []struct {
|
||||
name string
|
||||
req string
|
||||
wantSet string
|
||||
wantGet string
|
||||
}{{
|
||||
name: "upstream_dns",
|
||||
req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "bootstraps",
|
||||
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "blocking_mode_good",
|
||||
req: "{\"blocking_mode\":\"refused\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "blocking_mode_bad",
|
||||
req: "{\"blocking_mode\":\"custom_ip\"}",
|
||||
wantSet: "blocking_mode: incorrect value\n",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "ratelimit",
|
||||
req: "{\"ratelimit\":6}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "edns_cs_enabled",
|
||||
req: "{\"edns_cs_enabled\":true}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "dnssec_enabled",
|
||||
req: "{\"dnssec_enabled\":true}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "cache_size",
|
||||
req: "{\"cache_size\":1024}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "upstream_mode_parallel",
|
||||
req: "{\"upstream_mode\":\"parallel\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "upstream_mode_fastest_addr",
|
||||
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n",
|
||||
}, {
|
||||
name: "upstream_dns_bad",
|
||||
req: "{\"upstream_dns\":[\"\"]}",
|
||||
wantSet: "wrong upstreams specification: missing port in address\n",
|
||||
wantGet: defaultConfJSON,
|
||||
}, {
|
||||
name: "bootstraps_bad",
|
||||
req: "{\"bootstrap_dns\":[\"a\"]}",
|
||||
wantSet: "a can not be used as bootstrap dns cause: invalid bootstrap server address: Resolver a is not eligible to be a bootstrap DNS server\n",
|
||||
wantGet: defaultConfJSON,
|
||||
}, {
|
||||
name: "cache_bad_ttl",
|
||||
req: "{\"cache_ttl_min\":1024,\"cache_ttl_max\":512}",
|
||||
wantSet: "cache_ttl_min must be less or equal than cache_ttl_max\n",
|
||||
wantGet: defaultConfJSON,
|
||||
}, {
|
||||
name: "upstream_mode_bad",
|
||||
req: "{\"upstream_mode\":\"somethingelse\"}",
|
||||
wantSet: "upstream_mode: incorrect value\n",
|
||||
wantGet: defaultConfJSON,
|
||||
}, {
|
||||
name: "local_ptr_upstreams_good",
|
||||
wantSet: "",
|
||||
}, {
|
||||
name: "local_ptr_upstreams_null",
|
||||
wantSet: "",
|
||||
}}
|
||||
|
||||
var data map[string]struct {
|
||||
Req json.RawMessage `json:"req"`
|
||||
Want json.RawMessage `json:"want"`
|
||||
}
|
||||
loadTestData(t, t.Name()+jsonExt, &data)
|
||||
|
||||
for _, tc := range testCases {
|
||||
caseData, ok := data[tc.name]
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
s.conf = defaultConf
|
||||
})
|
||||
|
||||
rBody := ioutil.NopCloser(strings.NewReader(tc.req))
|
||||
rBody := ioutil.NopCloser(bytes.NewReader(caseData.Req))
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodPost, "http://example.com", rBody)
|
||||
require.Nil(t, err)
|
||||
@@ -208,7 +213,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||
w.Body.Reset()
|
||||
|
||||
s.handleGetConfig(w, nil)
|
||||
assert.Equal(t, tc.wantGet, w.Body.String())
|
||||
assert.JSONEq(t, string(caseData.Want), w.Body.String())
|
||||
w.Body.Reset()
|
||||
})
|
||||
}
|
||||
|
||||
83
internal/dnsforward/testdata/TestDNSForwardHTTTP_handleGetConfig.json
vendored
Normal file
83
internal/dnsforward/testdata/TestDNSForwardHTTTP_handleGetConfig.json
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"all_right": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
},
|
||||
"fastest_addr": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "fastest_addr",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
},
|
||||
"parallel": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "parallel",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
}
|
||||
525
internal/dnsforward/testdata/TestDNSForwardHTTTP_handleSetConfig.json
vendored
Normal file
525
internal/dnsforward/testdata/TestDNSForwardHTTTP_handleSetConfig.json
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
{
|
||||
"upstream_dns": {
|
||||
"req": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:77",
|
||||
"8.8.4.4:77"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:77",
|
||||
"8.8.4.4:77"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"bootstraps": {
|
||||
"req": {
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"blocking_mode_good": {
|
||||
"req": {
|
||||
"blocking_mode": "refused"
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "refused",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"blocking_mode_bad": {
|
||||
"req": {
|
||||
"blocking_mode": "custom_ip"
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"ratelimit": {
|
||||
"req": {
|
||||
"ratelimit": 6
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 6,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"edns_cs_enabled": {
|
||||
"req": {
|
||||
"edns_cs_enabled": true
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": true,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"dnssec_enabled": {
|
||||
"req": {
|
||||
"dnssec_enabled": true
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": true,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"cache_size": {
|
||||
"req": {
|
||||
"cache_size": 1024
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 1024,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"upstream_mode_parallel": {
|
||||
"req": {
|
||||
"upstream_mode": "parallel"
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "parallel",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"upstream_mode_fastest_addr": {
|
||||
"req": {
|
||||
"upstream_mode": "fastest_addr"
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "fastest_addr",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"upstream_dns_bad": {
|
||||
"req": {
|
||||
"upstream_dns": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"bootstraps_bad": {
|
||||
"req": {
|
||||
"bootstrap_dns": [
|
||||
"a"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"cache_bad_ttl": {
|
||||
"req": {
|
||||
"cache_ttl_min": 1024,
|
||||
"cache_ttl_max": 512
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"upstream_mode_bad": {
|
||||
"req": {
|
||||
"upstream_mode": "somethingelse"
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
},
|
||||
"local_ptr_upstreams_good": {
|
||||
"req": {
|
||||
"local_ptr_upstreams": [
|
||||
"123.123.123.123"
|
||||
]
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": [
|
||||
"123.123.123.123"
|
||||
]
|
||||
}
|
||||
},
|
||||
"local_ptr_upstreams_null": {
|
||||
"req": {
|
||||
"local_ptr_upstreams": null
|
||||
},
|
||||
"want": {
|
||||
"upstream_dns": [
|
||||
"8.8.8.8:53",
|
||||
"8.8.4.4:53"
|
||||
],
|
||||
"upstream_dns_file": "",
|
||||
"bootstrap_dns": [
|
||||
"9.9.9.10",
|
||||
"149.112.112.10",
|
||||
"2620:fe::10",
|
||||
"2620:fe::fe:10"
|
||||
],
|
||||
"protection_enabled": true,
|
||||
"ratelimit": 0,
|
||||
"blocking_mode": "",
|
||||
"blocking_ipv4": "",
|
||||
"blocking_ipv6": "",
|
||||
"edns_cs_enabled": false,
|
||||
"dnssec_enabled": false,
|
||||
"disable_ipv6": false,
|
||||
"upstream_mode": "",
|
||||
"cache_size": 0,
|
||||
"cache_ttl_min": 0,
|
||||
"cache_ttl_max": 0,
|
||||
"resolve_clients": false,
|
||||
"local_ptr_upstreams": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,6 @@ func IPStringFromAddr(addr net.Addr) (ipStr string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
func stringArrayDup(a []string) []string {
|
||||
a2 := make([]string, len(a))
|
||||
copy(a2, a)
|
||||
return a2
|
||||
}
|
||||
|
||||
// Find value in a sorted array
|
||||
func findSorted(ar []string, val string) int {
|
||||
i := sort.SearchStrings(ar, val)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
@@ -216,10 +217,10 @@ func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) {
|
||||
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
||||
}
|
||||
|
||||
cy.Tags = copyStrings(cli.Tags)
|
||||
cy.IDs = copyStrings(cli.IDs)
|
||||
cy.BlockedServices = copyStrings(cli.BlockedServices)
|
||||
cy.Upstreams = copyStrings(cli.Upstreams)
|
||||
cy.Tags = aghstrings.CloneSlice(cli.Tags)
|
||||
cy.IDs = aghstrings.CloneSlice(cli.IDs)
|
||||
cy.BlockedServices = aghstrings.CloneSlice(cli.BlockedServices)
|
||||
cy.Upstreams = aghstrings.CloneSlice(cli.Upstreams)
|
||||
|
||||
*objects = append(*objects, cy)
|
||||
}
|
||||
@@ -266,10 +267,6 @@ func (clients *clientsContainer) Exists(id string, source clientSource) (ok bool
|
||||
return source <= rc.Source
|
||||
}
|
||||
|
||||
func copyStrings(a []string) (b []string) {
|
||||
return append(b, a...)
|
||||
}
|
||||
|
||||
func toQueryLogWhois(wi *RuntimeClientWhoisInfo) (cw *querylog.ClientWhois) {
|
||||
if wi == nil {
|
||||
return &querylog.ClientWhois{}
|
||||
@@ -326,10 +323,10 @@ func (clients *clientsContainer) Find(id string) (c *Client, ok bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c.IDs = copyStrings(c.IDs)
|
||||
c.Tags = copyStrings(c.Tags)
|
||||
c.BlockedServices = copyStrings(c.BlockedServices)
|
||||
c.Upstreams = copyStrings(c.Upstreams)
|
||||
c.IDs = aghstrings.CloneSlice(c.IDs)
|
||||
c.Tags = aghstrings.CloneSlice(c.Tags)
|
||||
c.BlockedServices = aghstrings.CloneSlice(c.BlockedServices)
|
||||
c.Upstreams = aghstrings.CloneSlice(c.Upstreams)
|
||||
return c, true
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,13 @@ type dnsConfig struct {
|
||||
// For example, a machine called "myhost" can be addressed as
|
||||
// "myhost.lan" when AutohostTLD is "lan".
|
||||
AutohostTLD string `yaml:"autohost_tld"`
|
||||
|
||||
// ResolveClients enables and disables resolving clients with RDNS.
|
||||
ResolveClients bool `yaml:"resolve_clients"`
|
||||
|
||||
// LocalPTRResolvers is the slice of addresses to be used as upstreams
|
||||
// for PTR queries for locally-served networks.
|
||||
LocalPTRResolvers []string `yaml:"local_ptr_upstreams"`
|
||||
}
|
||||
|
||||
type tlsConfigSettings struct {
|
||||
@@ -150,6 +157,7 @@ var config = configuration{
|
||||
FilteringEnabled: true, // whether or not use filter lists
|
||||
FiltersUpdateIntervalHours: 24,
|
||||
AutohostTLD: "lan",
|
||||
ResolveClients: true,
|
||||
},
|
||||
TLS: tlsConfigSettings{
|
||||
PortHTTPS: 443,
|
||||
@@ -296,10 +304,12 @@ func (c *configuration) write() error {
|
||||
config.DNS.DnsfilterConf = c
|
||||
}
|
||||
|
||||
if Context.dnsServer != nil {
|
||||
if s := Context.dnsServer; s != nil {
|
||||
c := dnsforward.FilteringConfig{}
|
||||
Context.dnsServer.WriteDiskConfig(&c)
|
||||
s.WriteDiskConfig(&c)
|
||||
config.DNS.FilteringConfig = c
|
||||
|
||||
config.DNS.LocalPTRResolvers, config.DNS.ResolveClients = s.RDNSSettings()
|
||||
}
|
||||
|
||||
if Context.dhcpServer != nil {
|
||||
|
||||
@@ -67,7 +67,6 @@ func initDNSServer() error {
|
||||
Stats: Context.stats,
|
||||
QueryLog: Context.queryLog,
|
||||
SubnetDetector: Context.subnetDetector,
|
||||
LocalResolvers: Context.localResolvers,
|
||||
AutohostTLD: config.DNS.AutohostTLD,
|
||||
}
|
||||
if Context.dhcpServer != nil {
|
||||
@@ -95,7 +94,7 @@ func initDNSServer() error {
|
||||
return fmt.Errorf("dnsServer.Prepare: %w", err)
|
||||
}
|
||||
|
||||
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients, Context.subnetDetector, Context.localResolvers)
|
||||
Context.rdns = NewRDNS(Context.dnsServer, &Context.clients)
|
||||
Context.whois = initWhois(&Context.clients)
|
||||
|
||||
Context.filters.Init()
|
||||
@@ -113,7 +112,7 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ip.IsLoopback() {
|
||||
if config.DNS.ResolveClients && !ip.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
||||
@@ -200,6 +199,9 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||
newConf.FilterHandler = applyAdditionalFiltering
|
||||
newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
||||
|
||||
newConf.ResolveClients = dnsConf.ResolveClients
|
||||
newConf.LocalPTRResolvers = dnsConf.LocalPTRResolvers
|
||||
|
||||
return newConf, nil
|
||||
}
|
||||
|
||||
@@ -337,7 +339,7 @@ func startDNSServer() error {
|
||||
|
||||
const topClientsNumber = 100 // the number of clients to get
|
||||
for _, ip := range Context.stats.GetTopClientsIP(topClientsNumber) {
|
||||
if !Context.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||
if config.DNS.ResolveClients && !ip.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if !Context.subnetDetector.IsSpecialNetwork(ip) {
|
||||
|
||||
@@ -61,9 +61,7 @@ type homeContext struct {
|
||||
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
||||
updater *updater.Updater
|
||||
|
||||
subnetDetector *aghnet.SubnetDetector
|
||||
systemResolvers aghnet.SystemResolvers
|
||||
localResolvers aghnet.Exchanger
|
||||
subnetDetector *aghnet.SubnetDetector
|
||||
|
||||
// mux is our custom http.ServeMux.
|
||||
mux *http.ServeMux
|
||||
@@ -222,110 +220,6 @@ func setupConfig(args options) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultLocalTimeout = 5 * time.Second
|
||||
|
||||
// stringsSetSubtract subtracts b from a interpreted as sets.
|
||||
//
|
||||
// TODO(e.burkov): Move into our internal package for working with strings.
|
||||
func stringsSetSubtract(a, b []string) (c []string) {
|
||||
// unit is an object to be used as value in set.
|
||||
type unit = struct{}
|
||||
|
||||
cSet := make(map[string]unit)
|
||||
for _, k := range a {
|
||||
cSet[k] = unit{}
|
||||
}
|
||||
|
||||
for _, k := range b {
|
||||
delete(cSet, k)
|
||||
}
|
||||
|
||||
c = make([]string, len(cSet))
|
||||
i := 0
|
||||
for k := range cSet {
|
||||
c[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// collectAllIfacesAddrs returns the slice of all network interfaces IP
|
||||
// addresses without port number.
|
||||
func collectAllIfacesAddrs() (addrs []string, err error) {
|
||||
var ifaces []net.Interface
|
||||
ifaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting network interfaces: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
var ifaceAddrs []net.Addr
|
||||
ifaceAddrs, err = iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting addresses for %q: %w", iface.Name, err)
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
cidr := addr.String()
|
||||
var ip net.IP
|
||||
ip, _, err = net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %q as cidr: %w", cidr, err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// collectDNSIPAddrs returns the slice of IP addresses without port number which
|
||||
// we are listening on.
|
||||
func collectDNSIPaddrs() (addrs []string, err error) {
|
||||
addrs = make([]string, len(config.DNS.BindHosts))
|
||||
|
||||
for i, bh := range config.DNS.BindHosts {
|
||||
if bh.IsUnspecified() {
|
||||
return collectAllIfacesAddrs()
|
||||
}
|
||||
|
||||
addrs[i] = bh.String()
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func setupResolvers() {
|
||||
// TODO(e.burkov): Enhance when the config will contain local resolvers
|
||||
// addresses.
|
||||
|
||||
sysRes, err := aghnet.NewSystemResolvers(0, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Context.systemResolvers = sysRes
|
||||
|
||||
var ourAddrs []string
|
||||
ourAddrs, err = collectDNSIPaddrs()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): The approach of subtracting sets of strings is not
|
||||
// really applicable here since in case of listening on all network
|
||||
// interfaces we should check the whole interface's network to cut off
|
||||
// all the loopback addresses as well.
|
||||
addrs := stringsSetSubtract(sysRes.Get(), ourAddrs)
|
||||
|
||||
Context.localResolvers, err = aghnet.NewMultiAddrExchanger(addrs, defaultLocalTimeout)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// run performs configurating and starts AdGuard Home.
|
||||
func run(args options) {
|
||||
// configure config filename
|
||||
@@ -416,8 +310,6 @@ func run(args options) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
setupResolvers()
|
||||
|
||||
if !Context.firstRun {
|
||||
err = initDNSServer()
|
||||
if err != nil {
|
||||
|
||||
@@ -2,25 +2,19 @@ package home
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// RDNS resolves clients' addresses to enrich their metadata.
|
||||
type RDNS struct {
|
||||
dnsServer *dnsforward.Server
|
||||
clients *clientsContainer
|
||||
subnetDetector *aghnet.SubnetDetector
|
||||
localResolvers aghnet.Exchanger
|
||||
exchanger dnsforward.RDNSExchanger
|
||||
clients *clientsContainer
|
||||
|
||||
// ipCh used to pass client's IP to rDNS workerLoop.
|
||||
ipCh chan net.IP
|
||||
@@ -42,16 +36,12 @@ const (
|
||||
|
||||
// NewRDNS creates and returns initialized RDNS.
|
||||
func NewRDNS(
|
||||
dnsServer *dnsforward.Server,
|
||||
exchanger dnsforward.RDNSExchanger,
|
||||
clients *clientsContainer,
|
||||
snd *aghnet.SubnetDetector,
|
||||
lr aghnet.Exchanger,
|
||||
) (rDNS *RDNS) {
|
||||
rDNS = &RDNS{
|
||||
dnsServer: dnsServer,
|
||||
clients: clients,
|
||||
subnetDetector: snd,
|
||||
localResolvers: lr,
|
||||
exchanger: exchanger,
|
||||
clients: clients,
|
||||
ipCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
MaxCount: defaultRDNSCacheSize,
|
||||
@@ -92,73 +82,23 @@ func (r *RDNS) Begin(ip net.IP) {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// rDNSEmptyAnswerErr is returned by RDNS resolve method when the answer
|
||||
// section of respond is empty.
|
||||
rDNSEmptyAnswerErr agherr.Error = "the answer section is empty"
|
||||
|
||||
// rDNSNotPTRErr is returned by RDNS resolve method when the response is
|
||||
// not of PTR type.
|
||||
rDNSNotPTRErr agherr.Error = "the response is not a ptr"
|
||||
)
|
||||
|
||||
// resolve tries to resolve the ip in a suitable way.
|
||||
func (r *RDNS) resolve(ip net.IP) (host string, err error) {
|
||||
log.Tracef("rdns: resolving host for %q", ip)
|
||||
|
||||
arpa := dns.Fqdn(aghnet.ReverseAddr(ip))
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{{
|
||||
Name: arpa,
|
||||
Qtype: dns.TypePTR,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
|
||||
var resp *dns.Msg
|
||||
if r.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||
resp, err = r.localResolvers.Exchange(msg)
|
||||
} else {
|
||||
resp, err = r.dnsServer.Exchange(msg)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("performing lookup for %q: %w", arpa, err)
|
||||
}
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
return "", fmt.Errorf("lookup for %q: %w", arpa, rDNSEmptyAnswerErr)
|
||||
}
|
||||
|
||||
ptr, ok := resp.Answer[0].(*dns.PTR)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("type checking: %w", rDNSNotPTRErr)
|
||||
}
|
||||
|
||||
log.Tracef("rdns: ptr response for %q: %s", ip, ptr.String())
|
||||
|
||||
return strings.TrimSuffix(ptr.Ptr, "."), nil
|
||||
}
|
||||
|
||||
// workerLoop handles incoming IP addresses from ipChan and adds it into
|
||||
// clients.
|
||||
func (r *RDNS) workerLoop() {
|
||||
defer agherr.LogPanic("rdns")
|
||||
|
||||
for ip := range r.ipCh {
|
||||
host, err := r.resolve(ip)
|
||||
host, err := r.exchanger.Exchange(ip)
|
||||
if err != nil {
|
||||
log.Error("rdns: resolving %q: %s", ip, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't handle any errors since AddHost doesn't return non-nil
|
||||
// errors for now.
|
||||
_, _ = r.clients.AddHost(ip.String(), host, ClientSourceRDNS)
|
||||
if host != "" {
|
||||
// Don't handle any errors since AddHost doesn't return non-nil
|
||||
// errors for now.
|
||||
_, _ = r.clients.AddHost(ip.String(), host, ClientSourceRDNS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRDNS_Begin(t *testing.T) {
|
||||
@@ -105,90 +102,30 @@ func TestRDNS_Begin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRDNS_Resolve(t *testing.T) {
|
||||
extUpstream := &aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.1.1.in-addr.arpa.": {"one.one.one.one"},
|
||||
},
|
||||
}
|
||||
locUpstream := &aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
||||
"2.1.168.192.in-addr.arpa.": {},
|
||||
},
|
||||
}
|
||||
upstreamErr := errors.New("upstream error")
|
||||
errUpstream := &aghtest.TestErrUpstream{
|
||||
Err: upstreamErr,
|
||||
}
|
||||
nonPtrUpstream := &aghtest.TestBlockUpstream{
|
||||
Hostname: "some-host",
|
||||
Block: true,
|
||||
// rDNSExchanger is a mock dnsforward.RDNSExchanger implementation for tests.
|
||||
type rDNSExchanger struct {
|
||||
aghtest.Exchanger
|
||||
}
|
||||
|
||||
// Exchange implements dnsforward.RDNSExchanger interface for *RDNSExchanger.
|
||||
func (e *rDNSExchanger) Exchange(ip net.IP) (host string, err error) {
|
||||
req := &dns.Msg{
|
||||
Question: []dns.Question{{
|
||||
Name: ip.String(),
|
||||
Qtype: dns.TypePTR,
|
||||
}},
|
||||
}
|
||||
|
||||
dns := dnsforward.NewCustomServer(&proxy.Proxy{
|
||||
Config: proxy.Config{
|
||||
UpstreamConfig: &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{extUpstream},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
cc := &clientsContainer{}
|
||||
|
||||
snd, err := aghnet.NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
|
||||
localIP := net.IP{192, 168, 1, 1}
|
||||
testCases := []struct {
|
||||
name string
|
||||
want string
|
||||
wantErr error
|
||||
locUpstream upstream.Upstream
|
||||
req net.IP
|
||||
}{{
|
||||
name: "external_good",
|
||||
want: "one.one.one.one",
|
||||
wantErr: nil,
|
||||
locUpstream: nil,
|
||||
req: net.IP{1, 1, 1, 1},
|
||||
}, {
|
||||
name: "local_good",
|
||||
want: "local.domain",
|
||||
wantErr: nil,
|
||||
locUpstream: locUpstream,
|
||||
req: localIP,
|
||||
}, {
|
||||
name: "upstream_error",
|
||||
want: "",
|
||||
wantErr: upstreamErr,
|
||||
locUpstream: errUpstream,
|
||||
req: localIP,
|
||||
}, {
|
||||
name: "empty_answer_error",
|
||||
want: "",
|
||||
wantErr: rDNSEmptyAnswerErr,
|
||||
locUpstream: locUpstream,
|
||||
req: net.IP{192, 168, 1, 2},
|
||||
}, {
|
||||
name: "not_ptr_error",
|
||||
want: "",
|
||||
wantErr: rDNSNotPTRErr,
|
||||
locUpstream: nonPtrUpstream,
|
||||
req: localIP,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
rdns := NewRDNS(dns, cc, snd, &aghtest.Exchanger{
|
||||
Ups: tc.locUpstream,
|
||||
})
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r, rerr := rdns.resolve(tc.req)
|
||||
require.ErrorIs(t, rerr, tc.wantErr)
|
||||
assert.Equal(t, tc.want, r)
|
||||
})
|
||||
resp, err := e.Exchanger.Exchange(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return resp.Answer[0].Header().Name, nil
|
||||
}
|
||||
|
||||
func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
@@ -198,34 +135,33 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
|
||||
locUpstream := &aghtest.TestUpstream{
|
||||
Reverse: map[string][]string{
|
||||
"1.1.168.192.in-addr.arpa.": {"local.domain"},
|
||||
"192.168.1.1": {"local.domain"},
|
||||
},
|
||||
}
|
||||
|
||||
snd, err := aghnet.NewSubnetDetector()
|
||||
require.NoError(t, err)
|
||||
errUpstream := &aghtest.TestErrUpstream{
|
||||
Err: errors.New("1234"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
ups upstream.Upstream
|
||||
wantLog string
|
||||
name string
|
||||
cliIP net.IP
|
||||
}{{
|
||||
ups: locUpstream,
|
||||
wantLog: "",
|
||||
name: "all_good",
|
||||
cliIP: net.IP{192, 168, 1, 1},
|
||||
}, {
|
||||
wantLog: `rdns: resolving "192.168.1.2": lookup for "2.1.168.192.in-addr.arpa.": ` +
|
||||
string(rDNSEmptyAnswerErr),
|
||||
name: "resolve_error",
|
||||
cliIP: net.IP{192, 168, 1, 2},
|
||||
ups: errUpstream,
|
||||
wantLog: `rdns: resolving "192.168.1.2": errupstream: 1234`,
|
||||
name: "resolve_error",
|
||||
cliIP: net.IP{192, 168, 1, 2},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
w.Reset()
|
||||
|
||||
lr := &aghtest.Exchanger{
|
||||
Ups: locUpstream,
|
||||
}
|
||||
cc := &clientsContainer{
|
||||
list: map[string]*Client{},
|
||||
idIndex: map[string]*Client{},
|
||||
@@ -234,11 +170,13 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
||||
}
|
||||
ch := make(chan net.IP)
|
||||
rdns := &RDNS{
|
||||
dnsServer: nil,
|
||||
clients: cc,
|
||||
subnetDetector: snd,
|
||||
localResolvers: lr,
|
||||
ipCh: ch,
|
||||
exchanger: &rDNSExchanger{
|
||||
Exchanger: aghtest.Exchanger{
|
||||
Ups: tc.ups,
|
||||
},
|
||||
},
|
||||
clients: cc,
|
||||
ipCh: ch,
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
@@ -67,7 +67,7 @@ func whoisParse(data string) map[string]string {
|
||||
descr := ""
|
||||
netname := ""
|
||||
for len(data) != 0 {
|
||||
ln := util.SplitNext(&data, '\n')
|
||||
ln := aghstrings.SplitNext(&data, '\n')
|
||||
if len(ln) == 0 || ln[0] == '#' || ln[0] == '%' {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
"github.com/AdguardTeam/golibs/jsonutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
@@ -125,17 +126,6 @@ func getDoubleQuotesEnclosedValue(s *string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// inStr checks if string is in the slice of strings.
|
||||
func inStr(strs []string, str string) (ok bool) {
|
||||
for _, s := range strs {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseSearchCriteria - parses "searchCriteria" from the specified query parameter
|
||||
func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaType) (bool, searchCriteria, error) {
|
||||
val := q.Get(name)
|
||||
@@ -151,7 +141,7 @@ func (l *queryLog) parseSearchCriteria(q url.Values, name string, ct criteriaTyp
|
||||
c.strict = true
|
||||
}
|
||||
|
||||
if ct == ctFilteringStatus && !inStr(filteringStatusValues, c.value) {
|
||||
if ct == ctFilteringStatus && !aghstrings.InSlice(filteringStatusValues, c.value) {
|
||||
return false, c, fmt.Errorf("invalid value %s", c.value)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,30 +12,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SplitNext - split string by a byte and return the first chunk
|
||||
// Skip empty chunks
|
||||
// Whitespace is trimmed
|
||||
func SplitNext(str *string, splitBy byte) string {
|
||||
i := strings.IndexByte(*str, splitBy)
|
||||
s := ""
|
||||
if i != -1 {
|
||||
s = (*str)[0:i]
|
||||
*str = (*str)[i+1:]
|
||||
k := 0
|
||||
ch := rune(0)
|
||||
for k, ch = range *str {
|
||||
if byte(ch) != splitBy {
|
||||
break
|
||||
}
|
||||
}
|
||||
*str = (*str)[k:]
|
||||
} else {
|
||||
s = *str
|
||||
*str = ""
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// IsOpenWrt returns true if host OS is OpenWrt.
|
||||
func IsOpenWrt() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSplitNext(t *testing.T) {
|
||||
s := " a,b , c "
|
||||
|
||||
assert.Equal(t, "a", SplitNext(&s, ','))
|
||||
assert.Equal(t, "b", SplitNext(&s, ','))
|
||||
assert.Equal(t, "c", SplitNext(&s, ','))
|
||||
require.Empty(t, s)
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghstrings"
|
||||
)
|
||||
|
||||
// Channel constants.
|
||||
@@ -68,14 +70,6 @@ const (
|
||||
nltb = nl + tb
|
||||
)
|
||||
|
||||
// writeStrings is a convenient wrapper for strings.(*Builder).WriteString that
|
||||
// deals with multiple strings and ignores errors that are guaranteed to be nil.
|
||||
func writeStrings(b *strings.Builder, strs ...string) {
|
||||
for _, s := range strs {
|
||||
_, _ = b.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Constants defining the format of module information string.
|
||||
const (
|
||||
modInfoAtSep = "@"
|
||||
@@ -99,16 +93,16 @@ func fmtModule(m *debug.Module) (formatted string) {
|
||||
|
||||
b := &strings.Builder{}
|
||||
|
||||
writeStrings(b, m.Path)
|
||||
aghstrings.WriteToBuilder(b, m.Path)
|
||||
if ver := m.Version; ver != "" {
|
||||
sep := modInfoAtSep
|
||||
if ver == "(devel)" {
|
||||
sep = modInfoDevSep
|
||||
}
|
||||
writeStrings(b, sep, ver)
|
||||
aghstrings.WriteToBuilder(b, sep, ver)
|
||||
}
|
||||
if sum := m.Sum; sum != "" {
|
||||
writeStrings(b, modInfoSumLeft, sum, modInfoSumRight)
|
||||
aghstrings.WriteToBuilder(b, modInfoSumLeft, sum, modInfoSumRight)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
@@ -149,7 +143,7 @@ const (
|
||||
func Verbose() (v string) {
|
||||
b := &strings.Builder{}
|
||||
|
||||
writeStrings(
|
||||
aghstrings.WriteToBuilder(
|
||||
b,
|
||||
vFmtAGHHdr,
|
||||
nl,
|
||||
@@ -163,31 +157,31 @@ func Verbose() (v string) {
|
||||
runtime.Version(),
|
||||
)
|
||||
if buildtime != "" {
|
||||
writeStrings(b, nl, vFmtTimeHdr, buildtime)
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtTimeHdr, buildtime)
|
||||
}
|
||||
writeStrings(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtGOOSHdr, nl, vFmtGOARCHHdr)
|
||||
if goarm != "" {
|
||||
writeStrings(b, nl, vFmtGOARMHdr, "v", goarm)
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtGOARMHdr, "v", goarm)
|
||||
} else if gomips != "" {
|
||||
writeStrings(b, nl, vFmtGOMIPSHdr, gomips)
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtGOMIPSHdr, gomips)
|
||||
}
|
||||
writeStrings(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtRaceHdr, strconv.FormatBool(isRace))
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
writeStrings(b, nl, vFmtMainHdr, nltb, fmtModule(&info.Main))
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtMainHdr, nltb, fmtModule(&info.Main))
|
||||
|
||||
if len(info.Deps) == 0 {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
writeStrings(b, nl, vFmtDepsHdr)
|
||||
aghstrings.WriteToBuilder(b, nl, vFmtDepsHdr)
|
||||
for _, dep := range info.Deps {
|
||||
if depStr := fmtModule(dep); depStr != "" {
|
||||
writeStrings(b, nltb, depStr)
|
||||
aghstrings.WriteToBuilder(b, nltb, depStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user