diff options
Diffstat (limited to 'src/net/dnsclient_unix.go')
-rw-r--r-- | src/net/dnsclient_unix.go | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go new file mode 100644 index 000000000..7511083f7 --- /dev/null +++ b/src/net/dnsclient_unix.go @@ -0,0 +1,423 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +// DNS client: see RFC 1035. +// Has to be linked into package net for Dial. + +// TODO(rsc): +// Could potentially handle many outstanding lookups faster. +// Could have a small cache. +// Random UDP source port (net.Dial should do that for us). +// Random request IDs. + +package net + +import ( + "errors" + "io" + "math/rand" + "os" + "sync" + "time" +) + +// A dnsConn represents a DNS transport endpoint. +type dnsConn interface { + Conn + + // readDNSResponse reads a DNS response message from the DNS + // transport endpoint and returns the received DNS response + // message. + readDNSResponse() (*dnsMsg, error) + + // writeDNSQuery writes a DNS query message to the DNS + // connection endpoint. + writeDNSQuery(*dnsMsg) error +} + +func (c *UDPConn) readDNSResponse() (*dnsMsg, error) { + b := make([]byte, 512) // see RFC 1035 + n, err := c.Read(b) + if err != nil { + return nil, err + } + msg := &dnsMsg{} + if !msg.Unpack(b[:n]) { + return nil, errors.New("cannot unmarshal DNS message") + } + return msg, nil +} + +func (c *UDPConn) writeDNSQuery(msg *dnsMsg) error { + b, ok := msg.Pack() + if !ok { + return errors.New("cannot marshal DNS message") + } + if _, err := c.Write(b); err != nil { + return err + } + return nil +} + +func (c *TCPConn) readDNSResponse() (*dnsMsg, error) { + b := make([]byte, 1280) // 1280 is a reasonable initial size for IP over Ethernet, see RFC 4035 + if _, err := io.ReadFull(c, b[:2]); err != nil { + return nil, err + } + l := int(b[0])<<8 | int(b[1]) + if l > len(b) { + b = make([]byte, l) + } + n, err := io.ReadFull(c, b[:l]) + if err != nil { + return nil, err + } + msg := &dnsMsg{} + if !msg.Unpack(b[:n]) { + return nil, errors.New("cannot unmarshal DNS message") + } + return msg, nil +} + +func (c *TCPConn) writeDNSQuery(msg *dnsMsg) error { + b, ok := msg.Pack() + if !ok { + return errors.New("cannot marshal DNS message") + } + l := uint16(len(b)) + b = append([]byte{byte(l >> 8), byte(l)}, b...) + if _, err := c.Write(b); err != nil { + return err + } + return nil +} + +func (d *Dialer) dialDNS(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[i] are IP + // addresses, which Dial will use without a DNS lookup. + c, err := d.Dial(network, server) + if err != nil { + return nil, 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(server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) { + d := Dialer{Timeout: timeout} + out := dnsMsg{ + dnsMsgHdr: dnsMsgHdr{ + recursion_desired: true, + }, + question: []dnsQuestion{ + {name, qtype, dnsClassINET}, + }, + } + for _, network := range []string{"udp", "tcp"} { + c, err := d.dialDNS(network, server) + if err != nil { + return nil, err + } + defer c.Close() + if timeout > 0 { + c.SetDeadline(time.Now().Add(timeout)) + } + out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano()) + if err := c.writeDNSQuery(&out); err != nil { + return nil, err + } + in, err := c.readDNSResponse() + if err != nil { + return nil, err + } + if in.id != out.id { + return nil, errors.New("DNS message ID mismatch") + } + if in.truncated { // see RFC 5966 + continue + } + return in, nil + } + return nil, errors.New("no answer from DNS server") +} + +// Do a lookup for a single name, which must be rooted +// (otherwise answer will not find the answers). +func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) { + if len(cfg.servers) == 0 { + return "", nil, &DNSError{Err: "no DNS servers", Name: name} + } + if len(name) >= 256 { + return "", nil, &DNSError{Err: "DNS name too long", Name: name} + } + timeout := time.Duration(cfg.timeout) * time.Second + var lastErr error + for i := 0; i < cfg.attempts; i++ { + for _, server := range cfg.servers { + server = JoinHostPort(server, "53") + msg, err := exchange(server, name, qtype, timeout) + if err != nil { + lastErr = &DNSError{ + Err: err.Error(), + Name: name, + Server: server, + } + if nerr, ok := err.(Error); ok && nerr.Timeout() { + lastErr.(*DNSError).IsTimeout = true + } + continue + } + cname, addrs, err := answer(name, server, msg, qtype) + if err == nil || err.(*DNSError).Err == noSuchHost { + return cname, addrs, err + } + lastErr = err + } + } + return "", nil, lastErr +} + +func convertRR_A(records []dnsRR) []IP { + addrs := make([]IP, len(records)) + for i, rr := range records { + a := rr.(*dnsRR_A).A + addrs[i] = IPv4(byte(a>>24), byte(a>>16), byte(a>>8), byte(a)) + } + return addrs +} + +func convertRR_AAAA(records []dnsRR) []IP { + addrs := make([]IP, len(records)) + for i, rr := range records { + a := make(IP, IPv6len) + copy(a, rr.(*dnsRR_AAAA).AAAA[:]) + addrs[i] = a + } + return addrs +} + +var cfg struct { + ch chan struct{} + mu sync.RWMutex // protects dnsConfig and dnserr + dnsConfig *dnsConfig + dnserr error +} +var onceLoadConfig sync.Once + +// Assume dns config file is /etc/resolv.conf here +func loadDefaultConfig() { + loadConfig("/etc/resolv.conf", 5*time.Second, nil) +} + +func loadConfig(resolvConfPath string, reloadTime time.Duration, quit <-chan chan struct{}) { + var mtime time.Time + cfg.ch = make(chan struct{}, 1) + if fi, err := os.Stat(resolvConfPath); err != nil { + cfg.dnserr = err + } else { + mtime = fi.ModTime() + cfg.dnsConfig, cfg.dnserr = dnsReadConfig(resolvConfPath) + } + go func() { + for { + time.Sleep(reloadTime) + select { + case qresp := <-quit: + qresp <- struct{}{} + return + case <-cfg.ch: + } + + // In case of error, we keep the previous config + fi, err := os.Stat(resolvConfPath) + if err != nil { + continue + } + // If the resolv.conf mtime didn't change, do not reload + m := fi.ModTime() + if m.Equal(mtime) { + continue + } + mtime = m + // In case of error, we keep the previous config + ncfg, err := dnsReadConfig(resolvConfPath) + if err != nil || len(ncfg.servers) == 0 { + continue + } + cfg.mu.Lock() + cfg.dnsConfig = ncfg + cfg.dnserr = nil + cfg.mu.Unlock() + } + }() +} + +func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) { + if !isDomainName(name) { + return name, nil, &DNSError{Err: "invalid domain name", Name: name} + } + onceLoadConfig.Do(loadDefaultConfig) + + select { + case cfg.ch <- struct{}{}: + default: + } + + cfg.mu.RLock() + defer cfg.mu.RUnlock() + + if cfg.dnserr != nil || cfg.dnsConfig == nil { + err = cfg.dnserr + return + } + // If name is rooted (trailing dot) or has enough dots, + // try it by itself first. + rooted := len(name) > 0 && name[len(name)-1] == '.' + if rooted || count(name, '.') >= cfg.dnsConfig.ndots { + rname := name + if !rooted { + rname += "." + } + // Can try as ordinary name. + cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) + if rooted || err == nil { + return + } + } + + // Otherwise, try suffixes. + for i := 0; i < len(cfg.dnsConfig.search); i++ { + rname := name + "." + cfg.dnsConfig.search[i] + if rname[len(rname)-1] != '.' { + rname += "." + } + cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) + if err == nil { + return + } + } + + // Last ditch effort: try unsuffixed only if we haven't already, + // that is, name is not rooted and has less than ndots dots. + if count(name, '.') < cfg.dnsConfig.ndots { + cname, addrs, err = tryOneName(cfg.dnsConfig, name+".", qtype) + if err == nil { + return + } + } + + if e, ok := err.(*DNSError); ok { + // Show original name passed to lookup, not suffixed one. + // In general we might have tried many suffixes; showing + // just one is misleading. See also golang.org/issue/6324. + e.Name = name + } + return +} + +// goLookupHost is the native Go implementation of LookupHost. +// Used only if cgoLookupHost refuses to handle the request +// (that is, only if cgoLookupHost is the stub in cgo_stub.go). +// Normally we let cgo use the C library resolver instead of +// depending on our lookup code, so that Go and C get the same +// answers. +func goLookupHost(name string) (addrs []string, err error) { + // Use entries from /etc/hosts if they match. + addrs = lookupStaticHost(name) + if len(addrs) > 0 { + return + } + ips, err := goLookupIP(name) + if err != nil { + return + } + addrs = make([]string, 0, len(ips)) + for _, ip := range ips { + addrs = append(addrs, ip.String()) + } + return +} + +// goLookupIP is the native Go implementation of LookupIP. +// Used only if cgoLookupIP refuses to handle the request +// (that is, only if cgoLookupIP is the stub in cgo_stub.go). +// Normally we let cgo use the C library resolver instead of +// depending on our lookup code, so that Go and C get the same +// answers. +func goLookupIP(name string) (addrs []IP, err error) { + // Use entries from /etc/hosts if possible. + haddrs := lookupStaticHost(name) + if len(haddrs) > 0 { + for _, haddr := range haddrs { + if ip := ParseIP(haddr); ip != nil { + addrs = append(addrs, ip) + } + } + if len(addrs) > 0 { + return + } + } + type racer struct { + qtype uint16 + rrs []dnsRR + error + } + lane := make(chan racer, 1) + qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA} + for _, qtype := range qtypes { + go func(qtype uint16) { + _, rrs, err := lookup(name, qtype) + lane <- racer{qtype, rrs, err} + }(qtype) + } + var lastErr error + for range qtypes { + racer := <-lane + if racer.error != nil { + lastErr = racer.error + continue + } + switch racer.qtype { + case dnsTypeA: + addrs = append(addrs, convertRR_A(racer.rrs)...) + case dnsTypeAAAA: + addrs = append(addrs, convertRR_AAAA(racer.rrs)...) + } + } + if len(addrs) == 0 && lastErr != nil { + return nil, lastErr + } + return addrs, nil +} + +// goLookupCNAME is the native Go implementation of LookupCNAME. +// Used only if cgoLookupCNAME refuses to handle the request +// (that is, only if cgoLookupCNAME is the stub in cgo_stub.go). +// Normally we let cgo use the C library resolver instead of +// depending on our lookup code, so that Go and C get the same +// answers. +func goLookupCNAME(name string) (cname string, err error) { + _, rr, err := lookup(name, dnsTypeCNAME) + if err != nil { + return + } + cname = rr[0].(*dnsRR_CNAME).Cname + return +} |