src/pkg/net/dnsclient_unix.go - The Go Programming Language

Golang

Source file src/pkg/net/dnsclient_unix.go

     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 freebsd linux netbsd openbsd
     6	
     7	// DNS client: see RFC 1035.
     8	// Has to be linked into package net for Dial.
     9	
    10	// TODO(rsc):
    11	//	Check periodically whether /etc/resolv.conf has changed.
    12	//	Could potentially handle many outstanding lookups faster.
    13	//	Could have a small cache.
    14	//	Random UDP source port (net.Dial should do that for us).
    15	//	Random request IDs.
    16	
    17	package net
    18	
    19	import (
    20		"math/rand"
    21		"sync"
    22		"time"
    23	)
    24	
    25	// Send a request on the connection and hope for a reply.
    26	// Up to cfg.attempts attempts.
    27	func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error) {
    28		if len(name) >= 256 {
    29			return nil, &DNSError{Err: "name too long", Name: name}
    30		}
    31		out := new(dnsMsg)
    32		out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano())
    33		out.question = []dnsQuestion{
    34			{name, qtype, dnsClassINET},
    35		}
    36		out.recursion_desired = true
    37		msg, ok := out.Pack()
    38		if !ok {
    39			return nil, &DNSError{Err: "internal error - cannot pack message", Name: name}
    40		}
    41	
    42		for attempt := 0; attempt < cfg.attempts; attempt++ {
    43			n, err := c.Write(msg)
    44			if err != nil {
    45				return nil, err
    46			}
    47	
    48			if cfg.timeout == 0 {
    49				c.SetReadDeadline(time.Time{})
    50			} else {
    51				c.SetReadDeadline(time.Now().Add(time.Duration(cfg.timeout) * time.Second))
    52			}
    53	
    54			buf := make([]byte, 2000) // More than enough.
    55			n, err = c.Read(buf)
    56			if err != nil {
    57				if e, ok := err.(Error); ok && e.Timeout() {
    58					continue
    59				}
    60				return nil, err
    61			}
    62			buf = buf[0:n]
    63			in := new(dnsMsg)
    64			if !in.Unpack(buf) || in.id != out.id {
    65				continue
    66			}
    67			return in, nil
    68		}
    69		var server string
    70		if a := c.RemoteAddr(); a != nil {
    71			server = a.String()
    72		}
    73		return nil, &DNSError{Err: "no answer from server", Name: name, Server: server, IsTimeout: true}
    74	}
    75	
    76	// Do a lookup for a single name, which must be rooted
    77	// (otherwise answer will not find the answers).
    78	func tryOneName(cfg *dnsConfig, name string, qtype uint16) (cname string, addrs []dnsRR, err error) {
    79		if len(cfg.servers) == 0 {
    80			return "", nil, &DNSError{Err: "no DNS servers", Name: name}
    81		}
    82		for i := 0; i < len(cfg.servers); i++ {
    83			// Calling Dial here is scary -- we have to be sure
    84			// not to dial a name that will require a DNS lookup,
    85			// or Dial will call back here to translate it.
    86			// The DNS config parser has already checked that
    87			// all the cfg.servers[i] are IP addresses, which
    88			// Dial will use without a DNS lookup.
    89			server := cfg.servers[i] + ":53"
    90			c, cerr := Dial("udp", server)
    91			if cerr != nil {
    92				err = cerr
    93				continue
    94			}
    95			msg, merr := exchange(cfg, c, name, qtype)
    96			c.Close()
    97			if merr != nil {
    98				err = merr
    99				continue
   100			}
   101			cname, addrs, err = answer(name, server, msg, qtype)
   102			if err == nil || err.(*DNSError).Err == noSuchHost {
   103				break
   104			}
   105		}
   106		return
   107	}
   108	
   109	func convertRR_A(records []dnsRR) []IP {
   110		addrs := make([]IP, len(records))
   111		for i, rr := range records {
   112			a := rr.(*dnsRR_A).A
   113			addrs[i] = IPv4(byte(a>>24), byte(a>>16), byte(a>>8), byte(a))
   114		}
   115		return addrs
   116	}
   117	
   118	func convertRR_AAAA(records []dnsRR) []IP {
   119		addrs := make([]IP, len(records))
   120		for i, rr := range records {
   121			a := make(IP, IPv6len)
   122			copy(a, rr.(*dnsRR_AAAA).AAAA[:])
   123			addrs[i] = a
   124		}
   125		return addrs
   126	}
   127	
   128	var cfg *dnsConfig
   129	var dnserr error
   130	
   131	func loadConfig() { cfg, dnserr = dnsReadConfig() }
   132	
   133	var onceLoadConfig sync.Once
   134	
   135	func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) {
   136		if !isDomainName(name) {
   137			return name, nil, &DNSError{Err: "invalid domain name", Name: name}
   138		}
   139		onceLoadConfig.Do(loadConfig)
   140		if dnserr != nil || cfg == nil {
   141			err = dnserr
   142			return
   143		}
   144		// If name is rooted (trailing dot) or has enough dots,
   145		// try it by itself first.
   146		rooted := len(name) > 0 && name[len(name)-1] == '.'
   147		if rooted || count(name, '.') >= cfg.ndots {
   148			rname := name
   149			if !rooted {
   150				rname += "."
   151			}
   152			// Can try as ordinary name.
   153			cname, addrs, err = tryOneName(cfg, rname, qtype)
   154			if err == nil {
   155				return
   156			}
   157		}
   158		if rooted {
   159			return
   160		}
   161	
   162		// Otherwise, try suffixes.
   163		for i := 0; i < len(cfg.search); i++ {
   164			rname := name + "." + cfg.search[i]
   165			if rname[len(rname)-1] != '.' {
   166				rname += "."
   167			}
   168			cname, addrs, err = tryOneName(cfg, rname, qtype)
   169			if err == nil {
   170				return
   171			}
   172		}
   173	
   174		// Last ditch effort: try unsuffixed.
   175		rname := name
   176		if !rooted {
   177			rname += "."
   178		}
   179		cname, addrs, err = tryOneName(cfg, rname, qtype)
   180		if err == nil {
   181			return
   182		}
   183		return
   184	}
   185	
   186	// goLookupHost is the native Go implementation of LookupHost.
   187	// Used only if cgoLookupHost refuses to handle the request
   188	// (that is, only if cgoLookupHost is the stub in cgo_stub.go).
   189	// Normally we let cgo use the C library resolver instead of
   190	// depending on our lookup code, so that Go and C get the same
   191	// answers.
   192	func goLookupHost(name string) (addrs []string, err error) {
   193		// Use entries from /etc/hosts if they match.
   194		addrs = lookupStaticHost(name)
   195		if len(addrs) > 0 {
   196			return
   197		}
   198		onceLoadConfig.Do(loadConfig)
   199		if dnserr != nil || cfg == nil {
   200			err = dnserr
   201			return
   202		}
   203		ips, err := goLookupIP(name)
   204		if err != nil {
   205			return
   206		}
   207		addrs = make([]string, 0, len(ips))
   208		for _, ip := range ips {
   209			addrs = append(addrs, ip.String())
   210		}
   211		return
   212	}
   213	
   214	// goLookupIP is the native Go implementation of LookupIP.
   215	// Used only if cgoLookupIP refuses to handle the request
   216	// (that is, only if cgoLookupIP is the stub in cgo_stub.go).
   217	// Normally we let cgo use the C library resolver instead of
   218	// depending on our lookup code, so that Go and C get the same
   219	// answers.
   220	func goLookupIP(name string) (addrs []IP, err error) {
   221		// Use entries from /etc/hosts if possible.
   222		haddrs := lookupStaticHost(name)
   223		if len(haddrs) > 0 {
   224			for _, haddr := range haddrs {
   225				if ip := ParseIP(haddr); ip != nil {
   226					addrs = append(addrs, ip)
   227				}
   228			}
   229			if len(addrs) > 0 {
   230				return
   231			}
   232		}
   233		onceLoadConfig.Do(loadConfig)
   234		if dnserr != nil || cfg == nil {
   235			err = dnserr
   236			return
   237		}
   238		var records []dnsRR
   239		var cname string
   240		cname, records, err = lookup(name, dnsTypeA)
   241		if err != nil {
   242			return
   243		}
   244		addrs = convertRR_A(records)
   245		if cname != "" {
   246			name = cname
   247		}
   248		_, records, err = lookup(name, dnsTypeAAAA)
   249		if err != nil && len(addrs) > 0 {
   250			// Ignore error because A lookup succeeded.
   251			err = nil
   252		}
   253		if err != nil {
   254			return
   255		}
   256		addrs = append(addrs, convertRR_AAAA(records)...)
   257		return
   258	}
   259	
   260	// goLookupCNAME is the native Go implementation of LookupCNAME.
   261	// Used only if cgoLookupCNAME refuses to handle the request
   262	// (that is, only if cgoLookupCNAME is the stub in cgo_stub.go).
   263	// Normally we let cgo use the C library resolver instead of
   264	// depending on our lookup code, so that Go and C get the same
   265	// answers.
   266	func goLookupCNAME(name string) (cname string, err error) {
   267		onceLoadConfig.Do(loadConfig)
   268		if dnserr != nil || cfg == nil {
   269			err = dnserr
   270			return
   271		}
   272		_, rr, err := lookup(name, dnsTypeCNAME)
   273		if err != nil {
   274			return
   275		}
   276		cname = rr[0].(*dnsRR_CNAME).Cname
   277		return
   278	}