* Module name changed to `github.com/rajch/weave` * Added environment variable `CHECKPOINT_DISABLE=1` to default manifests, to bypass weaveworks telemetry
356 lines
8.1 KiB
Go
356 lines
8.1 KiB
Go
package nameserver
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/rajch/weave/net/address"
|
|
)
|
|
|
|
const (
|
|
topDomain = "."
|
|
reverseDNSdomain = "in-addr.arpa."
|
|
udpBuffSize = uint16(4096)
|
|
minUDPSize = 512
|
|
|
|
DefaultListenAddress = "0.0.0.0:53"
|
|
DefaultTTL = 1
|
|
DefaultClientTimeout = 5 * time.Second
|
|
)
|
|
|
|
type Upstream interface {
|
|
Config() (*dns.ClientConfig, error)
|
|
}
|
|
|
|
func NewUpstream(resolvConf, filterAddress string) Upstream {
|
|
return &upstream{
|
|
resolvConf: resolvConf,
|
|
filterAddress: filterAddress,
|
|
cachedConfig: &dns.ClientConfig{}}
|
|
}
|
|
|
|
type upstream struct {
|
|
sync.Mutex
|
|
|
|
resolvConf string
|
|
filterAddress string
|
|
cachedConfig *dns.ClientConfig
|
|
lastStat time.Time
|
|
lastModified time.Time
|
|
}
|
|
|
|
func (u *upstream) Config() (*dns.ClientConfig, error) {
|
|
u.Lock()
|
|
defer u.Unlock()
|
|
|
|
// If we checked less than five seconds ago return what we already have
|
|
now := time.Now()
|
|
if now.Sub(u.lastStat) < 5*time.Second {
|
|
return u.cachedConfig, nil
|
|
}
|
|
u.lastStat = now
|
|
|
|
// Reread file if it has changed
|
|
if fi, err := os.Stat(u.resolvConf); err != nil {
|
|
return u.cachedConfig, err
|
|
} else if fi.ModTime() != u.lastModified {
|
|
if config, err := dns.ClientConfigFromFile(u.resolvConf); err == nil {
|
|
config.Servers = filter(config.Servers, u.filterAddress)
|
|
u.lastModified = fi.ModTime()
|
|
u.cachedConfig = config
|
|
} else {
|
|
return u.cachedConfig, err
|
|
}
|
|
}
|
|
|
|
return u.cachedConfig, nil
|
|
}
|
|
|
|
func filter(ss []string, s string) []string {
|
|
for i := 0; i < len(ss); {
|
|
if ss[i] == s {
|
|
ss = append(ss[:i], ss[i+1:]...)
|
|
continue
|
|
}
|
|
i++
|
|
}
|
|
return ss
|
|
}
|
|
|
|
type DNSServer struct {
|
|
ns *Nameserver
|
|
domain string
|
|
ttl uint32
|
|
address string // listen address and port, e.g. 1.2.3.4:53
|
|
|
|
servers []*dns.Server
|
|
upstream Upstream
|
|
tcpClient *dns.Client
|
|
udpClient *dns.Client
|
|
}
|
|
|
|
func NewDNSServer(ns *Nameserver, domain, address string, upstream Upstream, ttl uint32, clientTimeout time.Duration) (*DNSServer, error) {
|
|
if _, _, err := net.SplitHostPort(address); err != nil {
|
|
return nil, fmt.Errorf("invalid DNS listen address %q: %v", address, err)
|
|
}
|
|
|
|
s := &DNSServer{
|
|
ns: ns,
|
|
domain: dns.Fqdn(domain),
|
|
ttl: ttl,
|
|
address: address,
|
|
upstream: upstream,
|
|
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
|
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
|
|
}
|
|
|
|
err := s.listen(address)
|
|
return s, err
|
|
}
|
|
|
|
func (d *DNSServer) String() string {
|
|
var buf bytes.Buffer
|
|
fmt.Fprintf(&buf, "WeaveDNS (%s)\n", d.ns.ourName)
|
|
fmt.Fprintf(&buf, " listening on %s, for domain %s\n", d.address, d.domain)
|
|
fmt.Fprintf(&buf, " response ttl %d\n", d.ttl)
|
|
return buf.String()
|
|
}
|
|
|
|
func (d *DNSServer) listen(address string) error {
|
|
udpListener, err := net.ListenPacket("udp", address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
udpServer := &dns.Server{PacketConn: udpListener, Handler: d.createMux(d.udpClient, minUDPSize)}
|
|
|
|
tcpListener, err := net.Listen("tcp", address)
|
|
if err != nil {
|
|
udpServer.Shutdown()
|
|
return err
|
|
}
|
|
tcpServer := &dns.Server{Listener: tcpListener, Handler: d.createMux(d.tcpClient, -1)}
|
|
|
|
d.servers = []*dns.Server{udpServer, tcpServer}
|
|
return nil
|
|
}
|
|
|
|
func (d *DNSServer) ActivateAndServe() {
|
|
for _, server := range d.servers {
|
|
go func(server *dns.Server) {
|
|
server.ActivateAndServe()
|
|
}(server)
|
|
}
|
|
}
|
|
|
|
func (d *DNSServer) Stop() error {
|
|
for _, server := range d.servers {
|
|
if err := server.Shutdown(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type handler struct {
|
|
*DNSServer
|
|
maxResponseSize int
|
|
client *dns.Client
|
|
}
|
|
|
|
func (d *DNSServer) createMux(client *dns.Client, defaultMaxResponseSize int) *dns.ServeMux {
|
|
m := dns.NewServeMux()
|
|
h := &handler{
|
|
DNSServer: d,
|
|
maxResponseSize: defaultMaxResponseSize,
|
|
client: client,
|
|
}
|
|
m.HandleFunc(d.domain, h.handleLocal)
|
|
m.HandleFunc(reverseDNSdomain, h.handleReverse)
|
|
m.HandleFunc(topDomain, h.handleRecursive)
|
|
return m
|
|
}
|
|
|
|
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
|
h.ns.debugf("local request: %+v", *req)
|
|
if len(req.Question) != 1 {
|
|
h.nameError(w, req)
|
|
return
|
|
}
|
|
|
|
hostname := dns.Fqdn(req.Question[0].Name)
|
|
if strings.Count(hostname, ".") == 1 {
|
|
hostname = hostname + h.domain
|
|
}
|
|
|
|
addrs := h.ns.Lookup(hostname)
|
|
if len(addrs) == 0 {
|
|
h.nameError(w, req)
|
|
return
|
|
}
|
|
// Per RFC4074, if we have an A but another type was requested,
|
|
// return 'no error' with empty answer section
|
|
if req.Question[0].Qtype != dns.TypeA {
|
|
h.respond(w, h.makeResponse(req, nil))
|
|
return
|
|
}
|
|
|
|
header := dns.RR_Header{
|
|
Name: req.Question[0].Name,
|
|
Rrtype: dns.TypeA,
|
|
Class: dns.ClassINET,
|
|
Ttl: h.ttl,
|
|
}
|
|
answers := make([]dns.RR, len(addrs))
|
|
for i, addr := range addrs {
|
|
ip := addr.IP4()
|
|
answers[i] = &dns.A{Hdr: header, A: ip}
|
|
}
|
|
shuffleAnswers(&answers)
|
|
|
|
h.respond(w, h.makeResponse(req, answers))
|
|
}
|
|
|
|
func (h *handler) handleReverse(w dns.ResponseWriter, req *dns.Msg) {
|
|
h.ns.debugf("reverse request: %+v", *req)
|
|
if len(req.Question) != 1 || req.Question[0].Qtype != dns.TypePTR {
|
|
h.nameError(w, req)
|
|
return
|
|
}
|
|
|
|
ipStr := strings.TrimSuffix(strings.ToLower(req.Question[0].Name), "."+reverseDNSdomain)
|
|
ip, err := address.ParseIP(ipStr)
|
|
if err != nil {
|
|
h.nameError(w, req)
|
|
return
|
|
}
|
|
|
|
hostname, err := h.ns.ReverseLookup(ip.Reverse())
|
|
if err != nil {
|
|
h.handleRecursive(w, req)
|
|
return
|
|
}
|
|
|
|
header := dns.RR_Header{
|
|
Name: req.Question[0].Name,
|
|
Rrtype: dns.TypePTR,
|
|
Class: dns.ClassINET,
|
|
Ttl: h.ttl,
|
|
}
|
|
answers := []dns.RR{&dns.PTR{
|
|
Hdr: header,
|
|
Ptr: hostname,
|
|
}}
|
|
|
|
h.respond(w, h.makeResponse(req, answers))
|
|
}
|
|
|
|
func (h *handler) handleRecursive(w dns.ResponseWriter, req *dns.Msg) {
|
|
h.ns.debugf("recursive request: %+v", *req)
|
|
|
|
// Resolve unqualified names locally
|
|
if len(req.Question) == 1 {
|
|
hostname := dns.Fqdn(req.Question[0].Name)
|
|
if strings.Count(hostname, ".") == 1 {
|
|
h.handleLocal(w, req)
|
|
return
|
|
}
|
|
}
|
|
|
|
upstreamConfig, err := h.upstream.Config()
|
|
if err != nil {
|
|
h.ns.errorf("unable to read upstream config: %s", err)
|
|
}
|
|
for _, server := range upstreamConfig.Servers {
|
|
reqCopy := req.Copy()
|
|
reqCopy.Id = dns.Id()
|
|
response, _, err := h.client.Exchange(reqCopy, fmt.Sprintf("%s:%s", server, upstreamConfig.Port))
|
|
if (err != nil) || response == nil {
|
|
h.ns.debugf("error trying %s: %v", server, err)
|
|
continue
|
|
}
|
|
response.Id = req.Id
|
|
if h.responseTooBig(req, response) {
|
|
response.Compress = true
|
|
}
|
|
h.respond(w, response)
|
|
return
|
|
}
|
|
|
|
h.respond(w, h.makeErrorResponse(req, dns.RcodeServerFailure))
|
|
}
|
|
|
|
func (h *handler) makeResponse(req *dns.Msg, answers []dns.RR) *dns.Msg {
|
|
response := &dns.Msg{}
|
|
response.SetReply(req)
|
|
response.RecursionAvailable = true
|
|
response.Authoritative = true
|
|
response.Answer = answers
|
|
if !h.responseTooBig(req, response) {
|
|
return response
|
|
}
|
|
|
|
// search for smallest i that is too big
|
|
maxSize := h.getMaxResponseSize(req)
|
|
i := sort.Search(len(answers), func(i int) bool {
|
|
// return true if too big
|
|
response.Answer = answers[:i+1]
|
|
return response.Len() > maxSize
|
|
})
|
|
|
|
response.Answer = answers[:i]
|
|
if i < len(answers) {
|
|
response.Truncated = true
|
|
}
|
|
return response
|
|
}
|
|
|
|
func (h *handler) makeErrorResponse(req *dns.Msg, code int) *dns.Msg {
|
|
response := &dns.Msg{}
|
|
response.SetReply(req)
|
|
response.RecursionAvailable = true
|
|
response.Rcode = code
|
|
return response
|
|
}
|
|
|
|
func (h *handler) responseTooBig(req, response *dns.Msg) bool {
|
|
return len(response.Answer) > 1 && h.maxResponseSize > 0 && response.Len() > h.getMaxResponseSize(req)
|
|
}
|
|
|
|
func (h *handler) respond(w dns.ResponseWriter, response *dns.Msg) {
|
|
h.ns.debugf("response: %+v", response)
|
|
if err := w.WriteMsg(response); err != nil {
|
|
h.ns.infof("error responding: %v", err)
|
|
}
|
|
}
|
|
|
|
func (h *handler) nameError(w dns.ResponseWriter, req *dns.Msg) {
|
|
h.respond(w, h.makeErrorResponse(req, dns.RcodeNameError))
|
|
}
|
|
|
|
func (h *handler) getMaxResponseSize(req *dns.Msg) int {
|
|
if opt := req.IsEdns0(); opt != nil {
|
|
return int(opt.UDPSize())
|
|
}
|
|
return h.maxResponseSize
|
|
}
|
|
|
|
func shuffleAnswers(answers *[]dns.RR) {
|
|
if len(*answers) <= 1 {
|
|
return
|
|
}
|
|
|
|
for i := range *answers {
|
|
j := rand.Intn(i + 1)
|
|
(*answers)[i], (*answers)[j] = (*answers)[j], (*answers)[i]
|
|
}
|
|
}
|