1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build unix
6
7package net
8
9import (
10	"context"
11	"errors"
12	"fmt"
13	"os"
14	"path"
15	"path/filepath"
16	"reflect"
17	"runtime"
18	"slices"
19	"strings"
20	"sync"
21	"sync/atomic"
22	"testing"
23	"time"
24
25	"golang.org/x/net/dns/dnsmessage"
26)
27
28// Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation.
29var TestAddr = [4]byte{0xc0, 0x00, 0x02, 0x01}
30
31// Test address from 2001:db8::/32 block, reserved by RFC 3849 for documentation.
32var TestAddr6 = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
33
34func mustNewName(name string) dnsmessage.Name {
35	nn, err := dnsmessage.NewName(name)
36	if err != nil {
37		panic(fmt.Sprint("creating name: ", err))
38	}
39	return nn
40}
41
42func mustQuestion(name string, qtype dnsmessage.Type, class dnsmessage.Class) dnsmessage.Question {
43	return dnsmessage.Question{
44		Name:  mustNewName(name),
45		Type:  qtype,
46		Class: class,
47	}
48}
49
50var dnsTransportFallbackTests = []struct {
51	server   string
52	question dnsmessage.Question
53	timeout  int
54	rcode    dnsmessage.RCode
55}{
56	// Querying "com." with qtype=255 usually makes an answer
57	// which requires more than 512 bytes.
58	{"8.8.8.8:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 2, dnsmessage.RCodeSuccess},
59	{"8.8.4.4:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 4, dnsmessage.RCodeSuccess},
60}
61
62func TestDNSTransportFallback(t *testing.T) {
63	fake := fakeDNSServer{
64		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
65			r := dnsmessage.Message{
66				Header: dnsmessage.Header{
67					ID:       q.Header.ID,
68					Response: true,
69					RCode:    dnsmessage.RCodeSuccess,
70				},
71				Questions: q.Questions,
72			}
73			if n == "udp" {
74				r.Header.Truncated = true
75			}
76			return r, nil
77		},
78	}
79	r := Resolver{PreferGo: true, Dial: fake.DialContext}
80	for _, tt := range dnsTransportFallbackTests {
81		ctx, cancel := context.WithCancel(context.Background())
82		defer cancel()
83		_, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP, false)
84		if err != nil {
85			t.Error(err)
86			continue
87		}
88		if h.RCode != tt.rcode {
89			t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
90			continue
91		}
92	}
93}
94
95func TestDNSTransportNoFallbackOnTCP(t *testing.T) {
96	fake := fakeDNSServer{
97		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
98			r := dnsmessage.Message{
99				Header: dnsmessage.Header{
100					ID:        q.Header.ID,
101					Response:  true,
102					RCode:     dnsmessage.RCodeSuccess,
103					Truncated: true,
104				},
105				Questions: q.Questions,
106			}
107			if n == "tcp" {
108				r.Answers = []dnsmessage.Resource{
109					{
110						Header: dnsmessage.ResourceHeader{
111							Name:   q.Questions[0].Name,
112							Type:   dnsmessage.TypeA,
113							Class:  dnsmessage.ClassINET,
114							Length: 4,
115						},
116						Body: &dnsmessage.AResource{
117							A: TestAddr,
118						},
119					},
120				}
121			}
122			return r, nil
123		},
124	}
125	r := Resolver{PreferGo: true, Dial: fake.DialContext}
126	for _, tt := range dnsTransportFallbackTests {
127		ctx, cancel := context.WithCancel(context.Background())
128		defer cancel()
129		p, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP, false)
130		if err != nil {
131			t.Error(err)
132			continue
133		}
134		if h.RCode != tt.rcode {
135			t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
136			continue
137		}
138		a, err := p.AllAnswers()
139		if err != nil {
140			t.Errorf("unexpected error %v getting all answers from %v", err, tt.server)
141			continue
142		}
143		if len(a) != 1 {
144			t.Errorf("got %d answers from %v; want 1", len(a), tt.server)
145			continue
146		}
147	}
148}
149
150// See RFC 6761 for further information about the reserved, pseudo
151// domain names.
152var specialDomainNameTests = []struct {
153	question dnsmessage.Question
154	rcode    dnsmessage.RCode
155}{
156	// Name resolution APIs and libraries should not recognize the
157	// followings as special.
158	{mustQuestion("1.0.168.192.in-addr.arpa.", dnsmessage.TypePTR, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
159	{mustQuestion("test.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
160	{mustQuestion("example.com.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeSuccess},
161
162	// Name resolution APIs and libraries should recognize the
163	// followings as special and should not send any queries.
164	// Though, we test those names here for verifying negative
165	// answers at DNS query-response interaction level.
166	{mustQuestion("localhost.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
167	{mustQuestion("invalid.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
168}
169
170func TestSpecialDomainName(t *testing.T) {
171	fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
172		r := dnsmessage.Message{
173			Header: dnsmessage.Header{
174				ID:       q.ID,
175				Response: true,
176			},
177			Questions: q.Questions,
178		}
179
180		switch q.Questions[0].Name.String() {
181		case "example.com.":
182			r.Header.RCode = dnsmessage.RCodeSuccess
183		default:
184			r.Header.RCode = dnsmessage.RCodeNameError
185		}
186
187		return r, nil
188	}}
189	r := Resolver{PreferGo: true, Dial: fake.DialContext}
190	server := "8.8.8.8:53"
191	for _, tt := range specialDomainNameTests {
192		ctx, cancel := context.WithCancel(context.Background())
193		defer cancel()
194		_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second, useUDPOrTCP, false)
195		if err != nil {
196			t.Error(err)
197			continue
198		}
199		if h.RCode != tt.rcode {
200			t.Errorf("got %v from %v; want %v", h.RCode, server, tt.rcode)
201			continue
202		}
203	}
204}
205
206// Issue 13705: don't try to resolve onion addresses, etc
207func TestAvoidDNSName(t *testing.T) {
208	tests := []struct {
209		name  string
210		avoid bool
211	}{
212		{"foo.com", false},
213		{"foo.com.", false},
214
215		{"foo.onion.", true},
216		{"foo.onion", true},
217		{"foo.ONION", true},
218		{"foo.ONION.", true},
219
220		// But do resolve *.local address; Issue 16739
221		{"foo.local.", false},
222		{"foo.local", false},
223		{"foo.LOCAL", false},
224		{"foo.LOCAL.", false},
225
226		{"", true}, // will be rejected earlier too
227
228		// Without stuff before onion/local, they're fine to
229		// use DNS. With a search path,
230		// "onion.vegetables.com" can use DNS. Without a
231		// search path (or with a trailing dot), the queries
232		// are just kinda useless, but don't reveal anything
233		// private.
234		{"local", false},
235		{"onion", false},
236		{"local.", false},
237		{"onion.", false},
238	}
239	for _, tt := range tests {
240		got := avoidDNS(tt.name)
241		if got != tt.avoid {
242			t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid)
243		}
244	}
245}
246
247func TestNameListAvoidDNS(t *testing.T) {
248	c := &dnsConfig{search: []string{"go.dev.", "onion."}}
249	got := c.nameList("www")
250	if !slices.Equal(got, []string{"www.", "www.go.dev."}) {
251		t.Fatalf(`nameList("www") = %v, want "www.", "www.go.dev."`, got)
252	}
253
254	got = c.nameList("www.onion")
255	if !slices.Equal(got, []string{"www.onion.go.dev."}) {
256		t.Fatalf(`nameList("www.onion") = %v, want "www.onion.go.dev."`, got)
257	}
258}
259
260var fakeDNSServerSuccessful = fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
261	r := dnsmessage.Message{
262		Header: dnsmessage.Header{
263			ID:       q.ID,
264			Response: true,
265		},
266		Questions: q.Questions,
267	}
268	if len(q.Questions) == 1 && q.Questions[0].Type == dnsmessage.TypeA {
269		r.Answers = []dnsmessage.Resource{
270			{
271				Header: dnsmessage.ResourceHeader{
272					Name:   q.Questions[0].Name,
273					Type:   dnsmessage.TypeA,
274					Class:  dnsmessage.ClassINET,
275					Length: 4,
276				},
277				Body: &dnsmessage.AResource{
278					A: TestAddr,
279				},
280			},
281		}
282	}
283	return r, nil
284}}
285
286// Issue 13705: don't try to resolve onion addresses, etc
287func TestLookupTorOnion(t *testing.T) {
288	defer dnsWaitGroup.Wait()
289	r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
290	addrs, err := r.LookupIPAddr(context.Background(), "foo.onion.")
291	if err != nil {
292		t.Fatalf("lookup = %v; want nil", err)
293	}
294	if len(addrs) > 0 {
295		t.Errorf("unexpected addresses: %v", addrs)
296	}
297}
298
299type resolvConfTest struct {
300	dir  string
301	path string
302	*resolverConfig
303}
304
305func newResolvConfTest() (*resolvConfTest, error) {
306	dir, err := os.MkdirTemp("", "go-resolvconftest")
307	if err != nil {
308		return nil, err
309	}
310	conf := &resolvConfTest{
311		dir:            dir,
312		path:           path.Join(dir, "resolv.conf"),
313		resolverConfig: &resolvConf,
314	}
315	conf.initOnce.Do(conf.init)
316	return conf, nil
317}
318
319func (conf *resolvConfTest) write(lines []string) error {
320	f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
321	if err != nil {
322		return err
323	}
324	if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil {
325		f.Close()
326		return err
327	}
328	f.Close()
329	return nil
330}
331
332func (conf *resolvConfTest) writeAndUpdate(lines []string) error {
333	return conf.writeAndUpdateWithLastCheckedTime(lines, time.Now().Add(time.Hour))
334}
335
336func (conf *resolvConfTest) writeAndUpdateWithLastCheckedTime(lines []string, lastChecked time.Time) error {
337	if err := conf.write(lines); err != nil {
338		return err
339	}
340	return conf.forceUpdate(conf.path, lastChecked)
341}
342
343func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error {
344	dnsConf := dnsReadConfig(name)
345	if !conf.forceUpdateConf(dnsConf, lastChecked) {
346		return fmt.Errorf("tryAcquireSema for %s failed", name)
347	}
348	return nil
349}
350
351func (conf *resolvConfTest) forceUpdateConf(c *dnsConfig, lastChecked time.Time) bool {
352	conf.dnsConfig.Store(c)
353	for i := 0; i < 5; i++ {
354		if conf.tryAcquireSema() {
355			conf.lastChecked = lastChecked
356			conf.releaseSema()
357			return true
358		}
359	}
360	return false
361}
362
363func (conf *resolvConfTest) servers() []string {
364	return conf.dnsConfig.Load().servers
365}
366
367func (conf *resolvConfTest) teardown() error {
368	err := conf.forceUpdate("/etc/resolv.conf", time.Time{})
369	os.RemoveAll(conf.dir)
370	return err
371}
372
373var updateResolvConfTests = []struct {
374	name    string   // query name
375	lines   []string // resolver configuration lines
376	servers []string // expected name servers
377}{
378	{
379		name:    "golang.org",
380		lines:   []string{"nameserver 8.8.8.8"},
381		servers: []string{"8.8.8.8:53"},
382	},
383	{
384		name:    "",
385		lines:   nil, // an empty resolv.conf should use defaultNS as name servers
386		servers: defaultNS,
387	},
388	{
389		name:    "www.example.com",
390		lines:   []string{"nameserver 8.8.4.4"},
391		servers: []string{"8.8.4.4:53"},
392	},
393}
394
395func TestUpdateResolvConf(t *testing.T) {
396	defer dnsWaitGroup.Wait()
397
398	r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
399
400	conf, err := newResolvConfTest()
401	if err != nil {
402		t.Fatal(err)
403	}
404	defer conf.teardown()
405
406	for i, tt := range updateResolvConfTests {
407		if err := conf.writeAndUpdate(tt.lines); err != nil {
408			t.Error(err)
409			continue
410		}
411		if tt.name != "" {
412			var wg sync.WaitGroup
413			const N = 10
414			wg.Add(N)
415			for j := 0; j < N; j++ {
416				go func(name string) {
417					defer wg.Done()
418					ips, err := r.LookupIPAddr(context.Background(), name)
419					if err != nil {
420						t.Error(err)
421						return
422					}
423					if len(ips) == 0 {
424						t.Errorf("no records for %s", name)
425						return
426					}
427				}(tt.name)
428			}
429			wg.Wait()
430		}
431		servers := conf.servers()
432		if !reflect.DeepEqual(servers, tt.servers) {
433			t.Errorf("#%d: got %v; want %v", i, servers, tt.servers)
434			continue
435		}
436	}
437}
438
439var goLookupIPWithResolverConfigTests = []struct {
440	name  string
441	lines []string // resolver configuration lines
442	error
443	a, aaaa bool // whether response contains A, AAAA-record
444}{
445	// no records, transport timeout
446	{
447		"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
448		[]string{
449			"options timeout:1 attempts:1",
450			"nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address
451		},
452		&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true},
453		false, false,
454	},
455
456	// no records, non-existent domain
457	{
458		"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
459		[]string{
460			"options timeout:3 attempts:1",
461			"nameserver 8.8.8.8",
462		},
463		&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false},
464		false, false,
465	},
466
467	// a few A records, no AAAA records
468	{
469		"ipv4.google.com.",
470		[]string{
471			"nameserver 8.8.8.8",
472			"nameserver 2001:4860:4860::8888",
473		},
474		nil,
475		true, false,
476	},
477	{
478		"ipv4.google.com",
479		[]string{
480			"domain golang.org",
481			"nameserver 2001:4860:4860::8888",
482			"nameserver 8.8.8.8",
483		},
484		nil,
485		true, false,
486	},
487	{
488		"ipv4.google.com",
489		[]string{
490			"search x.golang.org y.golang.org",
491			"nameserver 2001:4860:4860::8888",
492			"nameserver 8.8.8.8",
493		},
494		nil,
495		true, false,
496	},
497
498	// no A records, a few AAAA records
499	{
500		"ipv6.google.com.",
501		[]string{
502			"nameserver 2001:4860:4860::8888",
503			"nameserver 8.8.8.8",
504		},
505		nil,
506		false, true,
507	},
508	{
509		"ipv6.google.com",
510		[]string{
511			"domain golang.org",
512			"nameserver 8.8.8.8",
513			"nameserver 2001:4860:4860::8888",
514		},
515		nil,
516		false, true,
517	},
518	{
519		"ipv6.google.com",
520		[]string{
521			"search x.golang.org y.golang.org",
522			"nameserver 8.8.8.8",
523			"nameserver 2001:4860:4860::8888",
524		},
525		nil,
526		false, true,
527	},
528
529	// both A and AAAA records
530	{
531		"hostname.as112.net", // see RFC 7534
532		[]string{
533			"domain golang.org",
534			"nameserver 2001:4860:4860::8888",
535			"nameserver 8.8.8.8",
536		},
537		nil,
538		true, true,
539	},
540	{
541		"hostname.as112.net", // see RFC 7534
542		[]string{
543			"search x.golang.org y.golang.org",
544			"nameserver 2001:4860:4860::8888",
545			"nameserver 8.8.8.8",
546		},
547		nil,
548		true, true,
549	},
550}
551
552func TestGoLookupIPWithResolverConfig(t *testing.T) {
553	defer dnsWaitGroup.Wait()
554	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
555		switch s {
556		case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
557			break
558		default:
559			time.Sleep(10 * time.Millisecond)
560			return dnsmessage.Message{}, os.ErrDeadlineExceeded
561		}
562		r := dnsmessage.Message{
563			Header: dnsmessage.Header{
564				ID:       q.ID,
565				Response: true,
566			},
567			Questions: q.Questions,
568		}
569		for _, question := range q.Questions {
570			switch question.Type {
571			case dnsmessage.TypeA:
572				switch question.Name.String() {
573				case "hostname.as112.net.":
574					break
575				case "ipv4.google.com.":
576					r.Answers = append(r.Answers, dnsmessage.Resource{
577						Header: dnsmessage.ResourceHeader{
578							Name:   q.Questions[0].Name,
579							Type:   dnsmessage.TypeA,
580							Class:  dnsmessage.ClassINET,
581							Length: 4,
582						},
583						Body: &dnsmessage.AResource{
584							A: TestAddr,
585						},
586					})
587				default:
588
589				}
590			case dnsmessage.TypeAAAA:
591				switch question.Name.String() {
592				case "hostname.as112.net.":
593					break
594				case "ipv6.google.com.":
595					r.Answers = append(r.Answers, dnsmessage.Resource{
596						Header: dnsmessage.ResourceHeader{
597							Name:   q.Questions[0].Name,
598							Type:   dnsmessage.TypeAAAA,
599							Class:  dnsmessage.ClassINET,
600							Length: 16,
601						},
602						Body: &dnsmessage.AAAAResource{
603							AAAA: TestAddr6,
604						},
605					})
606				}
607			}
608		}
609		return r, nil
610	}}
611	r := Resolver{PreferGo: true, Dial: fake.DialContext}
612
613	conf, err := newResolvConfTest()
614	if err != nil {
615		t.Fatal(err)
616	}
617	defer conf.teardown()
618
619	for _, tt := range goLookupIPWithResolverConfigTests {
620		if err := conf.writeAndUpdate(tt.lines); err != nil {
621			t.Error(err)
622			continue
623		}
624		addrs, err := r.LookupIPAddr(context.Background(), tt.name)
625		if err != nil {
626			if err, ok := err.(*DNSError); !ok || tt.error != nil && (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) {
627				t.Errorf("got %v; want %v", err, tt.error)
628			}
629			continue
630		}
631		if len(addrs) == 0 {
632			t.Errorf("no records for %s", tt.name)
633		}
634		if !tt.a && !tt.aaaa && len(addrs) > 0 {
635			t.Errorf("unexpected %v for %s", addrs, tt.name)
636		}
637		for _, addr := range addrs {
638			if !tt.a && addr.IP.To4() != nil {
639				t.Errorf("got %v; must not be IPv4 address", addr)
640			}
641			if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil {
642				t.Errorf("got %v; must not be IPv6 address", addr)
643			}
644		}
645	}
646}
647
648// Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
649func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
650	defer dnsWaitGroup.Wait()
651
652	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, tm time.Time) (dnsmessage.Message, error) {
653		r := dnsmessage.Message{
654			Header: dnsmessage.Header{
655				ID:       q.ID,
656				Response: true,
657			},
658			Questions: q.Questions,
659		}
660		return r, nil
661	}}
662	r := Resolver{PreferGo: true, Dial: fake.DialContext}
663
664	// Add a config that simulates no dns servers being available.
665	conf, err := newResolvConfTest()
666	if err != nil {
667		t.Fatal(err)
668	}
669	defer conf.teardown()
670
671	if err := conf.writeAndUpdate([]string{}); err != nil {
672		t.Fatal(err)
673	}
674	// Redirect host file lookups.
675	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
676	hostsFilePath = "testdata/hosts"
677
678	for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} {
679		name := fmt.Sprintf("order %v", order)
680		// First ensure that we get an error when contacting a non-existent host.
681		_, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "notarealhost", order, nil)
682		if err == nil {
683			t.Errorf("%s: expected error while looking up name not in hosts file", name)
684			continue
685		}
686
687		// Now check that we get an address when the name appears in the hosts file.
688		addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "thor", order, nil) // entry is in "testdata/hosts"
689		if err != nil {
690			t.Errorf("%s: expected to successfully lookup host entry", name)
691			continue
692		}
693		if len(addrs) != 1 {
694			t.Errorf("%s: expected exactly one result, but got %v", name, addrs)
695			continue
696		}
697		if got, want := addrs[0].String(), "127.1.1.1"; got != want {
698			t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want)
699		}
700	}
701}
702
703// Issue 12712.
704// When using search domains, return the error encountered
705// querying the original name instead of an error encountered
706// querying a generated name.
707func TestErrorForOriginalNameWhenSearching(t *testing.T) {
708	defer dnsWaitGroup.Wait()
709
710	const fqdn = "doesnotexist.domain"
711
712	conf, err := newResolvConfTest()
713	if err != nil {
714		t.Fatal(err)
715	}
716	defer conf.teardown()
717
718	if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
719		t.Fatal(err)
720	}
721
722	fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
723		r := dnsmessage.Message{
724			Header: dnsmessage.Header{
725				ID:       q.ID,
726				Response: true,
727			},
728			Questions: q.Questions,
729		}
730
731		switch q.Questions[0].Name.String() {
732		case fqdn + ".servfail.":
733			r.Header.RCode = dnsmessage.RCodeServerFailure
734		default:
735			r.Header.RCode = dnsmessage.RCodeNameError
736		}
737
738		return r, nil
739	}}
740
741	cases := []struct {
742		strictErrors bool
743		wantErr      *DNSError
744	}{
745		{true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
746		{false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error(), IsNotFound: true}},
747	}
748	for _, tt := range cases {
749		r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
750		_, err = r.LookupIPAddr(context.Background(), fqdn)
751		if err == nil {
752			t.Fatal("expected an error")
753		}
754
755		want := tt.wantErr
756		if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary {
757			t.Errorf("got %v; want %v", err, want)
758		}
759	}
760}
761
762// Issue 15434. If a name server gives a lame referral, continue to the next.
763func TestIgnoreLameReferrals(t *testing.T) {
764	defer dnsWaitGroup.Wait()
765
766	conf, err := newResolvConfTest()
767	if err != nil {
768		t.Fatal(err)
769	}
770	defer conf.teardown()
771
772	if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
773		"nameserver 192.0.2.2"}); err != nil {
774		t.Fatal(err)
775	}
776
777	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
778		t.Log(s, q)
779		r := dnsmessage.Message{
780			Header: dnsmessage.Header{
781				ID:       q.ID,
782				Response: true,
783			},
784			Questions: q.Questions,
785		}
786
787		if s == "192.0.2.2:53" {
788			r.Header.RecursionAvailable = true
789			if q.Questions[0].Type == dnsmessage.TypeA {
790				r.Answers = []dnsmessage.Resource{
791					{
792						Header: dnsmessage.ResourceHeader{
793							Name:   q.Questions[0].Name,
794							Type:   dnsmessage.TypeA,
795							Class:  dnsmessage.ClassINET,
796							Length: 4,
797						},
798						Body: &dnsmessage.AResource{
799							A: TestAddr,
800						},
801					},
802				}
803			}
804		} else if s == "192.0.2.1:53" {
805			if q.Questions[0].Type == dnsmessage.TypeA && strings.HasPrefix(q.Questions[0].Name.String(), "empty.com.") {
806				var edns0Hdr dnsmessage.ResourceHeader
807				edns0Hdr.SetEDNS0(maxDNSPacketSize, dnsmessage.RCodeSuccess, false)
808
809				r.Additionals = []dnsmessage.Resource{
810					{
811						Header: edns0Hdr,
812						Body:   &dnsmessage.OPTResource{},
813					},
814				}
815			}
816		}
817
818		return r, nil
819	}}
820	r := Resolver{PreferGo: true, Dial: fake.DialContext}
821
822	addrs, err := r.LookupIP(context.Background(), "ip4", "www.golang.org")
823	if err != nil {
824		t.Fatal(err)
825	}
826
827	if got := len(addrs); got != 1 {
828		t.Fatalf("got %d addresses, want 1", got)
829	}
830
831	if got, want := addrs[0].String(), "192.0.2.1"; got != want {
832		t.Fatalf("got address %v, want %v", got, want)
833	}
834
835	_, err = r.LookupIP(context.Background(), "ip4", "empty.com")
836	de, ok := err.(*DNSError)
837	if !ok {
838		t.Fatalf("err = %#v; wanted a *net.DNSError", err)
839	}
840	if de.Err != errNoSuchHost.Error() {
841		t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
842	}
843}
844
845func BenchmarkGoLookupIP(b *testing.B) {
846	testHookUninstaller.Do(uninstallTestHooks)
847	ctx := context.Background()
848	b.ReportAllocs()
849
850	for i := 0; i < b.N; i++ {
851		goResolver.LookupIPAddr(ctx, "www.example.com")
852	}
853}
854
855func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
856	testHookUninstaller.Do(uninstallTestHooks)
857	ctx := context.Background()
858	b.ReportAllocs()
859
860	for i := 0; i < b.N; i++ {
861		goResolver.LookupIPAddr(ctx, "some.nonexistent")
862	}
863}
864
865func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
866	testHookUninstaller.Do(uninstallTestHooks)
867
868	conf, err := newResolvConfTest()
869	if err != nil {
870		b.Fatal(err)
871	}
872	defer conf.teardown()
873
874	lines := []string{
875		"nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737
876		"nameserver 8.8.8.8",
877	}
878	if err := conf.writeAndUpdate(lines); err != nil {
879		b.Fatal(err)
880	}
881	ctx := context.Background()
882	b.ReportAllocs()
883
884	for i := 0; i < b.N; i++ {
885		goResolver.LookupIPAddr(ctx, "www.example.com")
886	}
887}
888
889type fakeDNSServer struct {
890	rh        func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error)
891	alwaysTCP bool
892}
893
894func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) {
895	if server.alwaysTCP || n == "tcp" || n == "tcp4" || n == "tcp6" {
896		return &fakeDNSConn{tcp: true, server: server, n: n, s: s}, nil
897	}
898	return &fakeDNSPacketConn{fakeDNSConn: fakeDNSConn{tcp: false, server: server, n: n, s: s}}, nil
899}
900
901type fakeDNSConn struct {
902	Conn
903	tcp    bool
904	server *fakeDNSServer
905	n      string
906	s      string
907	q      dnsmessage.Message
908	t      time.Time
909	buf    []byte
910}
911
912func (f *fakeDNSConn) Close() error {
913	return nil
914}
915
916func (f *fakeDNSConn) Read(b []byte) (int, error) {
917	if len(f.buf) > 0 {
918		n := copy(b, f.buf)
919		f.buf = f.buf[n:]
920		return n, nil
921	}
922
923	resp, err := f.server.rh(f.n, f.s, f.q, f.t)
924	if err != nil {
925		return 0, err
926	}
927
928	bb := make([]byte, 2, 514)
929	bb, err = resp.AppendPack(bb)
930	if err != nil {
931		return 0, fmt.Errorf("cannot marshal DNS message: %v", err)
932	}
933
934	if f.tcp {
935		l := len(bb) - 2
936		bb[0] = byte(l >> 8)
937		bb[1] = byte(l)
938		f.buf = bb
939		return f.Read(b)
940	}
941
942	bb = bb[2:]
943	if len(b) < len(bb) {
944		return 0, errors.New("read would fragment DNS message")
945	}
946
947	copy(b, bb)
948	return len(bb), nil
949}
950
951func (f *fakeDNSConn) Write(b []byte) (int, error) {
952	if f.tcp && len(b) >= 2 {
953		b = b[2:]
954	}
955	if f.q.Unpack(b) != nil {
956		return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", f.n, len(b))
957	}
958	return len(b), nil
959}
960
961func (f *fakeDNSConn) SetDeadline(t time.Time) error {
962	f.t = t
963	return nil
964}
965
966type fakeDNSPacketConn struct {
967	PacketConn
968	fakeDNSConn
969}
970
971func (f *fakeDNSPacketConn) SetDeadline(t time.Time) error {
972	return f.fakeDNSConn.SetDeadline(t)
973}
974
975func (f *fakeDNSPacketConn) Close() error {
976	return f.fakeDNSConn.Close()
977}
978
979// UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
980func TestIgnoreDNSForgeries(t *testing.T) {
981	c, s := Pipe()
982	go func() {
983		b := make([]byte, maxDNSPacketSize)
984		n, err := s.Read(b)
985		if err != nil {
986			t.Error(err)
987			return
988		}
989
990		var msg dnsmessage.Message
991		if msg.Unpack(b[:n]) != nil {
992			t.Error("invalid DNS query:", err)
993			return
994		}
995
996		s.Write([]byte("garbage DNS response packet"))
997
998		msg.Header.Response = true
999		msg.Header.ID++ // make invalid ID
1000
1001		if b, err = msg.Pack(); err != nil {
1002			t.Error("failed to pack DNS response:", err)
1003			return
1004		}
1005		s.Write(b)
1006
1007		msg.Header.ID-- // restore original ID
1008		msg.Answers = []dnsmessage.Resource{
1009			{
1010				Header: dnsmessage.ResourceHeader{
1011					Name:   mustNewName("www.example.com."),
1012					Type:   dnsmessage.TypeA,
1013					Class:  dnsmessage.ClassINET,
1014					Length: 4,
1015				},
1016				Body: &dnsmessage.AResource{
1017					A: TestAddr,
1018				},
1019			},
1020		}
1021
1022		b, err = msg.Pack()
1023		if err != nil {
1024			t.Error("failed to pack DNS response:", err)
1025			return
1026		}
1027		s.Write(b)
1028	}()
1029
1030	msg := dnsmessage.Message{
1031		Header: dnsmessage.Header{
1032			ID: 42,
1033		},
1034		Questions: []dnsmessage.Question{
1035			{
1036				Name:  mustNewName("www.example.com."),
1037				Type:  dnsmessage.TypeA,
1038				Class: dnsmessage.ClassINET,
1039			},
1040		},
1041	}
1042
1043	b, err := msg.Pack()
1044	if err != nil {
1045		t.Fatal("Pack failed:", err)
1046	}
1047
1048	p, _, err := dnsPacketRoundTrip(c, 42, msg.Questions[0], b)
1049	if err != nil {
1050		t.Fatalf("dnsPacketRoundTrip failed: %v", err)
1051	}
1052
1053	p.SkipAllQuestions()
1054	as, err := p.AllAnswers()
1055	if err != nil {
1056		t.Fatal("AllAnswers failed:", err)
1057	}
1058	if got := as[0].Body.(*dnsmessage.AResource).A; got != TestAddr {
1059		t.Errorf("got address %v, want %v", got, TestAddr)
1060	}
1061}
1062
1063// Issue 16865. If a name server times out, continue to the next.
1064func TestRetryTimeout(t *testing.T) {
1065	defer dnsWaitGroup.Wait()
1066
1067	conf, err := newResolvConfTest()
1068	if err != nil {
1069		t.Fatal(err)
1070	}
1071	defer conf.teardown()
1072
1073	testConf := []string{
1074		"nameserver 192.0.2.1", // the one that will timeout
1075		"nameserver 192.0.2.2",
1076	}
1077	if err := conf.writeAndUpdate(testConf); err != nil {
1078		t.Fatal(err)
1079	}
1080
1081	var deadline0 time.Time
1082
1083	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1084		t.Log(s, q, deadline)
1085
1086		if deadline.IsZero() {
1087			t.Error("zero deadline")
1088		}
1089
1090		if s == "192.0.2.1:53" {
1091			deadline0 = deadline
1092			time.Sleep(10 * time.Millisecond)
1093			return dnsmessage.Message{}, os.ErrDeadlineExceeded
1094		}
1095
1096		if deadline.Equal(deadline0) {
1097			t.Error("deadline didn't change")
1098		}
1099
1100		return mockTXTResponse(q), nil
1101	}}
1102	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
1103
1104	_, err = r.LookupTXT(context.Background(), "www.golang.org")
1105	if err != nil {
1106		t.Fatal(err)
1107	}
1108
1109	if deadline0.IsZero() {
1110		t.Error("deadline0 still zero", deadline0)
1111	}
1112}
1113
1114func TestRotate(t *testing.T) {
1115	// without rotation, always uses the first server
1116	testRotate(t, false, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.1:53", "192.0.2.1:53"})
1117
1118	// with rotation, rotates through back to first
1119	testRotate(t, true, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.2:53", "192.0.2.1:53"})
1120}
1121
1122func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
1123	defer dnsWaitGroup.Wait()
1124
1125	conf, err := newResolvConfTest()
1126	if err != nil {
1127		t.Fatal(err)
1128	}
1129	defer conf.teardown()
1130
1131	var confLines []string
1132	for _, ns := range nameservers {
1133		confLines = append(confLines, "nameserver "+ns)
1134	}
1135	if rotate {
1136		confLines = append(confLines, "options rotate")
1137	}
1138
1139	if err := conf.writeAndUpdate(confLines); err != nil {
1140		t.Fatal(err)
1141	}
1142
1143	var usedServers []string
1144	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1145		usedServers = append(usedServers, s)
1146		return mockTXTResponse(q), nil
1147	}}
1148	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1149
1150	// len(nameservers) + 1 to allow rotation to get back to start
1151	for i := 0; i < len(nameservers)+1; i++ {
1152		if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil {
1153			t.Fatal(err)
1154		}
1155	}
1156
1157	if !reflect.DeepEqual(usedServers, wantServers) {
1158		t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers)
1159	}
1160}
1161
1162func mockTXTResponse(q dnsmessage.Message) dnsmessage.Message {
1163	r := dnsmessage.Message{
1164		Header: dnsmessage.Header{
1165			ID:                 q.ID,
1166			Response:           true,
1167			RecursionAvailable: true,
1168		},
1169		Questions: q.Questions,
1170		Answers: []dnsmessage.Resource{
1171			{
1172				Header: dnsmessage.ResourceHeader{
1173					Name:  q.Questions[0].Name,
1174					Type:  dnsmessage.TypeTXT,
1175					Class: dnsmessage.ClassINET,
1176				},
1177				Body: &dnsmessage.TXTResource{
1178					TXT: []string{"ok"},
1179				},
1180			},
1181		},
1182	}
1183
1184	return r
1185}
1186
1187// Issue 17448. With StrictErrors enabled, temporary errors should make
1188// LookupIP fail rather than return a partial result.
1189func TestStrictErrorsLookupIP(t *testing.T) {
1190	defer dnsWaitGroup.Wait()
1191
1192	conf, err := newResolvConfTest()
1193	if err != nil {
1194		t.Fatal(err)
1195	}
1196	defer conf.teardown()
1197
1198	confData := []string{
1199		"nameserver 192.0.2.53",
1200		"search x.golang.org y.golang.org",
1201	}
1202	if err := conf.writeAndUpdate(confData); err != nil {
1203		t.Fatal(err)
1204	}
1205
1206	const name = "test-issue19592"
1207	const server = "192.0.2.53:53"
1208	const searchX = "test-issue19592.x.golang.org."
1209	const searchY = "test-issue19592.y.golang.org."
1210	const ip4 = "192.0.2.1"
1211	const ip6 = "2001:db8::1"
1212
1213	type resolveWhichEnum int
1214	const (
1215		resolveOK resolveWhichEnum = iota
1216		resolveOpError
1217		resolveServfail
1218		resolveTimeout
1219	)
1220
1221	makeTempError := func(err string) error {
1222		return &DNSError{
1223			Err:         err,
1224			Name:        name,
1225			Server:      server,
1226			IsTemporary: true,
1227		}
1228	}
1229	makeTimeout := func() error {
1230		return &DNSError{
1231			Err:         os.ErrDeadlineExceeded.Error(),
1232			Name:        name,
1233			Server:      server,
1234			IsTimeout:   true,
1235			IsTemporary: true,
1236		}
1237	}
1238	makeNxDomain := func() error {
1239		return &DNSError{
1240			Err:        errNoSuchHost.Error(),
1241			Name:       name,
1242			Server:     server,
1243			IsNotFound: true,
1244		}
1245	}
1246
1247	cases := []struct {
1248		desc          string
1249		resolveWhich  func(quest dnsmessage.Question) resolveWhichEnum
1250		wantStrictErr error
1251		wantLaxErr    error
1252		wantIPs       []string
1253	}{
1254		{
1255			desc: "No errors",
1256			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1257				return resolveOK
1258			},
1259			wantIPs: []string{ip4, ip6},
1260		},
1261		{
1262			desc: "searchX error fails in strict mode",
1263			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1264				if quest.Name.String() == searchX {
1265					return resolveTimeout
1266				}
1267				return resolveOK
1268			},
1269			wantStrictErr: makeTimeout(),
1270			wantIPs:       []string{ip4, ip6},
1271		},
1272		{
1273			desc: "searchX IPv4-only timeout fails in strict mode",
1274			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1275				if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeA {
1276					return resolveTimeout
1277				}
1278				return resolveOK
1279			},
1280			wantStrictErr: makeTimeout(),
1281			wantIPs:       []string{ip4, ip6},
1282		},
1283		{
1284			desc: "searchX IPv6-only servfail fails in strict mode",
1285			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1286				if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeAAAA {
1287					return resolveServfail
1288				}
1289				return resolveOK
1290			},
1291			wantStrictErr: makeTempError("server misbehaving"),
1292			wantIPs:       []string{ip4, ip6},
1293		},
1294		{
1295			desc: "searchY error always fails",
1296			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1297				if quest.Name.String() == searchY {
1298					return resolveTimeout
1299				}
1300				return resolveOK
1301			},
1302			wantStrictErr: makeTimeout(),
1303			wantLaxErr:    makeNxDomain(), // This one reaches the "test." FQDN.
1304		},
1305		{
1306			desc: "searchY IPv4-only socket error fails in strict mode",
1307			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1308				if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeA {
1309					return resolveOpError
1310				}
1311				return resolveOK
1312			},
1313			wantStrictErr: makeTempError("write: socket on fire"),
1314			wantIPs:       []string{ip6},
1315		},
1316		{
1317			desc: "searchY IPv6-only timeout fails in strict mode",
1318			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1319				if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeAAAA {
1320					return resolveTimeout
1321				}
1322				return resolveOK
1323			},
1324			wantStrictErr: makeTimeout(),
1325			wantIPs:       []string{ip4},
1326		},
1327	}
1328
1329	for i, tt := range cases {
1330		fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1331			t.Log(s, q)
1332
1333			switch tt.resolveWhich(q.Questions[0]) {
1334			case resolveOK:
1335				// Handle below.
1336			case resolveOpError:
1337				return dnsmessage.Message{}, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")}
1338			case resolveServfail:
1339				return dnsmessage.Message{
1340					Header: dnsmessage.Header{
1341						ID:       q.ID,
1342						Response: true,
1343						RCode:    dnsmessage.RCodeServerFailure,
1344					},
1345					Questions: q.Questions,
1346				}, nil
1347			case resolveTimeout:
1348				return dnsmessage.Message{}, os.ErrDeadlineExceeded
1349			default:
1350				t.Fatal("Impossible resolveWhich")
1351			}
1352
1353			switch q.Questions[0].Name.String() {
1354			case searchX, name + ".":
1355				// Return NXDOMAIN to utilize the search list.
1356				return dnsmessage.Message{
1357					Header: dnsmessage.Header{
1358						ID:       q.ID,
1359						Response: true,
1360						RCode:    dnsmessage.RCodeNameError,
1361					},
1362					Questions: q.Questions,
1363				}, nil
1364			case searchY:
1365				// Return records below.
1366			default:
1367				return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1368			}
1369
1370			r := dnsmessage.Message{
1371				Header: dnsmessage.Header{
1372					ID:       q.ID,
1373					Response: true,
1374				},
1375				Questions: q.Questions,
1376			}
1377			switch q.Questions[0].Type {
1378			case dnsmessage.TypeA:
1379				r.Answers = []dnsmessage.Resource{
1380					{
1381						Header: dnsmessage.ResourceHeader{
1382							Name:   q.Questions[0].Name,
1383							Type:   dnsmessage.TypeA,
1384							Class:  dnsmessage.ClassINET,
1385							Length: 4,
1386						},
1387						Body: &dnsmessage.AResource{
1388							A: TestAddr,
1389						},
1390					},
1391				}
1392			case dnsmessage.TypeAAAA:
1393				r.Answers = []dnsmessage.Resource{
1394					{
1395						Header: dnsmessage.ResourceHeader{
1396							Name:   q.Questions[0].Name,
1397							Type:   dnsmessage.TypeAAAA,
1398							Class:  dnsmessage.ClassINET,
1399							Length: 16,
1400						},
1401						Body: &dnsmessage.AAAAResource{
1402							AAAA: TestAddr6,
1403						},
1404					},
1405				}
1406			default:
1407				return dnsmessage.Message{}, fmt.Errorf("Unexpected Type: %v", q.Questions[0].Type)
1408			}
1409			return r, nil
1410		}}
1411
1412		for _, strict := range []bool{true, false} {
1413			r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext}
1414			ips, err := r.LookupIPAddr(context.Background(), name)
1415
1416			var wantErr error
1417			if strict {
1418				wantErr = tt.wantStrictErr
1419			} else {
1420				wantErr = tt.wantLaxErr
1421			}
1422			if !reflect.DeepEqual(err, wantErr) {
1423				t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr)
1424			}
1425
1426			gotIPs := map[string]struct{}{}
1427			for _, ip := range ips {
1428				gotIPs[ip.String()] = struct{}{}
1429			}
1430			wantIPs := map[string]struct{}{}
1431			if wantErr == nil {
1432				for _, ip := range tt.wantIPs {
1433					wantIPs[ip] = struct{}{}
1434				}
1435			}
1436			if !reflect.DeepEqual(gotIPs, wantIPs) {
1437				t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs)
1438			}
1439		}
1440	}
1441}
1442
1443// Issue 17448. With StrictErrors enabled, temporary errors should make
1444// LookupTXT stop walking the search list.
1445func TestStrictErrorsLookupTXT(t *testing.T) {
1446	defer dnsWaitGroup.Wait()
1447
1448	conf, err := newResolvConfTest()
1449	if err != nil {
1450		t.Fatal(err)
1451	}
1452	defer conf.teardown()
1453
1454	confData := []string{
1455		"nameserver 192.0.2.53",
1456		"search x.golang.org y.golang.org",
1457	}
1458	if err := conf.writeAndUpdate(confData); err != nil {
1459		t.Fatal(err)
1460	}
1461
1462	const name = "test"
1463	const server = "192.0.2.53:53"
1464	const searchX = "test.x.golang.org."
1465	const searchY = "test.y.golang.org."
1466	const txt = "Hello World"
1467
1468	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1469		t.Log(s, q)
1470
1471		switch q.Questions[0].Name.String() {
1472		case searchX:
1473			return dnsmessage.Message{}, os.ErrDeadlineExceeded
1474		case searchY:
1475			return mockTXTResponse(q), nil
1476		default:
1477			return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1478		}
1479	}}
1480
1481	for _, strict := range []bool{true, false} {
1482		r := Resolver{StrictErrors: strict, Dial: fake.DialContext}
1483		p, _, err := r.lookup(context.Background(), name, dnsmessage.TypeTXT, nil)
1484		var wantErr error
1485		var wantRRs int
1486		if strict {
1487			wantErr = &DNSError{
1488				Err:         os.ErrDeadlineExceeded.Error(),
1489				Name:        name,
1490				Server:      server,
1491				IsTimeout:   true,
1492				IsTemporary: true,
1493			}
1494		} else {
1495			wantRRs = 1
1496		}
1497		if !reflect.DeepEqual(err, wantErr) {
1498			t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr)
1499		}
1500		a, err := p.AllAnswers()
1501		if err != nil {
1502			a = nil
1503		}
1504		if len(a) != wantRRs {
1505			t.Errorf("strict=%v: got %v; want %v", strict, len(a), wantRRs)
1506		}
1507	}
1508}
1509
1510// Test for a race between uninstalling the test hooks and closing a
1511// socket connection. This used to fail when testing with -race.
1512func TestDNSGoroutineRace(t *testing.T) {
1513	defer dnsWaitGroup.Wait()
1514
1515	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) {
1516		time.Sleep(10 * time.Microsecond)
1517		return dnsmessage.Message{}, os.ErrDeadlineExceeded
1518	}}
1519	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1520
1521	// The timeout here is less than the timeout used by the server,
1522	// so the goroutine started to query the (fake) server will hang
1523	// around after this test is done if we don't call dnsWaitGroup.Wait.
1524	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond)
1525	defer cancel()
1526	_, err := r.LookupIPAddr(ctx, "where.are.they.now")
1527	if err == nil {
1528		t.Fatal("fake DNS lookup unexpectedly succeeded")
1529	}
1530}
1531
1532func lookupWithFake(fake fakeDNSServer, name string, typ dnsmessage.Type) error {
1533	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1534
1535	conf := getSystemDNSConfig()
1536
1537	ctx, cancel := context.WithCancel(context.Background())
1538	defer cancel()
1539
1540	_, _, err := r.tryOneName(ctx, conf, name, typ)
1541	return err
1542}
1543
1544// Issue 8434: verify that Temporary returns true on an error when rcode
1545// is SERVFAIL
1546func TestIssue8434(t *testing.T) {
1547	err := lookupWithFake(fakeDNSServer{
1548		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1549			return dnsmessage.Message{
1550				Header: dnsmessage.Header{
1551					ID:       q.ID,
1552					Response: true,
1553					RCode:    dnsmessage.RCodeServerFailure,
1554				},
1555				Questions: q.Questions,
1556			}, nil
1557		},
1558	}, "golang.org.", dnsmessage.TypeALL)
1559	if err == nil {
1560		t.Fatal("expected an error")
1561	}
1562	if ne, ok := err.(Error); !ok {
1563		t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1564	} else if !ne.Temporary() {
1565		t.Fatalf("Temporary = false for err = %#v; want Temporary == true", err)
1566	}
1567	if de, ok := err.(*DNSError); !ok {
1568		t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1569	} else if !de.IsTemporary {
1570		t.Fatalf("IsTemporary = false for err = %#v; want IsTemporary == true", err)
1571	}
1572}
1573
1574func TestIssueNoSuchHostExists(t *testing.T) {
1575	err := lookupWithFake(fakeDNSServer{
1576		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1577			return dnsmessage.Message{
1578				Header: dnsmessage.Header{
1579					ID:       q.ID,
1580					Response: true,
1581					RCode:    dnsmessage.RCodeNameError,
1582				},
1583				Questions: q.Questions,
1584			}, nil
1585		},
1586	}, "golang.org.", dnsmessage.TypeALL)
1587	if err == nil {
1588		t.Fatal("expected an error")
1589	}
1590	if _, ok := err.(Error); !ok {
1591		t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1592	}
1593	if de, ok := err.(*DNSError); !ok {
1594		t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1595	} else if !de.IsNotFound {
1596		t.Fatalf("IsNotFound = false for err = %#v; want IsNotFound == true", err)
1597	}
1598}
1599
1600// TestNoSuchHost verifies that tryOneName works correctly when the domain does
1601// not exist.
1602//
1603// Issue 12778: verify that NXDOMAIN without RA bit errors as "no such host"
1604// and not "server misbehaving"
1605//
1606// Issue 25336: verify that NXDOMAIN errors fail fast.
1607//
1608// Issue 27525: verify that empty answers fail fast.
1609func TestNoSuchHost(t *testing.T) {
1610	tests := []struct {
1611		name string
1612		f    func(string, string, dnsmessage.Message, time.Time) (dnsmessage.Message, error)
1613	}{
1614		{
1615			"NXDOMAIN",
1616			func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1617				return dnsmessage.Message{
1618					Header: dnsmessage.Header{
1619						ID:                 q.ID,
1620						Response:           true,
1621						RCode:              dnsmessage.RCodeNameError,
1622						RecursionAvailable: false,
1623					},
1624					Questions: q.Questions,
1625				}, nil
1626			},
1627		},
1628		{
1629			"no answers",
1630			func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1631				return dnsmessage.Message{
1632					Header: dnsmessage.Header{
1633						ID:                 q.ID,
1634						Response:           true,
1635						RCode:              dnsmessage.RCodeSuccess,
1636						RecursionAvailable: false,
1637						Authoritative:      true,
1638					},
1639					Questions: q.Questions,
1640				}, nil
1641			},
1642		},
1643	}
1644
1645	for _, test := range tests {
1646		t.Run(test.name, func(t *testing.T) {
1647			lookups := 0
1648			err := lookupWithFake(fakeDNSServer{
1649				rh: func(n, s string, q dnsmessage.Message, d time.Time) (dnsmessage.Message, error) {
1650					lookups++
1651					return test.f(n, s, q, d)
1652				},
1653			}, ".", dnsmessage.TypeALL)
1654
1655			if lookups != 1 {
1656				t.Errorf("got %d lookups, wanted 1", lookups)
1657			}
1658
1659			if err == nil {
1660				t.Fatal("expected an error")
1661			}
1662			de, ok := err.(*DNSError)
1663			if !ok {
1664				t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1665			}
1666			if de.Err != errNoSuchHost.Error() {
1667				t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
1668			}
1669			if !de.IsNotFound {
1670				t.Fatalf("IsNotFound = %v wanted true", de.IsNotFound)
1671			}
1672		})
1673	}
1674}
1675
1676// Issue 26573: verify that Conns that don't implement PacketConn are treated
1677// as streams even when udp was requested.
1678func TestDNSDialTCP(t *testing.T) {
1679	fake := fakeDNSServer{
1680		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1681			r := dnsmessage.Message{
1682				Header: dnsmessage.Header{
1683					ID:       q.Header.ID,
1684					Response: true,
1685					RCode:    dnsmessage.RCodeSuccess,
1686				},
1687				Questions: q.Questions,
1688			}
1689			return r, nil
1690		},
1691		alwaysTCP: true,
1692	}
1693	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1694	ctx := context.Background()
1695	_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useUDPOrTCP, false)
1696	if err != nil {
1697		t.Fatal("exchange failed:", err)
1698	}
1699}
1700
1701// Issue 27763: verify that two strings in one TXT record are concatenated.
1702func TestTXTRecordTwoStrings(t *testing.T) {
1703	fake := fakeDNSServer{
1704		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1705			r := dnsmessage.Message{
1706				Header: dnsmessage.Header{
1707					ID:       q.Header.ID,
1708					Response: true,
1709					RCode:    dnsmessage.RCodeSuccess,
1710				},
1711				Questions: q.Questions,
1712				Answers: []dnsmessage.Resource{
1713					{
1714						Header: dnsmessage.ResourceHeader{
1715							Name:  q.Questions[0].Name,
1716							Type:  dnsmessage.TypeA,
1717							Class: dnsmessage.ClassINET,
1718						},
1719						Body: &dnsmessage.TXTResource{
1720							TXT: []string{"string1 ", "string2"},
1721						},
1722					},
1723					{
1724						Header: dnsmessage.ResourceHeader{
1725							Name:  q.Questions[0].Name,
1726							Type:  dnsmessage.TypeA,
1727							Class: dnsmessage.ClassINET,
1728						},
1729						Body: &dnsmessage.TXTResource{
1730							TXT: []string{"onestring"},
1731						},
1732					},
1733				},
1734			}
1735			return r, nil
1736		},
1737	}
1738	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1739	txt, err := r.lookupTXT(context.Background(), "golang.org")
1740	if err != nil {
1741		t.Fatal("LookupTXT failed:", err)
1742	}
1743	if want := 2; len(txt) != want {
1744		t.Fatalf("len(txt), got %d, want %d", len(txt), want)
1745	}
1746	if want := "string1 string2"; txt[0] != want {
1747		t.Errorf("txt[0], got %q, want %q", txt[0], want)
1748	}
1749	if want := "onestring"; txt[1] != want {
1750		t.Errorf("txt[1], got %q, want %q", txt[1], want)
1751	}
1752}
1753
1754// Issue 29644: support single-request resolv.conf option in pure Go resolver.
1755// The A and AAAA queries will be sent sequentially, not in parallel.
1756func TestSingleRequestLookup(t *testing.T) {
1757	defer dnsWaitGroup.Wait()
1758	var (
1759		firstcalled int32
1760		ipv4        int32 = 1
1761		ipv6        int32 = 2
1762	)
1763	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1764		r := dnsmessage.Message{
1765			Header: dnsmessage.Header{
1766				ID:       q.ID,
1767				Response: true,
1768			},
1769			Questions: q.Questions,
1770		}
1771		for _, question := range q.Questions {
1772			switch question.Type {
1773			case dnsmessage.TypeA:
1774				if question.Name.String() == "slowipv4.example.net." {
1775					time.Sleep(10 * time.Millisecond)
1776				}
1777				if !atomic.CompareAndSwapInt32(&firstcalled, 0, ipv4) {
1778					t.Errorf("the A query was received after the AAAA query !")
1779				}
1780				r.Answers = append(r.Answers, dnsmessage.Resource{
1781					Header: dnsmessage.ResourceHeader{
1782						Name:   q.Questions[0].Name,
1783						Type:   dnsmessage.TypeA,
1784						Class:  dnsmessage.ClassINET,
1785						Length: 4,
1786					},
1787					Body: &dnsmessage.AResource{
1788						A: TestAddr,
1789					},
1790				})
1791			case dnsmessage.TypeAAAA:
1792				atomic.CompareAndSwapInt32(&firstcalled, 0, ipv6)
1793				r.Answers = append(r.Answers, dnsmessage.Resource{
1794					Header: dnsmessage.ResourceHeader{
1795						Name:   q.Questions[0].Name,
1796						Type:   dnsmessage.TypeAAAA,
1797						Class:  dnsmessage.ClassINET,
1798						Length: 16,
1799					},
1800					Body: &dnsmessage.AAAAResource{
1801						AAAA: TestAddr6,
1802					},
1803				})
1804			}
1805		}
1806		return r, nil
1807	}}
1808	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1809
1810	conf, err := newResolvConfTest()
1811	if err != nil {
1812		t.Fatal(err)
1813	}
1814	defer conf.teardown()
1815	if err := conf.writeAndUpdate([]string{"options single-request"}); err != nil {
1816		t.Fatal(err)
1817	}
1818	for _, name := range []string{"hostname.example.net", "slowipv4.example.net"} {
1819		firstcalled = 0
1820		_, err := r.LookupIPAddr(context.Background(), name)
1821		if err != nil {
1822			t.Error(err)
1823		}
1824	}
1825}
1826
1827// Issue 29358. Add configuration knob to force TCP-only DNS requests in the pure Go resolver.
1828func TestDNSUseTCP(t *testing.T) {
1829	fake := fakeDNSServer{
1830		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1831			r := dnsmessage.Message{
1832				Header: dnsmessage.Header{
1833					ID:       q.Header.ID,
1834					Response: true,
1835					RCode:    dnsmessage.RCodeSuccess,
1836				},
1837				Questions: q.Questions,
1838			}
1839			if n == "udp" {
1840				t.Fatal("udp protocol was used instead of tcp")
1841			}
1842			return r, nil
1843		},
1844	}
1845	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1846	ctx, cancel := context.WithCancel(context.Background())
1847	defer cancel()
1848	_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly, false)
1849	if err != nil {
1850		t.Fatal("exchange failed:", err)
1851	}
1852}
1853
1854func TestDNSUseTCPTruncated(t *testing.T) {
1855	fake := fakeDNSServer{
1856		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1857			r := dnsmessage.Message{
1858				Header: dnsmessage.Header{
1859					ID:        q.Header.ID,
1860					Response:  true,
1861					RCode:     dnsmessage.RCodeSuccess,
1862					Truncated: true,
1863				},
1864				Questions: q.Questions,
1865				Answers: []dnsmessage.Resource{
1866					{
1867						Header: dnsmessage.ResourceHeader{
1868							Name:   q.Questions[0].Name,
1869							Type:   dnsmessage.TypeA,
1870							Class:  dnsmessage.ClassINET,
1871							Length: 4,
1872						},
1873						Body: &dnsmessage.AResource{
1874							A: TestAddr,
1875						},
1876					},
1877				},
1878			}
1879			if n == "udp" {
1880				t.Fatal("udp protocol was used instead of tcp")
1881			}
1882			return r, nil
1883		},
1884	}
1885	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1886	ctx, cancel := context.WithCancel(context.Background())
1887	defer cancel()
1888	p, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly, false)
1889	if err != nil {
1890		t.Fatal("exchange failed:", err)
1891	}
1892	a, err := p.AllAnswers()
1893	if err != nil {
1894		t.Fatalf("unexpected error %v getting all answers", err)
1895	}
1896	if len(a) != 1 {
1897		t.Fatalf("got %d answers; want 1", len(a))
1898	}
1899}
1900
1901// Issue 34660: PTR response with non-PTR answers should ignore non-PTR
1902func TestPTRandNonPTR(t *testing.T) {
1903	fake := fakeDNSServer{
1904		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1905			r := dnsmessage.Message{
1906				Header: dnsmessage.Header{
1907					ID:       q.Header.ID,
1908					Response: true,
1909					RCode:    dnsmessage.RCodeSuccess,
1910				},
1911				Questions: q.Questions,
1912				Answers: []dnsmessage.Resource{
1913					{
1914						Header: dnsmessage.ResourceHeader{
1915							Name:  q.Questions[0].Name,
1916							Type:  dnsmessage.TypePTR,
1917							Class: dnsmessage.ClassINET,
1918						},
1919						Body: &dnsmessage.PTRResource{
1920							PTR: dnsmessage.MustNewName("golang.org."),
1921						},
1922					},
1923					{
1924						Header: dnsmessage.ResourceHeader{
1925							Name:  q.Questions[0].Name,
1926							Type:  dnsmessage.TypeTXT,
1927							Class: dnsmessage.ClassINET,
1928						},
1929						Body: &dnsmessage.TXTResource{
1930							TXT: []string{"PTR 8 6 60 ..."}, // fake RRSIG
1931						},
1932					},
1933				},
1934			}
1935			return r, nil
1936		},
1937	}
1938	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1939	names, err := r.lookupAddr(context.Background(), "192.0.2.123")
1940	if err != nil {
1941		t.Fatalf("LookupAddr: %v", err)
1942	}
1943	if want := []string{"golang.org."}; !reflect.DeepEqual(names, want) {
1944		t.Errorf("names = %q; want %q", names, want)
1945	}
1946}
1947
1948func TestCVE202133195(t *testing.T) {
1949	fake := fakeDNSServer{
1950		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1951			r := dnsmessage.Message{
1952				Header: dnsmessage.Header{
1953					ID:                 q.Header.ID,
1954					Response:           true,
1955					RCode:              dnsmessage.RCodeSuccess,
1956					RecursionAvailable: true,
1957				},
1958				Questions: q.Questions,
1959			}
1960			switch q.Questions[0].Type {
1961			case dnsmessage.TypeCNAME:
1962				r.Answers = []dnsmessage.Resource{}
1963			case dnsmessage.TypeA: // CNAME lookup uses a A/AAAA as a proxy
1964				r.Answers = append(r.Answers,
1965					dnsmessage.Resource{
1966						Header: dnsmessage.ResourceHeader{
1967							Name:   dnsmessage.MustNewName("<html>.golang.org."),
1968							Type:   dnsmessage.TypeA,
1969							Class:  dnsmessage.ClassINET,
1970							Length: 4,
1971						},
1972						Body: &dnsmessage.AResource{
1973							A: TestAddr,
1974						},
1975					},
1976				)
1977			case dnsmessage.TypeSRV:
1978				n := q.Questions[0].Name
1979				if n.String() == "_hdr._tcp.golang.org." {
1980					n = dnsmessage.MustNewName("<html>.golang.org.")
1981				}
1982				r.Answers = append(r.Answers,
1983					dnsmessage.Resource{
1984						Header: dnsmessage.ResourceHeader{
1985							Name:   n,
1986							Type:   dnsmessage.TypeSRV,
1987							Class:  dnsmessage.ClassINET,
1988							Length: 4,
1989						},
1990						Body: &dnsmessage.SRVResource{
1991							Target: dnsmessage.MustNewName("<html>.golang.org."),
1992						},
1993					},
1994					dnsmessage.Resource{
1995						Header: dnsmessage.ResourceHeader{
1996							Name:   n,
1997							Type:   dnsmessage.TypeSRV,
1998							Class:  dnsmessage.ClassINET,
1999							Length: 4,
2000						},
2001						Body: &dnsmessage.SRVResource{
2002							Target: dnsmessage.MustNewName("good.golang.org."),
2003						},
2004					},
2005				)
2006			case dnsmessage.TypeMX:
2007				r.Answers = append(r.Answers,
2008					dnsmessage.Resource{
2009						Header: dnsmessage.ResourceHeader{
2010							Name:   dnsmessage.MustNewName("<html>.golang.org."),
2011							Type:   dnsmessage.TypeMX,
2012							Class:  dnsmessage.ClassINET,
2013							Length: 4,
2014						},
2015						Body: &dnsmessage.MXResource{
2016							MX: dnsmessage.MustNewName("<html>.golang.org."),
2017						},
2018					},
2019					dnsmessage.Resource{
2020						Header: dnsmessage.ResourceHeader{
2021							Name:   dnsmessage.MustNewName("good.golang.org."),
2022							Type:   dnsmessage.TypeMX,
2023							Class:  dnsmessage.ClassINET,
2024							Length: 4,
2025						},
2026						Body: &dnsmessage.MXResource{
2027							MX: dnsmessage.MustNewName("good.golang.org."),
2028						},
2029					},
2030				)
2031			case dnsmessage.TypeNS:
2032				r.Answers = append(r.Answers,
2033					dnsmessage.Resource{
2034						Header: dnsmessage.ResourceHeader{
2035							Name:   dnsmessage.MustNewName("<html>.golang.org."),
2036							Type:   dnsmessage.TypeNS,
2037							Class:  dnsmessage.ClassINET,
2038							Length: 4,
2039						},
2040						Body: &dnsmessage.NSResource{
2041							NS: dnsmessage.MustNewName("<html>.golang.org."),
2042						},
2043					},
2044					dnsmessage.Resource{
2045						Header: dnsmessage.ResourceHeader{
2046							Name:   dnsmessage.MustNewName("good.golang.org."),
2047							Type:   dnsmessage.TypeNS,
2048							Class:  dnsmessage.ClassINET,
2049							Length: 4,
2050						},
2051						Body: &dnsmessage.NSResource{
2052							NS: dnsmessage.MustNewName("good.golang.org."),
2053						},
2054					},
2055				)
2056			case dnsmessage.TypePTR:
2057				r.Answers = append(r.Answers,
2058					dnsmessage.Resource{
2059						Header: dnsmessage.ResourceHeader{
2060							Name:   dnsmessage.MustNewName("<html>.golang.org."),
2061							Type:   dnsmessage.TypePTR,
2062							Class:  dnsmessage.ClassINET,
2063							Length: 4,
2064						},
2065						Body: &dnsmessage.PTRResource{
2066							PTR: dnsmessage.MustNewName("<html>.golang.org."),
2067						},
2068					},
2069					dnsmessage.Resource{
2070						Header: dnsmessage.ResourceHeader{
2071							Name:   dnsmessage.MustNewName("good.golang.org."),
2072							Type:   dnsmessage.TypePTR,
2073							Class:  dnsmessage.ClassINET,
2074							Length: 4,
2075						},
2076						Body: &dnsmessage.PTRResource{
2077							PTR: dnsmessage.MustNewName("good.golang.org."),
2078						},
2079					},
2080				)
2081			}
2082			return r, nil
2083		},
2084	}
2085
2086	r := Resolver{PreferGo: true, Dial: fake.DialContext}
2087	// Change the default resolver to match our manipulated resolver
2088	originalDefault := DefaultResolver
2089	DefaultResolver = &r
2090	defer func() { DefaultResolver = originalDefault }()
2091	// Redirect host file lookups.
2092	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2093	hostsFilePath = "testdata/hosts"
2094
2095	tests := []struct {
2096		name string
2097		f    func(*testing.T)
2098	}{
2099		{
2100			name: "CNAME",
2101			f: func(t *testing.T) {
2102				expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2103				_, err := r.LookupCNAME(context.Background(), "golang.org")
2104				if err.Error() != expectedErr.Error() {
2105					t.Fatalf("unexpected error: %s", err)
2106				}
2107				_, err = LookupCNAME("golang.org")
2108				if err.Error() != expectedErr.Error() {
2109					t.Fatalf("unexpected error: %s", err)
2110				}
2111			},
2112		},
2113		{
2114			name: "SRV (bad record)",
2115			f: func(t *testing.T) {
2116				expected := []*SRV{
2117					{
2118						Target: "good.golang.org.",
2119					},
2120				}
2121				expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2122				_, records, err := r.LookupSRV(context.Background(), "target", "tcp", "golang.org")
2123				if err.Error() != expectedErr.Error() {
2124					t.Fatalf("unexpected error: %s", err)
2125				}
2126				if !reflect.DeepEqual(records, expected) {
2127					t.Error("Unexpected record set")
2128				}
2129				_, records, err = LookupSRV("target", "tcp", "golang.org")
2130				if err.Error() != expectedErr.Error() {
2131					t.Errorf("unexpected error: %s", err)
2132				}
2133				if !reflect.DeepEqual(records, expected) {
2134					t.Error("Unexpected record set")
2135				}
2136			},
2137		},
2138		{
2139			name: "SRV (bad header)",
2140			f: func(t *testing.T) {
2141				_, _, err := r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org.")
2142				if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2143					t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected)
2144				}
2145				_, _, err = LookupSRV("hdr", "tcp", "golang.org.")
2146				if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2147					t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected)
2148				}
2149			},
2150		},
2151		{
2152			name: "MX",
2153			f: func(t *testing.T) {
2154				expected := []*MX{
2155					{
2156						Host: "good.golang.org.",
2157					},
2158				}
2159				expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2160				records, err := r.LookupMX(context.Background(), "golang.org")
2161				if err.Error() != expectedErr.Error() {
2162					t.Fatalf("unexpected error: %s", err)
2163				}
2164				if !reflect.DeepEqual(records, expected) {
2165					t.Error("Unexpected record set")
2166				}
2167				records, err = LookupMX("golang.org")
2168				if err.Error() != expectedErr.Error() {
2169					t.Fatalf("unexpected error: %s", err)
2170				}
2171				if !reflect.DeepEqual(records, expected) {
2172					t.Error("Unexpected record set")
2173				}
2174			},
2175		},
2176		{
2177			name: "NS",
2178			f: func(t *testing.T) {
2179				expected := []*NS{
2180					{
2181						Host: "good.golang.org.",
2182					},
2183				}
2184				expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2185				records, err := r.LookupNS(context.Background(), "golang.org")
2186				if err.Error() != expectedErr.Error() {
2187					t.Fatalf("unexpected error: %s", err)
2188				}
2189				if !reflect.DeepEqual(records, expected) {
2190					t.Error("Unexpected record set")
2191				}
2192				records, err = LookupNS("golang.org")
2193				if err.Error() != expectedErr.Error() {
2194					t.Fatalf("unexpected error: %s", err)
2195				}
2196				if !reflect.DeepEqual(records, expected) {
2197					t.Error("Unexpected record set")
2198				}
2199			},
2200		},
2201		{
2202			name: "Addr",
2203			f: func(t *testing.T) {
2204				expected := []string{"good.golang.org."}
2205				expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "192.0.2.42"}
2206				records, err := r.LookupAddr(context.Background(), "192.0.2.42")
2207				if err.Error() != expectedErr.Error() {
2208					t.Fatalf("unexpected error: %s", err)
2209				}
2210				if !reflect.DeepEqual(records, expected) {
2211					t.Error("Unexpected record set")
2212				}
2213				records, err = LookupAddr("192.0.2.42")
2214				if err.Error() != expectedErr.Error() {
2215					t.Fatalf("unexpected error: %s", err)
2216				}
2217				if !reflect.DeepEqual(records, expected) {
2218					t.Error("Unexpected record set")
2219				}
2220			},
2221		},
2222	}
2223
2224	for _, tc := range tests {
2225		t.Run(tc.name, tc.f)
2226	}
2227
2228}
2229
2230func TestNullMX(t *testing.T) {
2231	fake := fakeDNSServer{
2232		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2233			r := dnsmessage.Message{
2234				Header: dnsmessage.Header{
2235					ID:       q.Header.ID,
2236					Response: true,
2237					RCode:    dnsmessage.RCodeSuccess,
2238				},
2239				Questions: q.Questions,
2240				Answers: []dnsmessage.Resource{
2241					{
2242						Header: dnsmessage.ResourceHeader{
2243							Name:  q.Questions[0].Name,
2244							Type:  dnsmessage.TypeMX,
2245							Class: dnsmessage.ClassINET,
2246						},
2247						Body: &dnsmessage.MXResource{
2248							MX: dnsmessage.MustNewName("."),
2249						},
2250					},
2251				},
2252			}
2253			return r, nil
2254		},
2255	}
2256	r := Resolver{PreferGo: true, Dial: fake.DialContext}
2257	rrset, err := r.LookupMX(context.Background(), "golang.org")
2258	if err != nil {
2259		t.Fatalf("LookupMX: %v", err)
2260	}
2261	if want := []*MX{&MX{Host: "."}}; !reflect.DeepEqual(rrset, want) {
2262		records := []string{}
2263		for _, rr := range rrset {
2264			records = append(records, fmt.Sprintf("%v", rr))
2265		}
2266		t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2267	}
2268}
2269
2270func TestRootNS(t *testing.T) {
2271	// See https://golang.org/issue/45715.
2272	fake := fakeDNSServer{
2273		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2274			r := dnsmessage.Message{
2275				Header: dnsmessage.Header{
2276					ID:       q.Header.ID,
2277					Response: true,
2278					RCode:    dnsmessage.RCodeSuccess,
2279				},
2280				Questions: q.Questions,
2281				Answers: []dnsmessage.Resource{
2282					{
2283						Header: dnsmessage.ResourceHeader{
2284							Name:  q.Questions[0].Name,
2285							Type:  dnsmessage.TypeNS,
2286							Class: dnsmessage.ClassINET,
2287						},
2288						Body: &dnsmessage.NSResource{
2289							NS: dnsmessage.MustNewName("i.root-servers.net."),
2290						},
2291					},
2292				},
2293			}
2294			return r, nil
2295		},
2296	}
2297	r := Resolver{PreferGo: true, Dial: fake.DialContext}
2298	rrset, err := r.LookupNS(context.Background(), ".")
2299	if err != nil {
2300		t.Fatalf("LookupNS: %v", err)
2301	}
2302	if want := []*NS{&NS{Host: "i.root-servers.net."}}; !reflect.DeepEqual(rrset, want) {
2303		records := []string{}
2304		for _, rr := range rrset {
2305			records = append(records, fmt.Sprintf("%v", rr))
2306		}
2307		t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2308	}
2309}
2310
2311func TestGoLookupIPCNAMEOrderHostsAliasesFilesOnlyMode(t *testing.T) {
2312	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2313	hostsFilePath = "testdata/aliases"
2314	mode := hostLookupFiles
2315
2316	for _, v := range lookupStaticHostAliasesTest {
2317		testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2318	}
2319}
2320
2321func TestGoLookupIPCNAMEOrderHostsAliasesFilesDNSMode(t *testing.T) {
2322	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2323	hostsFilePath = "testdata/aliases"
2324	mode := hostLookupFilesDNS
2325
2326	for _, v := range lookupStaticHostAliasesTest {
2327		testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2328	}
2329}
2330
2331var goLookupIPCNAMEOrderDNSFilesModeTests = []struct {
2332	lookup, res string
2333}{
2334	// 127.0.1.1
2335	{"invalid.invalid", "invalid.test"},
2336}
2337
2338func TestGoLookupIPCNAMEOrderHostsAliasesDNSFilesMode(t *testing.T) {
2339	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2340	hostsFilePath = "testdata/aliases"
2341	mode := hostLookupDNSFiles
2342
2343	for _, v := range goLookupIPCNAMEOrderDNSFilesModeTests {
2344		testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2345	}
2346}
2347
2348func testGoLookupIPCNAMEOrderHostsAliases(t *testing.T, mode hostLookupOrder, lookup, lookupRes string) {
2349	fake := fakeDNSServer{
2350		rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2351			var answers []dnsmessage.Resource
2352
2353			if mode != hostLookupDNSFiles {
2354				t.Fatal("received unexpected DNS query")
2355			}
2356
2357			return dnsmessage.Message{
2358				Header: dnsmessage.Header{
2359					ID:       q.Header.ID,
2360					Response: true,
2361				},
2362				Questions: []dnsmessage.Question{q.Questions[0]},
2363				Answers:   answers,
2364			}, nil
2365		},
2366	}
2367
2368	r := Resolver{PreferGo: true, Dial: fake.DialContext}
2369	ins := []string{lookup, absDomainName(lookup), strings.ToLower(lookup), strings.ToUpper(lookup)}
2370	for _, in := range ins {
2371		_, res, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", in, mode, nil)
2372		if err != nil {
2373			t.Errorf("expected err == nil, but got error: %v", err)
2374		}
2375		if res.String() != lookupRes {
2376			t.Errorf("goLookupIPCNAMEOrder(%v): got %v, want %v", in, res, lookupRes)
2377		}
2378	}
2379}
2380
2381// Test that we advertise support for a larger DNS packet size.
2382// This isn't a great test as it just tests the dnsmessage package
2383// against itself.
2384func TestDNSPacketSize(t *testing.T) {
2385	t.Run("enabled", func(t *testing.T) {
2386		testDNSPacketSize(t, false)
2387	})
2388	t.Run("disabled", func(t *testing.T) {
2389		testDNSPacketSize(t, true)
2390	})
2391}
2392
2393func testDNSPacketSize(t *testing.T, disable bool) {
2394	fake := fakeDNSServer{
2395		rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2396			if disable {
2397				if len(q.Additionals) > 0 {
2398					t.Error("unexpected additional record")
2399				}
2400			} else {
2401				if len(q.Additionals) == 0 {
2402					t.Error("missing EDNS record")
2403				} else if opt, ok := q.Additionals[0].Body.(*dnsmessage.OPTResource); !ok {
2404					t.Errorf("additional record type %T, expected OPTResource", q.Additionals[0])
2405				} else if len(opt.Options) != 0 {
2406					t.Errorf("found %d Options, expected none", len(opt.Options))
2407				} else {
2408					got := int(q.Additionals[0].Header.Class)
2409					t.Logf("EDNS packet size == %d", got)
2410					if got != maxDNSPacketSize {
2411						t.Errorf("EDNS packet size == %d, want %d", got, maxDNSPacketSize)
2412					}
2413				}
2414			}
2415
2416			// Hand back a dummy answer to verify that
2417			// LookupIPAddr completes.
2418			r := dnsmessage.Message{
2419				Header: dnsmessage.Header{
2420					ID:       q.Header.ID,
2421					Response: true,
2422					RCode:    dnsmessage.RCodeSuccess,
2423				},
2424				Questions: q.Questions,
2425			}
2426			if q.Questions[0].Type == dnsmessage.TypeA {
2427				r.Answers = []dnsmessage.Resource{
2428					{
2429						Header: dnsmessage.ResourceHeader{
2430							Name:   q.Questions[0].Name,
2431							Type:   dnsmessage.TypeA,
2432							Class:  dnsmessage.ClassINET,
2433							Length: 4,
2434						},
2435						Body: &dnsmessage.AResource{
2436							A: TestAddr,
2437						},
2438					},
2439				}
2440			}
2441			return r, nil
2442		},
2443	}
2444
2445	if disable {
2446		t.Setenv("GODEBUG", "netedns0=0")
2447	}
2448
2449	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2450	if _, err := r.LookupIPAddr(context.Background(), "go.dev"); err != nil {
2451		t.Errorf("lookup failed: %v", err)
2452	}
2453}
2454
2455func TestLongDNSNames(t *testing.T) {
2456	const longDNSsuffix = ".go.dev."
2457	const longDNSsuffixNoEndingDot = ".go.dev"
2458
2459	var longDNSPrefix = strings.Repeat("verylongdomainlabel.", 20)
2460
2461	var longDNSNamesTests = []struct {
2462		req  string
2463		fail bool
2464	}{
2465		{req: longDNSPrefix[:255-len(longDNSsuffix)] + longDNSsuffix, fail: true},
2466		{req: longDNSPrefix[:254-len(longDNSsuffix)] + longDNSsuffix},
2467		{req: longDNSPrefix[:253-len(longDNSsuffix)] + longDNSsuffix},
2468
2469		{req: longDNSPrefix[:253-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot},
2470		{req: longDNSPrefix[:254-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot, fail: true},
2471	}
2472
2473	fake := fakeDNSServer{
2474		rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2475			r := dnsmessage.Message{
2476				Header: dnsmessage.Header{
2477					ID:       q.Header.ID,
2478					Response: true,
2479					RCode:    dnsmessage.RCodeSuccess,
2480				},
2481				Questions: q.Questions,
2482				Answers: []dnsmessage.Resource{
2483					{
2484						Header: dnsmessage.ResourceHeader{
2485							Name:  q.Questions[0].Name,
2486							Type:  q.Questions[0].Type,
2487							Class: dnsmessage.ClassINET,
2488						},
2489					},
2490				},
2491			}
2492
2493			switch q.Questions[0].Type {
2494			case dnsmessage.TypeA:
2495				r.Answers[0].Body = &dnsmessage.AResource{A: TestAddr}
2496			case dnsmessage.TypeAAAA:
2497				r.Answers[0].Body = &dnsmessage.AAAAResource{AAAA: TestAddr6}
2498			case dnsmessage.TypeTXT:
2499				r.Answers[0].Body = &dnsmessage.TXTResource{TXT: []string{"."}}
2500			case dnsmessage.TypeMX:
2501				r.Answers[0].Body = &dnsmessage.MXResource{
2502					MX: dnsmessage.MustNewName("go.dev."),
2503				}
2504			case dnsmessage.TypeNS:
2505				r.Answers[0].Body = &dnsmessage.NSResource{
2506					NS: dnsmessage.MustNewName("go.dev."),
2507				}
2508			case dnsmessage.TypeSRV:
2509				r.Answers[0].Body = &dnsmessage.SRVResource{
2510					Target: dnsmessage.MustNewName("go.dev."),
2511				}
2512			case dnsmessage.TypeCNAME:
2513				r.Answers[0].Body = &dnsmessage.CNAMEResource{
2514					CNAME: dnsmessage.MustNewName("fake.cname."),
2515				}
2516			default:
2517				panic("unknown dnsmessage type")
2518			}
2519
2520			return r, nil
2521		},
2522	}
2523
2524	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2525
2526	methodTests := []string{"CNAME", "Host", "IP", "IPAddr", "MX", "NS", "NetIP", "SRV", "TXT"}
2527	query := func(t string, req string) error {
2528		switch t {
2529		case "CNAME":
2530			_, err := r.LookupCNAME(context.Background(), req)
2531			return err
2532		case "Host":
2533			_, err := r.LookupHost(context.Background(), req)
2534			return err
2535		case "IP":
2536			_, err := r.LookupIP(context.Background(), "ip", req)
2537			return err
2538		case "IPAddr":
2539			_, err := r.LookupIPAddr(context.Background(), req)
2540			return err
2541		case "MX":
2542			_, err := r.LookupMX(context.Background(), req)
2543			return err
2544		case "NS":
2545			_, err := r.LookupNS(context.Background(), req)
2546			return err
2547		case "NetIP":
2548			_, err := r.LookupNetIP(context.Background(), "ip", req)
2549			return err
2550		case "SRV":
2551			const service = "service"
2552			const proto = "proto"
2553			req = req[len(service)+len(proto)+4:]
2554			_, _, err := r.LookupSRV(context.Background(), service, proto, req)
2555			return err
2556		case "TXT":
2557			_, err := r.LookupTXT(context.Background(), req)
2558			return err
2559		}
2560		panic("unknown query method")
2561	}
2562
2563	for i, v := range longDNSNamesTests {
2564		for _, testName := range methodTests {
2565			err := query(testName, v.req)
2566			if v.fail {
2567				if err == nil {
2568					t.Errorf("%v: Lookup%v: unexpected success", i, testName)
2569					break
2570				}
2571
2572				expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: v.req, IsNotFound: true}
2573				var dnsErr *DNSError
2574				errors.As(err, &dnsErr)
2575				if dnsErr == nil || *dnsErr != expectedErr {
2576					t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2577				}
2578				break
2579			}
2580			if err != nil {
2581				t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2582			}
2583		}
2584	}
2585}
2586
2587func TestDNSTrustAD(t *testing.T) {
2588	fake := fakeDNSServer{
2589		rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2590			if q.Questions[0].Name.String() == "notrustad.go.dev." && q.Header.AuthenticData {
2591				t.Error("unexpected AD bit")
2592			}
2593
2594			if q.Questions[0].Name.String() == "trustad.go.dev." && !q.Header.AuthenticData {
2595				t.Error("expected AD bit")
2596			}
2597
2598			r := dnsmessage.Message{
2599				Header: dnsmessage.Header{
2600					ID:       q.Header.ID,
2601					Response: true,
2602					RCode:    dnsmessage.RCodeSuccess,
2603				},
2604				Questions: q.Questions,
2605			}
2606			if q.Questions[0].Type == dnsmessage.TypeA {
2607				r.Answers = []dnsmessage.Resource{
2608					{
2609						Header: dnsmessage.ResourceHeader{
2610							Name:   q.Questions[0].Name,
2611							Type:   dnsmessage.TypeA,
2612							Class:  dnsmessage.ClassINET,
2613							Length: 4,
2614						},
2615						Body: &dnsmessage.AResource{
2616							A: TestAddr,
2617						},
2618					},
2619				}
2620			}
2621
2622			return r, nil
2623		}}
2624
2625	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2626
2627	conf, err := newResolvConfTest()
2628	if err != nil {
2629		t.Fatal(err)
2630	}
2631	defer conf.teardown()
2632
2633	err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1"})
2634	if err != nil {
2635		t.Fatal(err)
2636	}
2637
2638	if _, err := r.LookupIPAddr(context.Background(), "notrustad.go.dev"); err != nil {
2639		t.Errorf("lookup failed: %v", err)
2640	}
2641
2642	err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1", "options trust-ad"})
2643	if err != nil {
2644		t.Fatal(err)
2645	}
2646
2647	if _, err := r.LookupIPAddr(context.Background(), "trustad.go.dev"); err != nil {
2648		t.Errorf("lookup failed: %v", err)
2649	}
2650}
2651
2652func TestDNSConfigNoReload(t *testing.T) {
2653	r := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
2654		if address != "192.0.2.1:53" {
2655			return nil, errors.New("configuration unexpectedly changed")
2656		}
2657		return fakeDNSServerSuccessful.DialContext(ctx, network, address)
2658	}}
2659
2660	conf, err := newResolvConfTest()
2661	if err != nil {
2662		t.Fatal(err)
2663	}
2664	defer conf.teardown()
2665
2666	err = conf.writeAndUpdateWithLastCheckedTime([]string{"nameserver 192.0.2.1", "options no-reload"}, time.Now().Add(-time.Hour))
2667	if err != nil {
2668		t.Fatal(err)
2669	}
2670
2671	if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2672		t.Fatal(err)
2673	}
2674
2675	err = conf.write([]string{"nameserver 192.0.2.200"})
2676	if err != nil {
2677		t.Fatal(err)
2678	}
2679
2680	if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2681		t.Fatal(err)
2682	}
2683}
2684
2685func TestLookupOrderFilesNoSuchHost(t *testing.T) {
2686	defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2687	if runtime.GOOS != "openbsd" {
2688		defer setSystemNSS(getSystemNSS(), 0)
2689		setSystemNSS(nssStr(t, "hosts: files"), time.Hour)
2690	}
2691
2692	conf, err := newResolvConfTest()
2693	if err != nil {
2694		t.Fatal(err)
2695	}
2696	defer conf.teardown()
2697
2698	resolvConf := dnsConfig{servers: defaultNS}
2699	if runtime.GOOS == "openbsd" {
2700		// Set error to ErrNotExist, so that the hostLookupOrder
2701		// returns hostLookupFiles for openbsd.
2702		resolvConf.err = os.ErrNotExist
2703	}
2704
2705	if !conf.forceUpdateConf(&resolvConf, time.Now().Add(time.Hour)) {
2706		t.Fatal("failed to update resolv config")
2707	}
2708
2709	tmpFile := filepath.Join(t.TempDir(), "hosts")
2710	if err := os.WriteFile(tmpFile, []byte{}, 0660); err != nil {
2711		t.Fatal(err)
2712	}
2713	hostsFilePath = tmpFile
2714
2715	const testName = "test.invalid"
2716
2717	order, _ := systemConf().hostLookupOrder(DefaultResolver, testName)
2718	if order != hostLookupFiles {
2719		// skip test for systems which do not return hostLookupFiles
2720		t.Skipf("hostLookupOrder did not return hostLookupFiles")
2721	}
2722
2723	var lookupTests = []struct {
2724		name   string
2725		lookup func(name string) error
2726	}{
2727		{
2728			name: "Host",
2729			lookup: func(name string) error {
2730				_, err = DefaultResolver.LookupHost(context.Background(), name)
2731				return err
2732			},
2733		},
2734		{
2735			name: "IP",
2736			lookup: func(name string) error {
2737				_, err = DefaultResolver.LookupIP(context.Background(), "ip", name)
2738				return err
2739			},
2740		},
2741		{
2742			name: "IPAddr",
2743			lookup: func(name string) error {
2744				_, err = DefaultResolver.LookupIPAddr(context.Background(), name)
2745				return err
2746			},
2747		},
2748		{
2749			name: "NetIP",
2750			lookup: func(name string) error {
2751				_, err = DefaultResolver.LookupNetIP(context.Background(), "ip", name)
2752				return err
2753			},
2754		},
2755	}
2756
2757	for _, v := range lookupTests {
2758		err := v.lookup(testName)
2759
2760		if err == nil {
2761			t.Errorf("Lookup%v: unexpected success", v.name)
2762			continue
2763		}
2764
2765		expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: testName, IsNotFound: true}
2766		var dnsErr *DNSError
2767		errors.As(err, &dnsErr)
2768		if dnsErr == nil || *dnsErr != expectedErr {
2769			t.Errorf("Lookup%v: unexpected error: %v", v.name, err)
2770		}
2771	}
2772}
2773
2774func TestExtendedRCode(t *testing.T) {
2775	fake := fakeDNSServer{
2776		rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2777			fraudSuccessCode := dnsmessage.RCodeSuccess | 1<<10
2778
2779			var edns0Hdr dnsmessage.ResourceHeader
2780			edns0Hdr.SetEDNS0(maxDNSPacketSize, fraudSuccessCode, false)
2781
2782			return dnsmessage.Message{
2783				Header: dnsmessage.Header{
2784					ID:       q.Header.ID,
2785					Response: true,
2786					RCode:    fraudSuccessCode,
2787				},
2788				Questions: []dnsmessage.Question{q.Questions[0]},
2789				Additionals: []dnsmessage.Resource{{
2790					Header: edns0Hdr,
2791					Body:   &dnsmessage.OPTResource{},
2792				}},
2793			}, nil
2794		},
2795	}
2796
2797	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2798	_, _, err := r.tryOneName(context.Background(), getSystemDNSConfig(), "go.dev.", dnsmessage.TypeA)
2799	var dnsErr *DNSError
2800	if !(errors.As(err, &dnsErr) && dnsErr.Err == errServerMisbehaving.Error()) {
2801		t.Fatalf("r.tryOneName(): unexpected error: %v", err)
2802	}
2803}
2804