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// +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris
6
7package net
8
9import (
10	"context"
11	"errors"
12	"fmt"
13	"internal/poll"
14	"io/ioutil"
15	"os"
16	"path"
17	"reflect"
18	"strings"
19	"sync"
20	"testing"
21	"time"
22
23	"internal/x/net/dns/dnsmessage"
24)
25
26var goResolver = Resolver{PreferGo: true}
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 VarTestAddr6 = [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)
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
95// See RFC 6761 for further information about the reserved, pseudo
96// domain names.
97var specialDomainNameTests = []struct {
98	question dnsmessage.Question
99	rcode    dnsmessage.RCode
100}{
101	// Name resolution APIs and libraries should not recognize the
102	// followings as special.
103	{mustQuestion("1.0.168.192.in-addr.arpa.", dnsmessage.TypePTR, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
104	{mustQuestion("test.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
105	{mustQuestion("example.com.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeSuccess},
106
107	// Name resolution APIs and libraries should recognize the
108	// followings as special and should not send any queries.
109	// Though, we test those names here for verifying negative
110	// answers at DNS query-response interaction level.
111	{mustQuestion("localhost.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
112	{mustQuestion("invalid.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
113}
114
115func TestSpecialDomainName(t *testing.T) {
116	fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
117		r := dnsmessage.Message{
118			Header: dnsmessage.Header{
119				ID:       q.ID,
120				Response: true,
121			},
122			Questions: q.Questions,
123		}
124
125		switch q.Questions[0].Name.String() {
126		case "example.com.":
127			r.Header.RCode = dnsmessage.RCodeSuccess
128		default:
129			r.Header.RCode = dnsmessage.RCodeNameError
130		}
131
132		return r, nil
133	}}
134	r := Resolver{PreferGo: true, Dial: fake.DialContext}
135	server := "8.8.8.8:53"
136	for _, tt := range specialDomainNameTests {
137		ctx, cancel := context.WithCancel(context.Background())
138		defer cancel()
139		_, h, err := r.exchange(ctx, server, tt.question, 3*time.Second)
140		if err != nil {
141			t.Error(err)
142			continue
143		}
144		if h.RCode != tt.rcode {
145			t.Errorf("got %v from %v; want %v", h.RCode, server, tt.rcode)
146			continue
147		}
148	}
149}
150
151// Issue 13705: don't try to resolve onion addresses, etc
152func TestAvoidDNSName(t *testing.T) {
153	tests := []struct {
154		name  string
155		avoid bool
156	}{
157		{"foo.com", false},
158		{"foo.com.", false},
159
160		{"foo.onion.", true},
161		{"foo.onion", true},
162		{"foo.ONION", true},
163		{"foo.ONION.", true},
164
165		// But do resolve *.local address; Issue 16739
166		{"foo.local.", false},
167		{"foo.local", false},
168		{"foo.LOCAL", false},
169		{"foo.LOCAL.", false},
170
171		{"", true}, // will be rejected earlier too
172
173		// Without stuff before onion/local, they're fine to
174		// use DNS. With a search path,
175		// "onion.vegegtables.com" can use DNS. Without a
176		// search path (or with a trailing dot), the queries
177		// are just kinda useless, but don't reveal anything
178		// private.
179		{"local", false},
180		{"onion", false},
181		{"local.", false},
182		{"onion.", false},
183	}
184	for _, tt := range tests {
185		got := avoidDNS(tt.name)
186		if got != tt.avoid {
187			t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid)
188		}
189	}
190}
191
192var fakeDNSServerSuccessful = fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
193	r := dnsmessage.Message{
194		Header: dnsmessage.Header{
195			ID:       q.ID,
196			Response: true,
197		},
198		Questions: q.Questions,
199	}
200	if len(q.Questions) == 1 && q.Questions[0].Type == dnsmessage.TypeA {
201		r.Answers = []dnsmessage.Resource{
202			{
203				Header: dnsmessage.ResourceHeader{
204					Name:   q.Questions[0].Name,
205					Type:   dnsmessage.TypeA,
206					Class:  dnsmessage.ClassINET,
207					Length: 4,
208				},
209				Body: &dnsmessage.AResource{
210					A: TestAddr,
211				},
212			},
213		}
214	}
215	return r, nil
216}}
217
218// Issue 13705: don't try to resolve onion addresses, etc
219func TestLookupTorOnion(t *testing.T) {
220	defer dnsWaitGroup.Wait()
221	r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
222	addrs, err := r.LookupIPAddr(context.Background(), "foo.onion")
223	if err != nil {
224		t.Fatalf("lookup = %v; want nil", err)
225	}
226	if len(addrs) > 0 {
227		t.Errorf("unexpected addresses: %v", addrs)
228	}
229}
230
231type resolvConfTest struct {
232	dir  string
233	path string
234	*resolverConfig
235}
236
237func newResolvConfTest() (*resolvConfTest, error) {
238	dir, err := ioutil.TempDir("", "go-resolvconftest")
239	if err != nil {
240		return nil, err
241	}
242	conf := &resolvConfTest{
243		dir:            dir,
244		path:           path.Join(dir, "resolv.conf"),
245		resolverConfig: &resolvConf,
246	}
247	conf.initOnce.Do(conf.init)
248	return conf, nil
249}
250
251func (conf *resolvConfTest) writeAndUpdate(lines []string) error {
252	f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
253	if err != nil {
254		return err
255	}
256	if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil {
257		f.Close()
258		return err
259	}
260	f.Close()
261	if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil {
262		return err
263	}
264	return nil
265}
266
267func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error {
268	dnsConf := dnsReadConfig(name)
269	conf.mu.Lock()
270	conf.dnsConfig = dnsConf
271	conf.mu.Unlock()
272	for i := 0; i < 5; i++ {
273		if conf.tryAcquireSema() {
274			conf.lastChecked = lastChecked
275			conf.releaseSema()
276			return nil
277		}
278	}
279	return fmt.Errorf("tryAcquireSema for %s failed", name)
280}
281
282func (conf *resolvConfTest) servers() []string {
283	conf.mu.RLock()
284	servers := conf.dnsConfig.servers
285	conf.mu.RUnlock()
286	return servers
287}
288
289func (conf *resolvConfTest) teardown() error {
290	err := conf.forceUpdate("/etc/resolv.conf", time.Time{})
291	os.RemoveAll(conf.dir)
292	return err
293}
294
295var updateResolvConfTests = []struct {
296	name    string   // query name
297	lines   []string // resolver configuration lines
298	servers []string // expected name servers
299}{
300	{
301		name:    "golang.org",
302		lines:   []string{"nameserver 8.8.8.8"},
303		servers: []string{"8.8.8.8:53"},
304	},
305	{
306		name:    "",
307		lines:   nil, // an empty resolv.conf should use defaultNS as name servers
308		servers: defaultNS,
309	},
310	{
311		name:    "www.example.com",
312		lines:   []string{"nameserver 8.8.4.4"},
313		servers: []string{"8.8.4.4:53"},
314	},
315}
316
317func TestUpdateResolvConf(t *testing.T) {
318	defer dnsWaitGroup.Wait()
319
320	r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
321
322	conf, err := newResolvConfTest()
323	if err != nil {
324		t.Fatal(err)
325	}
326	defer conf.teardown()
327
328	for i, tt := range updateResolvConfTests {
329		if err := conf.writeAndUpdate(tt.lines); err != nil {
330			t.Error(err)
331			continue
332		}
333		if tt.name != "" {
334			var wg sync.WaitGroup
335			const N = 10
336			wg.Add(N)
337			for j := 0; j < N; j++ {
338				go func(name string) {
339					defer wg.Done()
340					ips, err := r.LookupIPAddr(context.Background(), name)
341					if err != nil {
342						t.Error(err)
343						return
344					}
345					if len(ips) == 0 {
346						t.Errorf("no records for %s", name)
347						return
348					}
349				}(tt.name)
350			}
351			wg.Wait()
352		}
353		servers := conf.servers()
354		if !reflect.DeepEqual(servers, tt.servers) {
355			t.Errorf("#%d: got %v; want %v", i, servers, tt.servers)
356			continue
357		}
358	}
359}
360
361var goLookupIPWithResolverConfigTests = []struct {
362	name  string
363	lines []string // resolver configuration lines
364	error
365	a, aaaa bool // whether response contains A, AAAA-record
366}{
367	// no records, transport timeout
368	{
369		"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
370		[]string{
371			"options timeout:1 attempts:1",
372			"nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address
373		},
374		&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true},
375		false, false,
376	},
377
378	// no records, non-existent domain
379	{
380		"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
381		[]string{
382			"options timeout:3 attempts:1",
383			"nameserver 8.8.8.8",
384		},
385		&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false},
386		false, false,
387	},
388
389	// a few A records, no AAAA records
390	{
391		"ipv4.google.com.",
392		[]string{
393			"nameserver 8.8.8.8",
394			"nameserver 2001:4860:4860::8888",
395		},
396		nil,
397		true, false,
398	},
399	{
400		"ipv4.google.com",
401		[]string{
402			"domain golang.org",
403			"nameserver 2001:4860:4860::8888",
404			"nameserver 8.8.8.8",
405		},
406		nil,
407		true, false,
408	},
409	{
410		"ipv4.google.com",
411		[]string{
412			"search x.golang.org y.golang.org",
413			"nameserver 2001:4860:4860::8888",
414			"nameserver 8.8.8.8",
415		},
416		nil,
417		true, false,
418	},
419
420	// no A records, a few AAAA records
421	{
422		"ipv6.google.com.",
423		[]string{
424			"nameserver 2001:4860:4860::8888",
425			"nameserver 8.8.8.8",
426		},
427		nil,
428		false, true,
429	},
430	{
431		"ipv6.google.com",
432		[]string{
433			"domain golang.org",
434			"nameserver 8.8.8.8",
435			"nameserver 2001:4860:4860::8888",
436		},
437		nil,
438		false, true,
439	},
440	{
441		"ipv6.google.com",
442		[]string{
443			"search x.golang.org y.golang.org",
444			"nameserver 8.8.8.8",
445			"nameserver 2001:4860:4860::8888",
446		},
447		nil,
448		false, true,
449	},
450
451	// both A and AAAA records
452	{
453		"hostname.as112.net", // see RFC 7534
454		[]string{
455			"domain golang.org",
456			"nameserver 2001:4860:4860::8888",
457			"nameserver 8.8.8.8",
458		},
459		nil,
460		true, true,
461	},
462	{
463		"hostname.as112.net", // see RFC 7534
464		[]string{
465			"search x.golang.org y.golang.org",
466			"nameserver 2001:4860:4860::8888",
467			"nameserver 8.8.8.8",
468		},
469		nil,
470		true, true,
471	},
472}
473
474func TestGoLookupIPWithResolverConfig(t *testing.T) {
475	defer dnsWaitGroup.Wait()
476	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
477		switch s {
478		case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
479			break
480		default:
481			time.Sleep(10 * time.Millisecond)
482			return dnsmessage.Message{}, poll.ErrTimeout
483		}
484		r := dnsmessage.Message{
485			Header: dnsmessage.Header{
486				ID:       q.ID,
487				Response: true,
488			},
489			Questions: q.Questions,
490		}
491		for _, question := range q.Questions {
492			switch question.Type {
493			case dnsmessage.TypeA:
494				switch question.Name.String() {
495				case "hostname.as112.net.":
496					break
497				case "ipv4.google.com.":
498					r.Answers = append(r.Answers, dnsmessage.Resource{
499						Header: dnsmessage.ResourceHeader{
500							Name:   q.Questions[0].Name,
501							Type:   dnsmessage.TypeA,
502							Class:  dnsmessage.ClassINET,
503							Length: 4,
504						},
505						Body: &dnsmessage.AResource{
506							A: TestAddr,
507						},
508					})
509				default:
510
511				}
512			case dnsmessage.TypeAAAA:
513				switch question.Name.String() {
514				case "hostname.as112.net.":
515					break
516				case "ipv6.google.com.":
517					r.Answers = append(r.Answers, dnsmessage.Resource{
518						Header: dnsmessage.ResourceHeader{
519							Name:   q.Questions[0].Name,
520							Type:   dnsmessage.TypeAAAA,
521							Class:  dnsmessage.ClassINET,
522							Length: 16,
523						},
524						Body: &dnsmessage.AAAAResource{
525							AAAA: VarTestAddr6,
526						},
527					})
528				}
529			}
530		}
531		return r, nil
532	}}
533	r := Resolver{PreferGo: true, Dial: fake.DialContext}
534
535	conf, err := newResolvConfTest()
536	if err != nil {
537		t.Fatal(err)
538	}
539	defer conf.teardown()
540
541	for _, tt := range goLookupIPWithResolverConfigTests {
542		if err := conf.writeAndUpdate(tt.lines); err != nil {
543			t.Error(err)
544			continue
545		}
546		addrs, err := r.LookupIPAddr(context.Background(), tt.name)
547		if err != nil {
548			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) {
549				t.Errorf("got %v; want %v", err, tt.error)
550			}
551			continue
552		}
553		if len(addrs) == 0 {
554			t.Errorf("no records for %s", tt.name)
555		}
556		if !tt.a && !tt.aaaa && len(addrs) > 0 {
557			t.Errorf("unexpected %v for %s", addrs, tt.name)
558		}
559		for _, addr := range addrs {
560			if !tt.a && addr.IP.To4() != nil {
561				t.Errorf("got %v; must not be IPv4 address", addr)
562			}
563			if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil {
564				t.Errorf("got %v; must not be IPv6 address", addr)
565			}
566		}
567	}
568}
569
570// Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
571func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
572	defer dnsWaitGroup.Wait()
573
574	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, tm time.Time) (dnsmessage.Message, error) {
575		r := dnsmessage.Message{
576			Header: dnsmessage.Header{
577				ID:       q.ID,
578				Response: true,
579			},
580			Questions: q.Questions,
581		}
582		return r, nil
583	}}
584	r := Resolver{PreferGo: true, Dial: fake.DialContext}
585
586	// Add a config that simulates no dns servers being available.
587	conf, err := newResolvConfTest()
588	if err != nil {
589		t.Fatal(err)
590	}
591	if err := conf.writeAndUpdate([]string{}); err != nil {
592		t.Fatal(err)
593	}
594	// Redirect host file lookups.
595	defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath)
596	testHookHostsPath = "testdata/hosts"
597
598	for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} {
599		name := fmt.Sprintf("order %v", order)
600
601		// First ensure that we get an error when contacting a non-existent host.
602		_, _, err := r.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
603		if err == nil {
604			t.Errorf("%s: expected error while looking up name not in hosts file", name)
605			continue
606		}
607
608		// Now check that we get an address when the name appears in the hosts file.
609		addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
610		if err != nil {
611			t.Errorf("%s: expected to successfully lookup host entry", name)
612			continue
613		}
614		if len(addrs) != 1 {
615			t.Errorf("%s: expected exactly one result, but got %v", name, addrs)
616			continue
617		}
618		if got, want := addrs[0].String(), "127.1.1.1"; got != want {
619			t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want)
620		}
621	}
622	defer conf.teardown()
623}
624
625// Issue 12712.
626// When using search domains, return the error encountered
627// querying the original name instead of an error encountered
628// querying a generated name.
629func TestErrorForOriginalNameWhenSearching(t *testing.T) {
630	defer dnsWaitGroup.Wait()
631
632	const fqdn = "doesnotexist.domain"
633
634	conf, err := newResolvConfTest()
635	if err != nil {
636		t.Fatal(err)
637	}
638	defer conf.teardown()
639
640	if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
641		t.Fatal(err)
642	}
643
644	fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
645		r := dnsmessage.Message{
646			Header: dnsmessage.Header{
647				ID:       q.ID,
648				Response: true,
649			},
650			Questions: q.Questions,
651		}
652
653		switch q.Questions[0].Name.String() {
654		case fqdn + ".servfail.":
655			r.Header.RCode = dnsmessage.RCodeServerFailure
656		default:
657			r.Header.RCode = dnsmessage.RCodeNameError
658		}
659
660		return r, nil
661	}}
662
663	cases := []struct {
664		strictErrors bool
665		wantErr      *DNSError
666	}{
667		{true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
668		{false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}},
669	}
670	for _, tt := range cases {
671		r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
672		_, err = r.LookupIPAddr(context.Background(), fqdn)
673		if err == nil {
674			t.Fatal("expected an error")
675		}
676
677		want := tt.wantErr
678		if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary {
679			t.Errorf("got %v; want %v", err, want)
680		}
681	}
682}
683
684// Issue 15434. If a name server gives a lame referral, continue to the next.
685func TestIgnoreLameReferrals(t *testing.T) {
686	defer dnsWaitGroup.Wait()
687
688	conf, err := newResolvConfTest()
689	if err != nil {
690		t.Fatal(err)
691	}
692	defer conf.teardown()
693
694	if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
695		"nameserver 192.0.2.2"}); err != nil {
696		t.Fatal(err)
697	}
698
699	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
700		t.Log(s, q)
701		r := dnsmessage.Message{
702			Header: dnsmessage.Header{
703				ID:       q.ID,
704				Response: true,
705			},
706			Questions: q.Questions,
707		}
708
709		if s == "192.0.2.2:53" {
710			r.Header.RecursionAvailable = true
711			if q.Questions[0].Type == dnsmessage.TypeA {
712				r.Answers = []dnsmessage.Resource{
713					{
714						Header: dnsmessage.ResourceHeader{
715							Name:   q.Questions[0].Name,
716							Type:   dnsmessage.TypeA,
717							Class:  dnsmessage.ClassINET,
718							Length: 4,
719						},
720						Body: &dnsmessage.AResource{
721							A: TestAddr,
722						},
723					},
724				}
725			}
726		}
727
728		return r, nil
729	}}
730	r := Resolver{PreferGo: true, Dial: fake.DialContext}
731
732	addrs, err := r.LookupIPAddr(context.Background(), "www.golang.org")
733	if err != nil {
734		t.Fatal(err)
735	}
736
737	if got := len(addrs); got != 1 {
738		t.Fatalf("got %d addresses, want 1", got)
739	}
740
741	if got, want := addrs[0].String(), "192.0.2.1"; got != want {
742		t.Fatalf("got address %v, want %v", got, want)
743	}
744}
745
746func BenchmarkGoLookupIP(b *testing.B) {
747	testHookUninstaller.Do(uninstallTestHooks)
748	ctx := context.Background()
749	b.ReportAllocs()
750
751	for i := 0; i < b.N; i++ {
752		goResolver.LookupIPAddr(ctx, "www.example.com")
753	}
754}
755
756func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
757	testHookUninstaller.Do(uninstallTestHooks)
758	ctx := context.Background()
759	b.ReportAllocs()
760
761	for i := 0; i < b.N; i++ {
762		goResolver.LookupIPAddr(ctx, "some.nonexistent")
763	}
764}
765
766func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
767	testHookUninstaller.Do(uninstallTestHooks)
768
769	conf, err := newResolvConfTest()
770	if err != nil {
771		b.Fatal(err)
772	}
773	defer conf.teardown()
774
775	lines := []string{
776		"nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737
777		"nameserver 8.8.8.8",
778	}
779	if err := conf.writeAndUpdate(lines); err != nil {
780		b.Fatal(err)
781	}
782	ctx := context.Background()
783	b.ReportAllocs()
784
785	for i := 0; i < b.N; i++ {
786		goResolver.LookupIPAddr(ctx, "www.example.com")
787	}
788}
789
790type fakeDNSServer struct {
791	rh        func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error)
792	alwaysTCP bool
793}
794
795func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) {
796	if server.alwaysTCP || n == "tcp" || n == "tcp4" || n == "tcp6" {
797		return &fakeDNSConn{tcp: true, server: server, n: n, s: s}, nil
798	}
799	return &fakeDNSPacketConn{fakeDNSConn: fakeDNSConn{tcp: false, server: server, n: n, s: s}}, nil
800}
801
802type fakeDNSConn struct {
803	Conn
804	tcp    bool
805	server *fakeDNSServer
806	n      string
807	s      string
808	q      dnsmessage.Message
809	t      time.Time
810	buf    []byte
811}
812
813func (f *fakeDNSConn) Close() error {
814	return nil
815}
816
817func (f *fakeDNSConn) Read(b []byte) (int, error) {
818	if len(f.buf) > 0 {
819		n := copy(b, f.buf)
820		f.buf = f.buf[n:]
821		return n, nil
822	}
823
824	resp, err := f.server.rh(f.n, f.s, f.q, f.t)
825	if err != nil {
826		return 0, err
827	}
828
829	bb := make([]byte, 2, 514)
830	bb, err = resp.AppendPack(bb)
831	if err != nil {
832		return 0, fmt.Errorf("cannot marshal DNS message: %v", err)
833	}
834
835	if f.tcp {
836		l := len(bb) - 2
837		bb[0] = byte(l >> 8)
838		bb[1] = byte(l)
839		f.buf = bb
840		return f.Read(b)
841	}
842
843	bb = bb[2:]
844	if len(b) < len(bb) {
845		return 0, errors.New("read would fragment DNS message")
846	}
847
848	copy(b, bb)
849	return len(bb), nil
850}
851
852func (f *fakeDNSConn) Write(b []byte) (int, error) {
853	if f.tcp && len(b) >= 2 {
854		b = b[2:]
855	}
856	if f.q.Unpack(b) != nil {
857		return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", f.n, len(b))
858	}
859	return len(b), nil
860}
861
862func (f *fakeDNSConn) SetDeadline(t time.Time) error {
863	f.t = t
864	return nil
865}
866
867type fakeDNSPacketConn struct {
868	PacketConn
869	fakeDNSConn
870}
871
872func (f *fakeDNSPacketConn) SetDeadline(t time.Time) error {
873	return f.fakeDNSConn.SetDeadline(t)
874}
875
876func (f *fakeDNSPacketConn) Close() error {
877	return f.fakeDNSConn.Close()
878}
879
880// UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
881func TestIgnoreDNSForgeries(t *testing.T) {
882	c, s := Pipe()
883	go func() {
884		b := make([]byte, 512)
885		n, err := s.Read(b)
886		if err != nil {
887			t.Error(err)
888			return
889		}
890
891		var msg dnsmessage.Message
892		if msg.Unpack(b[:n]) != nil {
893			t.Error("invalid DNS query:", err)
894			return
895		}
896
897		s.Write([]byte("garbage DNS response packet"))
898
899		msg.Header.Response = true
900		msg.Header.ID++ // make invalid ID
901
902		if b, err = msg.Pack(); err != nil {
903			t.Error("failed to pack DNS response:", err)
904			return
905		}
906		s.Write(b)
907
908		msg.Header.ID-- // restore original ID
909		msg.Answers = []dnsmessage.Resource{
910			{
911				Header: dnsmessage.ResourceHeader{
912					Name:   mustNewName("www.example.com."),
913					Type:   dnsmessage.TypeA,
914					Class:  dnsmessage.ClassINET,
915					Length: 4,
916				},
917				Body: &dnsmessage.AResource{
918					A: TestAddr,
919				},
920			},
921		}
922
923		b, err = msg.Pack()
924		if err != nil {
925			t.Error("failed to pack DNS response:", err)
926			return
927		}
928		s.Write(b)
929	}()
930
931	msg := dnsmessage.Message{
932		Header: dnsmessage.Header{
933			ID: 42,
934		},
935		Questions: []dnsmessage.Question{
936			{
937				Name:  mustNewName("www.example.com."),
938				Type:  dnsmessage.TypeA,
939				Class: dnsmessage.ClassINET,
940			},
941		},
942	}
943
944	b, err := msg.Pack()
945	if err != nil {
946		t.Fatal("Pack failed:", err)
947	}
948
949	p, _, err := dnsPacketRoundTrip(c, 42, msg.Questions[0], b)
950	if err != nil {
951		t.Fatalf("dnsPacketRoundTrip failed: %v", err)
952	}
953
954	p.SkipAllQuestions()
955	as, err := p.AllAnswers()
956	if err != nil {
957		t.Fatal("AllAnswers failed:", err)
958	}
959	if got := as[0].Body.(*dnsmessage.AResource).A; got != TestAddr {
960		t.Errorf("got address %v, want %v", got, TestAddr)
961	}
962}
963
964// Issue 16865. If a name server times out, continue to the next.
965func TestRetryTimeout(t *testing.T) {
966	defer dnsWaitGroup.Wait()
967
968	conf, err := newResolvConfTest()
969	if err != nil {
970		t.Fatal(err)
971	}
972	defer conf.teardown()
973
974	testConf := []string{
975		"nameserver 192.0.2.1", // the one that will timeout
976		"nameserver 192.0.2.2",
977	}
978	if err := conf.writeAndUpdate(testConf); err != nil {
979		t.Fatal(err)
980	}
981
982	var deadline0 time.Time
983
984	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
985		t.Log(s, q, deadline)
986
987		if deadline.IsZero() {
988			t.Error("zero deadline")
989		}
990
991		if s == "192.0.2.1:53" {
992			deadline0 = deadline
993			time.Sleep(10 * time.Millisecond)
994			return dnsmessage.Message{}, poll.ErrTimeout
995		}
996
997		if deadline.Equal(deadline0) {
998			t.Error("deadline didn't change")
999		}
1000
1001		return mockTXTResponse(q), nil
1002	}}
1003	r := &Resolver{PreferGo: true, Dial: fake.DialContext}
1004
1005	_, err = r.LookupTXT(context.Background(), "www.golang.org")
1006	if err != nil {
1007		t.Fatal(err)
1008	}
1009
1010	if deadline0.IsZero() {
1011		t.Error("deadline0 still zero", deadline0)
1012	}
1013}
1014
1015func TestRotate(t *testing.T) {
1016	// without rotation, always uses the first server
1017	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"})
1018
1019	// with rotation, rotates through back to first
1020	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"})
1021}
1022
1023func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
1024	defer dnsWaitGroup.Wait()
1025
1026	conf, err := newResolvConfTest()
1027	if err != nil {
1028		t.Fatal(err)
1029	}
1030	defer conf.teardown()
1031
1032	var confLines []string
1033	for _, ns := range nameservers {
1034		confLines = append(confLines, "nameserver "+ns)
1035	}
1036	if rotate {
1037		confLines = append(confLines, "options rotate")
1038	}
1039
1040	if err := conf.writeAndUpdate(confLines); err != nil {
1041		t.Fatal(err)
1042	}
1043
1044	var usedServers []string
1045	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1046		usedServers = append(usedServers, s)
1047		return mockTXTResponse(q), nil
1048	}}
1049	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1050
1051	// len(nameservers) + 1 to allow rotation to get back to start
1052	for i := 0; i < len(nameservers)+1; i++ {
1053		if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil {
1054			t.Fatal(err)
1055		}
1056	}
1057
1058	if !reflect.DeepEqual(usedServers, wantServers) {
1059		t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers)
1060	}
1061}
1062
1063func mockTXTResponse(q dnsmessage.Message) dnsmessage.Message {
1064	r := dnsmessage.Message{
1065		Header: dnsmessage.Header{
1066			ID:                 q.ID,
1067			Response:           true,
1068			RecursionAvailable: true,
1069		},
1070		Questions: q.Questions,
1071		Answers: []dnsmessage.Resource{
1072			{
1073				Header: dnsmessage.ResourceHeader{
1074					Name:  q.Questions[0].Name,
1075					Type:  dnsmessage.TypeTXT,
1076					Class: dnsmessage.ClassINET,
1077				},
1078				Body: &dnsmessage.TXTResource{
1079					TXT: []string{"ok"},
1080				},
1081			},
1082		},
1083	}
1084
1085	return r
1086}
1087
1088// Issue 17448. With StrictErrors enabled, temporary errors should make
1089// LookupIP fail rather than return a partial result.
1090func TestStrictErrorsLookupIP(t *testing.T) {
1091	defer dnsWaitGroup.Wait()
1092
1093	conf, err := newResolvConfTest()
1094	if err != nil {
1095		t.Fatal(err)
1096	}
1097	defer conf.teardown()
1098
1099	confData := []string{
1100		"nameserver 192.0.2.53",
1101		"search x.golang.org y.golang.org",
1102	}
1103	if err := conf.writeAndUpdate(confData); err != nil {
1104		t.Fatal(err)
1105	}
1106
1107	const name = "test-issue19592"
1108	const server = "192.0.2.53:53"
1109	const searchX = "test-issue19592.x.golang.org."
1110	const searchY = "test-issue19592.y.golang.org."
1111	const ip4 = "192.0.2.1"
1112	const ip6 = "2001:db8::1"
1113
1114	type resolveWhichEnum int
1115	const (
1116		resolveOK resolveWhichEnum = iota
1117		resolveOpError
1118		resolveServfail
1119		resolveTimeout
1120	)
1121
1122	makeTempError := func(err string) error {
1123		return &DNSError{
1124			Err:         err,
1125			Name:        name,
1126			Server:      server,
1127			IsTemporary: true,
1128		}
1129	}
1130	makeTimeout := func() error {
1131		return &DNSError{
1132			Err:       poll.ErrTimeout.Error(),
1133			Name:      name,
1134			Server:    server,
1135			IsTimeout: true,
1136		}
1137	}
1138	makeNxDomain := func() error {
1139		return &DNSError{
1140			Err:    errNoSuchHost.Error(),
1141			Name:   name,
1142			Server: server,
1143		}
1144	}
1145
1146	cases := []struct {
1147		desc          string
1148		resolveWhich  func(quest dnsmessage.Question) resolveWhichEnum
1149		wantStrictErr error
1150		wantLaxErr    error
1151		wantIPs       []string
1152	}{
1153		{
1154			desc: "No errors",
1155			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1156				return resolveOK
1157			},
1158			wantIPs: []string{ip4, ip6},
1159		},
1160		{
1161			desc: "searchX error fails in strict mode",
1162			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1163				if quest.Name.String() == searchX {
1164					return resolveTimeout
1165				}
1166				return resolveOK
1167			},
1168			wantStrictErr: makeTimeout(),
1169			wantIPs:       []string{ip4, ip6},
1170		},
1171		{
1172			desc: "searchX IPv4-only timeout fails in strict mode",
1173			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1174				if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeA {
1175					return resolveTimeout
1176				}
1177				return resolveOK
1178			},
1179			wantStrictErr: makeTimeout(),
1180			wantIPs:       []string{ip4, ip6},
1181		},
1182		{
1183			desc: "searchX IPv6-only servfail fails in strict mode",
1184			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1185				if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeAAAA {
1186					return resolveServfail
1187				}
1188				return resolveOK
1189			},
1190			wantStrictErr: makeTempError("server misbehaving"),
1191			wantIPs:       []string{ip4, ip6},
1192		},
1193		{
1194			desc: "searchY error always fails",
1195			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1196				if quest.Name.String() == searchY {
1197					return resolveTimeout
1198				}
1199				return resolveOK
1200			},
1201			wantStrictErr: makeTimeout(),
1202			wantLaxErr:    makeNxDomain(), // This one reaches the "test." FQDN.
1203		},
1204		{
1205			desc: "searchY IPv4-only socket error fails in strict mode",
1206			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1207				if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeA {
1208					return resolveOpError
1209				}
1210				return resolveOK
1211			},
1212			wantStrictErr: makeTempError("write: socket on fire"),
1213			wantIPs:       []string{ip6},
1214		},
1215		{
1216			desc: "searchY IPv6-only timeout fails in strict mode",
1217			resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1218				if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeAAAA {
1219					return resolveTimeout
1220				}
1221				return resolveOK
1222			},
1223			wantStrictErr: makeTimeout(),
1224			wantIPs:       []string{ip4},
1225		},
1226	}
1227
1228	for i, tt := range cases {
1229		fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1230			t.Log(s, q)
1231
1232			switch tt.resolveWhich(q.Questions[0]) {
1233			case resolveOK:
1234				// Handle below.
1235			case resolveOpError:
1236				return dnsmessage.Message{}, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")}
1237			case resolveServfail:
1238				return dnsmessage.Message{
1239					Header: dnsmessage.Header{
1240						ID:       q.ID,
1241						Response: true,
1242						RCode:    dnsmessage.RCodeServerFailure,
1243					},
1244					Questions: q.Questions,
1245				}, nil
1246			case resolveTimeout:
1247				return dnsmessage.Message{}, poll.ErrTimeout
1248			default:
1249				t.Fatal("Impossible resolveWhich")
1250			}
1251
1252			switch q.Questions[0].Name.String() {
1253			case searchX, name + ".":
1254				// Return NXDOMAIN to utilize the search list.
1255				return dnsmessage.Message{
1256					Header: dnsmessage.Header{
1257						ID:       q.ID,
1258						Response: true,
1259						RCode:    dnsmessage.RCodeNameError,
1260					},
1261					Questions: q.Questions,
1262				}, nil
1263			case searchY:
1264				// Return records below.
1265			default:
1266				return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1267			}
1268
1269			r := dnsmessage.Message{
1270				Header: dnsmessage.Header{
1271					ID:       q.ID,
1272					Response: true,
1273				},
1274				Questions: q.Questions,
1275			}
1276			switch q.Questions[0].Type {
1277			case dnsmessage.TypeA:
1278				r.Answers = []dnsmessage.Resource{
1279					{
1280						Header: dnsmessage.ResourceHeader{
1281							Name:   q.Questions[0].Name,
1282							Type:   dnsmessage.TypeA,
1283							Class:  dnsmessage.ClassINET,
1284							Length: 4,
1285						},
1286						Body: &dnsmessage.AResource{
1287							A: TestAddr,
1288						},
1289					},
1290				}
1291			case dnsmessage.TypeAAAA:
1292				r.Answers = []dnsmessage.Resource{
1293					{
1294						Header: dnsmessage.ResourceHeader{
1295							Name:   q.Questions[0].Name,
1296							Type:   dnsmessage.TypeAAAA,
1297							Class:  dnsmessage.ClassINET,
1298							Length: 16,
1299						},
1300						Body: &dnsmessage.AAAAResource{
1301							AAAA: VarTestAddr6,
1302						},
1303					},
1304				}
1305			default:
1306				return dnsmessage.Message{}, fmt.Errorf("Unexpected Type: %v", q.Questions[0].Type)
1307			}
1308			return r, nil
1309		}}
1310
1311		for _, strict := range []bool{true, false} {
1312			r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext}
1313			ips, err := r.LookupIPAddr(context.Background(), name)
1314
1315			var wantErr error
1316			if strict {
1317				wantErr = tt.wantStrictErr
1318			} else {
1319				wantErr = tt.wantLaxErr
1320			}
1321			if !reflect.DeepEqual(err, wantErr) {
1322				t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr)
1323			}
1324
1325			gotIPs := map[string]struct{}{}
1326			for _, ip := range ips {
1327				gotIPs[ip.String()] = struct{}{}
1328			}
1329			wantIPs := map[string]struct{}{}
1330			if wantErr == nil {
1331				for _, ip := range tt.wantIPs {
1332					wantIPs[ip] = struct{}{}
1333				}
1334			}
1335			if !reflect.DeepEqual(gotIPs, wantIPs) {
1336				t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs)
1337			}
1338		}
1339	}
1340}
1341
1342// Issue 17448. With StrictErrors enabled, temporary errors should make
1343// LookupTXT stop walking the search list.
1344func TestStrictErrorsLookupTXT(t *testing.T) {
1345	defer dnsWaitGroup.Wait()
1346
1347	conf, err := newResolvConfTest()
1348	if err != nil {
1349		t.Fatal(err)
1350	}
1351	defer conf.teardown()
1352
1353	confData := []string{
1354		"nameserver 192.0.2.53",
1355		"search x.golang.org y.golang.org",
1356	}
1357	if err := conf.writeAndUpdate(confData); err != nil {
1358		t.Fatal(err)
1359	}
1360
1361	const name = "test"
1362	const server = "192.0.2.53:53"
1363	const searchX = "test.x.golang.org."
1364	const searchY = "test.y.golang.org."
1365	const txt = "Hello World"
1366
1367	fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1368		t.Log(s, q)
1369
1370		switch q.Questions[0].Name.String() {
1371		case searchX:
1372			return dnsmessage.Message{}, poll.ErrTimeout
1373		case searchY:
1374			return mockTXTResponse(q), nil
1375		default:
1376			return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1377		}
1378	}}
1379
1380	for _, strict := range []bool{true, false} {
1381		r := Resolver{StrictErrors: strict, Dial: fake.DialContext}
1382		p, _, err := r.lookup(context.Background(), name, dnsmessage.TypeTXT)
1383		var wantErr error
1384		var wantRRs int
1385		if strict {
1386			wantErr = &DNSError{
1387				Err:       poll.ErrTimeout.Error(),
1388				Name:      name,
1389				Server:    server,
1390				IsTimeout: true,
1391			}
1392		} else {
1393			wantRRs = 1
1394		}
1395		if !reflect.DeepEqual(err, wantErr) {
1396			t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr)
1397		}
1398		a, err := p.AllAnswers()
1399		if err != nil {
1400			a = nil
1401		}
1402		if len(a) != wantRRs {
1403			t.Errorf("strict=%v: got %v; want %v", strict, len(a), wantRRs)
1404		}
1405	}
1406}
1407
1408// Test for a race between uninstalling the test hooks and closing a
1409// socket connection. This used to fail when testing with -race.
1410func TestDNSGoroutineRace(t *testing.T) {
1411	defer dnsWaitGroup.Wait()
1412
1413	fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) {
1414		time.Sleep(10 * time.Microsecond)
1415		return dnsmessage.Message{}, poll.ErrTimeout
1416	}}
1417	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1418
1419	// The timeout here is less than the timeout used by the server,
1420	// so the goroutine started to query the (fake) server will hang
1421	// around after this test is done if we don't call dnsWaitGroup.Wait.
1422	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond)
1423	defer cancel()
1424	_, err := r.LookupIPAddr(ctx, "where.are.they.now")
1425	if err == nil {
1426		t.Fatal("fake DNS lookup unexpectedly succeeded")
1427	}
1428}
1429
1430func lookupWithFake(fake fakeDNSServer, name string, typ dnsmessage.Type) error {
1431	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1432
1433	resolvConf.mu.RLock()
1434	conf := resolvConf.dnsConfig
1435	resolvConf.mu.RUnlock()
1436
1437	ctx, cancel := context.WithCancel(context.Background())
1438	defer cancel()
1439
1440	_, _, err := r.tryOneName(ctx, conf, name, typ)
1441	return err
1442}
1443
1444// Issue 8434: verify that Temporary returns true on an error when rcode
1445// is SERVFAIL
1446func TestIssue8434(t *testing.T) {
1447	err := lookupWithFake(fakeDNSServer{
1448		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1449			return dnsmessage.Message{
1450				Header: dnsmessage.Header{
1451					ID:       q.ID,
1452					Response: true,
1453					RCode:    dnsmessage.RCodeServerFailure,
1454				},
1455				Questions: q.Questions,
1456			}, nil
1457		},
1458	}, "golang.org.", dnsmessage.TypeALL)
1459	if err == nil {
1460		t.Fatal("expected an error")
1461	}
1462	if ne, ok := err.(Error); !ok {
1463		t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1464	} else if !ne.Temporary() {
1465		t.Fatalf("Temporary = false for err = %#v; want Temporary == true", err)
1466	}
1467	if de, ok := err.(*DNSError); !ok {
1468		t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1469	} else if !de.IsTemporary {
1470		t.Fatalf("IsTemporary = false for err = %#v; want IsTemporary == true", err)
1471	}
1472}
1473
1474// TestNoSuchHost verifies that tryOneName works correctly when the domain does
1475// not exist.
1476//
1477// Issue 12778: verify that NXDOMAIN without RA bit errors as "no such host"
1478// and not "server misbehaving"
1479//
1480// Issue 25336: verify that NXDOMAIN errors fail fast.
1481//
1482// Issue 27525: verify that empty answers fail fast.
1483func TestNoSuchHost(t *testing.T) {
1484	tests := []struct {
1485		name string
1486		f    func(string, string, dnsmessage.Message, time.Time) (dnsmessage.Message, error)
1487	}{
1488		{
1489			"NXDOMAIN",
1490			func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1491				return dnsmessage.Message{
1492					Header: dnsmessage.Header{
1493						ID:                 q.ID,
1494						Response:           true,
1495						RCode:              dnsmessage.RCodeNameError,
1496						RecursionAvailable: false,
1497					},
1498					Questions: q.Questions,
1499				}, nil
1500			},
1501		},
1502		{
1503			"no answers",
1504			func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1505				return dnsmessage.Message{
1506					Header: dnsmessage.Header{
1507						ID:                 q.ID,
1508						Response:           true,
1509						RCode:              dnsmessage.RCodeSuccess,
1510						RecursionAvailable: false,
1511						Authoritative:      true,
1512					},
1513					Questions: q.Questions,
1514				}, nil
1515			},
1516		},
1517	}
1518
1519	for _, test := range tests {
1520		t.Run(test.name, func(t *testing.T) {
1521			lookups := 0
1522			err := lookupWithFake(fakeDNSServer{
1523				rh: func(n, s string, q dnsmessage.Message, d time.Time) (dnsmessage.Message, error) {
1524					lookups++
1525					return test.f(n, s, q, d)
1526				},
1527			}, ".", dnsmessage.TypeALL)
1528
1529			if lookups != 1 {
1530				t.Errorf("got %d lookups, wanted 1", lookups)
1531			}
1532
1533			if err == nil {
1534				t.Fatal("expected an error")
1535			}
1536			de, ok := err.(*DNSError)
1537			if !ok {
1538				t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1539			}
1540			if de.Err != errNoSuchHost.Error() {
1541				t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
1542			}
1543		})
1544	}
1545}
1546
1547// Issue 26573: verify that Conns that don't implement PacketConn are treated
1548// as streams even when udp was requested.
1549func TestDNSDialTCP(t *testing.T) {
1550	fake := fakeDNSServer{
1551		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1552			r := dnsmessage.Message{
1553				Header: dnsmessage.Header{
1554					ID:       q.Header.ID,
1555					Response: true,
1556					RCode:    dnsmessage.RCodeSuccess,
1557				},
1558				Questions: q.Questions,
1559			}
1560			return r, nil
1561		},
1562		alwaysTCP: true,
1563	}
1564	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1565	ctx := context.Background()
1566	_, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second)
1567	if err != nil {
1568		t.Fatal("exhange failed:", err)
1569	}
1570}
1571
1572// Issue 27763: verify that two strings in one TXT record are concatenated.
1573func TestTXTRecordTwoStrings(t *testing.T) {
1574	fake := fakeDNSServer{
1575		rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1576			r := dnsmessage.Message{
1577				Header: dnsmessage.Header{
1578					ID:       q.Header.ID,
1579					Response: true,
1580					RCode:    dnsmessage.RCodeSuccess,
1581				},
1582				Questions: q.Questions,
1583				Answers: []dnsmessage.Resource{
1584					{
1585						Header: dnsmessage.ResourceHeader{
1586							Name:  q.Questions[0].Name,
1587							Type:  dnsmessage.TypeA,
1588							Class: dnsmessage.ClassINET,
1589						},
1590						Body: &dnsmessage.TXTResource{
1591							TXT: []string{"string1 ", "string2"},
1592						},
1593					},
1594					{
1595						Header: dnsmessage.ResourceHeader{
1596							Name:  q.Questions[0].Name,
1597							Type:  dnsmessage.TypeA,
1598							Class: dnsmessage.ClassINET,
1599						},
1600						Body: &dnsmessage.TXTResource{
1601							TXT: []string{"onestring"},
1602						},
1603					},
1604				},
1605			}
1606			return r, nil
1607		},
1608	}
1609	r := Resolver{PreferGo: true, Dial: fake.DialContext}
1610	txt, err := r.lookupTXT(context.Background(), "golang.org")
1611	if err != nil {
1612		t.Fatal("LookupTXT failed:", err)
1613	}
1614	if want := 2; len(txt) != want {
1615		t.Fatalf("len(txt), got %d, want %d", len(txt), want)
1616	}
1617	if want := "string1 string2"; txt[0] != want {
1618		t.Errorf("txt[0], got %q, want %q", txt[0], want)
1619	}
1620	if want := "onestring"; txt[1] != want {
1621		t.Errorf("txt[1], got %q, want %q", txt[1], want)
1622	}
1623}
1624