package net
import (
"os"
"rand"
"sync"
"time"
)
type DNSError struct {
Error string
Name string
Server string
IsTimeout bool
}
func (e *DNSError) String() string {
if e == nil {
return "<nil>"
}
s := "lookup " + e.Name
if e.Server != "" {
s += " on " + e.Server
}
s += ": " + e.Error
return s
}
func (e *DNSError) Timeout() bool { return e.IsTimeout }
func (e *DNSError) Temporary() bool { return e.IsTimeout }
const noSuchHost = "no such host"
func exchange(cfg *dnsConfig, c Conn, name string, qtype uint16) (*dnsMsg, os.Error) {
if len(name) >= 256 {
return nil, &DNSError{Error: "name too long", Name: name}
}
out := new(dnsMsg)
out.id = uint16(rand.Int()) ^ uint16(time.Nanoseconds())
out.question = []dnsQuestion{
dnsQuestion{name, qtype, dnsClassINET},
}
out.recursion_desired = true
msg, ok := out.Pack()
if !ok {
return nil, &DNSError{Error: "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
}
c.SetReadTimeout(int64(cfg.timeout) * 1e9)
buf := make([]byte, 2000)
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{Error: "no answer from server", Name: name, Server: server, IsTimeout: true}
}
func answer(name, server string, dns *dnsMsg, qtype uint16) (addrs []dnsRR, err os.Error) {
addrs = make([]dnsRR, 0, len(dns.answer))
if dns.rcode == dnsRcodeNameError && dns.recursion_available {
return nil, &DNSError{Error: noSuchHost, Name: name}
}
if dns.rcode != dnsRcodeSuccess {
return nil, &DNSError{Error: "server misbehaving", Name: name, Server: server}
}
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 == dnsClassINET && h.Name == name {
switch h.Rrtype {
case qtype:
n := len(addrs)
addrs = addrs[0 : n+1]
addrs[n] = rr
case dnsTypeCNAME:
name = rr.(*dnsRR_CNAME).Cname
continue Cname
}
}
}
if len(addrs) == 0 {
return nil, &DNSError{Error: noSuchHost, Name: name, Server: server}
}
return addrs, nil
}
return nil, &DNSError{Error: "too many redirects", Name: name, Server: server}
}
func tryOneName(cfg *dnsConfig, name string, qtype uint16) (addrs []dnsRR, err os.Error) {
if len(cfg.servers) == 0 {
return nil, &DNSError{Error: "no DNS servers", Name: name}
}
for i := 0; i < len(cfg.servers); i++ {
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
}
addrs, err = answer(name, server, msg, qtype)
if err == nil || err.(*DNSError).Error == noSuchHost {
break
}
}
return
}
func convertRR_A(records []dnsRR) []string {
addrs := make([]string, len(records))
for i := 0; i < len(records); i++ {
rr := records[i]
a := rr.(*dnsRR_A).A
addrs[i] = IPv4(byte(a>>24), byte(a>>16), byte(a>>8), byte(a)).String()
}
return addrs
}
var cfg *dnsConfig
var dnserr os.Error
func loadConfig() { cfg, dnserr = dnsReadConfig() }
func isDomainName(s string) bool {
if len(s) == 0 {
return false
}
if s[len(s)-1] != '.' {
s += "."
}
last := byte('.')
ok := false
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
ok = true
case '0' <= c && c <= '9':
case c == '-':
if last == '.' {
return false
}
case c == '.':
if last == '.' || last == '-' {
return false
}
}
last = c
}
return ok
}
var onceLoadConfig sync.Once
func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err os.Error) {
if !isDomainName(name) {
return name, nil, &DNSError{Error: "invalid domain name", Name: name}
}
onceLoadConfig.Do(loadConfig)
if dnserr != nil || cfg == nil {
err = dnserr
return
}
rooted := len(name) > 0 && name[len(name)-1] == '.'
if rooted || count(name, '.') >= cfg.ndots {
rname := name
if !rooted {
rname += "."
}
addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
cname = rname
return
}
}
if rooted {
return
}
for i := 0; i < len(cfg.search); i++ {
rname := name + "." + cfg.search[i]
if rname[len(rname)-1] != '.' {
rname += "."
}
addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
cname = rname
return
}
}
rname := name
if !rooted {
rname += "."
}
addrs, err = tryOneName(cfg, rname, qtype)
if err == nil {
cname = rname
return
}
return
}
func LookupHost(name string) (cname string, addrs []string, err os.Error) {
onceLoadConfig.Do(loadConfig)
if dnserr != nil || cfg == nil {
err = dnserr
return
}
addrs = lookupStaticHost(name)
if len(addrs) > 0 {
cname = name
return
}
var records []dnsRR
cname, records, err = lookup(name, dnsTypeA)
if err != nil {
return
}
addrs = convertRR_A(records)
return
}
type SRV struct {
Target string
Port uint16
Priority uint16
Weight uint16
}
func LookupSRV(name string) (cname string, addrs []*SRV, err os.Error) {
var records []dnsRR
cname, records, err = lookup(name, dnsTypeSRV)
if err != nil {
return
}
addrs = make([]*SRV, len(records))
for i := 0; i < len(records); i++ {
r := records[i].(*dnsRR_SRV)
addrs[i] = &SRV{r.Target, r.Port, r.Priority, r.Weight}
}
return
}
type MX struct {
Host string
Pref uint16
}
func LookupMX(name string) (entries []*MX, err os.Error) {
var records []dnsRR
_, records, err = lookup(name, dnsTypeMX)
if err != nil {
return
}
entries = make([]*MX, len(records))
for i := 0; i < len(records); i++ {
r := records[i].(*dnsRR_MX)
entries[i] = &MX{r.Mx, r.Pref}
}
return
}