mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-12 11:29:30 +00:00
424 lines
10 KiB
Go
424 lines
10 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 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
|
|
}
|