1
0
mirror of https://github.com/golang/go synced 2024-10-05 15:51:22 -06:00
go/src/pkg/net/dnsclient_unix.go
Brad Fitzpatrick b71883e9b0 net: change SetTimeout to SetDeadline
Previously, a timeout (in int64 nanoseconds) applied to a granularity
even smaller than one operation:  a 100 byte read with a 1 second timeout
could take 100 seconds, if the bytes all arrived on the network 1 second
apart.  This was confusing.

Rather than making the timeout granularity be per-Read/Write,
this CL makes callers set an absolute deadline (in time.Time)
after which operations will fail.  This makes it possible to
set deadlines at higher levels, without knowing exactly how
many read/write operations will happen in e.g. reading an HTTP
request.

Fixes #2723

R=r, rsc, dave
CC=golang-dev
https://golang.org/cl/5555048
2012-01-18 16:24:06 -08:00

279 lines
6.8 KiB
Go

// 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 freebsd linux netbsd openbsd
// DNS client: see RFC 1035.
// Has to be linked into package net for Dial.
// TODO(rsc):
// Check periodically whether /etc/resolv.conf has changed.
// 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 (
"math/rand"
"sync"
"time"
)
// Send a request on the connection and hope for a reply.
// Up to cfg.attempts attempts.
func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, error) {
if len(name) >= 256 {
return nil, &DNSError{Err: "name too long", Name: name}
}
out := new(dnsMsg)
out.id = uint16(rand.Int()) ^ uint16(time.Now().UnixNano())
out.question = []dnsQuestion{
{name, qtype, dnsClassINET},
}
out.recursion_desired = true
msg, ok := out.Pack()
if !ok {
return nil, &DNSError{Err: "internal error - cannot pack message", Name: name}
}
for attempt := 0; attempt < cfg.attempts; attempt++ {
n, err := c.Write(msg)
if err != nil {
return nil, err
}
if cfg.timeout == 0 {
c.SetReadDeadline(time.Time{})
} else {
c.SetReadDeadline(time.Now().Add(time.Duration(cfg.timeout) * time.Second))
}
buf := make([]byte, 2000) // More than enough.
n, err = c.Read(buf)
if err != nil {
if e, ok := err.(Error); ok && e.Timeout() {
continue
}
return nil, err
}
buf = buf[0:n]
in := new(dnsMsg)
if !in.Unpack(buf) || in.id != out.id {
continue
}
return in, nil
}
var server string
if a := c.RemoteAddr(); a != nil {
server = a.String()
}
return nil, &DNSError{Err: "no answer from server", Name: name, Server: server, IsTimeout: true}
}
// 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) (cname string, addrs []dnsRR, err error) {
if len(cfg.servers) == 0 {
return "", nil, &DNSError{Err: "no DNS servers", Name: name}
}
for i := 0; i < len(cfg.servers); i++ {
// 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.
server := cfg.servers[i] + ":53"
c, cerr := Dial("udp", server)
if cerr != nil {
err = cerr
continue
}
msg, merr := exchange(cfg, c, name, qtype)
c.Close()
if merr != nil {
err = merr
continue
}
cname, addrs, err = answer(name, server, msg, qtype)
if err == nil || err.(*DNSError).Err == noSuchHost {
break
}
}
return
}
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 *dnsConfig
var dnserr error
func loadConfig() { cfg, dnserr = dnsReadConfig() }
var onceLoadConfig sync.Once
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(loadConfig)
if dnserr != nil || cfg == nil {
err = 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.ndots {
rname := name
if !rooted {
rname += "."
}
// Can try as ordinary name.
cname, addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
return
}
}
if rooted {
return
}
// Otherwise, try suffixes.
for i := 0; i < len(cfg.search); i++ {
rname := name + "." + cfg.search[i]
if rname[len(rname)-1] != '.' {
rname += "."
}
cname, addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
return
}
}
// Last ditch effort: try unsuffixed.
rname := name
if !rooted {
rname += "."
}
cname, addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
return
}
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
}
onceLoadConfig.Do(loadConfig)
if dnserr != nil || cfg == nil {
err = dnserr
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
}
}
onceLoadConfig.Do(loadConfig)
if dnserr != nil || cfg == nil {
err = dnserr
return
}
var records []dnsRR
var cname string
cname, records, err = lookup(name, dnsTypeA)
if err != nil {
return
}
addrs = convertRR_A(records)
if cname != "" {
name = cname
}
_, records, err = lookup(name, dnsTypeAAAA)
if err != nil && len(addrs) > 0 {
// Ignore error because A lookup succeeded.
err = nil
}
if err != nil {
return
}
addrs = append(addrs, convertRR_AAAA(records)...)
return
}
// 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) {
onceLoadConfig.Do(loadConfig)
if dnserr != nil || cfg == nil {
err = dnserr
return
}
_, rr, err := lookup(name, dnsTypeCNAME)
if err != nil {
return
}
cname = rr[0].(*dnsRR_CNAME).Cname
return
}