github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/net/dnsclient_unix.go (about)

     1  // Copyright 2009 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 darwin dragonfly freebsd linux netbsd openbsd solaris
     6  
     7  // DNS client: see RFC 1035.
     8  // Has to be linked into package net for Dial.
     9  
    10  // TODO(rsc):
    11  //	Could potentially handle many outstanding lookups faster.
    12  //	Could have a small cache.
    13  //	Random UDP source port (net.Dial should do that for us).
    14  //	Random request IDs.
    15  
    16  package net
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"io"
    22  	"math/rand"
    23  	"os"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  // A dnsDialer provides dialing suitable for DNS queries.
    29  type dnsDialer interface {
    30  	dialDNS(ctx context.Context, network, addr string) (dnsConn, error)
    31  }
    32  
    33  var testHookDNSDialer = func() dnsDialer { return &Dialer{} }
    34  
    35  // A dnsConn represents a DNS transport endpoint.
    36  type dnsConn interface {
    37  	io.Closer
    38  
    39  	SetDeadline(time.Time) error
    40  
    41  	// dnsRoundTrip executes a single DNS transaction, returning a
    42  	// DNS response message for the provided DNS query message.
    43  	dnsRoundTrip(query *dnsMsg) (*dnsMsg, error)
    44  }
    45  
    46  func (c *UDPConn) dnsRoundTrip(query *dnsMsg) (*dnsMsg, error) {
    47  	return dnsRoundTripUDP(c, query)
    48  }
    49  
    50  // dnsRoundTripUDP implements the dnsRoundTrip interface for RFC 1035's
    51  // "UDP usage" transport mechanism. c should be a packet-oriented connection,
    52  // such as a *UDPConn.
    53  func dnsRoundTripUDP(c io.ReadWriter, query *dnsMsg) (*dnsMsg, error) {
    54  	b, ok := query.Pack()
    55  	if !ok {
    56  		return nil, errors.New("cannot marshal DNS message")
    57  	}
    58  	if _, err := c.Write(b); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	b = make([]byte, 512) // see RFC 1035
    63  	for {
    64  		n, err := c.Read(b)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		resp := &dnsMsg{}
    69  		if !resp.Unpack(b[:n]) || !resp.IsResponseTo(query) {
    70  			// Ignore invalid responses as they may be malicious
    71  			// forgery attempts. Instead continue waiting until
    72  			// timeout. See golang.org/issue/13281.
    73  			continue
    74  		}
    75  		return resp, nil
    76  	}
    77  }
    78  
    79  func (c *TCPConn) dnsRoundTrip(out *dnsMsg) (*dnsMsg, error) {
    80  	return dnsRoundTripTCP(c, out)
    81  }
    82  
    83  // dnsRoundTripTCP implements the dnsRoundTrip interface for RFC 1035's
    84  // "TCP usage" transport mechanism. c should be a stream-oriented connection,
    85  // such as a *TCPConn.
    86  func dnsRoundTripTCP(c io.ReadWriter, query *dnsMsg) (*dnsMsg, error) {
    87  	b, ok := query.Pack()
    88  	if !ok {
    89  		return nil, errors.New("cannot marshal DNS message")
    90  	}
    91  	l := len(b)
    92  	b = append([]byte{byte(l >> 8), byte(l)}, b...)
    93  	if _, err := c.Write(b); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	b = make([]byte, 1280) // 1280 is a reasonable initial size for IP over Ethernet, see RFC 4035
    98  	if _, err := io.ReadFull(c, b[:2]); err != nil {
    99  		return nil, err
   100  	}
   101  	l = int(b[0])<<8 | int(b[1])
   102  	if l > len(b) {
   103  		b = make([]byte, l)
   104  	}
   105  	n, err := io.ReadFull(c, b[:l])
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	resp := &dnsMsg{}
   110  	if !resp.Unpack(b[:n]) {
   111  		return nil, errors.New("cannot unmarshal DNS message")
   112  	}
   113  	if !resp.IsResponseTo(query) {
   114  		return nil, errors.New("invalid DNS response")
   115  	}
   116  	return resp, nil
   117  }
   118  
   119  func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn, error) {
   120  	switch network {
   121  	case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
   122  	default:
   123  		return nil, UnknownNetworkError(network)
   124  	}
   125  	// Calling Dial here is scary -- we have to be sure not to
   126  	// dial a name that will require a DNS lookup, or Dial will
   127  	// call back here to translate it. The DNS config parser has
   128  	// already checked that all the cfg.servers are IP
   129  	// addresses, which Dial will use without a DNS lookup.
   130  	c, err := d.DialContext(ctx, network, server)
   131  	if err != nil {
   132  		return nil, mapErr(err)
   133  	}
   134  	switch network {
   135  	case "tcp", "tcp4", "tcp6":
   136  		return c.(*TCPConn), nil
   137  	case "udp", "udp4", "udp6":
   138  		return c.(*UDPConn), nil
   139  	}
   140  	panic("unreachable")
   141  }
   142  
   143  // exchange sends a query on the connection and hopes for a response.
   144  func exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
   145  	d := testHookDNSDialer()
   146  	out := dnsMsg{
   147  		dnsMsgHdr: dnsMsgHdr{
   148  			recursion_desired: true,
   149  		},
   150  		question: []dnsQuestion{
   151  			{name, qtype, dnsClassINET},
   152  		},
   153  	}
   154  	for _, network := range []string{"udp", "tcp"} {
   155  		// TODO(mdempsky): Refactor so defers from UDP-based
   156  		// exchanges happen before TCP-based exchange.
   157  
   158  		ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
   159  		defer cancel()
   160  
   161  		c, err := d.dialDNS(ctx, network, server)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		defer c.Close()
   166  		if d, ok := ctx.Deadline(); ok && !d.IsZero() {
   167  			c.SetDeadline(d)
   168  		}
   169  		out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano())
   170  		in, err := c.dnsRoundTrip(&out)
   171  		if err != nil {
   172  			return nil, mapErr(err)
   173  		}
   174  		if in.truncated { // see RFC 5966
   175  			continue
   176  		}
   177  		return in, nil
   178  	}
   179  	return nil, errors.New("no answer from DNS server")
   180  }
   181  
   182  // Do a lookup for a single name, which must be rooted
   183  // (otherwise answer will not find the answers).
   184  func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) {
   185  	var lastErr error
   186  	serverOffset := cfg.serverOffset()
   187  	sLen := uint32(len(cfg.servers))
   188  
   189  	for i := 0; i < cfg.attempts; i++ {
   190  		for j := uint32(0); j < sLen; j++ {
   191  			server := cfg.servers[(serverOffset+j)%sLen]
   192  
   193  			msg, err := exchange(ctx, server, name, qtype, cfg.timeout)
   194  			if err != nil {
   195  				lastErr = &DNSError{
   196  					Err:    err.Error(),
   197  					Name:   name,
   198  					Server: server,
   199  				}
   200  				if nerr, ok := err.(Error); ok && nerr.Timeout() {
   201  					lastErr.(*DNSError).IsTimeout = true
   202  				}
   203  				// Set IsTemporary for socket-level errors. Note that this flag
   204  				// may also be used to indicate a SERVFAIL response.
   205  				if _, ok := err.(*OpError); ok {
   206  					lastErr.(*DNSError).IsTemporary = true
   207  				}
   208  				continue
   209  			}
   210  			// libresolv continues to the next server when it receives
   211  			// an invalid referral response. See golang.org/issue/15434.
   212  			if msg.rcode == dnsRcodeSuccess && !msg.authoritative && !msg.recursion_available && len(msg.answer) == 0 && len(msg.extra) == 0 {
   213  				lastErr = &DNSError{Err: "lame referral", Name: name, Server: server}
   214  				continue
   215  			}
   216  			cname, rrs, err := answer(name, server, msg, qtype)
   217  			// If answer errored for rcodes dnsRcodeSuccess or dnsRcodeNameError,
   218  			// it means the response in msg was not useful and trying another
   219  			// server probably won't help. Return now in those cases.
   220  			// TODO: indicate this in a more obvious way, such as a field on DNSError?
   221  			if err == nil || msg.rcode == dnsRcodeSuccess || msg.rcode == dnsRcodeNameError {
   222  				return cname, rrs, err
   223  			}
   224  			lastErr = err
   225  		}
   226  	}
   227  	return "", nil, lastErr
   228  }
   229  
   230  // addrRecordList converts and returns a list of IP addresses from DNS
   231  // address records (both A and AAAA). Other record types are ignored.
   232  func addrRecordList(rrs []dnsRR) []IPAddr {
   233  	addrs := make([]IPAddr, 0, 4)
   234  	for _, rr := range rrs {
   235  		switch rr := rr.(type) {
   236  		case *dnsRR_A:
   237  			addrs = append(addrs, IPAddr{IP: IPv4(byte(rr.A>>24), byte(rr.A>>16), byte(rr.A>>8), byte(rr.A))})
   238  		case *dnsRR_AAAA:
   239  			ip := make(IP, IPv6len)
   240  			copy(ip, rr.AAAA[:])
   241  			addrs = append(addrs, IPAddr{IP: ip})
   242  		}
   243  	}
   244  	return addrs
   245  }
   246  
   247  // A resolverConfig represents a DNS stub resolver configuration.
   248  type resolverConfig struct {
   249  	initOnce sync.Once // guards init of resolverConfig
   250  
   251  	// ch is used as a semaphore that only allows one lookup at a
   252  	// time to recheck resolv.conf.
   253  	ch          chan struct{} // guards lastChecked and modTime
   254  	lastChecked time.Time     // last time resolv.conf was checked
   255  
   256  	mu        sync.RWMutex // protects dnsConfig
   257  	dnsConfig *dnsConfig   // parsed resolv.conf structure used in lookups
   258  }
   259  
   260  var resolvConf resolverConfig
   261  
   262  // init initializes conf and is only called via conf.initOnce.
   263  func (conf *resolverConfig) init() {
   264  	// Set dnsConfig and lastChecked so we don't parse
   265  	// resolv.conf twice the first time.
   266  	conf.dnsConfig = systemConf().resolv
   267  	if conf.dnsConfig == nil {
   268  		conf.dnsConfig = dnsReadConfig("/etc/resolv.conf")
   269  	}
   270  	conf.lastChecked = time.Now()
   271  
   272  	// Prepare ch so that only one update of resolverConfig may
   273  	// run at once.
   274  	conf.ch = make(chan struct{}, 1)
   275  }
   276  
   277  // tryUpdate tries to update conf with the named resolv.conf file.
   278  // The name variable only exists for testing. It is otherwise always
   279  // "/etc/resolv.conf".
   280  func (conf *resolverConfig) tryUpdate(name string) {
   281  	conf.initOnce.Do(conf.init)
   282  
   283  	// Ensure only one update at a time checks resolv.conf.
   284  	if !conf.tryAcquireSema() {
   285  		return
   286  	}
   287  	defer conf.releaseSema()
   288  
   289  	now := time.Now()
   290  	if conf.lastChecked.After(now.Add(-5 * time.Second)) {
   291  		return
   292  	}
   293  	conf.lastChecked = now
   294  
   295  	var mtime time.Time
   296  	if fi, err := os.Stat(name); err == nil {
   297  		mtime = fi.ModTime()
   298  	}
   299  	if mtime.Equal(conf.dnsConfig.mtime) {
   300  		return
   301  	}
   302  
   303  	dnsConf := dnsReadConfig(name)
   304  	conf.mu.Lock()
   305  	conf.dnsConfig = dnsConf
   306  	conf.mu.Unlock()
   307  }
   308  
   309  func (conf *resolverConfig) tryAcquireSema() bool {
   310  	select {
   311  	case conf.ch <- struct{}{}:
   312  		return true
   313  	default:
   314  		return false
   315  	}
   316  }
   317  
   318  func (conf *resolverConfig) releaseSema() {
   319  	<-conf.ch
   320  }
   321  
   322  func (r *Resolver) lookup(ctx context.Context, name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
   323  	if !isDomainName(name) {
   324  		// We used to use "invalid domain name" as the error,
   325  		// but that is a detail of the specific lookup mechanism.
   326  		// Other lookups might allow broader name syntax
   327  		// (for example Multicast DNS allows UTF-8; see RFC 6762).
   328  		// For consistency with libc resolvers, report no such host.
   329  		return "", nil, &DNSError{Err: errNoSuchHost.Error(), Name: name}
   330  	}
   331  	resolvConf.tryUpdate("/etc/resolv.conf")
   332  	resolvConf.mu.RLock()
   333  	conf := resolvConf.dnsConfig
   334  	resolvConf.mu.RUnlock()
   335  	for _, fqdn := range conf.nameList(name) {
   336  		cname, rrs, err = tryOneName(ctx, conf, fqdn, qtype)
   337  		if err == nil {
   338  			break
   339  		}
   340  		if nerr, ok := err.(Error); ok && nerr.Temporary() && r.StrictErrors {
   341  			// If we hit a temporary error with StrictErrors enabled,
   342  			// stop immediately instead of trying more names.
   343  			break
   344  		}
   345  	}
   346  	if err, ok := err.(*DNSError); ok {
   347  		// Show original name passed to lookup, not suffixed one.
   348  		// In general we might have tried many suffixes; showing
   349  		// just one is misleading. See also golang.org/issue/6324.
   350  		err.Name = name
   351  	}
   352  	return
   353  }
   354  
   355  // avoidDNS reports whether this is a hostname for which we should not
   356  // use DNS. Currently this includes only .onion, per RFC 7686. See
   357  // golang.org/issue/13705. Does not cover .local names (RFC 6762),
   358  // see golang.org/issue/16739.
   359  func avoidDNS(name string) bool {
   360  	if name == "" {
   361  		return true
   362  	}
   363  	if name[len(name)-1] == '.' {
   364  		name = name[:len(name)-1]
   365  	}
   366  	return stringsHasSuffixFold(name, ".onion")
   367  }
   368  
   369  // nameList returns a list of names for sequential DNS queries.
   370  func (conf *dnsConfig) nameList(name string) []string {
   371  	if avoidDNS(name) {
   372  		return nil
   373  	}
   374  
   375  	// Check name length (see isDomainName).
   376  	l := len(name)
   377  	rooted := l > 0 && name[l-1] == '.'
   378  	if l > 254 || l == 254 && rooted {
   379  		return nil
   380  	}
   381  
   382  	// If name is rooted (trailing dot), try only that name.
   383  	if rooted {
   384  		return []string{name}
   385  	}
   386  
   387  	hasNdots := count(name, '.') >= conf.ndots
   388  	name += "."
   389  	l++
   390  
   391  	// Build list of search choices.
   392  	names := make([]string, 0, 1+len(conf.search))
   393  	// If name has enough dots, try unsuffixed first.
   394  	if hasNdots {
   395  		names = append(names, name)
   396  	}
   397  	// Try suffixes that are not too long (see isDomainName).
   398  	for _, suffix := range conf.search {
   399  		if l+len(suffix) <= 254 {
   400  			names = append(names, name+suffix)
   401  		}
   402  	}
   403  	// Try unsuffixed, if not tried first above.
   404  	if !hasNdots {
   405  		names = append(names, name)
   406  	}
   407  	return names
   408  }
   409  
   410  // hostLookupOrder specifies the order of LookupHost lookup strategies.
   411  // It is basically a simplified representation of nsswitch.conf.
   412  // "files" means /etc/hosts.
   413  type hostLookupOrder int
   414  
   415  const (
   416  	// hostLookupCgo means defer to cgo.
   417  	hostLookupCgo      hostLookupOrder = iota
   418  	hostLookupFilesDNS                 // files first
   419  	hostLookupDNSFiles                 // dns first
   420  	hostLookupFiles                    // only files
   421  	hostLookupDNS                      // only DNS
   422  )
   423  
   424  var lookupOrderName = map[hostLookupOrder]string{
   425  	hostLookupCgo:      "cgo",
   426  	hostLookupFilesDNS: "files,dns",
   427  	hostLookupDNSFiles: "dns,files",
   428  	hostLookupFiles:    "files",
   429  	hostLookupDNS:      "dns",
   430  }
   431  
   432  func (o hostLookupOrder) String() string {
   433  	if s, ok := lookupOrderName[o]; ok {
   434  		return s
   435  	}
   436  	return "hostLookupOrder=" + itoa(int(o)) + "??"
   437  }
   438  
   439  // goLookupHost is the native Go implementation of LookupHost.
   440  // Used only if cgoLookupHost refuses to handle the request
   441  // (that is, only if cgoLookupHost is the stub in cgo_stub.go).
   442  // Normally we let cgo use the C library resolver instead of
   443  // depending on our lookup code, so that Go and C get the same
   444  // answers.
   445  func (r *Resolver) goLookupHost(ctx context.Context, name string) (addrs []string, err error) {
   446  	return r.goLookupHostOrder(ctx, name, hostLookupFilesDNS)
   447  }
   448  
   449  func (r *Resolver) goLookupHostOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []string, err error) {
   450  	if order == hostLookupFilesDNS || order == hostLookupFiles {
   451  		// Use entries from /etc/hosts if they match.
   452  		addrs = lookupStaticHost(name)
   453  		if len(addrs) > 0 || order == hostLookupFiles {
   454  			return
   455  		}
   456  	}
   457  	ips, _, err := r.goLookupIPCNAMEOrder(ctx, name, order)
   458  	if err != nil {
   459  		return
   460  	}
   461  	addrs = make([]string, 0, len(ips))
   462  	for _, ip := range ips {
   463  		addrs = append(addrs, ip.String())
   464  	}
   465  	return
   466  }
   467  
   468  // lookup entries from /etc/hosts
   469  func goLookupIPFiles(name string) (addrs []IPAddr) {
   470  	for _, haddr := range lookupStaticHost(name) {
   471  		haddr, zone := splitHostZone(haddr)
   472  		if ip := ParseIP(haddr); ip != nil {
   473  			addr := IPAddr{IP: ip, Zone: zone}
   474  			addrs = append(addrs, addr)
   475  		}
   476  	}
   477  	sortByRFC6724(addrs)
   478  	return
   479  }
   480  
   481  // goLookupIP is the native Go implementation of LookupIP.
   482  // The libc versions are in cgo_*.go.
   483  func (r *Resolver) goLookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) {
   484  	order := systemConf().hostLookupOrder(host)
   485  	addrs, _, err = r.goLookupIPCNAMEOrder(ctx, host, order)
   486  	return
   487  }
   488  
   489  func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order hostLookupOrder) (addrs []IPAddr, cname string, err error) {
   490  	if order == hostLookupFilesDNS || order == hostLookupFiles {
   491  		addrs = goLookupIPFiles(name)
   492  		if len(addrs) > 0 || order == hostLookupFiles {
   493  			return addrs, name, nil
   494  		}
   495  	}
   496  	if !isDomainName(name) {
   497  		// See comment in func lookup above about use of errNoSuchHost.
   498  		return nil, "", &DNSError{Err: errNoSuchHost.Error(), Name: name}
   499  	}
   500  	resolvConf.tryUpdate("/etc/resolv.conf")
   501  	resolvConf.mu.RLock()
   502  	conf := resolvConf.dnsConfig
   503  	resolvConf.mu.RUnlock()
   504  	type racer struct {
   505  		cname string
   506  		rrs   []dnsRR
   507  		error
   508  	}
   509  	lane := make(chan racer, 1)
   510  	qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA}
   511  	var lastErr error
   512  	for _, fqdn := range conf.nameList(name) {
   513  		for _, qtype := range qtypes {
   514  			go func(qtype uint16) {
   515  				cname, rrs, err := tryOneName(ctx, conf, fqdn, qtype)
   516  				lane <- racer{cname, rrs, err}
   517  			}(qtype)
   518  		}
   519  		hitStrictError := false
   520  		for range qtypes {
   521  			racer := <-lane
   522  			if racer.error != nil {
   523  				if nerr, ok := racer.error.(Error); ok && nerr.Temporary() && r.StrictErrors {
   524  					// This error will abort the nameList loop.
   525  					hitStrictError = true
   526  					lastErr = racer.error
   527  				} else if lastErr == nil || fqdn == name+"." {
   528  					// Prefer error for original name.
   529  					lastErr = racer.error
   530  				}
   531  				continue
   532  			}
   533  			addrs = append(addrs, addrRecordList(racer.rrs)...)
   534  			if cname == "" {
   535  				cname = racer.cname
   536  			}
   537  		}
   538  		if hitStrictError {
   539  			// If either family hit an error with StrictErrors enabled,
   540  			// discard all addresses. This ensures that network flakiness
   541  			// cannot turn a dualstack hostname IPv4/IPv6-only.
   542  			addrs = nil
   543  			break
   544  		}
   545  		if len(addrs) > 0 {
   546  			break
   547  		}
   548  	}
   549  	if lastErr, ok := lastErr.(*DNSError); ok {
   550  		// Show original name passed to lookup, not suffixed one.
   551  		// In general we might have tried many suffixes; showing
   552  		// just one is misleading. See also golang.org/issue/6324.
   553  		lastErr.Name = name
   554  	}
   555  	sortByRFC6724(addrs)
   556  	if len(addrs) == 0 {
   557  		if order == hostLookupDNSFiles {
   558  			addrs = goLookupIPFiles(name)
   559  		}
   560  		if len(addrs) == 0 && lastErr != nil {
   561  			return nil, "", lastErr
   562  		}
   563  	}
   564  	return addrs, cname, nil
   565  }
   566  
   567  // goLookupCNAME is the native Go (non-cgo) implementation of LookupCNAME.
   568  func (r *Resolver) goLookupCNAME(ctx context.Context, host string) (cname string, err error) {
   569  	order := systemConf().hostLookupOrder(host)
   570  	_, cname, err = r.goLookupIPCNAMEOrder(ctx, host, order)
   571  	return
   572  }
   573  
   574  // goLookupPTR is the native Go implementation of LookupAddr.
   575  // Used only if cgoLookupPTR refuses to handle the request (that is,
   576  // only if cgoLookupPTR is the stub in cgo_stub.go).
   577  // Normally we let cgo use the C library resolver instead of depending
   578  // on our lookup code, so that Go and C get the same answers.
   579  func (r *Resolver) goLookupPTR(ctx context.Context, addr string) ([]string, error) {
   580  	names := lookupStaticAddr(addr)
   581  	if len(names) > 0 {
   582  		return names, nil
   583  	}
   584  	arpa, err := reverseaddr(addr)
   585  	if err != nil {
   586  		return nil, err
   587  	}
   588  	_, rrs, err := r.lookup(ctx, arpa, dnsTypePTR)
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  	ptrs := make([]string, len(rrs))
   593  	for i, rr := range rrs {
   594  		ptrs[i] = rr.(*dnsRR_PTR).Ptr
   595  	}
   596  	return ptrs, nil
   597  }