diff options
author | Russ Cox <rsc@golang.org> | 2008-12-18 15:42:39 -0800 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2008-12-18 15:42:39 -0800 |
commit | c4df9e3990cbb1a8e0eb7d52c0ea65441195403d (patch) | |
tree | 03a7b9e046e2ef76f628fd6ce1863871fbb3a842 /src/lib/net/dnsclient.go | |
parent | fbc8538a295e36d100fa93d0224fa65f224944ff (diff) | |
download | golang-c4df9e3990cbb1a8e0eb7d52c0ea65441195403d.tar.gz |
host and port name lookup
R=r,presotto
DELTA=1239 (935 added, 281 deleted, 23 changed)
OCL=21041
CL=21539
Diffstat (limited to 'src/lib/net/dnsclient.go')
-rw-r--r-- | src/lib/net/dnsclient.go | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/src/lib/net/dnsclient.go b/src/lib/net/dnsclient.go new file mode 100644 index 000000000..a447d3e91 --- /dev/null +++ b/src/lib/net/dnsclient.go @@ -0,0 +1,215 @@ +// 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. + +// DNS client. +// 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. +// More substantial error reporting. +// Remove use of fmt? + +package net + +import ( + "fmt"; + "io"; + "net"; + "once"; + "os"; + "strings"; +) + +export var ( + DNS_InternalError = os.NewError("internal dns error"); + DNS_MissingConfig = os.NewError("no dns configuration"); + DNS_NoAnswer = os.NewError("dns got no answer"); + DNS_BadRequest = os.NewError("malformed dns request"); + DNS_BadReply = os.NewError("malformed dns reply"); + DNS_ServerFailure = os.NewError("dns server failure"); + DNS_NoServers = os.NewError("no dns servers"); + DNS_NameTooLong = os.NewError("dns name too long"); + DNS_RedirectLoop = os.NewError("dns redirect loop"); + DNS_NameNotFound = os.NewError("dns name not found"); +); + +// Send a request on the connection and hope for a reply. +// Up to cfg.attempts attempts. +func Exchange(cfg *DNS_Config, c Conn, name string) (m *DNS_Msg, err *os.Error) { + if len(name) >= 256 { + return nil, DNS_NameTooLong + } + out := new(DNS_Msg); + out.id = 0x1234; + out.question = &[]DNS_Question{ + DNS_Question{ name, DNS_TypeA, DNS_ClassINET } + }; + out.recursion_desired = true; + msg, ok := out.Pack(); + if !ok { + return nil, DNS_InternalError + } + + for attempt := 0; attempt < cfg.attempts; attempt++ { + n, err := c.Write(msg); + if err != nil { + return nil, err + } + + // TODO(rsc): set up timeout or call ReadTimeout. + // right now net does not support that. + + buf := new([]byte, 2000); // More than enough. + n, err = c.Read(buf); + if err != nil { + // TODO(rsc): only continue if timed out + continue + } + buf = buf[0:n]; + in := new(DNS_Msg); + if !in.Unpack(buf) || in.id != out.id { + continue + } + return in, nil + } + return nil, DNS_NoAnswer +} + +// Find answer for name in dns message. +// On return, if err == nil, addrs != nil. +// TODO(rsc): Maybe return *[]*[]byte (==*[]IPAddr) instead? +func Answer(name string, dns *DNS_Msg) (addrs *[]string, err *os.Error) { + addrs = new([]string, len(dns.answer))[0:0]; + + if dns.rcode == DNS_RcodeNameError && dns.authoritative { + return nil, DNS_NameNotFound // authoritative "no such host" + } + if dns.rcode != DNS_RcodeSuccess { + // None of the error codes make sense + // for the query we sent. If we didn't get + // a name error and we didn't get success, + // the server is behaving incorrectly. + return nil, DNS_ServerFailure + } + + // Look for the name. + // Presotto says it's okay to assume that servers listed in + // /etc/resolv.conf are recursive resolvers. + // We asked for recursion, so it should have included + // all the answers we need in this one packet. +Cname: + for cnameloop := 0; cnameloop < 10; cnameloop++ { + addrs = addrs[0:0]; + for i := 0; i < len(dns.answer); i++ { + rr := dns.answer[i]; + h := rr.Header(); + if h.class == DNS_ClassINET && h.name == name { + switch h.rrtype { + case DNS_TypeA: + n := len(addrs); + a := rr.(*DNS_RR_A).a; + addrs = addrs[0:n+1]; + addrs[n] = fmt.sprintf("%d.%d.%d.%d", (a>>24), (a>>16)&0xFF, (a>>8)&0xFF, a&0xFF); + case DNS_TypeCNAME: + // redirect to cname + name = rr.(*DNS_RR_CNAME).cname; + continue Cname + } + } + } + if len(addrs) == 0 { + return nil, DNS_NameNotFound + } + return addrs, nil + } + + // Too many redirects + return nil, DNS_RedirectLoop +} + +// Do a lookup for a single name, which must be rooted +// (otherwise Answer will not find the answers). +func TryOneName(cfg *DNS_Config, name string) (addrs *[]string, err *os.Error) { + err = DNS_NoServers; + 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. + c, cerr := Dial("udp", "", cfg.servers[i] + ":53"); + if cerr != nil { + err = cerr; + continue; + } + msg, merr := Exchange(cfg, c, name); + c.Close(); + if merr != nil { + err = merr; + continue; + } + addrs, aerr := Answer(name, msg); + if aerr != nil && aerr != DNS_NameNotFound { + err = aerr; + continue; + } + return addrs, aerr; + } + return; +} + +var cfg *DNS_Config + +func LoadConfig() { + cfg = DNS_ReadConfig(); +} + +export func LookupHost(name string) (name1 string, addrs *[]string, err *os.Error) { + // TODO(rsc): Pick out obvious non-DNS names to avoid + // sending stupid requests to the server? + + once.Do(&LoadConfig); + if cfg == nil { + err = DNS_MissingConfig; + 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 || strings.count(name, ".") >= cfg.ndots { + rname := name; + if !rooted { + rname += "."; + } + // Can try as ordinary name. + addrs, aerr := TryOneName(cfg, rname); + if aerr == nil { + return rname, addrs, nil; + } + err = aerr; + } + if rooted { + return + } + + // Otherwise, try suffixes. + for i := 0; i < len(cfg.search); i++ { + newname := name+"."+cfg.search[i]; + if newname[len(newname)-1] != '.' { + newname += "." + } + addrs, aerr := TryOneName(cfg, newname); + if aerr == nil { + return newname, addrs, nil; + } + err = aerr; + } + return +} |