diff --git a/internal/dnsproxytest/dnsproxytest.go b/internal/dnsproxytest/dnsproxytest.go index 9ce4b369..7ad4c26c 100644 --- a/internal/dnsproxytest/dnsproxytest.go +++ b/internal/dnsproxytest/dnsproxytest.go @@ -26,18 +26,20 @@ const ( // TLSServerName is a common TLS server name value for tests. TLSServerName = "testdns.adguard.com" + + // Host is a common host for tests. + Host = "test.example" + + // TTL is a common time-to-live value in seconds for tests. + TTL = 300 ) var ( // LocalhostAnyPort is a [netip.AddrPort] having a value of 127.0.0.1:0. LocalhostAnyPort = netip.AddrPortFrom(netutil.IPv4Localhost(), 0) - // IPv4 is a common IPv4 address for test response A records. It uses the - // [net.IP] form for convenient comparisons with [dns.A.A]. - // - // TODO(f.setrakov): Use an address from one of the IPv4 documentation - // ranges. - IPv4 = net.IPv4(8, 8, 8, 8) + // IPv4 is a common IPv4 address for tests. + IPv4 = net.IPv4(192, 0, 2, 1) ) // DefaultTrustedProxies is a set of trusted proxies that includes all possible @@ -47,12 +49,14 @@ var DefaultTrustedProxies = netutil.SliceSubnetSet{ netip.MustParsePrefix("::/0"), } -// NewTestRequest returns common DNS request for tests. +// NewTestRequest returns common DNS request for tests. This helper must not be +// used in IPv6-related tests. func NewTestRequest() (msg *dns.Msg) { - return NewTestRequestWithHost("google-public-dns-a.google.com") + return NewTestRequestWithHost(Host) } // NewTestRequestWithHost returns DNS request with common values and given host. +// This helper must not be used in IPv6-related tests. func NewTestRequestWithHost(host string) (req *dns.Msg) { return &dns.Msg{ MsgHdr: dns.MsgHdr{ @@ -67,9 +71,28 @@ func NewTestRequestWithHost(host string) (req *dns.Msg) { } } +// NewTestResponse is a helper that returns new default response for given +// request. Response will contain single A record with [IPv4] value. req must +// not be nil. This helper must not be used in IPv6-related tests. +func NewTestResponse(req *dns.Msg) (resp *dns.Msg) { + resp = (&dns.Msg{}).SetReply(req) + resp.Answer = []dns.RR{&dns.A{ + Hdr: dns.RR_Header{ + Name: req.Question[0].Name, + Class: dns.ClassINET, + Rrtype: dns.TypeA, + Ttl: TTL, + }, + A: IPv4, + }} + + return resp +} + // RequireResponse is a test helper that ensures that the DNS reply matches the // request and contains an A record with [IPv4]. It is intended to be used -// alongside [NewTestRequest] or [NewTestRequestWithHost]. +// alongside [NewTestRequest] or [NewTestRequestWithHost]. This helper must not +// be used in IPv6-related tests. func RequireResponse(tb testing.TB, req, reply *dns.Msg) { tb.Helper() diff --git a/proxy/bogusnxdomain_internal_test.go b/proxy/bogusnxdomain_internal_test.go index 262d62a8..500db37f 100644 --- a/proxy/bogusnxdomain_internal_test.go +++ b/proxy/bogusnxdomain_internal_test.go @@ -18,10 +18,9 @@ func TestProxy_IsBogusNXDomain(t *testing.T) { var ans []dns.RR onExchange := newECSReplyHandler(&ans, nil, nil) - u := newTestECSUpstream(onExchange) - - upsConf := newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr) - upsConf.Upstreams = []upstream.Upstream{u} + upsConf := &UpstreamConfig{ + Upstreams: []upstream.Upstream{newTestUpstreamWithExchange(t, onExchange)}, + } prx := mustNew(t, &Config{ Logger: testLogger, diff --git a/proxy/cache_internal_test.go b/proxy/cache_internal_test.go index 7351c576..6089ed1c 100644 --- a/proxy/cache_internal_test.go +++ b/proxy/cache_internal_test.go @@ -52,7 +52,7 @@ func TestServeCached(t *testing.T) { Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, DNSSECEnabled: false, CacheEnabled: true, @@ -305,7 +305,7 @@ func TestCacheExpiration(t *testing.T) { Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, CacheEnabled: true, }) @@ -360,7 +360,7 @@ func TestCacheExpirationWithTTLOverride(t *testing.T) { var ans []dns.RR onExchange := newECSReplyHandler(&ans, nil, nil) - u := newTestECSUpstream(onExchange) + u := newTestUpstreamWithExchange(t, onExchange) dnsProxy := mustNew(t, &Config{ Logger: testLogger, diff --git a/proxy/dns64_internal_test.go b/proxy/dns64_internal_test.go index 4d01eba5..66084005 100644 --- a/proxy/dns64_internal_test.go +++ b/proxy/dns64_internal_test.go @@ -18,7 +18,7 @@ import ( const ipv4OnlyFqdn = "ipv4.only." -func TestProxy_handleDNSRequest_DNS64Race(t *testing.T) { +func TestProxy_HandleDNSRequest_dns64Race(t *testing.T) { ans := newRR(t, ipv4OnlyFqdn, dns.TypeA, 3600, dnsproxytest.IPv4) ups := &testUpstream{ OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) { diff --git a/proxy/handler_internal_test.go b/proxy/handler_internal_test.go index 30da96f6..26d4307e 100644 --- a/proxy/handler_internal_test.go +++ b/proxy/handler_internal_test.go @@ -43,7 +43,7 @@ func TestFilteringHandler(t *testing.T) { dnsProxy := mustNew(t, &Config{ Logger: testLogger, TrustedProxies: dnsproxytest.DefaultTrustedProxies, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), RequestHandler: reqHandler, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, diff --git a/proxy/lookup_internal_test.go b/proxy/lookup_internal_test.go index cf0e639e..4a72199f 100644 --- a/proxy/lookup_internal_test.go +++ b/proxy/lookup_internal_test.go @@ -1,45 +1,35 @@ package proxy import ( - "context" "net/netip" "testing" + "time" - "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/AdguardTeam/dnsproxy/internal/dnsproxytest" + "github.com/AdguardTeam/golibs/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// testTimeout is a common timeout for tests. +const testTimeout = 1 * time.Second + func TestLookupNetIP(t *testing.T) { - // Use AdGuard DNS here. - dnsUpstream, err := upstream.AddressToUpstream( - "94.140.14.14", - &upstream.Options{ - Logger: testLogger, - Timeout: defaultTimeout, - }, - ) - require.NoError(t, err) + t.Parallel() conf := &Config{ - Logger: testLogger, - UpstreamConfig: &UpstreamConfig{ - Upstreams: []upstream.Upstream{dnsUpstream}, - }, + Logger: testLogger, + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), } p, err := New(conf) require.NoError(t, err) - // Now let's try doing some lookups. - addrs, err := p.LookupNetIP(context.Background(), "", "dns.google") + ctx := testutil.ContextWithTimeout(t, testTimeout) + addrs, err := p.LookupNetIP(ctx, "", dnsproxytest.Host) require.NoError(t, err) require.NotEmpty(t, addrs) - assert.Contains(t, addrs, netip.MustParseAddr("8.8.8.8")) - assert.Contains(t, addrs, netip.MustParseAddr("8.8.4.4")) - if len(addrs) > 2 { - assert.Contains(t, addrs, netip.MustParseAddr("2001:4860:4860::8888")) - assert.Contains(t, addrs, netip.MustParseAddr("2001:4860:4860::8844")) - } + wantAddr := netip.AddrFrom4([4]byte(dnsproxytest.IPv4.To4())) + assert.Contains(t, addrs, wantAddr) } diff --git a/proxy/proxy_internal_test.go b/proxy/proxy_internal_test.go index 165caaa5..bc259ba0 100644 --- a/proxy/proxy_internal_test.go +++ b/proxy/proxy_internal_test.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "net" "net/netip" - "net/url" "sync" "testing" "time" @@ -13,6 +12,7 @@ import ( "github.com/AdguardTeam/dnsproxy/internal/dnsproxytest" "github.com/AdguardTeam/dnsproxy/upstream" glcache "github.com/AdguardTeam/golibs/cache" + "github.com/AdguardTeam/golibs/container" "github.com/AdguardTeam/golibs/contextutil" "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" @@ -24,8 +24,7 @@ import ( ) const ( - listenIP = "127.0.0.1" - testDefaultUpstreamAddr = "8.8.8.8:53" + listenIP = "127.0.0.1" // defaultTestTTL used to guarantee caching. defaultTestTTL = 1000 @@ -68,48 +67,42 @@ func firstIP(resp *dns.Msg) (ip net.IP) { return nil } -// newTestUpstreamConfigWithBoot creates a new UpstreamConfig with upstream -// addresses and a bootstrapped resolver. -func newTestUpstreamConfigWithBoot( - t require.TestingT, - timeout time.Duration, - addrs ...string, -) (u *UpstreamConfig) { - googleRslv, err := upstream.NewUpstreamResolver( - "8.8.8.8:53", - &upstream.Options{ - Logger: testLogger, - Timeout: timeout, - }, - ) - require.NoError(t, err) - - upsConf, err := ParseUpstreamsConfig(addrs, &upstream.Options{ - Logger: testLogger, - Timeout: timeout, - Bootstrap: upstream.NewCachingResolver(googleRslv), - }) - require.NoError(t, err) - - return upsConf -} - -// newTestUpstreamConfig creates a new UpstreamConfig with given upstream -// addresses and timeout. -func newTestUpstreamConfig( - tb testing.TB, - timeout time.Duration, - addrs ...string, -) (u *UpstreamConfig) { +// newTestUpstreamConfig creates a new UpstreamConfig with given upstreams set. +func newTestUpstreamConfig(tb testing.TB, ups ...upstream.Upstream) (u *UpstreamConfig) { tb.Helper() - upsConf, err := ParseUpstreamsConfig(addrs, &upstream.Options{ - Logger: testLogger, - Timeout: timeout, - }) - require.NoError(tb, err) + return &UpstreamConfig{ + Upstreams: ups, + } +} - return upsConf +// newTestUpstream returns default upstream mock, which responds with single A +// record with [dnsproxytest.IPv4] value to all requests. Other upstream +// methods will be replaced with stub implementations. +func newTestUpstream(tb testing.TB) (uc upstream.Upstream) { + tb.Helper() + + onExchange := func(req *dns.Msg) (resp *dns.Msg, err error) { + return dnsproxytest.NewTestResponse(req), nil + } + + return newTestUpstreamWithExchange(tb, onExchange) +} + +// newTestUpstreamWithExchange returns upstream mock which uses given callback +// for [upstream.Upstream.Exchange] implementation. Other upstream methods will +// be replaced with stub implementations. +func newTestUpstreamWithExchange( + tb testing.TB, + onExchange func(req *dns.Msg) (resp *dns.Msg, err error), +) (uc upstream.Upstream) { + tb.Helper() + + return &testUpstream{ + OnAddress: func() (addr string) { return "" }, + OnExchange: onExchange, + OnClose: func() (err error) { return nil }, + } } // newTxts returns new test TXT RR strings. @@ -352,95 +345,107 @@ func TestProxy_Resolve_dnssecCache(t *testing.T) { } } -func TestProxy_handleDNSRequest_exchangeWithReservedDomains(t *testing.T) { +func TestProxy_HandleDNSRequest_exchangeWithReservedDomains(t *testing.T) { t.Parallel() + emptyUpstream := newTestUpstreamWithExchange(t, func(req *dns.Msg) (resp *dns.Msg, err error) { + return (&dns.Msg{}).SetReply(req), nil + }) + + const ( + host1 = "example-1.test" + host2 = "test.example-1.test" + + fqdn1 = host1 + "." + fqdn2 = host2 + "." + ) + + upsConf := &UpstreamConfig{ + DomainReservedUpstreams: map[string][]upstream.Upstream{ + fqdn1: {emptyUpstream}, + fqdn2: {}, + }, + SpecifiedDomainUpstreams: map[string][]upstream.Upstream{ + fqdn1: {emptyUpstream}, + fqdn2: {}, + }, + SubdomainExclusions: container.NewMapSet[string](), + Upstreams: []upstream.Upstream{newTestUpstream(t)}, + } + dnsProxy := mustNew(t, &Config{ - Logger: testLogger, - UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfigWithBoot( - t, - dnsproxytest.Timeout, - "[/adguard.com/]192.0.2.1", - "[/google.ru/]192.0.2.2", - "[/maps.google.ru/]#", - "tls://1.1.1.1", - ), + Logger: testLogger, + UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, + TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, + UpstreamConfig: upsConf, TrustedProxies: dnsproxytest.DefaultTrustedProxies, }) servicetest.RequireRun(t, dnsProxy, dnsproxytest.Timeout) - // Create a DNS-over-TCP client connection. addr := dnsProxy.Addr(ProtoTCP) conn, err := dns.Dial("tcp", addr.String()) require.NoError(t, err) - // Create google-a test message. - req := dnsproxytest.NewTestRequest() - err = conn.WriteMsg(req) - require.NoError(t, err) + var res *dns.Msg + require.True(t, t.Run("common", func(t *testing.T) { + req := dnsproxytest.NewTestRequest() + err = conn.WriteMsg(req) + require.NoError(t, err) - // Make sure that dnsproxy is working. - res, err := conn.ReadMsg() - require.NoError(t, err) - dnsproxytest.RequireResponse(t, req, res) + res, err = conn.ReadMsg() + require.NoError(t, err) + dnsproxytest.RequireResponse(t, req, res) + })) - // Create adguard.com test message. - req = dnsproxytest.NewTestRequestWithHost("adguard.com") - err = conn.WriteMsg(req) - require.NoError(t, err) + require.True(t, t.Run("empty_upstream_response", func(t *testing.T) { + req := dnsproxytest.NewTestRequestWithHost(host1) + err = conn.WriteMsg(req) + require.NoError(t, err) - // Test message should not be resolved. - res, _ = conn.ReadMsg() - require.Nil(t, res.Answer) + res, err = conn.ReadMsg() + require.NoError(t, err) + require.Nil(t, res.Answer) + })) - // Create www.google.ru test message. - req = dnsproxytest.NewTestRequestWithHost("www.google.ru") - err = conn.WriteMsg(req) - require.NoError(t, err) + require.True(t, t.Run("non_empty_response_for_subdomain", func(t *testing.T) { + req := dnsproxytest.NewTestRequestWithHost(host2) + err = conn.WriteMsg(req) + require.NoError(t, err) - // Test message should not be resolved. - res, _ = conn.ReadMsg() - require.Empty(t, res.Answer) - - // Create maps.google.ru test message. - req = dnsproxytest.NewTestRequestWithHost("maps.google.ru") - err = conn.WriteMsg(req) - require.NoError(t, err) - - // Test message should be resolved. - res, _ = conn.ReadMsg() - require.NotNil(t, res.Answer) + res, err = conn.ReadMsg() + require.NoError(t, err) + dnsproxytest.RequireResponse(t, req, res) + })) } -func TestProxy_handleDNSRequest_oneByOneUpstreamsExchange(t *testing.T) { +func TestProxy_HandleDNSRequest_oneByOneUpstreamsExchange(t *testing.T) { t.Parallel() + errUpstream := newTestUpstreamWithExchange(t, func(req *dns.Msg) (resp *dns.Msg, err error) { + return nil, assert.AnError + }) + dnsProxy := mustNew(t, &Config{ Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfigWithBoot( + UpstreamConfig: newTestUpstreamConfig( t, - dnsproxytest.Timeout, - "https://fake-dns.com/fake-dns-query", - "tls://fake-dns.com", - "1.1.1.1", + errUpstream, + errUpstream, + newTestUpstream(t), ), TrustedProxies: dnsproxytest.DefaultTrustedProxies, - Fallbacks: newTestUpstreamConfig(t, dnsproxytest.Timeout, "1.2.3.4:567"), + Fallbacks: newTestUpstreamConfig(t, errUpstream), }) servicetest.RequireRun(t, dnsProxy, dnsproxytest.Timeout) - // create a DNS-over-TCP client connection addr := dnsProxy.Addr(ProtoTCP) conn, err := dns.Dial("tcp", addr.String()) require.NoError(t, err) - // make sure that the response is okay and resolved by valid upstream req := dnsproxytest.NewTestRequest() err = conn.WriteMsg(req) require.NoError(t, err) @@ -454,80 +459,72 @@ func TestProxy_handleDNSRequest_oneByOneUpstreamsExchange(t *testing.T) { assert.Greater(t, 3*dnsproxytest.Timeout, elapsed) } -// newLocalUpstreamListener creates a new localhost listener on the specified -// port for tcp4 network and returns its listening address. -func newLocalUpstreamListener(tb testing.TB, port uint16, h dns.Handler) (real netip.AddrPort) { - tb.Helper() - - startCh := make(chan struct{}) - upsSrv := &dns.Server{ - Addr: netip.AddrPortFrom(netutil.IPv4Localhost(), port).String(), - Net: "tcp", - Handler: h, - NotifyStartedFunc: func() { close(startCh) }, - } - go func() { - err := upsSrv.ListenAndServe() - require.NoError(testutil.NewPanicT(tb), err) - }() - - <-startCh - testutil.CleanupAndRequireSuccess(tb, upsSrv.Shutdown) - - return testutil.RequireTypeAssert[*net.TCPAddr](tb, upsSrv.Listener.Addr()).AddrPort() -} - -func TestProxy_handleDNSRequest_fallback(t *testing.T) { +func TestProxy_HandleDNSRequest_fallback(t *testing.T) { t.Parallel() + const ( + hostSuccess = "specific.example" + hostFail = "failing.example" + hostFallbackSuccess = "fallback.success.example" + + fqdnSuccess = hostSuccess + "." + fqdnFail = hostFail + "." + fqdnFallbackSuccess = hostFallbackSuccess + "." + ) + responseCh := make(chan uint16) failCh := make(chan uint16) - successHandler := dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { - testutil.RequireSend(testutil.NewPanicT(t), responseCh, r.Id, dnsproxytest.Timeout) + pt := testutil.NewPanicT(t) - require.NoError(testutil.NewPanicT(t), w.WriteMsg((&dns.Msg{}).SetReply(r))) - }) - failHandler := dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { - testutil.RequireSend(testutil.NewPanicT(t), failCh, r.Id, dnsproxytest.Timeout) + successExchange := func(req *dns.Msg) (resp *dns.Msg, err error) { + testutil.RequireSend(pt, responseCh, req.Id, dnsproxytest.Timeout) - require.NoError(testutil.NewPanicT(t), w.WriteMsg(&dns.Msg{})) + return dnsproxytest.NewTestResponse(req), nil + } + successUpstream := newTestUpstreamWithExchange(t, successExchange) + + failUpstream := newTestUpstreamWithExchange(t, func(req *dns.Msg) (resp *dns.Msg, err error) { + testutil.RequireSend(pt, failCh, req.Id, dnsproxytest.Timeout) + + return nil, assert.AnError }) - successAddr := (&url.URL{ - Scheme: string(ProtoTCP), - Host: newLocalUpstreamListener(t, 0, successHandler).String(), - }).String() - alsoSuccessAddr := (&url.URL{ - Scheme: string(ProtoTCP), - Host: newLocalUpstreamListener(t, 0, successHandler).String(), - }).String() - failAddr := (&url.URL{ - Scheme: string(ProtoTCP), - Host: newLocalUpstreamListener(t, 0, failHandler).String(), - }).String() + upsConf := &UpstreamConfig{ + DomainReservedUpstreams: map[string][]upstream.Upstream{ + fqdnSuccess: {successUpstream}, + fqdnFail: {failUpstream}, + }, + SpecifiedDomainUpstreams: map[string][]upstream.Upstream{ + fqdnSuccess: {successUpstream}, + fqdnFail: {failUpstream}, + }, + + SubdomainExclusions: container.NewMapSet[string](), + Upstreams: []upstream.Upstream{failUpstream}, + } + + fallbackConf := &UpstreamConfig{ + DomainReservedUpstreams: map[string][]upstream.Upstream{ + fqdnFail: {failUpstream}, + fqdnFallbackSuccess: {successUpstream}, + }, + SpecifiedDomainUpstreams: map[string][]upstream.Upstream{ + fqdnFail: {failUpstream}, + fqdnFallbackSuccess: {successUpstream}, + }, + + SubdomainExclusions: container.NewMapSet[string](), + Upstreams: []upstream.Upstream{failUpstream, successUpstream}, + } dnsProxy := mustNew(t, &Config{ - Logger: testLogger, - UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig( - t, - dnsproxytest.Timeout, - failAddr, - "[/specific.example/]"+alsoSuccessAddr, - // almost.failing.example will fall here first. - "[/failing.example/]"+failAddr, - ), + Logger: testLogger, + UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, + TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, + UpstreamConfig: upsConf, TrustedProxies: dnsproxytest.DefaultTrustedProxies, - Fallbacks: newTestUpstreamConfig( - t, - dnsproxytest.Timeout, - failAddr, - successAddr, - "[/failing.example/]"+failAddr, - "[/almost.failing.example/]"+alsoSuccessAddr, - ), + Fallbacks: fallbackConf, }) servicetest.RequireRun(t, dnsProxy, dnsproxytest.Timeout) @@ -539,7 +536,7 @@ func TestProxy_handleDNSRequest_fallback(t *testing.T) { name string wantSignals []chan uint16 }{{ - name: "general.example", + name: dnsproxytest.Host, wantSignals: []chan uint16{ failCh, // Both non-specific fallbacks tried. @@ -547,18 +544,18 @@ func TestProxy_handleDNSRequest_fallback(t *testing.T) { responseCh, }, }, { - name: "specific.example", + name: hostSuccess, wantSignals: []chan uint16{ responseCh, }, }, { - name: "failing.example", + name: hostFail, wantSignals: []chan uint16{ failCh, failCh, }, }, { - name: "almost.failing.example", + name: hostFallbackSuccess, wantSignals: []chan uint16{ failCh, responseCh, @@ -584,81 +581,28 @@ func TestProxy_handleDNSRequest_fallback(t *testing.T) { } } -func TestProxy_handleDNSRequest_fallbackFromInvalidBootstrap(t *testing.T) { - t.Parallel() - - invalidRslv, err := upstream.NewUpstreamResolver("8.8.8.8:555", &upstream.Options{ - Logger: testLogger, - Timeout: dnsproxytest.Timeout, - }) - require.NoError(t, err) - - // Prepare the proxy server - upsConf, err := ParseUpstreamsConfig([]string{"tls://dns.adguard.com"}, &upstream.Options{ - Logger: testLogger, - Bootstrap: invalidRslv, - Timeout: dnsproxytest.Timeout, - }) - require.NoError(t, err) - - dnsProxy := mustNew(t, &Config{ - Logger: testLogger, - UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: upsConf, - TrustedProxies: dnsproxytest.DefaultTrustedProxies, - Fallbacks: newTestUpstreamConfig( - t, - dnsproxytest.Timeout, - "1.0.0.1", - "8.8.8.8", - ), - }) - - servicetest.RequireRun(t, dnsProxy, dnsproxytest.Timeout) - - // Create a DNS-over-UDP client connection - addr := dnsProxy.Addr(ProtoUDP) - conn, err := dns.Dial("udp", addr.String()) - require.NoError(t, err) - - // Make sure that the response is okay and resolved by the fallback - req := dnsproxytest.NewTestRequest() - err = conn.WriteMsg(req) - require.NoError(t, err) - - start := time.Now() - res, err := conn.ReadMsg() - require.NoError(t, err) - dnsproxytest.RequireResponse(t, req, res) - - elapsed := time.Since(start) - assert.Greater(t, 3*dnsproxytest.Timeout, elapsed) -} - func TestProxy_Resolve_exchangeCustomUpstreamConfig(t *testing.T) { p := mustNew(t, &Config{ Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, }) servicetest.RequireRun(t, p, dnsproxytest.Timeout) - ansIP := net.IP{4, 3, 2, 1} ans := []dns.RR{&dns.A{ Hdr: dns.RR_Header{ Rrtype: dns.TypeA, - Name: "host.", + Name: dns.Fqdn(dnsproxytest.Host), Ttl: 60, }, - A: ansIP, + A: dnsproxytest.IPv4, }} onExchange := newECSReplyHandler(&ans, nil, nil) - u := newTestECSUpstream(onExchange) + u := newTestUpstreamWithExchange(t, onExchange) d := &DNSContext{ CustomUpstreamConfig: NewCustomUpstreamConfig( @@ -667,14 +611,14 @@ func TestProxy_Resolve_exchangeCustomUpstreamConfig(t *testing.T) { 0, false, ), - Req: dnsproxytest.NewTestRequestWithHost("host"), - Addr: netip.MustParseAddrPort("1.2.3.0:1234"), + Req: dnsproxytest.NewTestRequest(), + Addr: netip.MustParseAddrPort("192.0.2.0:1234"), } err := p.Resolve(testutil.ContextWithTimeout(t, defaultTimeout), d) require.NoError(t, err) - assert.Equal(t, ansIP, firstIP(d.Res)) + assert.Equal(t, dnsproxytest.IPv4, firstIP(d.Res)) } func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { @@ -682,7 +626,7 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, CacheEnabled: true, DNSSECEnabled: true, @@ -692,7 +636,6 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { var count int - ansIP := net.IP{4, 3, 2, 1} exchangeFunc := func(m *dns.Msg) (resp *dns.Msg, err error) { resp = &dns.Msg{} resp.SetReply(m) @@ -703,7 +646,7 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { Rrtype: dns.TypeA, Ttl: defaultTestTTL, }, - A: ansIP, + A: dnsproxytest.IPv4, }) count++ @@ -726,7 +669,7 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { d := &DNSContext{ CustomUpstreamConfig: customUpstreamConfig, Req: dnsproxytest.NewTestRequestWithHost("host"), - Addr: netip.MustParseAddrPort("1.2.3.0:1234"), + Addr: netip.MustParseAddrPort("192.0.2.0:1234"), } ctx := testutil.ContextWithTimeout(t, defaultTimeout) @@ -734,13 +677,13 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, count) - assert.Equal(t, ansIP, firstIP(d.Res)) + assert.Equal(t, dnsproxytest.IPv4, firstIP(d.Res).To16()) err = prx.Resolve(ctx, d) require.NoError(t, err) assert.Equal(t, 1, count) - assert.Equal(t, ansIP, firstIP(d.Res)) + assert.Equal(t, dnsproxytest.IPv4, firstIP(d.Res).To16()) customUpstreamConfig.ClearCache() @@ -748,7 +691,7 @@ func TestProxy_Resolve_customUpstreamConfigCache(t *testing.T) { require.NoError(t, err) assert.Equal(t, 2, count) - assert.Equal(t, ansIP, firstIP(d.Res)) + assert.Equal(t, dnsproxytest.IPv4, firstIP(d.Res).To16()) } func TestSetECS(t *testing.T) { @@ -805,7 +748,7 @@ func TestProxy_Resolve_ecs(t *testing.T) { ) onExchange := newECSReplyHandler(&ans, &ecsIP, &ecsReqIP) - u := newTestECSUpstream(onExchange) + u := newTestUpstreamWithExchange(t, onExchange) ans = []dns.RR{&dns.A{ Hdr: dns.RR_Header{Rrtype: dns.TypeA, Name: "host.", Ttl: 60}, @@ -923,7 +866,7 @@ func TestProxy_Resolve_ecsProxyCacheMinMaxTTL(t *testing.T) { ) onExchange := newECSReplyHandler(&ans, &ecsIP, nil) - u := newTestECSUpstream(onExchange) + u := newTestUpstreamWithExchange(t, onExchange) ans = []dns.RR{&dns.A{ Hdr: dns.RR_Header{ @@ -1116,7 +1059,7 @@ func TestProxy_Resolve_withOptimisticResolver(t *testing.T) { assert.EqualValues(t, nonOptimisticTTL, unpacked.m.Answer[0].Header().Ttl) } -func TestProxy_validateRequest(t *testing.T) { +func TestProxy_ValidateRequest(t *testing.T) { t.Parallel() const ( @@ -1267,18 +1210,6 @@ type testUpstream struct { OnClose func() (err error) } -// newTestECSUpstream creates a new test upstream with the given OnExchange -// handler. -func newTestECSUpstream( - onExchange func(req *dns.Msg) (resp *dns.Msg, err error), -) (u *testUpstream) { - return &testUpstream{ - OnAddress: func() string { return "" }, - OnClose: func() error { return nil }, - OnExchange: onExchange, - } -} - // newECSReplyHandler creates an OnExchange handler that builds a DNS reply // with optional answer records and ECS support. // diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 0f4026b6..2c0d0cf3 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -28,9 +28,6 @@ const ( // testTTL is a common time-to-live value in seconds for tests. testTTL = 60 - - // testDefaultUpstreamAddr is the default upstream address for tests. - testDefaultUpstreamAddr = "8.8.8.8:53" ) var ( @@ -41,24 +38,30 @@ var ( testIPv4 = net.IP{192, 0, 2, 0} ) -// newTestUpstreamConfig creates a new UpstreamConfig with given upstream -// addresses and timeout. +// newTestUpstreamConfig creates a new upstream config with the default mock +// upstream, which expects the request will contain a single question with the +// [proxytest.Host] name. // -// TODO(f.setrakov): Dry with internal version. -func newTestUpstreamConfig( - tb testing.TB, - timeout time.Duration, - addrs ...string, -) (u *proxy.UpstreamConfig) { - tb.Helper() +// TODO(f.setrakov): DRY with internal version. +func newTestUpstreamConfig(tb testing.TB) (uc *proxy.UpstreamConfig) { + onExchange := func(req *dns.Msg) (resp *dns.Msg, err error) { + pt := testutil.NewPanicT(tb) - upsConf, err := proxy.ParseUpstreamsConfig(addrs, &upstream.Options{ - Logger: testLogger, - Timeout: timeout, - }) - require.NoError(tb, err) + require.Len(pt, req.Question, 1) + assert.Equal(pt, dns.Fqdn(proxytest.Host), req.Question[0].Name) - return upsConf + return proxytest.NewTestResponse(req), nil + } + + u := &dnsproxytest.Upstream{ + OnAddress: func() (addr string) { return "" }, + OnExchange: onExchange, + OnClose: func() (err error) { return nil }, + } + + return &proxy.UpstreamConfig{ + Upstreams: []upstream.Upstream{u}, + } } // mustStartDefaultProxy starts a new proxy with default settings and returns @@ -70,7 +73,7 @@ func mustStartDefaultProxy(tb testing.TB) (p *proxy.Proxy) { Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(proxytest.LocalhostAnyPort)}, TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(proxytest.LocalhostAnyPort)}, - UpstreamConfig: newTestUpstreamConfig(tb, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(tb), TrustedProxies: proxytest.DefaultTrustedProxies, }) require.NoError(tb, err) @@ -181,15 +184,9 @@ func isCachedWithCustomConfig( return s[0].IsCached } -// TODO(f.setrakov): Make it work without a real network. func TestProxy_HandleDNSRequest_race(t *testing.T) { - upsConf := newTestUpstreamConfig( - t, - defaultTimeout, - // Use the same upstream twice so that we could rotate them - testDefaultUpstreamAddr, - testDefaultUpstreamAddr, - ) + upsConf := newTestUpstreamConfig(t) + dnsProxy, err := proxy.New(&proxy.Config{ Logger: testLogger, UDPListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(proxytest.LocalhostAnyPort)}, @@ -222,17 +219,18 @@ func TestProxy_HandleDNSRequest_race(t *testing.T) { require.NotNil(pt, res) require.Len(pt, res.Answer, 1) + + a := testutil.RequireTypeAssert[*dns.A](pt, res.Answer[0]) require.IsType(pt, &dns.A{}, res.Answer[0]) - a := res.Answer[0].(*dns.A) - require.Equal(pt, net.IPv4(8, 8, 8, 8), a.A.To16()) + require.Equal(pt, proxytest.IPv4.To16(), a.A.To16()) }) } wg.Wait() } -func TestProxy_handleDNSRequest_responseInRequest(t *testing.T) { +func TestProxy_HandleDNSRequest_responseInRequest(t *testing.T) { dnsProxy := mustStartDefaultProxy(t) addr := dnsProxy.Addr(proxy.ProtoTCP) diff --git a/proxy/serverdnscrypt_internal_test.go b/proxy/serverdnscrypt_internal_test.go index b0e3b42c..35ac4174 100644 --- a/proxy/serverdnscrypt_internal_test.go +++ b/proxy/serverdnscrypt_internal_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestDNSCryptProxy(t *testing.T) { +func TestProxy_HandleDNSRequest_dnscrypt(t *testing.T) { t.Parallel() // Prepare the proxy server. @@ -46,7 +46,6 @@ func newTestDNSCryptProxy(tb testing.TB) (p *Proxy, rc dnscrypt.ResolverConfig) require.NoError(tb, err) port := nettest.NewFreePort(tb) - upstreamConf := newTestUpstreamConfig(tb, defaultTimeout, testDefaultUpstreamAddr) p = mustNew(tb, &Config{ Logger: testLogger, DNSCryptUDPListenAddr: []*net.UDPAddr{{ @@ -55,7 +54,7 @@ func newTestDNSCryptProxy(tb testing.TB) (p *Proxy, rc dnscrypt.ResolverConfig) DNSCryptTCPListenAddr: []*net.TCPAddr{{ Port: int(port), IP: net.ParseIP(listenIP), }}, - UpstreamConfig: upstreamConf, + UpstreamConfig: newTestUpstreamConfig(tb, newTestUpstream(tb)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, EnableEDNSClientSubnet: true, CacheEnabled: true, diff --git a/proxy/serverhttps_internal_test.go b/proxy/serverhttps_internal_test.go index b3b1c957..c5eee196 100644 --- a/proxy/serverhttps_internal_test.go +++ b/proxy/serverhttps_internal_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestProxy_handleDNSRequest_https(t *testing.T) { +func TestProxy_HandleDNSRequest_https(t *testing.T) { testCases := []struct { name string http3 bool @@ -49,7 +49,7 @@ func TestProxy_handleDNSRequest_https(t *testing.T) { TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TLSConfig: tlsConf, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, HTTPConfig: httpConf, }) @@ -70,7 +70,7 @@ func TestProxy_handleDNSRequest_https(t *testing.T) { } } -func TestProxy_handleDNSRequest_trustedProxies(t *testing.T) { +func TestProxy_HandleDNSRequest_trustedProxies(t *testing.T) { var ( clientAddr = netip.MustParseAddr("1.2.3.4") proxyAddr = netip.MustParseAddr("127.0.0.1") @@ -93,7 +93,7 @@ func TestProxy_handleDNSRequest_trustedProxies(t *testing.T) { } dnsProxy := mustNew(t, &Config{ Logger: testLogger, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, RequestHandler: reqHandler, TLSConfig: tlsConf, diff --git a/proxy/serverquic_internal_test.go b/proxy/serverquic_internal_test.go index af0e8f9b..5dcac19e 100644 --- a/proxy/serverquic_internal_test.go +++ b/proxy/serverquic_internal_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestProxy_handleDNSRequest_quic(t *testing.T) { +func TestProxy_HandleDNSRequest_quic(t *testing.T) { serverConfig, caPem := dnsproxytest.NewTLSConfig(t) roots := x509.NewCertPool() @@ -35,7 +35,7 @@ func TestProxy_handleDNSRequest_quic(t *testing.T) { Logger: testLogger, QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TLSConfig: serverConfig, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, } @@ -63,7 +63,7 @@ func TestProxy_handleDNSRequest_quic(t *testing.T) { require.False(t, t.Failed()) conf.QUICListenAddr = []*net.UDPAddr{addr} - conf.UpstreamConfig = newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr) + conf.UpstreamConfig = newTestUpstreamConfig(t, newTestUpstream(t)) t.Run("rerun", func(t *testing.T) { dnsProxy := mustNew(t, conf) @@ -83,19 +83,21 @@ func TestProxy_handleDNSRequest_quic(t *testing.T) { }) } -func TestProxy_handleDNSRequest_quicLargePackets(t *testing.T) { - reqHandler := &testHandler{ - OnHandle: func(_ context.Context, _ *Proxy, d *DNSContext) (err error) { - d.Res = newTestResponse(d) +func TestProxy_HandleDNSRequest_quicLargePackets(t *testing.T) { + onHandle := func(_ context.Context, _ *Proxy, d *DNSContext) (err error) { + d.Res = dnsproxytest.NewTestResponse(d.Req) - return nil - }, + return nil + } + + reqHandler := &testHandler{ + OnHandle: onHandle, } serverConfig, caPem := dnsproxytest.NewTLSConfig(t) dnsProxy := mustNew(t, &Config{ Logger: testLogger, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, RequestHandler: reqHandler, TLSConfig: serverConfig, @@ -138,14 +140,14 @@ func TestProxy_handleDNSRequest_quicLargePackets(t *testing.T) { dnsproxytest.RequireResponse(t, msg, resp) } -func TestProxy_handleDNSRequest_quicTruncatedRequest(t *testing.T) { +func TestProxy_HandleDNSRequest_quicTruncatedRequest(t *testing.T) { serverConfig, caPem := dnsproxytest.NewTLSConfig(t) conf := &Config{ Logger: testLogger, QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(dnsproxytest.LocalhostAnyPort)}, TLSConfig: serverConfig, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t, newTestUpstream(t)), TrustedProxies: dnsproxytest.DefaultTrustedProxies, RequestHandler: &testHandler{ OnHandle: func(ctx context.Context, p *Proxy, d *DNSContext) (_ error) { @@ -218,22 +220,6 @@ func TestProxy_handleDNSRequest_quicTruncatedRequest(t *testing.T) { assert.Greater(t, n, minDNSPacketSize) } -// newTestResponse creates a test response for the specified request. -func newTestResponse(d *DNSContext) (resp *dns.Msg) { - resp = &dns.Msg{} - resp.SetReply(d.Req) - resp.Answer = []dns.RR{&dns.A{ - Hdr: dns.RR_Header{ - Name: d.Req.Question[0].Name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.IP{8, 8, 8, 8}, - }} - - return resp -} - // sendQUICMessage sends msg to the specified QUIC connection. func sendQUICMessage( t *testing.T, diff --git a/proxy/servertcp_test.go b/proxy/servertcp_test.go index abf61ae3..1467f3e5 100644 --- a/proxy/servertcp_test.go +++ b/proxy/servertcp_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestProxy_handleDNSRequest_tcp(t *testing.T) { +func TestProxy_HandleDNSRequest_tcp(t *testing.T) { dnsProxy := mustStartDefaultProxy(t) // Create a DNS-over-TCP client connection @@ -31,14 +31,14 @@ func TestProxy_handleDNSRequest_tcp(t *testing.T) { sendTestMessages(t, conn) } -func TestProxy_handleDNSRequest_tls(t *testing.T) { +func TestProxy_HandleDNSRequest_tls(t *testing.T) { serverConfig, caPem := proxytest.NewTLSConfig(t) dnsProxy, err := proxy.New(&proxy.Config{ Logger: testLogger, TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(proxytest.LocalhostAnyPort)}, QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(proxytest.LocalhostAnyPort)}, TLSConfig: serverConfig, - UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr), + UpstreamConfig: newTestUpstreamConfig(t), TrustedProxies: proxytest.DefaultTrustedProxies, }) require.NoError(t, err) diff --git a/proxy/serverudp_test.go b/proxy/serverudp_test.go index fe9572b0..062bca14 100644 --- a/proxy/serverudp_test.go +++ b/proxy/serverudp_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestProxy_handleDNSRequest_udp(t *testing.T) { +func TestProxy_HandleDNSRequest_udp(t *testing.T) { dnsProxy := mustStartDefaultProxy(t) // Create a DNS-over-UDP client connection diff --git a/proxy/stats_test.go b/proxy/stats_test.go index 859aa95c..f9f74166 100644 --- a/proxy/stats_test.go +++ b/proxy/stats_test.go @@ -15,7 +15,9 @@ import ( "github.com/stretchr/testify/require" ) -func TestCollectQueryStats(t *testing.T) { +func TestDNSContext_QueryStatistics(t *testing.T) { + t.Parallel() + testReq := &dns.Msg{ Question: []dns.Question{{ Name: "test.",