Files
weave/nameserver/server.go
T
Matthias Radestock 10f37db9da tidy up DNSServer fields
- make Zone, Iface and Upstream public - this is safe and makes sense
  since all are immutable, and the former two are passed to the
  constructor and are thus known anyway, and the latter is derived
  from external information
- remove IfaceName - we can just use Iface instead
- log Upstream on startup
2015-03-31 12:04:31 +01:00

353 lines
9.6 KiB
Go

package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/zettio/weave/common"
"net"
"sync"
"time"
)
const (
DefaultLocalDomain = "weave.local." // The default name used for the local domain
DefaultServerPort = 53 // The default server port
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
DefaultUDPBuflen = 4096 // bigger than the default 512
DefaultCacheLen = 8192 // default cache capacity
DefaultResolvWorkers = 8 // default number of resolution workers
DefaultTimeout = 5 // default timeout for DNS resolutions
DefaultResolvTries = 3 // max # of times a worker tries to resolve a query
)
type DNSServerConfig struct {
// (Optional) client config file for resolving upstream servers
UpstreamCfgFile string
// (Optional) DNS client config for the fallback server(s)
UpstreamCfg *dns.ClientConfig
// (Optional) port number (for TCP and UDP)
Port int
// (Optional) local domain (ie, "weave.local.")
LocalDomain string
// (Optional) cache size
CacheLen int
// (Optional) timeout for DNS queries
Timeout int
// (Optional) UDP buffer length
UDPBufLen int
}
type dnsProtocol uint8
const (
protUDP dnsProtocol = iota // UDP protocol
protTCP dnsProtocol = iota // TCP protocol
)
func (proto dnsProtocol) String() string {
switch proto {
case protUDP:
return "UDP"
case protTCP:
return "TCP"
}
return "unknown"
}
// get a new dns.Client for a protocol
func (proto dnsProtocol) GetNewClient(bufsize int) *dns.Client {
switch proto {
case protTCP:
return &dns.Client{Net: "tcp"}
case protUDP:
return &dns.Client{Net: "udp", UDPSize: uint16(bufsize)}
}
return nil
}
type DNSServer struct {
Zone Zone
Iface *net.Interface
Upstream *dns.ClientConfig
udpSrv *dns.Server
tcpSrv *dns.Server
mdnsCli *MDNSClient
mdnsSrv *MDNSServer
cache *Cache
timeout int
udpBuf int
listenersWg *sync.WaitGroup
Domain string // the local domain
ListenAddr string // the address the server is listening at
}
// Creates a new DNS server
func NewDNSServer(config DNSServerConfig, zone Zone, iface *net.Interface) (s *DNSServer, err error) {
s = &DNSServer{
Zone: zone,
Iface: iface,
listenersWg: new(sync.WaitGroup),
Domain: DefaultLocalDomain,
ListenAddr: fmt.Sprintf(":%d", DefaultServerPort),
}
// fill empty parameters with defaults...
if config.Port != 0 {
s.ListenAddr = fmt.Sprintf(":%d", config.Port)
}
if len(config.LocalDomain) > 0 {
s.Domain = config.LocalDomain
}
if config.UpstreamCfg != nil {
s.Upstream = config.UpstreamCfg
} else {
cfgFile := DefaultCLICfgFile
if len(config.UpstreamCfgFile) > 0 {
cfgFile = config.UpstreamCfgFile
}
if s.Upstream, err = dns.ClientConfigFromFile(cfgFile); err != nil {
return nil, err
}
}
if config.Timeout > 0 {
s.timeout = config.Timeout
}
if config.UDPBufLen > 0 {
s.udpBuf = config.UDPBufLen
}
s.mdnsCli, err = NewMDNSClient()
if err != nil {
return
}
s.mdnsSrv, err = NewMDNSServer(s.Zone)
if err != nil {
return
}
cacheLen := DefaultCacheLen
if config.CacheLen > 0 {
cacheLen = config.CacheLen
}
Debug.Printf("[dns] Initializing cache: %d entries", config.CacheLen)
s.cache, err = NewCache(cacheLen)
if err != nil {
return
}
// create two DNS request multiplexerers, depending on the protocol used by clients
// (we use the same protocol for asking upstream servers)
mux := func(proto dnsProtocol) *dns.ServeMux {
m := dns.NewServeMux()
m.HandleFunc(s.Domain, s.queryHandler([]Lookup{s.Zone, s.mdnsCli}, proto))
m.HandleFunc(RDNSDomain, s.rdnsHandler([]Lookup{s.Zone, s.mdnsCli}, proto))
m.HandleFunc(".", s.notUsHandler(proto))
return m
}
s.udpSrv = &dns.Server{Addr: s.ListenAddr, Net: "udp", Handler: mux(protUDP)}
s.tcpSrv = &dns.Server{Addr: s.ListenAddr, Net: "tcp", Handler: mux(protTCP)}
return
}
// Start the DNS server
func (s *DNSServer) Start() error {
Info.Printf("Using mDNS on %v", s.Iface)
err := s.mdnsCli.Start(s.Iface)
CheckFatal(err)
err = s.mdnsSrv.Start(s.Iface, s.Domain)
CheckFatal(err)
s.listenersWg.Add(2)
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (UDP)", s.ListenAddr)
err = s.udpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS UDP server exiting...")
}()
go func() {
defer s.listenersWg.Done()
Debug.Printf("Listening for DNS on %s (TCP)", s.ListenAddr)
err = s.tcpSrv.ListenAndServe()
CheckFatal(err)
Debug.Printf("DNS TCP server exiting...")
}()
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
s.listenersWg.Wait()
Info.Printf("WeaveDNS server exiting...")
return nil
}
// Return status string
func (s *DNSServer) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Local domain", s.Domain)
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
fmt.Fprintln(&buf, "mDNS interface", s.Iface)
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
fmt.Fprintf(&buf, "Zone database:\n%s", s.Zone)
return buf.String()
}
// Perform a graceful shutdown
func (s *DNSServer) Stop() error {
// Stop the listeners/handlers
if err := s.tcpSrv.Shutdown(); err != nil {
return err
}
if err := s.udpSrv.Shutdown(); err != nil {
return err
}
s.listenersWg.Wait()
// shutdown the mDNS server
s.mdnsSrv.Stop()
return nil
}
func (s *DNSServer) queryHandler(lookups []Lookup, proto dnsProtocol) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
now := time.Now()
q := r.Question[0]
maxLen := getMaxReplyLen(r, proto)
Debug.Printf("Query: %+v", q)
if q.Qtype != dns.TypeA {
Debug.Printf("[dns msgid %d] Unsuported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSNotImplResponse(r)
s.cache.Put(r, m, 0, now)
w.WriteMsg(m)
return
}
reply, err := s.cache.Get(r, maxLen, time.Now())
if err != nil {
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
w.WriteMsg(makeDNSFailResponse(r))
return
}
if reply != nil {
Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
w.WriteMsg(reply)
return
}
for _, lookup := range lookups {
if ip, err := lookup.LookupName(q.Name); err == nil {
m := makeAddressReply(r, &q, []net.IP{ip})
Debug.Printf("[dns msgid %d] Caching response for %s-query for \"%s\": %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, ip, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, 0, now)
w.WriteMsg(m)
return
}
now = time.Now()
}
Info.Printf("[dns msgid %d] No results for type %s query %s",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
m := makeDNSFailResponse(r)
s.cache.Put(r, m, 0, now)
w.WriteMsg(m)
}
}
func (s *DNSServer) rdnsHandler(lookups []Lookup, proto dnsProtocol) dns.HandlerFunc {
fallback := s.notUsHandler(proto)
return func(w dns.ResponseWriter, r *dns.Msg) {
now := time.Now()
q := r.Question[0]
maxLen := getMaxReplyLen(r, proto)
Debug.Printf("Reverse query: %+v", q)
if q.Qtype != dns.TypePTR {
Warning.Printf("[dns msgid %d] Unexpected reverse query type %s: %+v",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q)
m := makeDNSNotImplResponse(r)
s.cache.Put(r, m, 0, now)
w.WriteMsg(m)
return
}
reply, err := s.cache.Get(r, maxLen, time.Now())
if err != nil {
if err == errNoLocalReplies {
Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
fallback(w, r)
} else {
Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
w.WriteMsg(makeDNSFailResponse(r))
}
return
}
if reply != nil {
Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
w.WriteMsg(reply)
return
}
for _, lookup := range lookups {
if name, err := lookup.LookupInaddr(q.Name); err == nil {
m := makePTRReply(r, &q, []string{name})
Debug.Printf("[dns msgid %d] Caching response for %s-query for \"%s\": %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, name, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, 0, now)
w.WriteMsg(m)
return
}
now = time.Now()
}
s.cache.Put(r, nil, CacheNoLocalReplies, now)
fallback(w, r)
}
}
// When we receive a request for a name outside of our '.weave.local.'
// domain, ask the configured DNS server as a fallback.
func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
dnsClient := proto.GetNewClient(DefaultUDPBuflen)
return func(w dns.ResponseWriter, r *dns.Msg) {
maxLen := getMaxReplyLen(r, proto)
q := r.Question[0]
// create a request where we announce our max payload size
rcopy := r
rcopy.SetEdns0(uint16(maxLen), false)
Debug.Printf("[dns msgid %d] Fallback query: %+v [%s, max:%d bytes]", rcopy.MsgHdr.Id, q, proto, maxLen)
for _, server := range s.Upstream.Servers {
reply, _, err := dnsClient.Exchange(rcopy, fmt.Sprintf("%s:%s", server, s.Upstream.Port))
if err != nil {
Debug.Printf("[dns msgid %d] Network error trying %s (%s)",
r.MsgHdr.Id, server, err)
continue
}
if reply != nil && reply.Rcode != dns.RcodeSuccess {
Debug.Printf("[dns msgid %d] Failure reported by %s for query %s",
r.MsgHdr.Id, server, q.Name)
continue
}
Debug.Printf("[dns msgid %d] Given answer by %s for query %s",
r.MsgHdr.Id, server, q.Name)
w.WriteMsg(reply)
return
}
Warning.Printf("[dns msgid %d] Failed lookup for external name %s",
r.MsgHdr.Id, q.Name)
w.WriteMsg(makeDNSFailResponse(r))
}
}