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

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