Files
weave/nameserver/server.go
T

340 lines
9.4 KiB
Go

package nameserver
import (
"bytes"
"fmt"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
)
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
)
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, nil)
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(proto))
m.HandleFunc(RDNSDomain, s.rdnsHandler(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)
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(proto dnsProtocol) dns.HandlerFunc {
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
ips, err := lookup.LookupName(q.Name)
if err != nil {
return nil, nil, err
}
return makeAddressReply(r, q, ips), ips, nil
}
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(makeDNSFailResponse(r))
}
return s.commonQueryHandler(proto, "Query", dns.TypeA, zoneLookup, fallback)
}
func (s *DNSServer) rdnsHandler(proto dnsProtocol) dns.HandlerFunc {
zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
names, err := lookup.LookupInaddr(q.Name)
if err != nil {
return nil, nil, err
}
return makePTRReply(r, q, names), names, nil
}
notUsHandler := s.notUsHandler(proto)
fallback := func(w dns.ResponseWriter, r *dns.Msg) {
Info.Printf("[dns msgid %d] -> sending to fallback server", r.MsgHdr.Id)
notUsHandler(w, r)
}
return s.commonQueryHandler(proto, "Reverse query", dns.TypePTR, zoneLookup, fallback)
}
func (s *DNSServer) commonQueryHandler(proto dnsProtocol, kind string, qtype uint16,
zoneLookup func(ZoneLookup, *dns.Question, *dns.Msg) (*dns.Msg, []ZoneRecord, error), fallback dns.HandlerFunc) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
maxLen := getMaxReplyLen(r, proto)
lookups := []ZoneLookup{s.Zone, s.mdnsCli}
reply, err := s.cache.Get(r, maxLen)
if err != nil {
if err == errNoLocalReplies {
Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
lookups = []ZoneLookup{s.Zone}
} 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
}
// catch unsupported queries
if q.Qtype != qtype {
Debug.Printf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSFailResponse(r)
s.cache.Put(r, m, negLocalTTL, 0)
w.WriteMsg(m)
return
}
for _, lookup := range lookups {
if m, answers, err := zoneLookup(lookup, &q, r); err == nil {
m.Authoritative = true
Debug.Printf("[dns msgid %d] Caching response for type %s query for '%s': %s [code:%s]",
m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, answers, dns.RcodeToString[m.Rcode])
s.cache.Put(r, m, nullTTL, 0)
w.WriteMsg(m)
return
}
}
Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies)
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))
}
}