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: 830b0834 908452f8
Author: 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: 89cf93ad 8746005d
Author: 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:
Eugene Burkov
2021-04-07 20:16:06 +03:00
parent 908452f883
commit 80ed8be145
40 changed files with 1627 additions and 595 deletions

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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)
})

View File

@@ -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
}

View 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)
}
}

View 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") })
})
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
})
}

View File

@@ -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

View File

@@ -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

View File

@@ -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()
})
}

View 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": []
}
}

View 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": []
}
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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)
}
}
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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" {

View File

@@ -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)
}

View File

@@ -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)
}
}