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