Pull request #1558: add-dnssvc

Merge in DNS/adguard-home from add-dnssvc to master

Squashed commit of the following:

commit 55f4f114bab65a03c0d65383e89020a7356cff32
Merge: 95dc28d9 6e63757f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 20:53:07 2022 +0300

    Merge branch 'master' into add-dnssvc

commit 95dc28d9d77d06e8ac98c1e6772557bffbf1705b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Aug 15 20:52:50 2022 +0300

    all: imp tests, docs

commit 0d9d02950d84afd160b4b1c118da856cee6f12e5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 19:27:59 2022 +0300

    all: imp docs

commit 8990e038a81da4430468da12fcebedf79fe14df6
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 19:05:29 2022 +0300

    all: imp tests more

commit 92730d93a2a1ac77888c2655508e43efaf0e9fde
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 18:37:48 2022 +0300

    all: imp tests more

commit 8cd45ba30da7ac310e9dc666fb2af438e577b02d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Aug 11 18:11:15 2022 +0300

    all: add v1 dnssvc stub; refactor tests
This commit is contained in:
Ainar Garipov
2022-08-16 13:21:25 +03:00
parent 6e63757fc7
commit d4c3a43bcb
19 changed files with 742 additions and 319 deletions

View File

@@ -6,12 +6,18 @@ import (
"fmt"
"net"
"strings"
"sync"
"testing"
"github.com/AdguardTeam/golibs/errors"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
// Additional Upstream Testing Utilities
// Upstream is a mock implementation of upstream.Upstream.
//
// TODO(a.garipov): Replace with UpstreamMock and rename it to just Upstream.
type Upstream struct {
// CName is a map of hostname to canonical name.
CName map[string][]string
@@ -25,6 +31,43 @@ type Upstream struct {
Addr string
}
// RespondTo returns a response with answer if req has class cl, question type
// qt, and target targ.
func RespondTo(t testing.TB, req *dns.Msg, cl, qt uint16, targ, answer string) (resp *dns.Msg) {
t.Helper()
require.NotNil(t, req)
require.Len(t, req.Question, 1)
q := req.Question[0]
targ = dns.Fqdn(targ)
if q.Qclass != cl || q.Qtype != qt || q.Name != targ {
return nil
}
respHdr := dns.RR_Header{
Name: targ,
Rrtype: qt,
Class: cl,
Ttl: 60,
}
resp = new(dns.Msg).SetReply(req)
switch qt {
case dns.TypePTR:
resp.Answer = []dns.RR{
&dns.PTR{
Hdr: respHdr,
Ptr: answer,
},
}
default:
t.Fatalf("unsupported question type: %s", dns.Type(qt))
}
return resp
}
// Exchange implements the upstream.Upstream interface for *Upstream.
//
// TODO(a.garipov): Split further into handlers.
@@ -76,74 +119,57 @@ func (u *Upstream) Address() string {
return u.Addr
}
// TestBlockUpstream implements upstream.Upstream interface for replacing real
// upstream in tests.
type TestBlockUpstream struct {
Hostname string
// lock protects reqNum.
lock sync.RWMutex
reqNum int
Block bool
}
// Exchange returns a message unique for TestBlockUpstream's Hostname-Block
// pair.
func (u *TestBlockUpstream) Exchange(r *dns.Msg) (*dns.Msg, error) {
u.lock.Lock()
defer u.lock.Unlock()
u.reqNum++
hash := sha256.Sum256([]byte(u.Hostname))
hashToReturn := hex.EncodeToString(hash[:])
if !u.Block {
hashToReturn = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28)
// NewBlockUpstream returns an [*UpstreamMock] that works like an upstream that
// supports hash-based safe-browsing/adult-blocking feature. If shouldBlock is
// true, hostname's actual hash is returned, blocking it. Otherwise, it returns
// a different hash.
func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
hash := sha256.Sum256([]byte(hostname))
hashStr := hex.EncodeToString(hash[:])
if !shouldBlock {
hashStr = hex.EncodeToString(hash[:])[:2] + strings.Repeat("ab", 28)
}
m := &dns.Msg{}
m.SetReply(r)
m.Answer = []dns.RR{
&dns.TXT{
Hdr: dns.RR_Header{
Name: r.Question[0].Name,
},
Txt: []string{
hashToReturn,
},
ans := &dns.TXT{
Hdr: dns.RR_Header{
Name: "",
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: 60,
},
Txt: []string{hashStr},
}
respTmpl := &dns.Msg{
Answer: []dns.RR{ans},
}
return &UpstreamMock{
OnAddress: func() (addr string) {
return "sbpc.upstream.example"
},
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = respTmpl.Copy()
resp.SetReply(req)
resp.Answer[0].(*dns.TXT).Hdr.Name = req.Question[0].Name
return resp, nil
},
}
return m, nil
}
// Address always returns an empty string.
func (u *TestBlockUpstream) Address() string {
return ""
}
// ErrUpstream is the error returned from the [*UpstreamMock] created by
// [NewErrorUpstream].
const ErrUpstream errors.Error = "test upstream error"
// RequestsCount returns the number of handled requests. It's safe for
// concurrent use.
func (u *TestBlockUpstream) RequestsCount() int {
u.lock.Lock()
defer u.lock.Unlock()
return u.reqNum
}
// TestErrUpstream implements upstream.Upstream interface for replacing real
// upstream in tests.
type TestErrUpstream struct {
// The error returned by Exchange may be unwrapped to the Err.
Err error
}
// Exchange always returns nil Msg and non-nil error.
func (u *TestErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) {
return nil, fmt.Errorf("errupstream: %w", u.Err)
}
// Address always returns an empty string.
func (u *TestErrUpstream) Address() string {
return ""
// NewErrorUpstream returns an [*UpstreamMock] that returns [ErrUpstream] from
// its Exchange method.
func NewErrorUpstream() (u *UpstreamMock) {
return &UpstreamMock{
OnAddress: func() (addr string) {
return "error.upstream.example"
},
OnExchange: func(_ *dns.Msg) (resp *dns.Msg, err error) {
return nil, errors.Error("test upstream error")
},
}
}