mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-21 20:45:33 +03:00
Pull request: imp-ups-tests
Merge in DNS/adguard-home from imp-ups-tests to master
Squashed commit of the following:
commit 60c22e8dfb81c3613b00994ccf01ba96137946f9
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Fri Oct 28 19:26:34 2022 +0300
all: imp names, fix aaaa
commit c3bd8a9fa95a86f5af86ea2956565337ee620c99
Merge: 23f82237 746e9df7
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Fri Oct 28 19:16:56 2022 +0300
Merge branch 'master' into imp-ups-tests
commit 23f82237a81b0ef22cf418afdc2ec95dc50639a3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Fri Oct 28 18:14:27 2022 +0300
all: imp upstream tests
This commit is contained in:
parent
746e9df727
commit
ac7634da37
4 changed files with 155 additions and 147 deletions
|
@ -5,13 +5,12 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Additional Upstream Testing Utilities
|
||||
|
@ -26,51 +25,10 @@ type Upstream struct {
|
|||
IPv4 map[string][]net.IP
|
||||
// IPv6 is a map of hostname to IPv6.
|
||||
IPv6 map[string][]net.IP
|
||||
// Reverse is a map of address to domain name.
|
||||
Reverse map[string][]string
|
||||
// Addr is the address for Address method.
|
||||
Addr string
|
||||
}
|
||||
|
||||
var _ upstream.Upstream = (*Upstream)(nil)
|
||||
|
||||
// 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.
|
||||
|
@ -105,10 +63,6 @@ func (u *Upstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
|
|||
for _, ip := range u.IPv6[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.AAAA{Hdr: hdr, AAAA: ip})
|
||||
}
|
||||
case dns.TypePTR:
|
||||
for _, name := range u.Reverse[name] {
|
||||
resp.Answer = append(resp.Answer, &dns.PTR{Hdr: hdr, Ptr: name})
|
||||
}
|
||||
}
|
||||
if len(resp.Answer) == 0 {
|
||||
resp.SetRcode(m, dns.RcodeNameError)
|
||||
|
@ -119,7 +73,7 @@ func (u *Upstream) Exchange(m *dns.Msg) (resp *dns.Msg, err error) {
|
|||
|
||||
// Address implements [upstream.Upstream] interface for *Upstream.
|
||||
func (u *Upstream) Address() string {
|
||||
return u.Addr
|
||||
return "todo.upstream.example"
|
||||
}
|
||||
|
||||
// Close implements [upstream.Upstream] interface for *Upstream.
|
||||
|
@ -127,6 +81,98 @@ func (u *Upstream) Close() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MatchedResponse is a test helper that returns a response with answer if req
|
||||
// has question type qt, and target targ. Otherwise, it returns nil.
|
||||
//
|
||||
// req must not be nil and req.Question must have a length of 1. Answer is
|
||||
// interpreted in the following ways:
|
||||
//
|
||||
// - For A and AAAA queries, answer must be an IP address of the corresponding
|
||||
// protocol version.
|
||||
//
|
||||
// - For PTR queries, answer should be a domain name in the response.
|
||||
//
|
||||
// If the answer does not correspond to the question type, MatchedResponse panics.
|
||||
// Panics are used instead of [testing.TB], because the helper is intended to
|
||||
// use in [UpstreamMock.OnExchange] callbacks, which are usually called in a
|
||||
// separate goroutine.
|
||||
//
|
||||
// TODO(a.garipov): Consider adding version with DNS class as well.
|
||||
func MatchedResponse(req *dns.Msg, qt uint16, targ, answer string) (resp *dns.Msg) {
|
||||
if req == nil || len(req.Question) != 1 {
|
||||
panic(fmt.Errorf("bad req: %+v", req))
|
||||
}
|
||||
|
||||
q := req.Question[0]
|
||||
targ = dns.Fqdn(targ)
|
||||
if q.Qclass != dns.ClassINET || q.Qtype != qt || q.Name != targ {
|
||||
return nil
|
||||
}
|
||||
|
||||
respHdr := dns.RR_Header{
|
||||
Name: targ,
|
||||
Rrtype: qt,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
}
|
||||
|
||||
resp = new(dns.Msg).SetReply(req)
|
||||
switch qt {
|
||||
case dns.TypeA:
|
||||
resp.Answer = mustAnsA(respHdr, answer)
|
||||
case dns.TypeAAAA:
|
||||
resp.Answer = mustAnsAAAA(respHdr, answer)
|
||||
case dns.TypePTR:
|
||||
resp.Answer = []dns.RR{&dns.PTR{
|
||||
Hdr: respHdr,
|
||||
Ptr: answer,
|
||||
}}
|
||||
default:
|
||||
panic(fmt.Errorf("aghtest: bad question type: %s", dns.Type(qt)))
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// mustAnsA returns valid answer records if s is a valid IPv4 address.
|
||||
// Otherwise, mustAnsA panics.
|
||||
func mustAnsA(respHdr dns.RR_Header, s string) (ans []dns.RR) {
|
||||
ip, err := netip.ParseAddr(s)
|
||||
if err != nil || !ip.Is4() {
|
||||
panic(fmt.Errorf("aghtest: bad A answer: %+v", s))
|
||||
}
|
||||
|
||||
return []dns.RR{&dns.A{
|
||||
Hdr: respHdr,
|
||||
A: ip.AsSlice(),
|
||||
}}
|
||||
}
|
||||
|
||||
// mustAnsAAAA returns valid answer records if s is a valid IPv6 address.
|
||||
// Otherwise, mustAnsAAAA panics.
|
||||
func mustAnsAAAA(respHdr dns.RR_Header, s string) (ans []dns.RR) {
|
||||
ip, err := netip.ParseAddr(s)
|
||||
if err != nil || !ip.Is6() {
|
||||
panic(fmt.Errorf("aghtest: bad AAAA answer: %+v", s))
|
||||
}
|
||||
|
||||
return []dns.RR{&dns.AAAA{
|
||||
Hdr: respHdr,
|
||||
AAAA: ip.AsSlice(),
|
||||
}}
|
||||
}
|
||||
|
||||
// NewUpstreamMock returns an [*UpstreamMock], fields OnAddress and OnClose of
|
||||
// which are set to stubs that return "upstream.example" and nil respectively.
|
||||
// The field OnExchange is set to onExc.
|
||||
func NewUpstreamMock(onExc func(req *dns.Msg) (resp *dns.Msg, err error)) (u *UpstreamMock) {
|
||||
return &UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "upstream.example" },
|
||||
OnExchange: onExc,
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -152,9 +198,7 @@ func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
|
|||
}
|
||||
|
||||
return &UpstreamMock{
|
||||
OnAddress: func() (addr string) {
|
||||
return "sbpc.upstream.example"
|
||||
},
|
||||
OnAddress: func() (addr string) { return "sbpc.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = respTmpl.Copy()
|
||||
resp.SetReply(req)
|
||||
|
@ -162,6 +206,7 @@ func NewBlockUpstream(hostname string, shouldBlock bool) (u *UpstreamMock) {
|
|||
|
||||
return resp, nil
|
||||
},
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,11 +218,10 @@ const ErrUpstream errors.Error = "test upstream error"
|
|||
// its Exchange method.
|
||||
func NewErrorUpstream() (u *UpstreamMock) {
|
||||
return &UpstreamMock{
|
||||
OnAddress: func() (addr string) {
|
||||
return "error.upstream.example"
|
||||
},
|
||||
OnAddress: func() (addr string) { return "error.upstream.example" },
|
||||
OnExchange: func(_ *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return nil, errors.Error("test upstream error")
|
||||
},
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
}
|
||||
|
|
|
@ -457,19 +457,13 @@ func TestServer_ProcessRestrictLocal(t *testing.T) {
|
|||
intPTRAnswer = "some.local-client."
|
||||
)
|
||||
|
||||
ups := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = aghalg.Coalesce(
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, extPTRQuestion, extPTRAnswer),
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, intPTRQuestion, intPTRAnswer),
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, extPTRQuestion, extPTRAnswer),
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, intPTRQuestion, intPTRAnswer),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
), nil
|
||||
})
|
||||
|
||||
s := createTestServer(t, &filtering.Config{}, ServerConfig{
|
||||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
|
@ -547,18 +541,12 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) {
|
|||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
},
|
||||
&aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = aghalg.Coalesce(
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, reqAddr, locDomain),
|
||||
aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, reqAddr, locDomain),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
OnClose: func() (err error) { return nil },
|
||||
},
|
||||
), nil
|
||||
}),
|
||||
)
|
||||
|
||||
var proxyCtx *proxy.DNSContext
|
||||
|
|
|
@ -161,8 +161,23 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte)
|
|||
return s, certPem
|
||||
}
|
||||
|
||||
const googleDomainName = "google-public-dns-a.google.com."
|
||||
|
||||
func createGoogleATestMessage() *dns.Msg {
|
||||
return createTestMessage("google-public-dns-a.google.com.")
|
||||
return createTestMessage(googleDomainName)
|
||||
}
|
||||
|
||||
func newGoogleUpstream() (u upstream.Upstream) {
|
||||
return &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "google.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, googleDomainName, "8.8.8.8"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
},
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
func createTestMessage(host string) *dns.Msg {
|
||||
|
@ -247,13 +262,7 @@ func TestServer(t *testing.T) {
|
|||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
testCases := []struct {
|
||||
|
@ -320,13 +329,7 @@ func TestServerWithProtectionDisabled(t *testing.T) {
|
|||
UDPListenAddrs: []*net.UDPAddr{{}},
|
||||
TCPListenAddrs: []*net.TCPAddr{{}},
|
||||
}, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
// Message over UDP.
|
||||
|
@ -343,13 +346,7 @@ func TestDoTServer(t *testing.T) {
|
|||
s, certPem := createTestTLS(t, TLSConfig{
|
||||
TLSListenAddrs: []*net.TCPAddr{{}},
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
// Add our self-signed generated config to roots.
|
||||
|
@ -373,13 +370,7 @@ func TestDoQServer(t *testing.T) {
|
|||
s, _ := createTestTLS(t, TLSConfig{
|
||||
QUICListenAddrs: []*net.UDPAddr{{IP: net.IP{127, 0, 0, 1}}},
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
// Create a DNS-over-QUIC upstream.
|
||||
|
@ -417,13 +408,7 @@ func TestServerRace(t *testing.T) {
|
|||
ConfigModified: func() {},
|
||||
}
|
||||
s := createTestServer(t, filterConf, forwardConf, nil)
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"google-public-dns-a.google.com.": {{8, 8, 8, 8}},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{newGoogleUpstream()}
|
||||
startDeferStop(t, s)
|
||||
|
||||
// Message over UDP.
|
||||
|
@ -557,11 +542,12 @@ func TestServerCustomClientUpstream(t *testing.T) {
|
|||
}
|
||||
s := createTestServer(t, &filtering.Config{}, forwardConf, nil)
|
||||
s.conf.GetCustomUpstreamByClient = func(_ string) (conf *proxy.UpstreamConfig, err error) {
|
||||
ups := &aghtest.Upstream{
|
||||
IPv4: map[string][]net.IP{
|
||||
"host.": {{192, 168, 0, 1}},
|
||||
},
|
||||
}
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "host", "192.168.0.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
})
|
||||
|
||||
return &proxy.UpstreamConfig{
|
||||
Upstreams: []upstream.Upstream{ups},
|
||||
|
@ -604,7 +590,6 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) {
|
|||
testUpstm := &aghtest.Upstream{
|
||||
CName: testCNAMEs,
|
||||
IPv4: testIPv4,
|
||||
IPv6: nil,
|
||||
}
|
||||
s.conf.ProtectionEnabled = false
|
||||
s.dnsProxy.UpstreamConfig = &proxy.UpstreamConfig{
|
||||
|
@ -931,16 +916,13 @@ func TestRewrite(t *testing.T) {
|
|||
},
|
||||
}))
|
||||
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{
|
||||
&aghtest.Upstream{
|
||||
CName: map[string][]string{
|
||||
"example.org": {"somename"},
|
||||
},
|
||||
IPv4: map[string][]net.IP{
|
||||
"example.org.": {{4, 3, 2, 1}},
|
||||
},
|
||||
},
|
||||
}
|
||||
ups := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypeA, "example.org", "4.3.2.1"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
), nil
|
||||
})
|
||||
s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ups}
|
||||
startDeferStop(t, s)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
@ -1212,12 +1194,10 @@ func TestServer_Exchange(t *testing.T) {
|
|||
extUpstream := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "external.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = aghalg.Coalesce(
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revExtIPv4, onesHost),
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revExtIPv4, onesHost),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
), nil
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1227,12 +1207,10 @@ func TestServer_Exchange(t *testing.T) {
|
|||
locUpstream := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "local.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = aghalg.Coalesce(
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revLocIPv4, localDomainHost),
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revLocIPv4, localDomainHost),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
), nil
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -189,13 +189,11 @@ func TestRDNS_WorkerLoop(t *testing.T) {
|
|||
locUpstream := &aghtest.UpstreamMock{
|
||||
OnAddress: func() (addr string) { return "local.upstream.example" },
|
||||
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = aghalg.Coalesce(
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv4, "local.domain"),
|
||||
aghtest.RespondTo(t, req, dns.ClassINET, dns.TypePTR, revIPv6, "ipv6.domain"),
|
||||
return aghalg.Coalesce(
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revIPv4, "local.domain"),
|
||||
aghtest.MatchedResponse(req, dns.TypePTR, revIPv6, "ipv6.domain"),
|
||||
new(dns.Msg).SetRcode(req, dns.RcodeNameError),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
), nil
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue