229 lines
6.2 KiB
Go
229 lines
6.2 KiB
Go
package nameserver
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/miekg/dns"
|
|
. "github.com/zettio/weave/common"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
DEFAULT_LOCAL_DOMAIN = "weave.local." // The default name used for the local domain
|
|
DEFAULT_SERVER_PORT = 53 // The default server port
|
|
DEFAULT_CLI_CFG_FILE = "/etc/resolv.conf" // default "resolv.conf" file to try to load
|
|
UDPBufSize = 4096 // bigger than the default 512
|
|
)
|
|
|
|
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.RecursionAvailable = true
|
|
m.Rcode = dns.RcodeNameError
|
|
return m
|
|
}
|
|
|
|
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
|
|
// DNS zone served
|
|
Zone Zone
|
|
// Interface where serving
|
|
Iface *net.Interface
|
|
// (Optional) port number (for TCP and UDP)
|
|
Port int
|
|
// (Optional) local domain (ie, "weave.local.")
|
|
LocalDomain string
|
|
}
|
|
|
|
type DNSServer struct {
|
|
config *DNSServerConfig
|
|
udpSrv *dns.Server
|
|
tcpSrv *dns.Server
|
|
mdnsCli *MDNSClient
|
|
mdnsSrv *MDNSServer
|
|
|
|
IfaceName string // the interface where mDNS is working on
|
|
ListenAddr string // the address the server is listening at
|
|
}
|
|
|
|
// Creates a new DNS server
|
|
func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) {
|
|
// fill empty parameters with defaults...
|
|
if config.Port == 0 {
|
|
config.Port = DEFAULT_SERVER_PORT
|
|
}
|
|
if len(config.LocalDomain) == 0 {
|
|
config.LocalDomain = DEFAULT_LOCAL_DOMAIN
|
|
}
|
|
if len(config.UpstreamCfgFIle) == 0 {
|
|
config.UpstreamCfgFIle = DEFAULT_CLI_CFG_FILE
|
|
}
|
|
if config.UpstreamCfg == nil {
|
|
if config.UpstreamCfg, err = dns.ClientConfigFromFile(config.UpstreamCfgFIle); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
mdnsClient, err := NewMDNSClient()
|
|
if err != nil {
|
|
return
|
|
}
|
|
mdnsServer, err := NewMDNSServer(config.Zone)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ifaceName := "default interface"
|
|
if config.Iface != nil {
|
|
ifaceName = config.Iface.Name
|
|
}
|
|
|
|
return &DNSServer{
|
|
config: &config,
|
|
mdnsCli: mdnsClient,
|
|
mdnsSrv: mdnsServer,
|
|
IfaceName: ifaceName,
|
|
ListenAddr: fmt.Sprintf(":%d", config.Port),
|
|
}, nil
|
|
}
|
|
|
|
// Start the DNS server
|
|
func (s *DNSServer) Start() error {
|
|
Info.Printf("Using mDNS on %s", s.IfaceName)
|
|
err := s.mdnsCli.Start(s.config.Iface)
|
|
CheckFatal(err)
|
|
err = s.mdnsSrv.Start(s.config.Iface, s.config.LocalDomain)
|
|
CheckFatal(err)
|
|
|
|
// create two DNS request multiplexerers, depending on the protocol used by clients
|
|
// (we use the same protocol for asking upstream servers)
|
|
mux := func(client *dns.Client) *dns.ServeMux {
|
|
m := dns.NewServeMux()
|
|
m.HandleFunc(s.config.LocalDomain, s.queryHandler([]Lookup{s.config.Zone, s.mdnsCli}))
|
|
m.HandleFunc(RDNS_DOMAIN, s.rdnsHandler([]Lookup{s.config.Zone, s.mdnsCli}, client))
|
|
m.HandleFunc(".", s.notUsHandler(client))
|
|
return m
|
|
}
|
|
|
|
mdnsServer, err := NewMDNSServer(s.zone)
|
|
CheckFatal(err)
|
|
|
|
err = mdnsServer.Start(s.iface)
|
|
CheckFatal(err)
|
|
|
|
address := fmt.Sprintf(":%d", s.port)
|
|
s.udpSrv = &dns.Server{Addr: address, Net: "udp", Handler: mux(&dns.Client{Net: "udp", UDPSize: UDPBufSize})}
|
|
s.tcpSrv = &dns.Server{Addr: address, Net: "tcp", Handler: mux(&dns.Client{Net: "tcp"})}
|
|
|
|
wg := new(sync.WaitGroup)
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
Debug.Printf("Listening for DNS on %s (UDP)", address)
|
|
err = s.udpSrv.ListenAndServe()
|
|
CheckFatal(err)
|
|
Debug.Printf("DNS UDP server exiting...")
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
Debug.Printf("Listening for DNS on %s (TCP)", address)
|
|
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)
|
|
wg.Wait()
|
|
|
|
Info.Printf("WeaveDNS server exiting...")
|
|
return nil
|
|
}
|
|
|
|
// Perform a graceful shutdown
|
|
func (s *DNSServer) Stop() error {
|
|
if err := s.tcpSrv.Shutdown(); err != nil {
|
|
return err
|
|
}
|
|
if err := s.udpSrv.Shutdown(); err != nil {
|
|
return err
|
|
}
|
|
// TODO: shutdown the mDNS client/server
|
|
return nil
|
|
}
|
|
|
|
func (s *DNSServer) queryHandler(lookups []Lookup) dns.HandlerFunc {
|
|
return func(w dns.ResponseWriter, r *dns.Msg) {
|
|
q := r.Question[0]
|
|
Debug.Printf("Query: %+v", q)
|
|
if q.Qtype == dns.TypeA {
|
|
for _, lookup := range lookups {
|
|
if ip, err := lookup.LookupName(q.Name); err == nil {
|
|
m := makeAddressReply(r, &q, []net.IP{ip})
|
|
w.WriteMsg(m)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
Info.Printf("[dns msgid %d] No results for type %s query %s",
|
|
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
|
|
w.WriteMsg(makeDNSFailResponse(r))
|
|
}
|
|
}
|
|
|
|
func (s *DNSServer) rdnsHandler(lookups []Lookup, client *dns.Client) dns.HandlerFunc {
|
|
fallback := s.notUsHandler(client)
|
|
return func(w dns.ResponseWriter, r *dns.Msg) {
|
|
q := r.Question[0]
|
|
Debug.Printf("Reverse query: %+v", q)
|
|
if q.Qtype == dns.TypePTR {
|
|
for _, lookup := range lookups {
|
|
if name, err := lookup.LookupInaddr(q.Name); err == nil {
|
|
m := makePTRReply(r, &q, []string{name})
|
|
w.WriteMsg(m)
|
|
return
|
|
}
|
|
}
|
|
fallback(w, r)
|
|
return
|
|
}
|
|
Warning.Printf("[dns msgid %d] Unexpected reverse query type %s: %+v",
|
|
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q)
|
|
}
|
|
}
|
|
|
|
// 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(dnsClient *dns.Client) dns.HandlerFunc {
|
|
return func(w dns.ResponseWriter, r *dns.Msg) {
|
|
q := r.Question[0]
|
|
Debug.Printf("[dns msgid %d] Fallback query: %+v", r.MsgHdr.Id, q)
|
|
for _, server := range s.config.UpstreamCfg.Servers {
|
|
reply, _, err := dnsClient.Exchange(r, fmt.Sprintf("%s:%s", server, s.config.UpstreamCfg.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))
|
|
}
|
|
}
|