"time"
)
-// A dnsDialer provides dialing suitable for DNS queries.
-type dnsDialer interface {
- dialDNS(ctx context.Context, network, addr string) (dnsConn, error)
-}
-
-var testHookDNSDialer = func() dnsDialer { return &Dialer{} }
-
// A dnsConn represents a DNS transport endpoint.
type dnsConn interface {
io.Closer
return resp, nil
}
-func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn, error) {
- switch network {
- case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
- default:
- return nil, UnknownNetworkError(network)
- }
- // Calling Dial here is scary -- we have to be sure not to
- // dial a name that will require a DNS lookup, or Dial will
- // call back here to translate it. The DNS config parser has
- // already checked that all the cfg.servers are IP
- // addresses, which Dial will use without a DNS lookup.
- c, err := d.DialContext(ctx, network, server)
- if err != nil {
- return nil, mapErr(err)
- }
- switch network {
- case "tcp", "tcp4", "tcp6":
- return c.(*TCPConn), nil
- case "udp", "udp4", "udp6":
- return c.(*UDPConn), nil
- }
- panic("unreachable")
-}
-
// exchange sends a query on the connection and hopes for a response.
-func exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
- d := testHookDNSDialer()
+func (r *Resolver) exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
out := dnsMsg{
dnsMsgHdr: dnsMsgHdr{
recursion_desired: true,
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
- c, err := d.dialDNS(ctx, network, server)
+ c, err := r.dial(ctx, network, server)
if err != nil {
return nil, err
}
// Do a lookup for a single name, which must be rooted
// (otherwise answer will not find the answers).
-func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) {
+func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) {
var lastErr error
serverOffset := cfg.serverOffset()
sLen := uint32(len(cfg.servers))
for j := uint32(0); j < sLen; j++ {
server := cfg.servers[(serverOffset+j)%sLen]
- msg, err := exchange(ctx, server, name, qtype, cfg.timeout)
+ msg, err := r.exchange(ctx, server, name, qtype, cfg.timeout)
if err != nil {
lastErr = &DNSError{
Err: err.Error(),
conf := resolvConf.dnsConfig
resolvConf.mu.RUnlock()
for _, fqdn := range conf.nameList(name) {
- cname, rrs, err = tryOneName(ctx, conf, fqdn, qtype)
+ cname, rrs, err = r.tryOneName(ctx, conf, fqdn, qtype)
if err == nil {
break
}
for _, fqdn := range conf.nameList(name) {
for _, qtype := range qtypes {
go func(qtype uint16) {
- cname, rrs, err := tryOneName(ctx, conf, fqdn, qtype)
+ cname, rrs, err := r.tryOneName(ctx, conf, fqdn, qtype)
lane <- racer{cname, rrs, err}
}(qtype)
}
"context"
"fmt"
"internal/poll"
- "internal/testenv"
"io/ioutil"
"os"
"path"
"time"
)
+var goResolver = Resolver{PreferGo: true}
+
// Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation.
const TestAddr uint32 = 0xc0000201
}
func TestDNSTransportFallback(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
-
+ fake := fakeDNSServer{
+ rh: func(n, _ string, _ *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ r := &dnsMsg{
+ dnsMsgHdr: dnsMsgHdr{
+ rcode: dnsRcodeSuccess,
+ },
+ }
+ if n == "udp" {
+ r.truncated = true
+ }
+ return r, nil
+ },
+ }
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
for _, tt := range dnsTransportFallbackTests {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- msg, err := exchange(ctx, tt.server, tt.name, tt.qtype, time.Second)
+ msg, err := r.exchange(ctx, tt.server, tt.name, tt.qtype, time.Second)
if err != nil {
t.Error(err)
continue
}
switch msg.rcode {
- case tt.rcode, dnsRcodeServerFailure:
+ case tt.rcode:
default:
t.Errorf("got %v from %v; want %v", msg.rcode, tt.server, tt.rcode)
continue
}
func TestSpecialDomainName(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
+ fake := fakeDNSServer{func(_, _ string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ r := &dnsMsg{
+ dnsMsgHdr: dnsMsgHdr{
+ id: q.id,
+ },
+ }
+ switch q.question[0].Name {
+ case "example.com.":
+ r.rcode = dnsRcodeSuccess
+ default:
+ r.rcode = dnsRcodeNameError
+ }
+
+ return r, nil
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
server := "8.8.8.8:53"
for _, tt := range specialDomainNameTests {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- msg, err := exchange(ctx, server, tt.name, tt.qtype, 3*time.Second)
+ msg, err := r.exchange(ctx, server, tt.name, tt.qtype, 3*time.Second)
if err != nil {
t.Error(err)
continue
}
}
+var fakeDNSServerSuccessful = fakeDNSServer{func(_, _ string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ r := &dnsMsg{
+ dnsMsgHdr: dnsMsgHdr{
+ id: q.id,
+ response: true,
+ },
+ question: q.question,
+ }
+ if len(q.question) == 1 && q.question[0].Qtype == dnsTypeA {
+ r.answer = []dnsRR{
+ &dnsRR_A{
+ Hdr: dnsRR_Header{
+ Name: q.question[0].Name,
+ Rrtype: dnsTypeA,
+ Class: dnsClassINET,
+ Rdlength: 4,
+ },
+ A: TestAddr,
+ },
+ }
+ }
+ return r, nil
+}}
+
// Issue 13705: don't try to resolve onion addresses, etc
func TestLookupTorOnion(t *testing.T) {
- addrs, err := DefaultResolver.goLookupIP(context.Background(), "foo.onion")
- if len(addrs) > 0 {
- t.Errorf("unexpected addresses: %v", addrs)
- }
+ r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
+ addrs, err := r.LookupIPAddr(context.Background(), "foo.onion")
if err != nil {
t.Fatalf("lookup = %v; want nil", err)
}
+ if len(addrs) > 0 {
+ t.Errorf("unexpected addresses: %v", addrs)
+ }
}
type resolvConfTest struct {
}
func TestUpdateResolvConf(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
+ r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
conf, err := newResolvConfTest()
if err != nil {
for j := 0; j < N; j++ {
go func(name string) {
defer wg.Done()
- ips, err := DefaultResolver.goLookupIP(context.Background(), name)
+ ips, err := r.LookupIPAddr(context.Background(), name)
if err != nil {
t.Error(err)
return
}
func TestGoLookupIPWithResolverConfig(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
+ fake := fakeDNSServer{func(n, s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ switch s {
+ case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
+ break
+ default:
+ time.Sleep(10 * time.Millisecond)
+ return nil, poll.ErrTimeout
+ }
+ r := &dnsMsg{
+ dnsMsgHdr: dnsMsgHdr{
+ id: q.id,
+ response: true,
+ },
+ question: q.question,
+ }
+ for _, question := range q.question {
+ switch question.Qtype {
+ case dnsTypeA:
+ switch question.Name {
+ case "hostname.as112.net.":
+ break
+ case "ipv4.google.com.":
+ r.answer = append(r.answer, &dnsRR_A{
+ Hdr: dnsRR_Header{
+ Name: q.question[0].Name,
+ Rrtype: dnsTypeA,
+ Class: dnsClassINET,
+ Rdlength: 4,
+ },
+ A: TestAddr,
+ })
+ default:
+
+ }
+ case dnsTypeAAAA:
+ switch question.Name {
+ case "hostname.as112.net.":
+ break
+ case "ipv6.google.com.":
+ r.answer = append(r.answer, &dnsRR_AAAA{
+ Hdr: dnsRR_Header{
+ Name: q.question[0].Name,
+ Rrtype: dnsTypeAAAA,
+ Class: dnsClassINET,
+ Rdlength: 16,
+ },
+ AAAA: TestAddr6,
+ })
+ }
+ }
+ }
+ return r, nil
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
conf, err := newResolvConfTest()
if err != nil {
t.Error(err)
continue
}
- addrs, err := DefaultResolver.goLookupIP(context.Background(), tt.name)
+ addrs, err := r.LookupIPAddr(context.Background(), tt.name)
if err != nil {
- // This test uses external network connectivity.
- // We need to take care with errors on both
- // DNS message exchange layer and DNS
- // transport layer because goLookupIP may fail
- // when the IP connectivity on node under test
- // gets lost during its run.
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) {
t.Errorf("got %v; want %v", err, tt.error)
}
// Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
+ fake := fakeDNSServer{func(n, s string, q *dnsMsg, tm time.Time) (*dnsMsg, error) {
+ r := &dnsMsg{
+ dnsMsgHdr: dnsMsgHdr{
+ id: q.id,
+ response: true,
+ },
+ question: q.question,
+ }
+ return r, nil
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
// Add a config that simulates no dns servers being available.
conf, err := newResolvConfTest()
name := fmt.Sprintf("order %v", order)
// First ensure that we get an error when contacting a non-existent host.
- _, _, err := DefaultResolver.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
+ _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
if err == nil {
t.Errorf("%s: expected error while looking up name not in hosts file", name)
continue
}
// Now check that we get an address when the name appears in the hosts file.
- addrs, _, err := DefaultResolver.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
+ addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
if err != nil {
t.Errorf("%s: expected to successfully lookup host entry", name)
continue
func TestErrorForOriginalNameWhenSearching(t *testing.T) {
const fqdn = "doesnotexist.domain"
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
- d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, _ string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{
id: q.id,
}
return r, nil
- }
+ }}
cases := []struct {
strictErrors bool
{false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}},
}
for _, tt := range cases {
- r := Resolver{StrictErrors: tt.strictErrors}
- _, err = r.goLookupIP(context.Background(), fqdn)
+ r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
+ _, err = r.LookupIPAddr(context.Background(), fqdn)
if err == nil {
t.Fatal("expected an error")
}
// Issue 15434. If a name server gives a lame referral, continue to the next.
func TestIgnoreLameReferrals(t *testing.T) {
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
- d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
t.Log(s, q)
r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{
}
return r, nil
- }
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
- addrs, err := DefaultResolver.goLookupIP(context.Background(), "www.golang.org")
+ addrs, err := r.LookupIPAddr(context.Background(), "www.golang.org")
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
for i := 0; i < b.N; i++ {
- DefaultResolver.goLookupIP(ctx, "www.example.com")
+ goResolver.LookupIPAddr(ctx, "www.example.com")
}
}
ctx := context.Background()
for i := 0; i < b.N; i++ {
- DefaultResolver.goLookupIP(ctx, "some.nonexistent")
+ goResolver.LookupIPAddr(ctx, "some.nonexistent")
}
}
ctx := context.Background()
for i := 0; i < b.N; i++ {
- DefaultResolver.goLookupIP(ctx, "www.example.com")
+ goResolver.LookupIPAddr(ctx, "www.example.com")
}
}
-type fakeDNSDialer struct {
- // reply handler
- rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
+type fakeDNSServer struct {
+ rh func(n, s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
}
-func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) {
- return &fakeDNSConn{f.rh, s, time.Time{}}, nil
+func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) {
+ return &fakeDNSConn{nil, server, n, s, time.Time{}}, nil
}
type fakeDNSConn struct {
- rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error)
- s string
- t time.Time
+ Conn
+ server *fakeDNSServer
+ n string
+ s string
+ t time.Time
}
func (f *fakeDNSConn) Close() error {
}
func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) {
- return f.rh(f.s, q, f.t)
+ return f.server.rh(f.n, f.s, q, f.t)
}
// UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
// Issue 16865. If a name server times out, continue to the next.
func TestRetryTimeout(t *testing.T) {
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
var deadline0 time.Time
- d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
t.Log(s, q, deadline)
if deadline.IsZero() {
}
return mockTXTResponse(q), nil
- }
+ }}
+ r := &Resolver{PreferGo: true, Dial: fake.DialContext}
- _, err = LookupTXT("www.golang.org")
+ _, err = r.LookupTXT(context.Background(), "www.golang.org")
if err != nil {
t.Fatal(err)
}
}
func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
var usedServers []string
- d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
usedServers = append(usedServers, s)
return mockTXTResponse(q), nil
- }
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
// len(nameservers) + 1 to allow rotation to get back to start
for i := 0; i < len(nameservers)+1; i++ {
- if _, err := LookupTXT("www.golang.org"); err != nil {
+ if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil {
t.Fatal(err)
}
}
// Issue 17448. With StrictErrors enabled, temporary errors should make
// LookupIP fail rather than return a partial result.
func TestStrictErrorsLookupIP(t *testing.T) {
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
}
for i, tt := range cases {
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
- d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
t.Log(s, q)
switch tt.resolveWhich(&q.question[0]) {
return nil, fmt.Errorf("Unexpected Qtype: %v", q.question[0].Qtype)
}
return r, nil
- }
+ }}
for _, strict := range []bool{true, false} {
- r := Resolver{StrictErrors: strict}
- ips, err := r.goLookupIP(context.Background(), name)
+ r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext}
+ ips, err := r.LookupIPAddr(context.Background(), name)
var wantErr error
if strict {
// Issue 17448. With StrictErrors enabled, temporary errors should make
// LookupTXT stop walking the search list.
func TestStrictErrorsLookupTXT(t *testing.T) {
- origTestHookDNSDialer := testHookDNSDialer
- defer func() { testHookDNSDialer = origTestHookDNSDialer }()
-
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
const searchY = "test.y.golang.org."
const txt = "Hello World"
- d := &fakeDNSDialer{}
- testHookDNSDialer = func() dnsDialer { return d }
-
- d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
+ fake := fakeDNSServer{func(_, s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) {
t.Log(s, q)
switch q.question[0].Name {
default:
return nil, fmt.Errorf("Unexpected Name: %v", q.question[0].Name)
}
- }
+ }}
for _, strict := range []bool{true, false} {
- r := Resolver{StrictErrors: strict}
+ r := Resolver{StrictErrors: strict, Dial: fake.DialContext}
_, rrs, err := r.lookup(context.Background(), name, dnsTypeTXT)
var wantErr error
var wantRRs int