Remove legacy WeaveDNS code.
This commit is contained in:
1 parent
f7739ef04c
commit
01f7201a9e
28 files changed
+5
-5503
No files matched your search
@@ -1,4 +1,4 @@
|
||||
PUBLISH=publish_weave publish_weavedns publish_weaveexec
|
||||
PUBLISH=publish_weave publish_weaveexec
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
|
||||
@@ -10,26 +10,23 @@ DOCKERHUB_USER=weaveworks
|
||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEDNS_EXE=prog/weavedns/weavedns
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
NETCHECK_EXE=prog/netcheck/netcheck
|
||||
COVER_EXE=testing/cover/cover
|
||||
|
||||
EXES=$(WEAVER_EXE) $(WEAVEDNS_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(NETCHECK_EXE) $(COVER_EXE)
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(NETCHECK_EXE) $(COVER_EXE)
|
||||
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEDNS_UPTODATE=.weavedns.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEDNS_UPTODATE) $(WEAVEEXEC_UPTODATE)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEDNS_IMAGE=$(DOCKERHUB_USER)/weavedns
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEDNS_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE)
|
||||
|
||||
WEAVE_EXPORT=weave.tar
|
||||
|
||||
@@ -64,13 +61,12 @@ else
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVEDNS_EXE) $(WEAVEPROXY_EXE) $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
$(WEAVEPROXY_EXE) $(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build -ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go prog/weaver/main.go
|
||||
$(WEAVEDNS_EXE): nameserver/*.go prog/weavedns/main.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
|
||||
$(NETCHECK_EXE): prog/netcheck/netcheck.go
|
||||
|
||||
@@ -88,10 +84,6 @@ $(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEDNS_UPTODATE): prog/weavedns/Dockerfile $(WEAVEDNS_EXE)
|
||||
$(SUDO) docker build -t $(WEAVEDNS_IMAGE) prog/weavedns
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(NETCHECK_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
)
|
||||
|
||||
// A callable is a function that is called and return an (optional) next scheduled time
|
||||
type callable func() time.Time
|
||||
|
||||
type schedCall struct {
|
||||
t time.Time
|
||||
c callable
|
||||
}
|
||||
|
||||
func (sc *schedCall) FromNow(now time.Time) time.Duration {
|
||||
if sc.t.After(now) {
|
||||
return time.Duration(sc.t.Sub(now).Nanoseconds())
|
||||
}
|
||||
return time.Duration(0)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// A min-heap of scheduled callables
|
||||
type schedCallsHeap []*schedCall
|
||||
|
||||
func (ch *schedCallsHeap) Len() int { return len(*ch) }
|
||||
func (ch *schedCallsHeap) Less(i, j int) bool { return (*ch)[i].t.Before((*ch)[j].t) }
|
||||
func (ch *schedCallsHeap) Swap(i, j int) {
|
||||
(*ch)[i], (*ch)[j] = (*ch)[j], (*ch)[i]
|
||||
}
|
||||
|
||||
func (ch *schedCallsHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
entry := x.(*schedCall)
|
||||
*ch = append(*ch, entry)
|
||||
}
|
||||
|
||||
func (ch *schedCallsHeap) Pop() interface{} {
|
||||
old := *ch
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
*ch = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SchedQueue is queue of scheduled callables
|
||||
type SchedQueue struct {
|
||||
clock clock.Clock
|
||||
callablesH schedCallsHeap
|
||||
schedChan chan *schedCall
|
||||
closeChan chan bool
|
||||
counter uint64 // number of calls invoked so far (used for stats). Note: it will wrap.
|
||||
}
|
||||
|
||||
// NewSchedQueue creates a new scheduled queue
|
||||
func NewSchedQueue(clock clock.Clock) *SchedQueue {
|
||||
cq := SchedQueue{
|
||||
clock: clock,
|
||||
schedChan: make(chan *schedCall),
|
||||
closeChan: make(chan bool),
|
||||
}
|
||||
heap.Init(&cq.callablesH)
|
||||
return &cq
|
||||
}
|
||||
|
||||
// Start starts the scheduled queue
|
||||
func (cq *SchedQueue) Start() {
|
||||
go func() {
|
||||
defer func() { Log.Debugf("[sched-q] Quitting (%d calls pending)", len(cq.callablesH)) }()
|
||||
|
||||
var now time.Time
|
||||
timer := cq.clock.Timer(time.Duration(math.MaxInt64))
|
||||
|
||||
for {
|
||||
// Wait until an insertion, until the next callable or until we are Stop()ed
|
||||
select {
|
||||
case ce := <-cq.schedChan:
|
||||
heap.Push(&cq.callablesH, ce)
|
||||
now = cq.clock.Now()
|
||||
case <-cq.closeChan:
|
||||
return
|
||||
case now = <-timer.C:
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
durationUntilNext := cq.durationUntilNext(now)
|
||||
for durationUntilNext == 0 {
|
||||
sched := heap.Pop(&cq.callablesH).(*schedCall)
|
||||
|
||||
atomic.AddUint64(&cq.counter, 1)
|
||||
schedNextTime := sched.c()
|
||||
|
||||
if !schedNextTime.IsZero() {
|
||||
sched.t = schedNextTime
|
||||
heap.Push(&cq.callablesH, sched) // TODO: use a Fix() instead of Pop()&Push()
|
||||
}
|
||||
|
||||
now = cq.clock.Now()
|
||||
durationUntilNext = cq.durationUntilNext(now)
|
||||
}
|
||||
|
||||
timer = cq.clock.Timer(durationUntilNext)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops the scheduled queue
|
||||
func (cq *SchedQueue) Stop() {
|
||||
Log.Debugf("[sched-q] Stopping...")
|
||||
cq.closeChan <- true
|
||||
}
|
||||
|
||||
// Add schedules a call.
|
||||
// The callable should not modify the scheduled queue in any way.
|
||||
func (cq *SchedQueue) Add(c callable, t time.Time) {
|
||||
Log.Debugf("[sched-q] Adding call at %s", t)
|
||||
ce := schedCall{c: c, t: t}
|
||||
cq.schedChan <- &ce
|
||||
}
|
||||
|
||||
// Counter returns the number of calls invoked in the queued
|
||||
// Note: the result will wrap over time.
|
||||
func (cq *SchedQueue) Count() uint64 {
|
||||
return atomic.LoadUint64(&cq.counter)
|
||||
}
|
||||
|
||||
func (cq *SchedQueue) durationUntilNext(now time.Time) time.Duration {
|
||||
if len(cq.callablesH) > 0 {
|
||||
firstSched := cq.callablesH[0]
|
||||
return firstSched.FromNow(now)
|
||||
}
|
||||
return time.Duration(math.MaxInt64)
|
||||
}
|
||||
|
||||
// ensure that the background goroutine has executed any pending callbacks
|
||||
func (cq *SchedQueue) Flush() {
|
||||
ch := make(chan struct{})
|
||||
flushFunc := func() time.Time {
|
||||
atomic.AddUint64(&cq.counter, ^uint64(0)) // decrement so nobody sees this
|
||||
close(ch)
|
||||
return time.Time{}
|
||||
}
|
||||
cq.schedChan <- &schedCall{c: flushFunc, t: time.Time{}}
|
||||
<-ch
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Ensure we can add new calls while forwarding the clock
|
||||
func TestSchedCallsBasic(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestSchedCallsBasic starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c := func() time.Time {
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
require.Equal(t, testSecs, (int)(schedQueue.Count()), "Number of calls")
|
||||
}
|
||||
|
||||
// Ensure we can create a 100 seconds gap in the middle of the time travel
|
||||
func TestSchedCallsGap(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestSchedCallsGap starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c2 := func() time.Time {
|
||||
if schedQueue.Count() == testSecs/2 {
|
||||
return clk.Now().Add(time.Duration(100) * time.Second)
|
||||
}
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c2, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
require.Equal(t, testSecs-100+1, (int)(schedQueue.Count()), "Number of calls")
|
||||
}
|
||||
|
||||
func TestSchedCallsStop(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestSchedCallsStop starting")
|
||||
|
||||
const testSecs = 1000
|
||||
clk := clock.NewMock()
|
||||
schedQueue := NewSchedQueue(clk)
|
||||
schedQueue.Start()
|
||||
defer schedQueue.Stop()
|
||||
|
||||
c2 := func() time.Time {
|
||||
if schedQueue.Count() == testSecs/2 {
|
||||
return time.Time{}
|
||||
}
|
||||
return clk.Now().Add(time.Second)
|
||||
}
|
||||
|
||||
schedQueue.Add(c2, clk.Now().Add(time.Second))
|
||||
schedQueue.Flush()
|
||||
for i := 0; i < testSecs; i++ {
|
||||
clk.Add(time.Second)
|
||||
schedQueue.Flush()
|
||||
}
|
||||
|
||||
t.Logf("Now: %s - calls: %d", clk.Now(), schedQueue.Count())
|
||||
require.Equal(t, testSecs/2, (int)(schedQueue.Count()), "Number of calls")
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// A zone record
|
||||
//
|
||||
// Note: all the zone records information is concentrated here. Maybe
|
||||
// not all queries will use things like priorities or weights, but we
|
||||
// do not want to create a hierarchy for dealing with all possible
|
||||
// queries...
|
||||
type ZoneRecord interface {
|
||||
Name() string // The name for this IP
|
||||
IP() net.IP // The IP (v4) address
|
||||
Priority() int // The priority
|
||||
Weight() int // The weight
|
||||
TTL() int // TTL
|
||||
}
|
||||
|
||||
type ZoneLookup interface {
|
||||
// Lookup for a name
|
||||
LookupName(name string) ([]ZoneRecord, error)
|
||||
// Lookup for an address
|
||||
LookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
|
||||
type ZoneLookupFunc func(target string) ([]ZoneRecord, error)
|
||||
|
||||
type ZoneObservable interface {
|
||||
// Observe anything that affects a particular name in the zone
|
||||
ObserveName(name string, observer ZoneRecordObserver) error
|
||||
// Observe anything that affects a particular IP in the zone
|
||||
ObserveInaddr(inaddr string, observer ZoneRecordObserver) error
|
||||
}
|
||||
|
||||
type ZoneObserverFunc func(string, ZoneRecordObserver) error
|
||||
type ZoneRecordObserver func()
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
// A basic record struct (satisfying the ZoneRecord interface)
|
||||
type Record struct {
|
||||
name string
|
||||
ip net.IP
|
||||
priority int
|
||||
weight int
|
||||
ttl int
|
||||
}
|
||||
|
||||
func (r Record) Name() string { return r.name }
|
||||
func (r Record) IP() net.IP { return r.ip }
|
||||
func (r Record) Priority() int { return r.priority }
|
||||
func (r Record) Weight() int { return r.weight }
|
||||
func (r Record) TTL() int { return r.ttl }
|
||||
|
||||
func (r Record) String() string {
|
||||
var buf bytes.Buffer
|
||||
if len(r.Name()) > 0 {
|
||||
fmt.Fprintf(&buf, "%s", r.Name())
|
||||
}
|
||||
if !r.IP().IsUnspecified() {
|
||||
fmt.Fprintf(&buf, "[%s]", r.IP())
|
||||
}
|
||||
if r.Priority() > 0 {
|
||||
fmt.Fprintf(&buf, "/P:%d", r.Priority())
|
||||
}
|
||||
if r.Weight() > 0 {
|
||||
fmt.Fprintf(&buf, "/W:%d", r.Weight())
|
||||
}
|
||||
if r.TTL() > 0 {
|
||||
fmt.Fprintf(&buf, "/TTL:%d", r.TTL())
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
func newParseError(reason string, addr string) *net.ParseError {
|
||||
return &net.ParseError{Type: reason, Text: addr}
|
||||
}
|
||||
|
||||
// simple IPv4 type that can be used as a key in a map (in contrast with net.IP), used for sets of IPs, etc...
|
||||
type IPv4 [4]byte
|
||||
|
||||
func (ip IPv4) toNetIP() net.IP { return net.IPv4(ip[0], ip[1], ip[2], ip[3]) }
|
||||
func (ip IPv4) String() string { return ip.toNetIP().String() }
|
||||
func ipToIPv4(nip net.IP) IPv4 { ip := nip.To4(); return IPv4([4]byte{ip[0], ip[1], ip[2], ip[3]}) }
|
||||
|
||||
// Parse an address (eg, "10.13.12.11") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func addrToIPv4(addr string) (IPv4, error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
return IPv4{}, newParseError("IP address", addr)
|
||||
}
|
||||
return ipToIPv4(ip), nil
|
||||
}
|
||||
|
||||
// Parse a reverse address (eg, "11.12.13.10.in-addr.arpa.") and return the corresponding IPv4 (eg, "10.13.12.11")
|
||||
func raddrToIPv4(addr string) (IPv4, error) {
|
||||
l := len(addr)
|
||||
if l < rdnsDomainLen+1 {
|
||||
return IPv4{}, newParseError("too short reverse IP address", addr)
|
||||
}
|
||||
|
||||
suffixLen := l - rdnsDomainLen
|
||||
suffix := addr[suffixLen+1:]
|
||||
if suffix != RDNSDomain {
|
||||
return IPv4{}, newParseError("suffix of reverse IP address", addr)
|
||||
}
|
||||
ipStr := addr[:suffixLen]
|
||||
revIP := net.ParseIP(ipStr)
|
||||
if revIP == nil {
|
||||
return IPv4{}, newParseError("reverse IP address", addr)
|
||||
}
|
||||
|
||||
revIP4 := revIP.To4()
|
||||
return IPv4([4]byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}), nil
|
||||
}
|
||||
|
||||
func raddrToIP(addr string) (net.IP, error) {
|
||||
r, err := raddrToIPv4(addr)
|
||||
if err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
return r.toNetIP(), nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func TestAddrs(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestAddrs starting")
|
||||
|
||||
ip, err := addrToIPv4("10.13.12.11")
|
||||
require.NoError(t, err)
|
||||
require.True(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr.arpa.")
|
||||
require.NoError(t, err)
|
||||
require.True(t, net.ParseIP("10.13.12.11").Equal(ip.toNetIP()), "IP")
|
||||
|
||||
// some malformed addresses
|
||||
ip, err = addrToIPv4("10.13.12")
|
||||
require.True(t, err != nil, "when parsing malformed address")
|
||||
ip, err = addrToIPv4("10.13.AA.12")
|
||||
require.True(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-axxx.arpa.")
|
||||
require.True(t, err != nil, "when parsing malformed address")
|
||||
ip, err = raddrToIPv4("11.12.13.10.in-addr")
|
||||
require.True(t, err != nil, "when parsing malformed address")
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCapacity = errors.New("Invalid cache capacity")
|
||||
errCouldNotResolve = errors.New("Could not resolve")
|
||||
errTimeout = errors.New("Timeout while waiting for resolution")
|
||||
errNoLocalReplies = errors.New("No local replies")
|
||||
)
|
||||
|
||||
const (
|
||||
defPendingTimeout int = 5 // timeout for a resolution
|
||||
)
|
||||
|
||||
const nullTTL = 0 // a null TTL
|
||||
|
||||
type entryStatus uint8
|
||||
|
||||
const (
|
||||
stPending entryStatus = iota // someone is waiting for the resolution
|
||||
stResolved entryStatus = iota // resolved
|
||||
)
|
||||
|
||||
var statusToString = map[entryStatus]string{
|
||||
stPending: "pending",
|
||||
stResolved: "resolved",
|
||||
}
|
||||
|
||||
const (
|
||||
CacheNoLocalReplies uint8 = 1 << iota // not found in local network (stored in the cache so we skip another local lookup or some time)
|
||||
)
|
||||
|
||||
// a cache entry
|
||||
type cacheEntry struct {
|
||||
Status entryStatus // status of the entry
|
||||
Flags uint8 // some extra flags
|
||||
ReplyLen int
|
||||
|
||||
question dns.Question
|
||||
protocol dnsProtocol
|
||||
reply dns.Msg
|
||||
|
||||
validUntil time.Time // obtained from the reply and stored here for convenience/speed
|
||||
putTime time.Time
|
||||
|
||||
index int // for fast lookups in the heap
|
||||
}
|
||||
|
||||
func newCacheEntry(question *dns.Question, now time.Time) *cacheEntry {
|
||||
e := &cacheEntry{
|
||||
Status: stPending,
|
||||
validUntil: now.Add(time.Second * time.Duration(defPendingTimeout)),
|
||||
question: *question,
|
||||
index: -1,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Get a copy of the reply stored in the entry, but with some values adjusted like the TTL
|
||||
func (e *cacheEntry) getReply(request *dns.Msg, maxLen int, now time.Time) (*dns.Msg, error) {
|
||||
if e.Status != stResolved {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if the reply has expired or is invalid, force the caller to start a new resolution
|
||||
if e.hasExpired(now) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return nil, errNoLocalReplies
|
||||
}
|
||||
|
||||
if e.ReplyLen >= maxLen {
|
||||
Log.Debugf("[cache msgid %d] returning truncated reponse: %d > %d", request.MsgHdr.Id, e.ReplyLen, maxLen)
|
||||
return makeTruncatedReply(request), nil
|
||||
}
|
||||
|
||||
// create a copy of the reply, with values for this particular query
|
||||
reply := e.reply.Copy()
|
||||
reply.SetReply(request)
|
||||
|
||||
// adjust the TTLs
|
||||
passedSecs := uint32(now.Sub(e.putTime).Seconds())
|
||||
for _, rr := range reply.Answer {
|
||||
hdr := rr.Header()
|
||||
ttl := hdr.Ttl
|
||||
if passedSecs < ttl {
|
||||
hdr.Ttl = ttl - passedSecs
|
||||
} else {
|
||||
return nil, nil // it is expired: do not spend more time and return nil...
|
||||
}
|
||||
}
|
||||
|
||||
reply.Rcode = e.reply.Rcode
|
||||
reply.Authoritative = true
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (e cacheEntry) hasExpired(now time.Time) bool {
|
||||
return e.validUntil.Before(now) || e.validUntil == now
|
||||
}
|
||||
|
||||
// set the reply for the entry
|
||||
// returns True if the entry has changed the validUntil time
|
||||
func (e *cacheEntry) setReply(reply *dns.Msg, ttl int, flags uint8, now time.Time) bool {
|
||||
var prevValidUntil time.Time
|
||||
if e.Status == stResolved {
|
||||
if reply != nil {
|
||||
Log.Debugf("[cache msgid %d] replacing response in cache", reply.MsgHdr.Id)
|
||||
}
|
||||
prevValidUntil = e.validUntil
|
||||
}
|
||||
|
||||
// make sure we do not overwrite noLocalReplies entries
|
||||
if flags&CacheNoLocalReplies != 0 {
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if ttl != nullTTL {
|
||||
e.validUntil = now.Add(time.Second * time.Duration(ttl))
|
||||
} else if reply != nil {
|
||||
// calculate the validUntil from the reply TTL
|
||||
var minTTL uint32 = math.MaxUint32
|
||||
for _, rr := range reply.Answer {
|
||||
ttl := rr.Header().Ttl
|
||||
if ttl < minTTL {
|
||||
minTTL = ttl // TODO: improve the minTTL calculation (maybe we should skip some RRs)
|
||||
}
|
||||
}
|
||||
e.validUntil = now.Add(time.Second * time.Duration(minTTL))
|
||||
} else {
|
||||
Log.Warningf("[cache] no valid TTL could be calculated")
|
||||
}
|
||||
|
||||
e.Status = stResolved
|
||||
e.Flags = flags
|
||||
e.putTime = now
|
||||
|
||||
if reply != nil {
|
||||
e.reply = *reply.Copy()
|
||||
e.ReplyLen = reply.Len()
|
||||
}
|
||||
|
||||
return (prevValidUntil != e.validUntil)
|
||||
}
|
||||
|
||||
func (e *cacheEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
q := e.question
|
||||
fmt.Fprintf(&buf, "'%s'[%s]: ", q.Name, dns.TypeToString[q.Qtype])
|
||||
if e.Flags&CacheNoLocalReplies != 0 {
|
||||
fmt.Fprintf(&buf, "neg-local")
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%s", statusToString[e.Status])
|
||||
}
|
||||
fmt.Fprintf(&buf, "(%d bytes)", e.ReplyLen)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// An entriesPtrHeap is a min-heap of cache entries.
|
||||
type entriesPtrsHeap []*cacheEntry
|
||||
|
||||
func (h entriesPtrsHeap) Len() int { return len(h) }
|
||||
func (h entriesPtrsHeap) Less(i, j int) bool { return h[i].validUntil.Before(h[j].validUntil) }
|
||||
func (h entriesPtrsHeap) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
h[i].index = i
|
||||
h[j].index = j
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Push(x interface{}) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
n := len(*h)
|
||||
entry := x.(*cacheEntry)
|
||||
entry.index = n
|
||||
*h = append(*h, entry)
|
||||
}
|
||||
|
||||
func (h *entriesPtrsHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
item.index = -1 // for safety
|
||||
*h = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The cache interface
|
||||
type ZoneCache interface {
|
||||
// Get from the cache
|
||||
Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error)
|
||||
// Put in the cache
|
||||
Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int
|
||||
// Remove from the cache
|
||||
Remove(question *dns.Question)
|
||||
// Purge all expired entries from the cache
|
||||
Purge()
|
||||
// Remove all entries
|
||||
Clear()
|
||||
// Return the cache length
|
||||
Len() int
|
||||
// Return the max capacity
|
||||
Capacity() int
|
||||
}
|
||||
|
||||
type cacheKey dns.Question
|
||||
type entries map[cacheKey]*cacheEntry
|
||||
|
||||
// Cache is a thread-safe fixed capacity LRU cache.
|
||||
type Cache struct {
|
||||
capacity int
|
||||
entries entries
|
||||
entriesH entriesPtrsHeap // len(entriesH) <= len(entries), as pending entries can be in entries but not in entriesH
|
||||
clock clock.Clock
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache creates a cache of the given capacity
|
||||
func NewCache(capacity int, clk clock.Clock) (*Cache, error) {
|
||||
if capacity <= 0 {
|
||||
return nil, errInvalidCapacity
|
||||
}
|
||||
c := &Cache{
|
||||
capacity: capacity,
|
||||
entries: make(entries, capacity),
|
||||
clock: clk,
|
||||
}
|
||||
|
||||
if c.clock == nil {
|
||||
c.clock = clock.New()
|
||||
}
|
||||
|
||||
heap.Init(&c.entriesH)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Clear removes all the entries in the cache
|
||||
func (c *Cache) Clear() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.entries = make(entries, c.capacity)
|
||||
heap.Init(&c.entriesH)
|
||||
}
|
||||
|
||||
// Purge removes the old elements in the cache
|
||||
func (c *Cache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
for i, entry := range c.entriesH {
|
||||
if entry.hasExpired(now) {
|
||||
heap.Remove(&c.entriesH, i)
|
||||
delete(c.entries, cacheKey(entry.question))
|
||||
} else {
|
||||
return // all remaining entries must be still valid...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a reply to the cache.
|
||||
// When `ttl` is equal to `nullTTL`, the cache entry will be valid until the closest TTL in the `reply`
|
||||
func (c *Cache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
ent, found := c.entries[key]
|
||||
if found {
|
||||
updated := ent.setReply(reply, ttl, flags, now)
|
||||
if updated {
|
||||
heap.Fix(&c.entriesH, ent.index)
|
||||
}
|
||||
} else {
|
||||
// If we will add a new item and the capacity has been exceeded, make some room...
|
||||
if len(c.entriesH) >= c.capacity {
|
||||
lowestEntry := heap.Pop(&c.entriesH).(*cacheEntry)
|
||||
delete(c.entries, cacheKey(lowestEntry.question))
|
||||
}
|
||||
ent = newCacheEntry(&question, now)
|
||||
ent.setReply(reply, ttl, flags, now)
|
||||
heap.Push(&c.entriesH, ent)
|
||||
c.entries[key] = ent
|
||||
}
|
||||
return ent.ReplyLen
|
||||
}
|
||||
|
||||
// Look up for a question's reply from the cache.
|
||||
// If no reply is stored in the cache, it returns a `nil` reply and no error.
|
||||
func (c *Cache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
now := c.clock.Now()
|
||||
question := request.Question[0]
|
||||
key := cacheKey(question)
|
||||
if ent, found := c.entries[key]; found {
|
||||
reply, err = ent.getReply(request, maxLen, now)
|
||||
if ent.hasExpired(now) {
|
||||
Log.Debugf("[cache msgid %d] expired: removing", request.MsgHdr.Id)
|
||||
if ent.index > 0 {
|
||||
heap.Remove(&c.entriesH, ent.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
reply = nil
|
||||
}
|
||||
} else {
|
||||
// we are the first asking for this name: create an entry with no reply... the caller must wait
|
||||
Log.Debugf("[cache msgid %d] addind in pending state", request.MsgHdr.Id)
|
||||
c.entries[key] = newCacheEntry(&question, now)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided question from the cache.
|
||||
func (c *Cache) Remove(question *dns.Question) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key := cacheKey(*question)
|
||||
if entry, found := c.entries[key]; found {
|
||||
Log.Debugf("[cache] removing %s-response for '%s'", dns.TypeToString[question.Qtype], question.Name)
|
||||
if entry.index > 0 {
|
||||
heap.Remove(&c.entriesH, entry.index)
|
||||
}
|
||||
delete(c.entries, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of entries in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return len(c.entries)
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) Capacity() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.capacity
|
||||
}
|
||||
|
||||
// Return the max capacity
|
||||
func (c *Cache) String() string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, entry := range c.entries {
|
||||
fmt.Fprintf(&buf, "%s\n", entry)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Check that the cache keeps its intended capacity constant
|
||||
func TestCacheLength(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestCacheLength starting")
|
||||
|
||||
const cacheLen = 128
|
||||
|
||||
l, err := NewCache(cacheLen, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
insTime := time.Now()
|
||||
|
||||
t.Logf("Inserting 256 questions in the cache at '%s', with TTL from 0 to 255", insTime)
|
||||
for i := 0; i < cacheLen*2; i++ {
|
||||
name := fmt.Sprintf("name%d", i)
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion(name, dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i))
|
||||
records := []ZoneRecord{Record{name, ip, 0, 0, 0}}
|
||||
|
||||
reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
reply.Answer[0].Header().Ttl = uint32(i)
|
||||
|
||||
l.Put(questionMsg, reply, 0, 0)
|
||||
}
|
||||
|
||||
require.Equal(t, cacheLen, l.Len(), "cache length")
|
||||
|
||||
minExpectedTime := insTime.Add(time.Duration(cacheLen) * time.Second)
|
||||
t.Logf("Checking all remaining entries expire after insert_time + %d secs='%s'", cacheLen, minExpectedTime)
|
||||
for _, entry := range l.entries {
|
||||
if entry.validUntil.Before(minExpectedTime) {
|
||||
t.Fatalf("Entry valid until %s", entry.validUntil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the cache entries are ok
|
||||
func TestCacheEntries(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestCacheEntries starting")
|
||||
|
||||
Log.Infoln("Checking cache consistency")
|
||||
|
||||
const cacheLen = 128
|
||||
clk := clock.NewMock()
|
||||
|
||||
l, err := NewCache(cacheLen, clk)
|
||||
require.NoError(t, err)
|
||||
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion("some.name", dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
|
||||
question := &questionMsg.Question[0]
|
||||
|
||||
t.Logf("Trying to get a name")
|
||||
resp, err := l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
t.Logf("Trying to get it again")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did not expect a reponse from Get() yet")
|
||||
}
|
||||
|
||||
t.Logf("Inserting the reply")
|
||||
records := []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.1"), 0, 0, 0}}
|
||||
reply1 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply1, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we can Get() the reply now")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp != nil, "reponse from Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
require.IsType(t, (*dns.A)(nil), resp.Answer[0], "DNS record")
|
||||
ttlGet1 := resp.Answer[0].Header().Ttl
|
||||
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
t.Logf("Checking that a second Get(), after 1 second, gets the same result, but with reduced TTL")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp != nil, "reponse from a second Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
require.IsType(t, (*dns.A)(nil), resp.Answer[0], "DNS record")
|
||||
ttlGet2 := resp.Answer[0].Header().Ttl
|
||||
require.Equal(t, 1, int(ttlGet1-ttlGet2), "TTL difference")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL) * time.Second)
|
||||
t.Logf("Checking that a third Get(), after %d second, gets no result", DefaultLocalTTL)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp)
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the second Get()")
|
||||
}
|
||||
|
||||
t.Logf("Checking that an Remove() results in Get() returning nothing")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, replyTemp, nullTTL, 0)
|
||||
lenBefore := l.Len()
|
||||
l.Remove(question)
|
||||
require.Equal(t, lenBefore-1, l.Len(), "cache length")
|
||||
l.Remove(question) // do it again: should have no effect...
|
||||
require.Equal(t, lenBefore-1, l.Len(), "cache length")
|
||||
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp == nil, "reponse from the Get() after a Remove()")
|
||||
|
||||
t.Logf("Inserting a two replies for the same query")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.2"), 0, 0, 0}}
|
||||
reply2 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply2, nullTTL, 0)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.3"), 0, 0, 0}}
|
||||
reply3 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
l.Put(questionMsg, reply3, nullTTL, 0)
|
||||
|
||||
t.Logf("Checking we get the last one...")
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
require.IsType(t, (*dns.A)(nil), resp.Answer[0], "DNS record")
|
||||
require.Equal(t, "10.0.1.3", resp.Answer[0].(*dns.A).A.String(), "IP address")
|
||||
require.Equal(t, int(DefaultLocalTTL), int(resp.Answer[0].Header().Ttl), "TTL")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL-1) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp != nil, "reponse from the Get()")
|
||||
t.Logf("Received '%s'", resp.Answer[0])
|
||||
require.IsType(t, (*dns.A)(nil), resp.Answer[0], "DNS record")
|
||||
require.Equal(t, "10.0.1.3", resp.Answer[0].(*dns.A).A.String(), "IP address")
|
||||
require.Equal(t, 1, int(resp.Answer[0].Header().Ttl), "TTL")
|
||||
|
||||
t.Logf("Checking we get empty replies when they are expired...")
|
||||
lenBefore = l.Len()
|
||||
clk.Add(time.Duration(DefaultLocalTTL) * time.Second)
|
||||
resp, err = l.Get(questionMsg, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
if resp != nil {
|
||||
t.Logf("Got\n%s", resp.Answer[0])
|
||||
t.Fatalf("ERROR: Did NOT expect a reponse from the Get()")
|
||||
}
|
||||
require.Equal(t, lenBefore-1, l.Len(), "cache length (after getting an expired entry)")
|
||||
|
||||
questionMsg2 := new(dns.Msg)
|
||||
questionMsg2.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg2.RecursionDesired = true
|
||||
question2 := &questionMsg2.Question[0]
|
||||
|
||||
t.Logf("Trying to Get() a name")
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp, "reponse from Get() yet")
|
||||
|
||||
t.Logf("Checking that an Remove() between Get() and Put() does not break things")
|
||||
records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}}
|
||||
replyTemp2 := makeAddressReply(questionMsg2, question2, records, DefaultLocalTTL)
|
||||
l.Remove(question2)
|
||||
l.Put(questionMsg2, replyTemp2, nullTTL, 0)
|
||||
resp, err = l.Get(questionMsg2, minUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp, "reponse from Get()")
|
||||
|
||||
questionMsg3 := new(dns.Msg)
|
||||
questionMsg3.SetQuestion("some.other.name", dns.TypeA)
|
||||
questionMsg3.RecursionDesired = true
|
||||
question3 := &questionMsg3.Question[0]
|
||||
|
||||
t.Logf("Checking that a entry with CacheNoLocalReplies return an error")
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
require.Nil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
require.True(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
t.Logf("Checking that more Put's do not make the negative TTL longer")
|
||||
clk.Add(time.Duration(DefaultLocalTTL/2) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
clk.Add(time.Duration(1) * time.Second)
|
||||
l.Put(questionMsg3, nil, DefaultLocalTTL, CacheNoLocalReplies)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
require.Nil(t, resp, "Get() response with CacheNoLocalReplies")
|
||||
require.True(t, err == errNoLocalReplies, "Get() error with CacheNoLocalReplies")
|
||||
|
||||
clk.Add(time.Duration(DefaultLocalTTL/2) * time.Second)
|
||||
t.Logf("Checking that we get an expired response after %d seconds", DefaultLocalTTL)
|
||||
clk.Add(time.Second * time.Duration(DefaultLocalTTL+1))
|
||||
t.Logf("Checking that we get an expired response after %d seconds", DefaultLocalTTL)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
require.Nil(t, resp, "expired Get() response with CacheNoLocalReplies")
|
||||
require.Nil(t, err, "expired Get() error with CacheNoLocalReplies")
|
||||
|
||||
l.Remove(question3)
|
||||
t.Logf("Checking that Put&Get with CacheNoLocalReplies with a Remove in the middle returns nothing")
|
||||
l.Put(questionMsg3, nil, nullTTL, CacheNoLocalReplies)
|
||||
l.Remove(question3)
|
||||
resp, err = l.Get(questionMsg3, minUDPSize)
|
||||
require.Nil(t, resp, "Get() reponse with CacheNoLocalReplies")
|
||||
require.Nil(t, err, "Get() error with CacheNoLocalReplies")
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
minUDPSize = 512
|
||||
maxUDPSize = 65535
|
||||
)
|
||||
|
||||
func makeHeader(r *dns.Msg, q *dns.Question, ttl int) *dns.RR_Header {
|
||||
return &dns.RR_Header{
|
||||
Name: q.Name,
|
||||
Rrtype: q.Qtype,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: uint32(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func makeReply(r *dns.Msg, as []dns.RR) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Answer = as
|
||||
return m
|
||||
}
|
||||
|
||||
func makeTruncatedReply(r *dns.Msg) *dns.Msg {
|
||||
// for truncated response, we create a minimal reply with the Truncated bit set
|
||||
reply := new(dns.Msg)
|
||||
reply.SetReply(r)
|
||||
reply.Truncated = true
|
||||
return reply
|
||||
}
|
||||
|
||||
type DNSResponseBuilder func(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg
|
||||
|
||||
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg {
|
||||
answers := make([]dns.RR, len(addrs))
|
||||
header := makeHeader(r, q, ttl)
|
||||
count := 0
|
||||
for _, addr := range addrs {
|
||||
ip := addr.IP()
|
||||
ip4 := ip.To4()
|
||||
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
if ip4 != nil {
|
||||
answers[count] = &dns.A{Hdr: *header, A: ip}
|
||||
count++
|
||||
}
|
||||
case dns.TypeAAAA:
|
||||
if ip4 == nil {
|
||||
answers[count] = &dns.AAAA{Hdr: *header, AAAA: ip}
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeReply(r, answers[:count])
|
||||
}
|
||||
|
||||
func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord, ttl int) *dns.Msg {
|
||||
answers := make([]dns.RR, len(names))
|
||||
header := makeHeader(r, q, ttl)
|
||||
for i, name := range names {
|
||||
answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()}
|
||||
}
|
||||
return makeReply(r, answers)
|
||||
}
|
||||
|
||||
func makeDNSFailResponse(r *dns.Msg) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.RecursionAvailable = true
|
||||
m.Rcode = dns.RcodeNameError
|
||||
return m
|
||||
}
|
||||
|
||||
// get the maximum UDP-reply length
|
||||
func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
|
||||
maxLen := minUDPSize
|
||||
if proto == protTCP {
|
||||
maxLen = maxUDPSize
|
||||
} else if opt := r.IsEdns0(); opt != nil {
|
||||
maxLen = int(opt.UDPSize())
|
||||
}
|
||||
return maxLen
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
// shuffleAnswers reorders answers for very basic load balancing
|
||||
func shuffleAnswers(answers []dns.RR) []dns.RR {
|
||||
if len(answers) > 1 {
|
||||
for i := range answers {
|
||||
j := rand.Intn(i + 1)
|
||||
answers[i], answers[j] = answers[j], answers[i]
|
||||
}
|
||||
}
|
||||
|
||||
return answers
|
||||
}
|
||||
|
||||
// only take the first `num` answers
|
||||
func pruneAnswers(answers []dns.RR, num int) []dns.RR {
|
||||
if num > 0 && len(answers) > num {
|
||||
// TODO: we should have some prefer locally-introduced answers, etc...
|
||||
return answers[:num]
|
||||
}
|
||||
return answers
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Check that we can prune an answer
|
||||
func TestPrune(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestPrune starting")
|
||||
|
||||
questionMsg := new(dns.Msg)
|
||||
questionMsg.SetQuestion("name", dns.TypeA)
|
||||
questionMsg.RecursionDesired = true
|
||||
question := &questionMsg.Question[0]
|
||||
records := []ZoneRecord{
|
||||
Record{"name", net.ParseIP("10.0.1.1"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.2"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.3"), 0, 0, 0},
|
||||
Record{"name", net.ParseIP("10.0.1.4"), 0, 0, 0},
|
||||
}
|
||||
|
||||
reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL)
|
||||
reply.Answer[0].Header().Ttl = DefaultLocalTTL
|
||||
|
||||
pruned := pruneAnswers(reply.Answer, 1)
|
||||
require.Equal(t, 1, len(pruned), "wrong number of answers")
|
||||
|
||||
pruned = pruneAnswers(reply.Answer, 2)
|
||||
require.Equal(t, 2, len(pruned), "wrong number of answers")
|
||||
|
||||
pruned = pruneAnswers(reply.Answer, 0)
|
||||
require.Equal(t, len(records), len(pruned), "wrong number of answers")
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
)
|
||||
|
||||
func httpErrorAndLog(w http.ResponseWriter, msg string,
|
||||
status int, logmsg string, logargs ...interface{}) {
|
||||
http.Error(w, msg, status)
|
||||
Log.Warningf("[http] "+logmsg, logargs...)
|
||||
}
|
||||
|
||||
func extractFQDN(r *http.Request) (string, string) {
|
||||
if fqdnStr := r.FormValue("fqdn"); fqdnStr != "" {
|
||||
return fqdnStr, fqdnStr
|
||||
}
|
||||
return "*", ""
|
||||
}
|
||||
|
||||
func ServeHTTP(listener net.Listener, version string, server *DNSServer, dockerCli *docker.Client) {
|
||||
|
||||
muxRouter := mux.NewRouter()
|
||||
|
||||
muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "weave DNS", version)
|
||||
fmt.Fprintln(w, server.Status())
|
||||
fmt.Fprintln(w, server.Zone.Status())
|
||||
})
|
||||
|
||||
muxRouter.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, server.Zone.Domain())
|
||||
})
|
||||
|
||||
muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
reqError := func(msg string, logmsg string, logargs ...interface{}) {
|
||||
httpErrorAndLog(w, msg, http.StatusBadRequest, logmsg, logargs...)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
name := r.FormValue("fqdn")
|
||||
|
||||
if name == "" {
|
||||
reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form)
|
||||
return
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
reqError("Invalid IP", "Invalid IP in request: %s", ipStr)
|
||||
return
|
||||
}
|
||||
|
||||
domain := server.Zone.Domain()
|
||||
if !dns.IsSubDomain(domain, name) {
|
||||
Log.Infof("[http] Ignoring name %s, not in %s", name, domain)
|
||||
return
|
||||
}
|
||||
Log.Infof("[http] Adding %s -> %s", name, ipStr)
|
||||
if err := server.Zone.AddRecord(idStr, name, ip); err != nil {
|
||||
if _, ok := err.(DuplicateError); !ok {
|
||||
httpErrorAndLog(
|
||||
w, "Internal error", http.StatusInternalServerError,
|
||||
"Unexpected error from DB: %s", err)
|
||||
return
|
||||
} // oh, I already know this. whatever.
|
||||
}
|
||||
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(idStr) {
|
||||
Log.Infof("[http] '%s' is not running: removing", idStr)
|
||||
server.Zone.DeleteRecords(idStr, name, ip)
|
||||
}
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
httpErrorAndLog(
|
||||
w, "Invalid IP in request", http.StatusBadRequest,
|
||||
"Invalid IP in request: %s", ipStr)
|
||||
return
|
||||
}
|
||||
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Log.Infof("[http] Deleting ID %s, IP %s, FQDN %s", idStr, ipStr, fqdnStr)
|
||||
server.Zone.DeleteRecords(idStr, fqdn, ip)
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name/{id:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars["id"]
|
||||
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Log.Infof("[http] Deleting ID %s, IP *, FQDN %s", idStr, fqdnStr)
|
||||
server.Zone.DeleteRecords(idStr, fqdn, net.IP{})
|
||||
})
|
||||
|
||||
muxRouter.Methods("DELETE").Path("/name").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fqdnStr, fqdn := extractFQDN(r)
|
||||
|
||||
Log.Infof("[http] Deleting ID *, IP *, FQDN %s", fqdnStr)
|
||||
server.Zone.DeleteRecords("", fqdn, net.IP{})
|
||||
})
|
||||
|
||||
http.Handle("/", muxRouter)
|
||||
|
||||
if err := http.Serve(listener, nil); err != nil {
|
||||
Log.Fatal("[http] Unable to serve http: ", err)
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func genForm(method string, url string, data url.Values) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest(method, url, strings.NewReader(data.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func TestHttp(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testDomain = "weave.local."
|
||||
successTestName = "test1." + testDomain
|
||||
testAddr1 = "10.2.2.1/24"
|
||||
testAddr2 = "10.2.2.2/24"
|
||||
dockerIP = "9.8.7.6"
|
||||
)
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{Domain: testDomain})
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
httpListener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create http listener: ", err)
|
||||
}
|
||||
|
||||
// Create a useless server... it will not even be started
|
||||
srv, _ := NewDNSServer(DNSServerConfig{Zone: zone})
|
||||
|
||||
port := httpListener.Addr().(*net.TCPAddr).Port
|
||||
go ServeHTTP(httpListener, "", srv, nil)
|
||||
|
||||
// Ask the http server to add our test address into the database
|
||||
addrParts := strings.Split(testAddr1, "/")
|
||||
addrURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts[0])
|
||||
resp, err := genForm("PUT", addrURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
|
||||
// Check that the address is now there.
|
||||
ip, _, _ := net.ParseCIDR(testAddr1)
|
||||
foundIP, err := zone.LookupName(successTestName)
|
||||
require.NoError(t, err)
|
||||
if !foundIP[0].IP().Equal(ip) {
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
|
||||
// Adding exactly the same address should be OK
|
||||
resp, err = genForm("PUT", addrURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http success response for duplicate add")
|
||||
|
||||
// Now try adding the same address again with a different ident --
|
||||
// again should be fine
|
||||
otherURL := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, "other", addrParts[0])
|
||||
resp, err = genForm("PUT", otherURL,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts[1]}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
|
||||
// Adding a new IP for the same name should be OK
|
||||
addrParts2 := strings.Split(testAddr2, "/")
|
||||
addrURL2 := fmt.Sprintf("http://localhost:%d/name/%s/%s", port, containerID, addrParts2[0])
|
||||
resp, err = genForm("PUT", addrURL2,
|
||||
url.Values{"fqdn": {successTestName}, "local_ip": {dockerIP}, "routing_prefix": {addrParts2[1]}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http success response for second IP")
|
||||
|
||||
// Check that we can get two IPs for that name
|
||||
ip2, _, _ := net.ParseCIDR(testAddr2)
|
||||
foundIP, err = zone.LookupName(successTestName)
|
||||
require.NoError(t, err)
|
||||
if len(foundIP) != 2 {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result length: received %d responses", len(foundIP))
|
||||
}
|
||||
if !(foundIP[0].IP().Equal(ip) || foundIP[0].IP().Equal(ip2)) {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
if !(foundIP[1].IP().Equal(ip) || foundIP[1].IP().Equal(ip2)) {
|
||||
t.Logf("IPs found: %s", foundIP)
|
||||
t.Fatalf("Unexpected result for %s: received %s, expected %s", successTestName, foundIP, ip)
|
||||
}
|
||||
|
||||
// Delete the address
|
||||
resp, err = genForm("DELETE", addrURL, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
|
||||
// Check that the address is still resolvable.
|
||||
x, err := zone.LookupName(successTestName)
|
||||
t.Logf("Got %s", x)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Delete the address record mentioning the other container
|
||||
resp, err = genForm("DELETE", otherURL, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
|
||||
// Delete the second IP
|
||||
resp, err = genForm("DELETE", addrURL2, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "http response")
|
||||
|
||||
// Check that the address is gone
|
||||
x, err = zone.LookupName(successTestName)
|
||||
t.Logf("Got %s", x)
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, "fully-removed address")
|
||||
|
||||
// Would like to shut down the http server at the end of this test
|
||||
// but it's complicated.
|
||||
// See https://groups.google.com/forum/#!topic/golang-nuts/vLHWa5sHnCE
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import "net"
|
||||
|
||||
// A basic mDNS service
|
||||
type ZoneMDNS interface {
|
||||
// Start the service
|
||||
Start(*net.Interface) error
|
||||
// Stop the service
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// A mDNS server
|
||||
type ZoneMDNSServer interface {
|
||||
ZoneMDNS
|
||||
// Return the Zone database used by the server
|
||||
Zone() Zone
|
||||
}
|
||||
|
||||
// A mDNS client interface
|
||||
type ZoneMDNSClient interface {
|
||||
ZoneMDNS
|
||||
ZoneLookup
|
||||
// Perform an insistent lookup for a name
|
||||
InsistentLookupName(name string) ([]ZoneRecord, error)
|
||||
// Perform an insistent lookup for a reverse address
|
||||
InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Portions of this code taken from github.com/armon/mdns
|
||||
|
||||
const (
|
||||
ipv4mdns = "224.0.0.251" // link-local multicast address
|
||||
mdnsPort = 5353 // mDNS assigned port
|
||||
// We wait this long to hear a response from other mDNS servers on
|
||||
// the network.
|
||||
mDNSTimeout = 500 * time.Millisecond
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
MailboxSize = 16
|
||||
)
|
||||
|
||||
var (
|
||||
ipv4Addr = &net.UDPAddr{
|
||||
IP: net.ParseIP(ipv4mdns),
|
||||
Port: mdnsPort,
|
||||
}
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
name string
|
||||
addr net.IP
|
||||
ttl int
|
||||
err error
|
||||
}
|
||||
|
||||
func (r Response) Name() string { return r.name }
|
||||
func (r Response) IP() net.IP { return r.addr }
|
||||
func (r Response) Priority() int { return 0 }
|
||||
func (r Response) Weight() int { return 0 }
|
||||
func (r Response) TTL() int { return r.ttl }
|
||||
|
||||
func (r Response) Equal(r2 *Response) bool {
|
||||
if r.name != r2.name {
|
||||
return false
|
||||
}
|
||||
if !r.addr.Equal(r2.addr) {
|
||||
return false
|
||||
}
|
||||
if r.err != r2.err {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r Response) String() string {
|
||||
var buf bytes.Buffer
|
||||
if r.err != nil {
|
||||
fmt.Fprintf(&buf, "%s", r.err)
|
||||
} else {
|
||||
if len(r.Name()) > 0 {
|
||||
fmt.Fprintf(&buf, "%s", r.Name())
|
||||
}
|
||||
if !r.IP().IsUnspecified() {
|
||||
fmt.Fprintf(&buf, "[%s]", r.IP())
|
||||
}
|
||||
if r.ttl > 0 {
|
||||
fmt.Fprintf(&buf, "(TTL:%d)", r.TTL())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type responseInfo struct {
|
||||
timeout time.Time // if no answer by this time, give up
|
||||
insistent bool // insistent queries are not removed on the first reply
|
||||
ch chan<- *Response
|
||||
}
|
||||
|
||||
// Represents one query that we have sent for one name.
|
||||
// If we, internally, get several requests for the same name while we have
|
||||
// a query in flight, then we don't want to send more queries out.
|
||||
// Invariant on responseInfos: they are in ascending order of timeout.
|
||||
type inflightQuery struct {
|
||||
name string
|
||||
id uint16 // the DNS message ID
|
||||
responseInfos []*responseInfo
|
||||
}
|
||||
|
||||
type MDNSClient struct {
|
||||
// note unorthodox use of 'Server' class in client logic - using it to
|
||||
// listen on a multicast socket and call callback whenever a message comes in.
|
||||
listener *dns.Server
|
||||
conn *net.UDPConn
|
||||
addr *net.UDPAddr
|
||||
inflight map[string]*inflightQuery
|
||||
actionChan chan<- MDNSAction
|
||||
}
|
||||
|
||||
type mDNSQueryInfo struct {
|
||||
name string
|
||||
querytype uint16
|
||||
responseCh chan<- *Response
|
||||
}
|
||||
|
||||
func NewMDNSClient() (*MDNSClient, error) {
|
||||
return &MDNSClient{
|
||||
addr: ipv4Addr,
|
||||
inflight: make(map[string]*inflightQuery)}, nil
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Start(ifi *net.Interface) (err error) {
|
||||
if c.conn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
multicast, err := LinkLocalMulticastListener(ifi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// Don't want to handle queries here, so filter anything out that isn't a response
|
||||
if len(r.Answer) > 0 {
|
||||
c.ResponseCallback(r)
|
||||
}
|
||||
}
|
||||
|
||||
actionChan := make(chan MDNSAction, MailboxSize)
|
||||
c.actionChan = actionChan
|
||||
go c.actorLoop(actionChan)
|
||||
|
||||
c.listener = &dns.Server{Unsafe: true, PacketConn: multicast, Handler: dns.HandlerFunc(handleMDNS)}
|
||||
go c.listener.ActivateAndServe()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Stop() error {
|
||||
c.actionChan <- nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func LinkLocalMulticastListener(ifi *net.Interface) (net.PacketConn, error) {
|
||||
return net.ListenMulticastUDP("udp", ifi, ipv4Addr)
|
||||
}
|
||||
|
||||
// ACTOR client API
|
||||
|
||||
type MDNSAction func()
|
||||
|
||||
// Async
|
||||
func (c *MDNSClient) SendQuery(name string, querytype uint16, insistent bool, responseCh chan<- *Response) {
|
||||
c.actionChan <- func() {
|
||||
query, found := c.inflight[name]
|
||||
if !found {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, querytype)
|
||||
m.RecursionDesired = false
|
||||
buf, err := m.Pack()
|
||||
if err != nil {
|
||||
responseCh <- &Response{err: err}
|
||||
close(responseCh)
|
||||
return
|
||||
}
|
||||
query = &inflightQuery{
|
||||
name: name,
|
||||
id: m.Id,
|
||||
}
|
||||
if _, err = c.conn.WriteTo(buf, c.addr); err != nil {
|
||||
responseCh <- &Response{err: err}
|
||||
close(responseCh)
|
||||
return
|
||||
}
|
||||
c.inflight[name] = query
|
||||
}
|
||||
info := &responseInfo{
|
||||
ch: responseCh,
|
||||
timeout: time.Now().Add(mDNSTimeout),
|
||||
insistent: insistent,
|
||||
}
|
||||
// Invariant on responseInfos: they are in ascending order of
|
||||
// timeout. Since we use a fixed interval from Now(), this
|
||||
// must be after all existing timeouts.
|
||||
query.responseInfos = append(query.responseInfos, info)
|
||||
}
|
||||
}
|
||||
|
||||
// Async - called from dns library multiplexer
|
||||
func (c *MDNSClient) ResponseCallback(r *dns.Msg) {
|
||||
c.actionChan <- func() {
|
||||
for _, answer := range r.Answer {
|
||||
var name string
|
||||
var res *Response
|
||||
|
||||
switch rr := answer.(type) {
|
||||
case *dns.A:
|
||||
name = rr.Hdr.Name
|
||||
res = &Response{name: name, addr: rr.A, ttl: int(rr.Hdr.Ttl)}
|
||||
case *dns.PTR:
|
||||
name = rr.Hdr.Name
|
||||
raddr, _ := raddrToIP(name)
|
||||
res = &Response{name: rr.Ptr, addr: raddr, ttl: int(rr.Hdr.Ttl)}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if query, found := c.inflight[name]; found {
|
||||
var newResponseInfos []*responseInfo
|
||||
for _, resp := range query.responseInfos {
|
||||
resp.ch <- res
|
||||
// insistent queries are not removed on the first reply, but on the timeout
|
||||
if resp.insistent {
|
||||
newResponseInfos = append(newResponseInfos, resp)
|
||||
} else {
|
||||
close(resp.ch)
|
||||
}
|
||||
}
|
||||
if len(newResponseInfos) == 0 {
|
||||
delete(c.inflight, name)
|
||||
} else {
|
||||
query.responseInfos = newResponseInfos
|
||||
}
|
||||
} else {
|
||||
// We've received a response that didn't match a query
|
||||
// Do we want to cache it?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
// Check all in-flight queries, close all that have already timed out,
|
||||
// and return the duration until the next timeout
|
||||
func (c *MDNSClient) checkInFlightQueries() time.Duration {
|
||||
now := time.Now()
|
||||
after := MaxDuration
|
||||
for name, query := range c.inflight {
|
||||
// Invariant on responseInfos: they are in ascending order of timeout.
|
||||
numClosed := 0
|
||||
for _, item := range query.responseInfos {
|
||||
duration := item.timeout.Sub(now)
|
||||
if duration <= 0 { // timed out
|
||||
close(item.ch)
|
||||
numClosed++
|
||||
} else {
|
||||
if duration < after {
|
||||
after = duration
|
||||
}
|
||||
break // don't need to look at any more for this query
|
||||
}
|
||||
}
|
||||
// Remove timed-out items from the slice
|
||||
query.responseInfos = query.responseInfos[numClosed:]
|
||||
if len(query.responseInfos) == 0 {
|
||||
delete(c.inflight, name)
|
||||
}
|
||||
}
|
||||
return after
|
||||
}
|
||||
|
||||
func (c *MDNSClient) actorLoop(actionChan <-chan MDNSAction) {
|
||||
timer := time.NewTimer(MaxDuration)
|
||||
run := func() { timer.Reset(c.checkInFlightQueries()) }
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
if action == nil {
|
||||
c.listener.Shutdown()
|
||||
break loop
|
||||
} else {
|
||||
action()
|
||||
run()
|
||||
}
|
||||
case <-timer.C:
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
||||
// Close all response channels at termination
|
||||
for _, query := range c.inflight {
|
||||
for _, item := range query.responseInfos {
|
||||
close(item.ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
successTestName = "test1.weave."
|
||||
failTestName = "test2.weave."
|
||||
testAddr = net.ParseIP("9.8.7.6")
|
||||
testInAddr = "6.7.8.9.in-addr.arpa."
|
||||
)
|
||||
|
||||
func minimalServer(w dns.ResponseWriter, req *dns.Msg) {
|
||||
|
||||
sendAnswer := func(m *dns.Msg) {
|
||||
buf, err := m.Pack()
|
||||
CheckFatal(err)
|
||||
if buf == nil {
|
||||
log.Fatal("Nil buffer")
|
||||
}
|
||||
//log.Println("minimalServer sending:", buf)
|
||||
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
|
||||
sendconn, err := net.DialUDP("udp", nil, ipv4Addr)
|
||||
CheckFatal(err)
|
||||
|
||||
_, err = sendconn.Write(buf)
|
||||
sendconn.Close()
|
||||
CheckFatal(err)
|
||||
}
|
||||
|
||||
//log.Println("minimalServer received:", req)
|
||||
if len(req.Answer) > 0 {
|
||||
return // Only interested in questions.
|
||||
}
|
||||
if len(req.Question) != 1 {
|
||||
return // We only handle single-question messages
|
||||
}
|
||||
|
||||
if req.Question[0].Qtype == dns.TypeA && req.Question[0].Name == successTestName {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET, Ttl: 3600}
|
||||
a := &dns.A{Hdr: hdr, A: testAddr}
|
||||
m.Answer = append(m.Answer, a)
|
||||
sendAnswer(m)
|
||||
} else if req.Question[0].Qtype == dns.TypePTR && req.Question[0].Name == testInAddr {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
hdr := dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR,
|
||||
Class: dns.ClassINET, Ttl: 3600}
|
||||
a := &dns.PTR{Hdr: hdr, Ptr: successTestName}
|
||||
m.Answer = append(m.Answer, a)
|
||||
sendAnswer(m)
|
||||
}
|
||||
}
|
||||
|
||||
func RunLocalMulticastServer() (*dns.Server, error) {
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := &dns.Server{
|
||||
PacketConn: multicast,
|
||||
Handler: dns.HandlerFunc(minimalServer),
|
||||
ReadTimeout: 100 * time.Millisecond}
|
||||
go server.ActivateAndServe()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (*MDNSClient, *dns.Server, error) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
|
||||
server, err := RunLocalMulticastServer()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to run test server: %s. No default multicast interface?", err)
|
||||
}
|
||||
|
||||
mdnsClient, err := NewMDNSClient()
|
||||
require.NoError(t, err)
|
||||
err = mdnsClient.Start(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return mdnsClient, server, err
|
||||
}
|
||||
|
||||
func (c *MDNSClient) Shutdown() {
|
||||
c.actionChan <- nil
|
||||
}
|
||||
|
||||
type testContext struct {
|
||||
receivedAddr net.IP
|
||||
receivedCount int
|
||||
channel chan *Response
|
||||
}
|
||||
|
||||
func newTestContext() *testContext {
|
||||
return &testContext{channel: make(chan *Response, 4)}
|
||||
}
|
||||
|
||||
func (c *testContext) checkResponse(t *testing.T, channelOk bool, resp *Response) {
|
||||
if !channelOk {
|
||||
c.channel = nil
|
||||
return
|
||||
}
|
||||
require.NoError(t, resp.err)
|
||||
log.Printf("Got address response %s addr %s", resp.Name(), resp.IP())
|
||||
c.receivedAddr = resp.IP()
|
||||
c.receivedCount++
|
||||
}
|
||||
|
||||
func TestSimpleQuery(t *testing.T) {
|
||||
log.Println("TestSimpleQuery starting")
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
context := newTestContext()
|
||||
|
||||
// First, a test we expect to succeed
|
||||
mdnsClient.SendQuery(successTestName, dns.TypeA, false, context.channel)
|
||||
for resp := range context.channel {
|
||||
context.checkResponse(t, true, resp)
|
||||
}
|
||||
|
||||
if !context.receivedAddr.Equal(testAddr) || context.receivedCount != 1 {
|
||||
t.Fatal("Unexpected result for", successTestName, context.receivedAddr, context.receivedCount)
|
||||
}
|
||||
|
||||
// Now, a test we expect to time out with no responses
|
||||
context = newTestContext()
|
||||
mdnsClient.SendQuery("test2.weave.", dns.TypeA, false, context.channel)
|
||||
for resp := range context.channel {
|
||||
context.checkResponse(t, true, resp)
|
||||
}
|
||||
|
||||
if context.receivedCount > 0 {
|
||||
t.Fatal("Unexpected result for test2.weave", context.receivedAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParallelQuery(t *testing.T) {
|
||||
log.Println("TestParallelQuery starting")
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
context1 := newTestContext()
|
||||
context2 := newTestContext()
|
||||
|
||||
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context1.channel)
|
||||
go mdnsClient.SendQuery(successTestName, dns.TypeA, false, context2.channel)
|
||||
timeout := time.After(2 * time.Second)
|
||||
outerloop:
|
||||
for context1.channel != nil || context2.channel != nil {
|
||||
select {
|
||||
case resp, ok := <-context1.channel:
|
||||
context1.checkResponse(t, ok, resp)
|
||||
case resp, ok := <-context2.channel:
|
||||
context2.checkResponse(t, ok, resp)
|
||||
case <-timeout:
|
||||
break outerloop
|
||||
}
|
||||
}
|
||||
|
||||
if !context1.receivedAddr.Equal(testAddr) || !context2.receivedAddr.Equal(testAddr) || context1.receivedCount != 1 || context2.receivedCount != 1 {
|
||||
t.Fatal("Unexpected result for", successTestName, context1.receivedAddr, context2.receivedAddr, context1.receivedCount, context2.receivedCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsLookup(t *testing.T) {
|
||||
mdnsClient, server, _ := setup(t)
|
||||
defer mdnsClient.Shutdown()
|
||||
defer server.Shutdown()
|
||||
|
||||
ips, err := mdnsClient.LookupName(successTestName)
|
||||
require.NoError(t, err)
|
||||
if !testAddr.Equal(ips[0].IP()) {
|
||||
t.Fatalf("Returned address incorrect %s", ips)
|
||||
}
|
||||
|
||||
ips, err = mdnsClient.LookupName("foo.example.com.")
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, "unknown hostname")
|
||||
|
||||
names, err := mdnsClient.LookupInaddr(testInAddr)
|
||||
require.NoError(t, err)
|
||||
if !(successTestName == names[0].Name()) {
|
||||
t.Fatalf("Expected name %s, got %s", successTestName, names)
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func mdnsLookup(client *MDNSClient, name string, qtype uint16, insistent bool) ([]ZoneRecord, error) {
|
||||
var responses []*Response
|
||||
channel := make(chan *Response)
|
||||
client.SendQuery(name, qtype, insistent, channel)
|
||||
|
||||
for resp := range channel {
|
||||
if err := resp.err; err != nil {
|
||||
Log.Debugf("[mdns] Error for query type %s name %s: %s", dns.TypeToString[qtype], name, err)
|
||||
return nil, err
|
||||
}
|
||||
Log.Debugf("[mdns] Got response name for %s-query about name '%s': name '%s', addr '%s'",
|
||||
dns.TypeToString[qtype], name, resp.name, resp.addr)
|
||||
|
||||
if !insistent {
|
||||
return []ZoneRecord{resp}, nil // non-background queries return the first response...
|
||||
}
|
||||
|
||||
// add the response, avoiding duplicates
|
||||
// TODO: replace this linear search by something better... (not a big deal as we don't expect long responses)
|
||||
duplicate := false
|
||||
for _, r := range responses {
|
||||
if r.Equal(resp) {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !duplicate {
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
}
|
||||
|
||||
if len(responses) == 0 {
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
// convert []Response to []ZoneRecord
|
||||
res := make([]ZoneRecord, len(responses))
|
||||
for i, rr := range responses {
|
||||
res[i] = ZoneRecord(rr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name, returning either an IP address or an error
|
||||
func (client *MDNSClient) LookupName(name string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, name, dns.TypeA, false)
|
||||
}
|
||||
|
||||
// Perform a lookup for an IP address, returning either a name or an error
|
||||
func (client *MDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, inaddr, dns.TypePTR, false)
|
||||
}
|
||||
|
||||
// Perform lookup for a name, returning either a list of IP addresses or an error
|
||||
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
|
||||
func (client *MDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, name, dns.TypeA, true)
|
||||
}
|
||||
|
||||
// Perform an insistent lookup for an IP address, returning either a list of names or an error
|
||||
// Insistent queries will wait up to `mDNSTimeout` milliseconds for responses
|
||||
func (client *MDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mdnsLookup(client, inaddr, dns.TypePTR, true)
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type MDNSServer struct {
|
||||
localAddrs []net.Addr
|
||||
sendconn *net.UDPConn
|
||||
srv *dns.Server
|
||||
zone Zone
|
||||
allowLocal bool
|
||||
ttl int
|
||||
}
|
||||
|
||||
// Create a new mDNS server
|
||||
// Nothing will be done (including port bindings) until you `Start()` the server
|
||||
func NewMDNSServer(zone Zone, local bool, ttl int) (*MDNSServer, error) {
|
||||
return &MDNSServer{
|
||||
zone: zone,
|
||||
allowLocal: local,
|
||||
ttl: ttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return true if testaddr is a UDP address with IP matching my local i/f
|
||||
func (s *MDNSServer) addrIsLocal(testaddr net.Addr) bool {
|
||||
if udpaddr, ok := testaddr.(*net.UDPAddr); ok {
|
||||
for _, localaddr := range s.localAddrs {
|
||||
if ipnetlocal, ok := localaddr.(*net.IPNet); ok {
|
||||
if ipnetlocal.IP.Equal(udpaddr.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Start the mDNS server
|
||||
func (s *MDNSServer) Start(ifi *net.Interface) (err error) {
|
||||
// This is a bit of a kludge - per the RFC we should send responses from 5353, but that doesn't seem to work
|
||||
s.sendconn, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := LinkLocalMulticastListener(ifi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ifi == nil {
|
||||
s.localAddrs, err = net.InterfaceAddrs()
|
||||
} else {
|
||||
s.localAddrs, err = ifi.Addrs()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handleLocal := s.makeHandler(dns.TypeA,
|
||||
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
|
||||
if ips, err := zone.LookupName(q.Name); err == nil {
|
||||
return makeAddressReply(r, q, ips, s.ttl)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
handleReverse := s.makeHandler(dns.TypePTR,
|
||||
func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg {
|
||||
if names, err := zone.LookupInaddr(q.Name); err == nil {
|
||||
return makePTRReply(r, q, names, s.ttl)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
mux := dns.NewServeMux()
|
||||
mux.HandleFunc(s.zone.Domain(), handleLocal)
|
||||
mux.HandleFunc(RDNSDomain, handleReverse)
|
||||
|
||||
s.srv = &dns.Server{
|
||||
Listener: nil,
|
||||
PacketConn: conn,
|
||||
Handler: mux,
|
||||
}
|
||||
go s.srv.ActivateAndServe()
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop the mDNS server
|
||||
func (s *MDNSServer) Stop() error {
|
||||
return s.srv.Shutdown()
|
||||
}
|
||||
|
||||
func (s *MDNSServer) Zone() Zone {
|
||||
return s.zone
|
||||
}
|
||||
|
||||
type LookupFunc func(ZoneLookup, *dns.Msg, *dns.Question) *dns.Msg
|
||||
|
||||
func (s *MDNSServer) makeHandler(qtype uint16, lookup LookupFunc) dns.HandlerFunc {
|
||||
return func(rw dns.ResponseWriter, r *dns.Msg) {
|
||||
// Handle only questions, ignore answers. We might also ignore
|
||||
// questions that arise locally (i.e., that come from an IP we
|
||||
// think is local), but in the interest of avoiding
|
||||
// complication, and easier testing, this is elided on the
|
||||
// assumption that the client wouldn't ask if it already knew
|
||||
// the answer, and if it does ask, it'll be happy to get an
|
||||
// answer.
|
||||
remoteAddr := rw.RemoteAddr()
|
||||
if s.addrIsLocal(remoteAddr) && !s.allowLocal {
|
||||
Log.Debugf("[mdns] srv: mDNS query from local host ('%s'): ignored", remoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: use any Answer in the query for adding records in the Zone database...
|
||||
if len(r.Answer) == 0 && len(r.Question) > 0 {
|
||||
q := &r.Question[0]
|
||||
if q.Qtype == qtype {
|
||||
Log.Debugf("[mdns msgid %d] srv: trying to answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
|
||||
if m := lookup(s.zone, r, q); m != nil {
|
||||
Log.Debugf("[mdns msgid %d] srv: found local answer to mDNS query '%s'", r.MsgHdr.Id, q.Name)
|
||||
if err := s.sendResponse(m); err != nil {
|
||||
Log.Warningf("[mdns msgid %d] srv: error writing mDNS response to %v", r.MsgHdr.Id, s.sendconn)
|
||||
} else {
|
||||
Log.Debugf("[mdns msgid %d] srv: response sent: %d answers", r.MsgHdr.Id, len(m.Answer))
|
||||
}
|
||||
} else {
|
||||
Log.Debugf("[mdns msgid %d] srv: no local answer for answering mDNS query '%s'",
|
||||
r.MsgHdr.Id, q.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MDNSServer) sendResponse(m *dns.Msg) error {
|
||||
buf, err := m.Pack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.sendconn.WriteTo(buf, ipv4Addr)
|
||||
return err
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
func TestServerSimpleQuery(t *testing.T) {
|
||||
var (
|
||||
testRecord1 = Record{"test.weave.local.", net.ParseIP("10.20.20.10"), 0, 0, 0}
|
||||
testRecord2 = Record{"test.weave.local.", net.ParseIP("10.20.20.20"), 0, 0, 0}
|
||||
testInAddr1 = "10.20.20.10.in-addr.arpa."
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestServerSimpleQuery starting")
|
||||
|
||||
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1, testRecord2})
|
||||
mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL)
|
||||
require.NoError(t, err)
|
||||
err = mdnsServer.Start(nil)
|
||||
require.NoError(t, err)
|
||||
defer mdnsServer.Stop()
|
||||
|
||||
var receivedAddrs []net.IP
|
||||
receivedName := ""
|
||||
recvChan := make(chan interface{})
|
||||
receivedCount := 0
|
||||
|
||||
// Implement a minimal listener for responses
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
handleMDNS := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
// Only handle responses here
|
||||
if len(r.Answer) > 0 {
|
||||
t.Logf("Received %d answer(s)", len(r.Answer))
|
||||
for _, answer := range r.Answer {
|
||||
recvChan <- answer
|
||||
}
|
||||
recvChan <- "ok"
|
||||
}
|
||||
}
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddrs = make([]net.IP, 0)
|
||||
receivedName = ""
|
||||
receivedCount = 0
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, querytype)
|
||||
m.RecursionDesired = false
|
||||
buf, err := m.Pack()
|
||||
require.NoError(t, err)
|
||||
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
require.NoError(t, err)
|
||||
Log.Debugf("Sending UDP packet to %s", ipv4Addr)
|
||||
_, err = conn.WriteTo(buf, ipv4Addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
Log.Debugf("Waiting for response")
|
||||
for {
|
||||
select {
|
||||
case x := <-recvChan:
|
||||
switch rr := x.(type) {
|
||||
case *dns.A:
|
||||
t.Logf("... A:\n%+v", rr)
|
||||
receivedAddrs = append(receivedAddrs, rr.A)
|
||||
receivedCount++
|
||||
case *dns.PTR:
|
||||
t.Logf("... PTR:\n%+v", rr)
|
||||
receivedName = rr.Ptr
|
||||
receivedCount++
|
||||
case string:
|
||||
return
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
Log.Debugf("Timeout while waiting for response")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listener := &dns.Server{
|
||||
Unsafe: true,
|
||||
PacketConn: multicast,
|
||||
Handler: dns.HandlerFunc(handleMDNS),
|
||||
ReadTimeout: 100 * time.Millisecond}
|
||||
go listener.ActivateAndServe()
|
||||
defer listener.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Log.Debugf("Checking that we get 2 IPs fo name '%s' [A]", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 2 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
if !(receivedAddrs[0].Equal(testRecord1.IP()) || receivedAddrs[0].Equal(testRecord2.IP())) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
|
||||
}
|
||||
if !(receivedAddrs[1].Equal(testRecord1.IP()) || receivedAddrs[1].Equal(testRecord2.IP())) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddrs, testRecord1.Name())
|
||||
}
|
||||
|
||||
Log.Debugf("Checking that 'testfail.weave.' [A] gets no answers")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Log.Debugf("Checking that '%s' [PTR] gets one name", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
} else if !(testRecord1.Name() == receivedName) {
|
||||
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Check that we can use a regular mDNS server with a regular mDNS client
|
||||
func TestClientServerSimpleQuery(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
|
||||
testRecord1 := Record{"test.weave.local.", net.ParseIP("10.2.2.1"), 0, 0, 0}
|
||||
testInAddr1 := "1.2.2.10.in-addr.arpa."
|
||||
|
||||
mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1})
|
||||
mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL)
|
||||
require.NoError(t, err)
|
||||
err = mdnsServer.Start(nil)
|
||||
require.NoError(t, err)
|
||||
defer mdnsServer.Stop()
|
||||
|
||||
var receivedAddr net.IP
|
||||
var receivedName string
|
||||
receivedCount := 0
|
||||
|
||||
mdnsCli, err := NewMDNSClient()
|
||||
require.NoError(t, err)
|
||||
err = mdnsCli.Start(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddr = nil
|
||||
receivedName = ""
|
||||
receivedCount = 0
|
||||
Log.Debugf("Sending query...")
|
||||
switch querytype {
|
||||
case dns.TypeA:
|
||||
r, err := mdnsCli.LookupName(name)
|
||||
if len(r) > 0 {
|
||||
receivedAddr = r[0].IP()
|
||||
if err == nil {
|
||||
receivedCount++
|
||||
}
|
||||
}
|
||||
case dns.TypePTR:
|
||||
r, err := mdnsCli.LookupInaddr(name)
|
||||
if len(r) > 0 {
|
||||
receivedName = r[0].Name()
|
||||
if err == nil {
|
||||
receivedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Log.Debugf("Query: %s dns.TypeA", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
if !receivedAddr.Equal(testRecord1.IP()) {
|
||||
t.Fatalf("Unexpected result %s for %s", receivedAddr, testRecord1.Name())
|
||||
}
|
||||
|
||||
Log.Debugf("Query: testfail.weave. dns.TypeA")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Log.Debugf("Query: %s dns.TypePTR", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
} else if !(testRecord1.Name() == receivedName) {
|
||||
t.Fatalf("Expected answer %s to query for %s, got %s", testRecord1.Name(), testInAddr1, receivedName)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we can use a use "insistent" queries
|
||||
func TestClientServerInsistentQuery(t *testing.T) {
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
|
||||
testRecord1 := Record{"test.weave.local.", net.ParseIP("10.2.2.1"), 0, 0, 0}
|
||||
testInAddr1 := "1.2.2.10.in-addr.arpa."
|
||||
testRecord2 := Record{"test.weave.local.", net.ParseIP("10.2.2.2"), 0, 0, 0}
|
||||
testInAddr2 := "2.2.2.10.in-addr.arpa."
|
||||
|
||||
mzone1 := newMockedZoneWithRecords([]ZoneRecord{testRecord1})
|
||||
mdnsServer1, err := NewMDNSServer(mzone1, true, DefaultLocalTTL)
|
||||
require.NoError(t, err)
|
||||
err = mdnsServer1.Start(nil)
|
||||
require.NoError(t, err)
|
||||
defer mdnsServer1.Stop()
|
||||
|
||||
mzone2 := newMockedZoneWithRecords([]ZoneRecord{testRecord2})
|
||||
mdnsServer2, err := NewMDNSServer(mzone2, true, DefaultLocalTTL)
|
||||
require.NoError(t, err)
|
||||
err = mdnsServer2.Start(nil)
|
||||
require.NoError(t, err)
|
||||
defer mdnsServer2.Stop()
|
||||
|
||||
// create a third server with exactly the same info as the second server (so we can test duplicates removals)
|
||||
mdnsServer3, err := NewMDNSServer(mzone2, true, DefaultLocalTTL)
|
||||
require.NoError(t, err)
|
||||
err = mdnsServer3.Start(nil)
|
||||
require.NoError(t, err)
|
||||
defer mdnsServer3.Stop()
|
||||
|
||||
var receivedAddrs []ZoneRecord
|
||||
var receivedNames []ZoneRecord
|
||||
receivedCount := 0
|
||||
|
||||
mdnsCli, err := NewMDNSClient()
|
||||
require.NoError(t, err)
|
||||
err = mdnsCli.Start(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sendQuery := func(name string, querytype uint16) {
|
||||
receivedAddrs = nil
|
||||
receivedNames = nil
|
||||
receivedCount = 0
|
||||
Log.Debugf("Sending query...")
|
||||
switch querytype {
|
||||
case dns.TypeA:
|
||||
receivedAddrs, err = mdnsCli.InsistentLookupName(name)
|
||||
if err == nil {
|
||||
receivedCount = len(receivedAddrs)
|
||||
}
|
||||
case dns.TypePTR:
|
||||
receivedNames, err = mdnsCli.InsistentLookupInaddr(name)
|
||||
if err == nil {
|
||||
receivedCount = len(receivedNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // Allow for server to get going
|
||||
|
||||
Log.Debugf("Query: %s dns.TypeA", testRecord1.Name())
|
||||
sendQuery(testRecord1.Name(), dns.TypeA)
|
||||
if receivedCount != 2 {
|
||||
t.Fatalf("Unexpected result count %d for %s", receivedCount, testRecord1.Name())
|
||||
}
|
||||
|
||||
Log.Debugf("Query: testfail.weave. dns.TypeA")
|
||||
sendQuery("testfail.weave.", dns.TypeA)
|
||||
if receivedCount != 0 {
|
||||
t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
|
||||
}
|
||||
|
||||
Log.Debugf("Query: %s dns.TypePTR", testInAddr1)
|
||||
sendQuery(testInAddr1, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
|
||||
}
|
||||
|
||||
Log.Debugf("Query: %s dns.TypePTR", testInAddr2)
|
||||
sendQuery(testInAddr2, dns.TypePTR)
|
||||
if receivedCount != 1 {
|
||||
t.Fatalf("Expected an answer to %s, got %d answers", testInAddr2, receivedCount)
|
||||
}
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
testSocketTimeout = 100 // in millisecs
|
||||
)
|
||||
|
||||
// Warn about some methods that some day should be implemented...
|
||||
func notImplWarn() { Log.Warningf("Mocked method. Not implemented.") }
|
||||
|
||||
// A mocked Zone that always returns the same records
|
||||
// * it does not send/receive any mDNS query
|
||||
type mockedZoneWithRecords struct {
|
||||
sync.RWMutex
|
||||
|
||||
records []ZoneRecord
|
||||
|
||||
// Statistics
|
||||
NumLookupsName int
|
||||
NumLookupsInaddr int
|
||||
}
|
||||
|
||||
func newMockedZoneWithRecords(zr []ZoneRecord) *mockedZoneWithRecords {
|
||||
return &mockedZoneWithRecords{records: zr}
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) Domain() string { return DefaultLocalDomain }
|
||||
func (mz *mockedZoneWithRecords) LookupName(name string) ([]ZoneRecord, error) {
|
||||
Log.Debugf("[mocked zone]: LookupName: returning records %s", mz.records)
|
||||
mz.Lock()
|
||||
defer mz.Unlock()
|
||||
|
||||
mz.NumLookupsName++
|
||||
var res []ZoneRecord
|
||||
for _, r := range mz.records {
|
||||
if r.Name() == name {
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mz *mockedZoneWithRecords) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
Log.Debugf("[mocked zone]: LookupInaddr: returning records %s", mz.records)
|
||||
mz.Lock()
|
||||
defer mz.Unlock()
|
||||
|
||||
mz.NumLookupsInaddr++
|
||||
var res []ZoneRecord
|
||||
for _, r := range mz.records {
|
||||
revIP, err := raddrToIP(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
if r.IP().Equal(revIP) {
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mz *mockedZoneWithRecords) DomainLookupName(name string) ([]ZoneRecord, error) {
|
||||
return mz.LookupName(name)
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) DomainLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
return mz.LookupInaddr(inaddr)
|
||||
}
|
||||
|
||||
// the following methods are not currently needed...
|
||||
func (mz *mockedZoneWithRecords) AddRecord(ident string, name string, ip net.IP) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) DeleteRecords(ident string, name string, ip net.IP) int {
|
||||
notImplWarn()
|
||||
return 0
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) Status() string { notImplWarn(); return "nothing" }
|
||||
func (mz *mockedZoneWithRecords) ObserveName(name string, observer ZoneRecordObserver) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
func (mz *mockedZoneWithRecords) ObserveInaddr(inaddr string, observer ZoneRecordObserver) error {
|
||||
notImplWarn()
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked mDNS server
|
||||
type mockedMDNSServer struct {
|
||||
zone Zone
|
||||
}
|
||||
|
||||
func newMockedMDNSServer(z Zone) *mockedMDNSServer { return &mockedMDNSServer{zone: z} }
|
||||
func (ms *mockedMDNSServer) Start(_ *net.Interface) error { return nil }
|
||||
func (ms *mockedMDNSServer) Stop() error { return nil }
|
||||
func (ms *mockedMDNSServer) Zone() Zone { return ms.zone }
|
||||
|
||||
// a useful mix of NewMockedZone and NewMockedMDNSServer
|
||||
func newMockedMDNSServerWithRecord(zr ZoneRecord) *mockedMDNSServer {
|
||||
return newMockedMDNSServer(newMockedZoneWithRecords([]ZoneRecord{zr}))
|
||||
}
|
||||
func newMockedMDNSServerWithRecords(zrs []ZoneRecord) *mockedMDNSServer {
|
||||
return newMockedMDNSServer(newMockedZoneWithRecords(zrs))
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked mDNS client.
|
||||
// This mock asks a group of (potentially mocked) mDNS servers
|
||||
type mockedMDNSClient struct {
|
||||
sync.RWMutex
|
||||
|
||||
servers map[ZoneMDNSServer]ZoneMDNSServer
|
||||
|
||||
// Statistics
|
||||
NumLookupsName int
|
||||
NumLookupsInaddr int
|
||||
NumInsLookupsName int
|
||||
NumInsLookupsInaddr int
|
||||
}
|
||||
|
||||
func newMockedMDNSClient(srvrs []*mockedMDNSServer) *mockedMDNSClient {
|
||||
r := mockedMDNSClient{
|
||||
servers: make(map[ZoneMDNSServer]ZoneMDNSServer),
|
||||
}
|
||||
if srvrs != nil {
|
||||
for _, server := range srvrs {
|
||||
r.servers[server] = server
|
||||
}
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) Start(ifi *net.Interface) error { return nil }
|
||||
func (mc *mockedMDNSClient) Stop() error { return nil }
|
||||
func (mc *mockedMDNSClient) Domain() string { return DefaultLocalDomain }
|
||||
func (mc *mockedMDNSClient) LookupName(name string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
// get a random result
|
||||
mc.NumLookupsName++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupName(name)
|
||||
if err == nil {
|
||||
return r, nil // return the first answer
|
||||
}
|
||||
}
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
// get a random result
|
||||
mc.NumLookupsInaddr++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupInaddr(inaddr)
|
||||
if err == nil {
|
||||
return r, nil // return the first answer
|
||||
}
|
||||
}
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) InsistentLookupName(name string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
var res []ZoneRecord
|
||||
mc.NumInsLookupsName++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupName(name)
|
||||
if err == nil {
|
||||
res = append(res, r...)
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) InsistentLookupInaddr(inaddr string) ([]ZoneRecord, error) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
var res []ZoneRecord
|
||||
mc.NumInsLookupsInaddr++
|
||||
for _, server := range mc.servers {
|
||||
r, err := server.Zone().LookupInaddr(inaddr)
|
||||
if err == nil {
|
||||
res = append(res, r...)
|
||||
}
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) AddServer(srv ZoneMDNSServer) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
mc.servers[srv] = srv
|
||||
}
|
||||
|
||||
func (mc *mockedMDNSClient) RemoveServer(srv ZoneMDNSServer) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
delete(mc.servers, srv)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
type zbmEntry struct {
|
||||
Server *mockedMDNSServer
|
||||
Client *mockedMDNSClient
|
||||
Zone *ZoneDb
|
||||
}
|
||||
type zoneDbsWithMockedMDns []*zbmEntry
|
||||
|
||||
// Creates a group of (real) zone databases linked through mocked mDNS servers and clients
|
||||
// This effectively creates a cluster of WeaveDNS peers
|
||||
func newZoneDbsWithMockedMDns(num int, config ZoneConfig) zoneDbsWithMockedMDns {
|
||||
res := make(zoneDbsWithMockedMDns, num)
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
res[i] = new(zbmEntry)
|
||||
|
||||
Log.Debugf("[test] Creating mocked mDNS server #%d", i)
|
||||
res[i].Server = newMockedMDNSServer(nil)
|
||||
|
||||
Log.Debugf("[test] Creating mocked mDNS client #%d", i)
|
||||
res[i].Client = newMockedMDNSClient([]*mockedMDNSServer{})
|
||||
|
||||
cfg := config
|
||||
cfg.MDNSClient = res[i].Client
|
||||
cfg.MDNSServer = res[i].Server
|
||||
|
||||
Log.Debugf("[test] Creating ZoneDb #%d", i)
|
||||
res[i].Zone, _ = NewZoneDb(cfg)
|
||||
|
||||
// link the mDNS server to its zone
|
||||
// ZoneDbs are connected to mDNS in two directions: they use their mDNS
|
||||
// clients for asking other peers about names/IPs, and export their records
|
||||
// through a mDNS server.
|
||||
res[i].Server.zone = res[i].Zone
|
||||
}
|
||||
|
||||
// link all the clients to all the servers
|
||||
for i := 0; i < num; i++ {
|
||||
for j := 0; j < num; j++ {
|
||||
// with this check, the local client will not ask to the local server
|
||||
// this is not what we currently do, but can be convenient in some cases
|
||||
// for finding some bugs...
|
||||
if i != j {
|
||||
Log.Debugf("[test] Linking mocked mDNS client #%d to server #%d", i, j)
|
||||
res[i].Client.AddServer(res[j].Server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Start() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.Start()
|
||||
}
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Stop() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (zbs zoneDbsWithMockedMDns) Flush() {
|
||||
for _, entry := range zbs {
|
||||
entry.Zone.refreshScheds.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked cache where we never find a single thing... ;)
|
||||
type mockedCache struct {
|
||||
NumGets int
|
||||
NumPuts int
|
||||
NumRemovals int
|
||||
}
|
||||
|
||||
func newMockedCache() *mockedCache { return &mockedCache{} }
|
||||
|
||||
func (c *mockedCache) Get(request *dns.Msg, maxLen int) (reply *dns.Msg, err error) {
|
||||
c.NumGets++
|
||||
return nil, nil
|
||||
}
|
||||
func (c *mockedCache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
|
||||
c.NumPuts++
|
||||
return 0
|
||||
}
|
||||
func (c *mockedCache) Remove(question *dns.Question) { c.NumRemovals++ }
|
||||
func (c *mockedCache) Purge() {}
|
||||
func (c *mockedCache) Clear() {}
|
||||
func (c *mockedCache) Len() int { return 0 }
|
||||
func (c *mockedCache) Capacity() int { return DefaultCacheLen }
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// A mocked fallback server
|
||||
type mockedFallback struct {
|
||||
CliConfig *dns.ClientConfig
|
||||
Addr string
|
||||
Port int
|
||||
|
||||
udpSrv *dns.Server
|
||||
tcpSrv *dns.Server
|
||||
}
|
||||
|
||||
// Create a mocked fallback server
|
||||
// You can use the server's `CliConfig` as the `UpstreamCfg` of a `DNSServer`...
|
||||
func newMockedFallback(udpH dns.HandlerFunc, tcpH dns.HandlerFunc) (*mockedFallback, error) {
|
||||
udpSrv, fallbackAddr, err := runLocalUDPServer("127.0.0.1:0", udpH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, fallbackPort, err := net.SplitHostPort(fallbackAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fallbackPortI, _ := strconv.Atoi(fallbackPort)
|
||||
|
||||
res := mockedFallback{
|
||||
udpSrv: udpSrv,
|
||||
Addr: fallbackAddr,
|
||||
Port: fallbackPortI,
|
||||
CliConfig: &dns.ClientConfig{
|
||||
Servers: []string{"127.0.0.1"},
|
||||
Port: fallbackPort,
|
||||
},
|
||||
}
|
||||
|
||||
if tcpH != nil {
|
||||
fallbackTCPAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort)
|
||||
res.tcpSrv, _, err = runLocalTCPServer(fallbackTCPAddr, tcpH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// Start the fallback server
|
||||
func (mf *mockedFallback) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop the fallback server
|
||||
func (mf *mockedFallback) Stop() error {
|
||||
if mf.tcpSrv != nil {
|
||||
mf.udpSrv.Shutdown()
|
||||
mf.udpSrv = nil
|
||||
}
|
||||
if mf.tcpSrv != nil {
|
||||
mf.tcpSrv.Shutdown()
|
||||
mf.udpSrv = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run a UDP fallback server
|
||||
func runLocalUDPServer(laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
|
||||
Log.Debugf("[mocked fallback] Starting fallback UDP server at %s", laddr)
|
||||
pc, err := net.ListenPacket("udp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
server := &dns.Server{PacketConn: pc, Handler: handler, ReadTimeout: testSocketTimeout * time.Millisecond}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
pc.Close()
|
||||
}()
|
||||
|
||||
Log.Debugf("[mocked fallback] Fallback UDP server listening at %s", pc.LocalAddr())
|
||||
return server, pc.LocalAddr().String(), nil
|
||||
}
|
||||
|
||||
// Run a TCP fallback server
|
||||
func runLocalTCPServer(laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
|
||||
Log.Debugf("[mocked fallback] Starting fallback TCP server at %s", laddr)
|
||||
laddrTCP, err := net.ResolveTCPAddr("tcp", laddr)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", laddrTCP)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
server := &dns.Server{Listener: l, Handler: handler, ReadTimeout: testSocketTimeout * time.Millisecond}
|
||||
|
||||
go func() {
|
||||
server.ActivateAndServe()
|
||||
l.Close()
|
||||
}()
|
||||
|
||||
Log.Debugf("[mocked fallback] Fallback TCP server listening at %s", l.Addr().String())
|
||||
return server, l.Addr().String(), nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
type mockedClock struct {
|
||||
*clock.Mock
|
||||
}
|
||||
|
||||
func newMockedClock() *mockedClock {
|
||||
return &mockedClock{clock.NewMock()}
|
||||
}
|
||||
|
||||
// Note: moving the clock forward does not take into account the data that could
|
||||
// be waiting in channels. So your code should not depend on the channels
|
||||
// for creating new timers. Otherwise, time travelling will not be reliable...
|
||||
func (clk *mockedClock) Forward(secs int) {
|
||||
Log.Debugf(">>>>>>> Moving clock forward %d seconds - Time traveling >>>>>>>", secs)
|
||||
clk.Add(time.Duration(secs) * time.Second)
|
||||
Log.Debugf("<<<<<<< Time travel finished! We are at %s <<<<<<<", clk.Now())
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// Perform a DNS query and assert the reply code, number or answers, etc
|
||||
func assertExchange(t *testing.T, z string, ty uint16, port int, minAnswers int, maxAnswers int, expErr int) (*dns.Msg, *dns.Msg) {
|
||||
require.NotEqual(t, 0, port, "invalid DNS server port")
|
||||
|
||||
c := &dns.Client{
|
||||
UDPSize: testUDPBufSize,
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.RecursionDesired = true
|
||||
m.SetQuestion(z, ty)
|
||||
m.SetEdns0(testUDPBufSize, false) // we don't want to play with truncation here...
|
||||
|
||||
lstAddr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
r, _, err := c.Exchange(m, lstAddr)
|
||||
t.Logf("Response from '%s':\n%+v\n", lstAddr, r)
|
||||
if err != nil {
|
||||
t.Errorf("Error when querying DNS server at %s: %s", lstAddr, err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if minAnswers == 0 && maxAnswers == 0 {
|
||||
require.Equal(t, expErr, r.Rcode, "DNS response code")
|
||||
} else {
|
||||
require.Equal(t, dns.RcodeSuccess, r.Rcode, "DNS response code")
|
||||
}
|
||||
answers := len(r.Answer)
|
||||
if minAnswers >= 0 && answers < minAnswers {
|
||||
require.FailNow(t, fmt.Sprintf("Number of answers >= %d", minAnswers))
|
||||
}
|
||||
if maxAnswers >= 0 && answers > maxAnswers {
|
||||
require.FailNow(t, fmt.Sprintf("Number of answers <= %d", maxAnswers))
|
||||
}
|
||||
return m, r
|
||||
}
|
||||
|
||||
// Assert that we have a response in the cache for a query `q`
|
||||
func assertInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, r, fmt.Sprintf("value in the cache: %s", desc))
|
||||
}
|
||||
|
||||
// Assert that we have a response in the cache for a query `q`
|
||||
func assertNotLocalInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
if !(r == nil && err == errNoLocalReplies) {
|
||||
t.Fatalf("Cache does not return a noLocalReplies error for query %s", q)
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that we do not have a response in the cache for a query `q`
|
||||
func assertNotInCache(t *testing.T, cache ZoneCache, q *dns.Msg, desc string) {
|
||||
r, err := cache.Get(q, maxUDPSize)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, r, fmt.Sprintf("value in the cache: %s\n%s", desc, r))
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultServerPort = 53 // The default server port
|
||||
DefaultHTTPPort = 6785 // The default http port
|
||||
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
|
||||
DefaultLocalTTL = 30 // default TTL for responses for local domain queries
|
||||
DefaultUDPBuflen = 4096 // bigger than the default 512
|
||||
DefaultCacheLen = 8192 // default cache capacity
|
||||
DefaultTimeout = 5000 // default timeout for DNS resolutions (millisecs)
|
||||
DefaultMaxAnswers = 1 // default number of answers provided to users
|
||||
)
|
||||
|
||||
type DNSServerConfig struct {
|
||||
// The zone
|
||||
Zone Zone
|
||||
// (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) cache size
|
||||
CacheLen int
|
||||
// (Optional) disable the cache
|
||||
CacheDisabled bool
|
||||
// (Optional) timeout for DNS queries
|
||||
Timeout int
|
||||
// (Optional) UDP buffer length
|
||||
UDPBufLen int
|
||||
// (Optional) force a specific cache
|
||||
Cache ZoneCache
|
||||
// (Optional) TTL for local domain responses
|
||||
LocalTTL int
|
||||
// (Optional) TTL for negative results in the local domain (defaults to LocalTTL)
|
||||
CacheNegLocalTTL int
|
||||
// (Optional) for a specific clock provider
|
||||
Clock clock.Clock
|
||||
// (Optional) Listening socket read timeout (in milliseconds)
|
||||
ListenReadTimeout int
|
||||
// Maximum number of answers provided to users
|
||||
MaxAnswers 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, timeout time.Duration) *dns.Client {
|
||||
Log.Debugf("[dns] Creating %s DNS client with %s timeout", proto, timeout)
|
||||
switch proto {
|
||||
case protTCP:
|
||||
return &dns.Client{Net: "tcp", ReadTimeout: timeout}
|
||||
case protUDP:
|
||||
return &dns.Client{Net: "udp", ReadTimeout: timeout, UDPSize: uint16(bufsize)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// a DNS server
|
||||
type DNSServer struct {
|
||||
Zone Zone
|
||||
Upstream *dns.ClientConfig
|
||||
Domain string // the local domain
|
||||
ListenAddr string // the address the server is listening at
|
||||
|
||||
udpSrv *dns.Server
|
||||
tcpSrv *dns.Server
|
||||
pc net.PacketConn
|
||||
lst net.Listener
|
||||
cache ZoneCache
|
||||
cacheDisabled bool
|
||||
maxAnswers int
|
||||
localTTL int
|
||||
negLocalTTL int
|
||||
timeout time.Duration
|
||||
readTimeout time.Duration
|
||||
udpBuf int
|
||||
listenersWg *sync.WaitGroup
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// Creates a new DNS server
|
||||
func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) {
|
||||
s = &DNSServer{
|
||||
Zone: config.Zone,
|
||||
Domain: DefaultLocalDomain,
|
||||
ListenAddr: fmt.Sprintf(":%d", config.Port),
|
||||
|
||||
listenersWg: new(sync.WaitGroup),
|
||||
timeout: DefaultTimeout * time.Millisecond,
|
||||
readTimeout: DefaultTimeout * time.Millisecond,
|
||||
cacheDisabled: false,
|
||||
maxAnswers: DefaultMaxAnswers,
|
||||
localTTL: DefaultLocalTTL,
|
||||
clock: config.Clock,
|
||||
}
|
||||
|
||||
// check some basic parameters are valid
|
||||
if s.Zone == nil {
|
||||
return nil, fmt.Errorf("No valid Zone provided in server initialization")
|
||||
}
|
||||
if len(s.Domain) == 0 {
|
||||
return nil, fmt.Errorf("No valid Domain provided in server initialization")
|
||||
}
|
||||
if s.clock == nil {
|
||||
s.clock = clock.New()
|
||||
}
|
||||
|
||||
// fill empty parameters with defaults...
|
||||
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 = time.Duration(config.Timeout) * time.Millisecond
|
||||
}
|
||||
if config.ListenReadTimeout > 0 {
|
||||
s.readTimeout = time.Duration(config.ListenReadTimeout) * time.Millisecond
|
||||
}
|
||||
if config.UDPBufLen > 0 {
|
||||
s.udpBuf = config.UDPBufLen
|
||||
}
|
||||
if config.MaxAnswers > 0 {
|
||||
s.maxAnswers = config.MaxAnswers
|
||||
}
|
||||
if config.LocalTTL > 0 {
|
||||
s.localTTL = config.LocalTTL
|
||||
}
|
||||
if config.CacheNegLocalTTL > 0 {
|
||||
s.negLocalTTL = config.CacheNegLocalTTL
|
||||
} else {
|
||||
s.negLocalTTL = s.localTTL
|
||||
}
|
||||
if config.CacheDisabled {
|
||||
s.cacheDisabled = true
|
||||
}
|
||||
if !s.cacheDisabled {
|
||||
if config.Cache != nil {
|
||||
s.cache = config.Cache
|
||||
} else {
|
||||
cacheLen := DefaultCacheLen
|
||||
if config.CacheLen > 0 {
|
||||
cacheLen = config.CacheLen
|
||||
}
|
||||
if s.cache, err = NewCache(cacheLen, s.clock); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start the DNS server
|
||||
func (s *DNSServer) Start() error {
|
||||
Log.Infof("[dns] Upstream server(s): %+v", s.Upstream)
|
||||
if s.cacheDisabled {
|
||||
Log.Infof("[dns] Cache: disabled")
|
||||
} else {
|
||||
Log.Infof("[dns] Cache: %d entries", s.cache.Capacity())
|
||||
}
|
||||
|
||||
pc, err := net.ListenPacket("udp", s.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.pc = pc
|
||||
|
||||
_, port, err := net.SplitHostPort(pc.LocalAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.ListenAddr = fmt.Sprintf(":%s", port)
|
||||
s.udpSrv = &dns.Server{PacketConn: s.pc, Handler: s.createMux(protUDP), ReadTimeout: s.readTimeout}
|
||||
|
||||
// Bind the TCP socket at the same port, aborting otherwise
|
||||
l, err := net.Listen("tcp", s.ListenAddr)
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
return err
|
||||
}
|
||||
s.lst = l
|
||||
s.tcpSrv = &dns.Server{Listener: l, Handler: s.createMux(protTCP), ReadTimeout: s.readTimeout}
|
||||
|
||||
s.listenersWg.Add(2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DNSServer) ActivateAndServe() {
|
||||
go func() {
|
||||
defer s.listenersWg.Done()
|
||||
|
||||
Log.Infof("[dns] Listening for DNS on %s (UDP)", s.ListenAddr)
|
||||
err := s.udpSrv.ActivateAndServe()
|
||||
CheckFatal(err)
|
||||
Log.Debugf("[dns] DNS UDP server exiting...")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer s.listenersWg.Done()
|
||||
|
||||
Log.Infof("[dns] Listening for DNS on %s (TCP)", s.ListenAddr)
|
||||
err := s.tcpSrv.ActivateAndServe()
|
||||
CheckFatal(err)
|
||||
Log.Debugf("[dns] DNS TCP server exiting...")
|
||||
}()
|
||||
|
||||
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
|
||||
s.listenersWg.Wait()
|
||||
|
||||
Log.Infof("[dns] Server exiting...")
|
||||
}
|
||||
|
||||
// Return status string
|
||||
func (s *DNSServer) Status() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
|
||||
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Perform a graceful shutdown
|
||||
func (s *DNSServer) Stop() error {
|
||||
// Stop the listeners/handlers
|
||||
if s.tcpSrv != nil {
|
||||
if err := s.tcpSrv.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.lst.Close()
|
||||
s.tcpSrv = nil
|
||||
}
|
||||
if s.udpSrv != nil {
|
||||
if err := s.udpSrv.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.pc.Close()
|
||||
s.udpSrv = nil
|
||||
}
|
||||
s.listenersWg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a multiplexer for requests
|
||||
// We must create two DNS request multiplexers, depending on the protocol used by
|
||||
// clients (as we use the same protocol for asking upstream servers)
|
||||
func (s *DNSServer) createMux(proto dnsProtocol) *dns.ServeMux {
|
||||
failFallback := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
notUsHandler := s.notUsHandler(proto)
|
||||
notUsFallback := func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
Log.Infof("[dns msgid %d] -> sending to fallback server", r.MsgHdr.Id)
|
||||
notUsHandler(w, r)
|
||||
}
|
||||
|
||||
// create the multiplexer
|
||||
m := dns.NewServeMux()
|
||||
m.HandleFunc(s.Zone.Domain(), s.localHandler(proto, "Query", dns.TypeA,
|
||||
s.Zone.DomainLookupName, makeAddressReply, s.Zone.ObserveName, failFallback))
|
||||
m.HandleFunc(RDNSDomain, s.localHandler(proto, "Reverse query", dns.TypePTR,
|
||||
s.Zone.DomainLookupInaddr, makePTRReply, s.Zone.ObserveInaddr, notUsFallback))
|
||||
m.HandleFunc(".", s.notUsHandler(proto))
|
||||
return m
|
||||
}
|
||||
|
||||
// Process a request for a name/address in the '.weave.local.' zone
|
||||
func (s *DNSServer) localHandler(proto dnsProtocol, kind string, qtype uint16,
|
||||
lookup func(inaddr string) ([]ZoneRecord, error),
|
||||
msgBuilder DNSResponseBuilder, observer ZoneObserverFunc, fallback dns.HandlerFunc) dns.HandlerFunc {
|
||||
|
||||
return func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
q := r.Question[0]
|
||||
Log.Debugf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
|
||||
maxLen := getMaxReplyLen(r, proto)
|
||||
|
||||
// cache a response if the cache is enabled, and observe the name/IP
|
||||
maybeCache := func(m *dns.Msg, ttl int, flags uint8) {
|
||||
if !s.cacheDisabled {
|
||||
s.cache.Put(r, m, ttl, flags)
|
||||
// any change in the Zone database for this IP/name will lead to this
|
||||
// cache entry being removed...
|
||||
// TODO: this closure results in unnecessary `Remove`s and some wasted
|
||||
// mem... but we can live with that.
|
||||
observer(q.Name, func() { s.cache.Remove(&q) })
|
||||
}
|
||||
}
|
||||
|
||||
if !s.cacheDisabled {
|
||||
reply, err := s.cache.Get(r, maxLen)
|
||||
if err != nil {
|
||||
if err == errNoLocalReplies {
|
||||
Log.Debugf("[dns msgid %d] Cached no-local-replies", r.MsgHdr.Id)
|
||||
fallback(w, r)
|
||||
} else {
|
||||
Log.Debugf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
return
|
||||
}
|
||||
if reply != nil {
|
||||
reply.Answer = pruneAnswers(shuffleAnswers(reply.Answer), s.maxAnswers)
|
||||
Log.Debugf("[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 {
|
||||
Log.Debugf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
|
||||
m := makeDNSFailResponse(r)
|
||||
maybeCache(m, s.negLocalTTL, 0)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
}
|
||||
|
||||
if answers, err := lookup(q.Name); err != nil {
|
||||
Log.Infof("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
|
||||
r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
|
||||
|
||||
maybeCache(nil, s.negLocalTTL, CacheNoLocalReplies)
|
||||
fallback(w, r)
|
||||
} else {
|
||||
m := msgBuilder(r, &q, answers, s.localTTL)
|
||||
m.Authoritative = true
|
||||
maybeCache(m, nullTTL, 0)
|
||||
m.Answer = pruneAnswers(shuffleAnswers(m.Answer), s.maxAnswers)
|
||||
Log.Debugf("[dns msgid %d] Sending response: %s/%d answers [code:%s]",
|
||||
m.MsgHdr.Id, dns.TypeToString[q.Qtype], len(m.Answer), dns.RcodeToString[m.Rcode])
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, s.timeout)
|
||||
|
||||
return func(w dns.ResponseWriter, r *dns.Msg) {
|
||||
q := r.Question[0]
|
||||
|
||||
// announce our max payload size as the max payload our client supports
|
||||
maxLen := getMaxReplyLen(r, proto)
|
||||
rcopy := r
|
||||
rcopy.SetEdns0(uint16(maxLen), false)
|
||||
|
||||
Log.Debugf("[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 {
|
||||
Log.Debugf("[dns msgid %d] Network error trying %s (%s)",
|
||||
r.MsgHdr.Id, server, err)
|
||||
continue
|
||||
}
|
||||
if reply != nil && reply.Rcode != dns.RcodeSuccess {
|
||||
Log.Debugf("[dns msgid %d] Failure reported by %s for query %s",
|
||||
r.MsgHdr.Id, server, q.Name)
|
||||
continue
|
||||
}
|
||||
Log.Debugf("[dns msgid %d] Given answer by %s for query %s",
|
||||
r.MsgHdr.Id, server, q.Name)
|
||||
w.WriteMsg(reply)
|
||||
return
|
||||
}
|
||||
Log.Warningf("[dns msgid %d] Failed lookup for external name %s", r.MsgHdr.Id, q.Name)
|
||||
w.WriteMsg(makeDNSFailResponse(r))
|
||||
}
|
||||
}
|
||||
|
||||
// Get the listen port
|
||||
func (s *DNSServer) GetPort() (int, error) {
|
||||
_, portS, err := net.SplitHostPort(s.ListenAddr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(portS)
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Check that AddRecord/DeleteRecord/... in the Zone database lead to cache invalidations
|
||||
func TestServerDbCacheInvalidation(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestServerDbCacheInvalidation starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
Log.Debugf("Creating mocked mDNS client and server")
|
||||
mdnsServer1 := newMockedMDNSServerWithRecord(Record{testName1, net.ParseIP("10.2.2.9"), 0, 0, 0})
|
||||
mdnsCli1 := newMockedMDNSClient([]*mockedMDNSServer{mdnsServer1})
|
||||
|
||||
Log.Debugf("Creating zone database with the mocked mDNS client and server")
|
||||
zoneConfig := ZoneConfig{
|
||||
MDNSServer: mdnsServer1,
|
||||
MDNSClient: mdnsCli1,
|
||||
Clock: clk,
|
||||
}
|
||||
zone, err := NewZoneDb(zoneConfig)
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
Log.Debugf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
require.NoError(t, err)
|
||||
|
||||
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
if len(req.Question) == 1 {
|
||||
m.Rcode = dns.RcodeNameError
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// Run another DNS server for fallback
|
||||
fallback, err := newMockedFallback(fallbackHandler, nil)
|
||||
require.NoError(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
Log.Debugf("Creating a real DNS server with a mocked cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
MaxAnswers: 4,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Start()
|
||||
require.NoError(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow server goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, testPort, "invalid listen port")
|
||||
|
||||
Log.Debugf("Adding two IPs to %s", testName1)
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2"))
|
||||
q, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
assertInCache(t, cache, q, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2
|
||||
|
||||
zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.1"))
|
||||
assertInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName2))
|
||||
|
||||
// we should have an entry in the cache for this query
|
||||
// if we add another IP, that cache entry should be removed
|
||||
Log.Debugf("Adding a new IP to %s: the cache entry should be removed", testName1)
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.3"))
|
||||
assertNotInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName1))
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
Log.Debugf("Querying again (so a cache entry will be created)")
|
||||
q, _ = assertExchange(t, testName1, dns.TypeA, testPort, 3, 4, 0)
|
||||
assertInCache(t, cache, q, "after asking about the name")
|
||||
Log.Debugf("... and removing one of the IP addresses")
|
||||
zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.2"))
|
||||
assertNotInCache(t, cache, q, "after deleting IP for 10.2.2.2")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
// generate cache responses
|
||||
Log.Debugf("Querying for a raddr")
|
||||
qname, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
qptr, _ := assertExchange(t, "1.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qotherName, _ := assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherPtr, _ := assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qwrongName, _ := assertExchange(t, "wrong.weave.local.", dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qname, "after asking for name")
|
||||
assertInCache(t, cache, qptr, "after asking for address")
|
||||
assertInCache(t, cache, qotherName, "after asking for second name")
|
||||
assertInCache(t, cache, qotherPtr, "after asking for second address")
|
||||
assertNotLocalInCache(t, cache, qwrongName, "after asking for a wrong name")
|
||||
|
||||
// now we will check if a removal affects all the responses
|
||||
Log.Debugf("... and removing an IP should invalidate both the cached responses for name and raddr")
|
||||
zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
assertNotInCache(t, cache, qptr, "after deleting record")
|
||||
assertNotInCache(t, cache, qname, "after deleting record")
|
||||
assertInCache(t, cache, qotherName, "after deleting record")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.3
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
// generate cache responses
|
||||
Log.Debugf("Querying for a raddr")
|
||||
qptr, _ = assertExchange(t, "3.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
qname, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherName, _ = assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0)
|
||||
qotherPtr, _ = assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0)
|
||||
assertInCache(t, cache, qname, "after asking for name")
|
||||
assertInCache(t, cache, qptr, "after asking for PTR")
|
||||
assertInCache(t, cache, qotherName, "after asking for second name")
|
||||
assertInCache(t, cache, qotherPtr, "after asking for second address")
|
||||
|
||||
// let's repeat this, but adding an IP
|
||||
Log.Debugf("... and adding a new IP should invalidate both the cached responses for the name")
|
||||
zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.7"))
|
||||
assertNotInCache(t, cache, qname, "after adding a new IP")
|
||||
assertInCache(t, cache, qotherName, "after adding a new IP")
|
||||
assertInCache(t, cache, qotherPtr, "after adding a new IP")
|
||||
|
||||
// check that after some time, the cache entry is expired
|
||||
clk.Forward(int(DefaultLocalTTL) + 1)
|
||||
assertNotInCache(t, cache, qotherName, "after passing some time")
|
||||
assertNotInCache(t, cache, qwrongName, "after passing some time")
|
||||
|
||||
// Zone database at this point:
|
||||
// first.weave.local = 10.2.2.3 10.2.2.7
|
||||
// second.weave.local = 10.9.9.1
|
||||
|
||||
zone.DeleteRecords(containerID, "", net.IP{})
|
||||
assertNotInCache(t, cache, qotherName, "after removing container")
|
||||
assertNotInCache(t, cache, qotherPtr, "after removing container")
|
||||
}
|
||||
|
||||
// Check if the entries in the cache expire
|
||||
func TestServerCacheExpiration(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
negativeLocalTTL = 10
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestServerCacheExpiration starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
Log.Debugf("Creating 2 zone databases")
|
||||
zoneConfig := ZoneConfig{
|
||||
RefreshInterval: 0, // no name updates
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zoneConfig)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
Log.Debugf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
require.NoError(t, err)
|
||||
|
||||
Log.Debugf("Creating a real DNS server for the first zone database and with the cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: dbs[0].Zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
CacheNegLocalTTL: negativeLocalTTL,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Start()
|
||||
require.NoError(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow server goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, testPort, "invalid listen port")
|
||||
|
||||
// Check that the DNS server knows nothing about testName1
|
||||
qName1, _ := assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
// there should be a negative reply cached during [0, negativeLocalTTL]
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(negativeLocalTTL - 1)
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
// after negativeLocalTTL, there should be absolutely nothing in the cache
|
||||
clk.Forward(2)
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// We add the IP in the second peer right after
|
||||
Log.Debugf("Adding an IPs to %s in database #2", testName1)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
t.Logf("Zone database #2:\n%s", dbs[1].Zone)
|
||||
|
||||
// Check that the DNS server returns updated information, and it will be
|
||||
// cached during [0, DefaultLocalTTL]
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(int(DefaultLocalTTL) - 1)
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
// and it will expire after DefaultLocalTTL
|
||||
clk.Forward(2)
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
|
||||
// We delete the IP in the second peer right after
|
||||
Log.Debugf("Removing the IPs from %s in database #2", testName1)
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
|
||||
// Check that we return to the initial state: a neg-local entry in the cache
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertNotLocalInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
clk.Forward(negativeLocalTTL + 1)
|
||||
|
||||
// Check that all entries should be outdated
|
||||
cache.Purge()
|
||||
if cache.Len() != 0 {
|
||||
t.Fatalf("Some entries still valid in cache:\n%s", cache)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the names updates lead to cache invalidations
|
||||
func TestServerCacheRefresh(t *testing.T) {
|
||||
const (
|
||||
containerID = "somecontainer"
|
||||
testName1 = "first.weave.local."
|
||||
testName2 = "second.weave.local."
|
||||
refreshInterval = int(DefaultLocalTTL) / 3
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestServerCacheRefresh starting")
|
||||
clk := newMockedClock()
|
||||
|
||||
Log.Debugf("Creating 2 zone databases")
|
||||
zoneConfig := ZoneConfig{
|
||||
RefreshInterval: refreshInterval,
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zoneConfig)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
Log.Debugf("Creating a cache")
|
||||
cache, err := NewCache(1024, clk)
|
||||
require.NoError(t, err)
|
||||
|
||||
Log.Debugf("Creating a real DNS server for the first zone database and with the cache")
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: dbs[0].Zone,
|
||||
Cache: cache,
|
||||
Clock: clk,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
MaxAnswers: 4,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Start()
|
||||
require.NoError(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, testPort, "listen port")
|
||||
|
||||
Log.Debugf("Adding an IPs to %s", testName1)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
|
||||
dbs.Flush() // ensure the immediate refresh is done
|
||||
|
||||
// Zone database #2 at this point:
|
||||
// first.weave.local = 10.2.2.1
|
||||
|
||||
// testName1 and testName2 should have no IPs yet
|
||||
qName1, _ := assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0)
|
||||
qName2, _ := assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qName1, "after asking for first name")
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for second name")
|
||||
|
||||
clk.Forward(refreshInterval / 2)
|
||||
dbs.Flush()
|
||||
|
||||
Log.Debugf("Adding an IP to %s and to %s", testName1, testName2)
|
||||
dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2"))
|
||||
dbs[1].Zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.2"))
|
||||
|
||||
// Zone database #2 at this point:
|
||||
// first.weave.local = 10.2.2.1 10.2.2.2
|
||||
// second.weave.local = 10.9.9.2
|
||||
clk.Forward(refreshInterval/2 + 2)
|
||||
dbs.Flush()
|
||||
|
||||
// at this point, the testName1 should have been refreshed
|
||||
// so it should have two IPs, and the cache entry should have been invalidated
|
||||
assertNotInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
assertNotLocalInCache(t, cache, qName2, fmt.Sprintf("after asking for %s", testName2))
|
||||
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0)
|
||||
qName2, _ = assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertInCache(t, cache, qName1, fmt.Sprintf("after asking for %s", testName1))
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for a unknown name")
|
||||
|
||||
// delete the IPs, and some time passes by so the cache should be purged...
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1"))
|
||||
dbs[1].Zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.2"))
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
|
||||
qName1, _ = assertExchange(t, testName1, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
qName2, _ = assertExchange(t, testName2, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
assertNotLocalInCache(t, cache, qName1, "after asking for a unknown name")
|
||||
assertNotLocalInCache(t, cache, qName2, "after asking for a unknown name")
|
||||
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
testRDNSsuccess = "1.2.2.10.in-addr.arpa."
|
||||
testRDNSfail = "4.3.2.1.in-addr.arpa."
|
||||
testRDNSnonlocal = "8.8.8.8.in-addr.arpa."
|
||||
testUDPBufSize = 16384
|
||||
)
|
||||
|
||||
func setupForTest(t *testing.T) {
|
||||
// fail early if we cannot find a default multicast interface
|
||||
multicast, err := LinkLocalMulticastListener(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create multicast listener: %s. No default multicast interface?", err)
|
||||
}
|
||||
multicast.Close()
|
||||
}
|
||||
|
||||
func TestUDPDNSServer(t *testing.T) {
|
||||
setupForTest(t)
|
||||
|
||||
const (
|
||||
successTestName = "test1.weave.local."
|
||||
failTestName = "fail.weave.local."
|
||||
nonLocalName = "weave.works."
|
||||
testAddr1 = "10.2.2.1"
|
||||
containerID = "somecontainer"
|
||||
)
|
||||
testCIDR1 := testAddr1 + "/24"
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestUDPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip, _, _ := net.ParseCIDR(testCIDR1)
|
||||
zone.AddRecord(containerID, successTestName, ip)
|
||||
|
||||
fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
q := req.Question[0]
|
||||
if q.Name == nonLocalName && q.Qtype == dns.TypeMX {
|
||||
m.Answer = make([]dns.RR, 1)
|
||||
m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName}
|
||||
} else if q.Name == nonLocalName && q.Qtype == dns.TypeANY {
|
||||
m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1)
|
||||
for i := range m.Answer {
|
||||
m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)}
|
||||
}
|
||||
} else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR {
|
||||
m.Answer = make([]dns.RR, 1)
|
||||
m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."}
|
||||
} else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR {
|
||||
m.Rcode = dns.RcodeNameError
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
// Run another DNS server for fallback
|
||||
fallback, err := newMockedFallback(fallbackHandler, nil)
|
||||
require.NoError(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
CacheDisabled: true,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Start()
|
||||
require.NoError(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
var r *dns.Msg
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, testPort, "invalid listen port")
|
||||
|
||||
_, r = assertExchange(t, successTestName, dns.TypeA, testPort, 1, 1, 0)
|
||||
require.IsType(t, (*dns.A)(nil), r.Answer[0], "DNS record")
|
||||
require.Equal(t, testAddr1, r.Answer[0].(*dns.A).A.String(), "IP address")
|
||||
|
||||
assertExchange(t, failTestName, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
_, r = assertExchange(t, testRDNSsuccess, dns.TypePTR, testPort, 1, 1, 0)
|
||||
require.IsType(t, (*dns.PTR)(nil), r.Answer[0], "DNS record")
|
||||
require.Equal(t, successTestName, r.Answer[0].(*dns.PTR).Ptr, "IP address")
|
||||
|
||||
assertExchange(t, testRDNSfail, dns.TypePTR, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
// This should fail because we don't handle MX records
|
||||
assertExchange(t, successTestName, dns.TypeMX, testPort, 0, 0, dns.RcodeNameError)
|
||||
|
||||
// This non-local query for an MX record should succeed by being
|
||||
// passed on to the fallback server
|
||||
assertExchange(t, nonLocalName, dns.TypeMX, testPort, 1, -1, 0)
|
||||
|
||||
// Now ask a query that we expect to return a lot of data.
|
||||
assertExchange(t, nonLocalName, dns.TypeANY, testPort, 5, -1, 0)
|
||||
|
||||
assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0)
|
||||
|
||||
// Not testing MDNS functionality of server here (yet), since it
|
||||
// needs two servers, each listening on its own address
|
||||
}
|
||||
|
||||
func TestTCPDNSServer(t *testing.T) {
|
||||
setupForTest(t)
|
||||
|
||||
const (
|
||||
numAnswers = 512
|
||||
nonLocalName = "weave.works."
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestTCPDNSServer starting")
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
// generate a list of `numAnswers` IP addresses
|
||||
var addrs []ZoneRecord
|
||||
bs := make([]byte, 4)
|
||||
for i := 0; i < numAnswers; i++ {
|
||||
binary.LittleEndian.PutUint32(bs, uint32(i))
|
||||
ip := net.IPv4(bs[0], bs[1], bs[2], bs[3])
|
||||
addrs = append(addrs, ZoneRecord(Record{"", ip, 0, 0, 0}))
|
||||
}
|
||||
|
||||
// handler for the fallback server: it will just return a very long response
|
||||
fallbackUDPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
maxLen := getMaxReplyLen(req, protUDP)
|
||||
t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers)
|
||||
q := req.Question[0]
|
||||
m := makeAddressReply(req, &q, addrs, DefaultLocalTTL)
|
||||
mLen := m.Len()
|
||||
m.SetEdns0(uint16(maxLen), false)
|
||||
|
||||
if mLen > maxLen {
|
||||
t.Logf("... truncated response (%d > %d)", mLen, maxLen)
|
||||
m.Truncated = true
|
||||
}
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
if len(req.Question) == 0 {
|
||||
return // ignore empty queries (sent when shutting down the server)
|
||||
}
|
||||
t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers)
|
||||
q := req.Question[0]
|
||||
m := makeAddressReply(req, &q, addrs, DefaultLocalTTL)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
t.Logf("Running a DNS fallback server with UDP")
|
||||
fallback, err := newMockedFallback(fallbackUDPHandler, fallbackTCPHandler)
|
||||
require.NoError(t, err)
|
||||
fallback.Start()
|
||||
defer fallback.Stop()
|
||||
|
||||
t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%d", fallback.Port)
|
||||
srv, err := NewDNSServer(DNSServerConfig{
|
||||
Zone: zone,
|
||||
UpstreamCfg: fallback.CliConfig,
|
||||
CacheDisabled: true,
|
||||
ListenReadTimeout: testSocketTimeout,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Start()
|
||||
require.NoError(t, err)
|
||||
go srv.ActivateAndServe()
|
||||
defer srv.Stop()
|
||||
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
|
||||
|
||||
testPort, err := srv.GetPort()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, testPort, "listen port")
|
||||
dnsAddr := fmt.Sprintf("127.0.0.1:%d", testPort)
|
||||
|
||||
t.Logf("Creating a UDP and a TCP client")
|
||||
uc := new(dns.Client)
|
||||
uc.UDPSize = minUDPSize
|
||||
tc := new(dns.Client)
|
||||
tc.Net = "tcp"
|
||||
|
||||
t.Logf("Creating DNS query message")
|
||||
m := new(dns.Msg)
|
||||
m.RecursionDesired = true
|
||||
m.SetQuestion(nonLocalName, dns.TypeA)
|
||||
|
||||
t.Logf("Checking the fallback server at %s returns a truncated response with UDP", fallback.Addr)
|
||||
r, _, err := uc.Exchange(m, fallback.Addr)
|
||||
t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer))
|
||||
t.Logf("Response:\n%+v\n", r)
|
||||
require.NoError(t, err)
|
||||
require.True(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
|
||||
require.NotEqual(t, numAnswers, len(r.Answer), "number of answers (UDP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s returns a truncated reponse with UDP", dnsAddr)
|
||||
r, _, err = uc.Exchange(m, dnsAddr)
|
||||
t.Logf("UDP Response:\n%+v\n", r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
require.True(t, r.MsgHdr.Truncated, "DNS truncated reponse flag")
|
||||
require.NotEqual(t, numAnswers, len(r.Answer), "number of answers (UDP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with TCP", dnsAddr)
|
||||
r, _, err = tc.Exchange(m, dnsAddr)
|
||||
t.Logf("TCP Response:\n%+v\n", r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
require.False(t, r.MsgHdr.Truncated, "DNS truncated response flag")
|
||||
require.Equal(t, numAnswers, len(r.Answer), "number of answers (TCP)")
|
||||
|
||||
t.Logf("Checking the WeaveDNS server at %s does not return a truncated reponse with UDP with a bigger buffer", dnsAddr)
|
||||
m.SetEdns0(testUDPBufSize, false)
|
||||
r, _, err = uc.Exchange(m, dnsAddr)
|
||||
t.Logf("UDP-large Response:\n%+v\n", r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, r, "response")
|
||||
t.Logf("%d answers", len(r.Answer))
|
||||
require.NoError(t, err)
|
||||
require.False(t, r.MsgHdr.Truncated, "DNS truncated response flag")
|
||||
require.Equal(t, numAnswers, len(r.Answer), "number of answers (UDP-long)")
|
||||
}
|
||||
@@ -1,739 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
RDNSDomain = "in-addr.arpa."
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultLocalDomain = "weave.local." // The default name used for the local domain
|
||||
DefaultRefreshInterval = int(DefaultLocalTTL) // Period for background updates with mDNS
|
||||
DefaultRelevantTime = 60 // When to forget info about remote info if nobody asks...
|
||||
|
||||
defaultRemoteIdent = "weave:remote" // Ident used for info obtained from mDNS
|
||||
)
|
||||
|
||||
// +1 to also exclude a dot
|
||||
var rdnsDomainLen = len(RDNSDomain) + 1
|
||||
|
||||
type LookupError string
|
||||
|
||||
func (ops LookupError) Error() string {
|
||||
return "Unable to find " + string(ops)
|
||||
}
|
||||
|
||||
type DuplicateError struct {
|
||||
}
|
||||
|
||||
func (dup DuplicateError) Error() string {
|
||||
return "Tried to add a duplicate entry"
|
||||
}
|
||||
|
||||
type Zone interface {
|
||||
ZoneObservable
|
||||
ZoneLookup
|
||||
// The domain where we operate (eg, "weave.local.")
|
||||
Domain() string
|
||||
// Add a record in the local database
|
||||
AddRecord(ident string, name string, ip net.IP) error
|
||||
// Delete matching records (uninitialised values act as wildcards)
|
||||
DeleteRecords(ident string, name string, ip net.IP) (count int)
|
||||
// Lookup for a name in the whole domain
|
||||
DomainLookupName(name string) ([]ZoneRecord, error)
|
||||
// Lookup for an address in the whole domain
|
||||
DomainLookupInaddr(inaddr string) ([]ZoneRecord, error)
|
||||
// Return a status string
|
||||
Status() string
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Zone database overview:
|
||||
//
|
||||
// idents names entry
|
||||
// *-------* *------* *------*
|
||||
// zone -> | ident | --*--> | name | --*-> | IPv4 |
|
||||
// *-------* | *------* | *------*
|
||||
// | ident | *--> | name | *-> | IPv4 |
|
||||
// *-------* *------* *------*
|
||||
// ... ...
|
||||
//
|
||||
// The zone database keeps an ident per container, but also a special
|
||||
// ident for names/IPs obtained from remote peers.
|
||||
//
|
||||
// Each name can have multiple IPs (so far, we only consider IPv4s), and
|
||||
// each IP is stored in a `recordEntry` in the database. Entries store
|
||||
// some additional information like priority and weight of the IP (for
|
||||
// example, for future use with SRV records)
|
||||
//
|
||||
|
||||
// An entry in the zone database
|
||||
type recordEntry struct {
|
||||
Record
|
||||
|
||||
observers []ZoneRecordObserver // the observers for this record
|
||||
insTime time.Time // time when this record was inserted
|
||||
}
|
||||
|
||||
func newRecordEntry(zr ZoneRecord, now time.Time) *recordEntry {
|
||||
return &recordEntry{
|
||||
Record: Record{
|
||||
name: zr.Name(),
|
||||
ip: zr.IP(),
|
||||
priority: zr.Priority(),
|
||||
weight: zr.Weight(),
|
||||
ttl: zr.TTL(),
|
||||
},
|
||||
insTime: now,
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (re *recordEntry) addIPObserver(zro ZoneRecordObserver) {
|
||||
re.observers = append(re.observers, zro)
|
||||
}
|
||||
|
||||
// Notify all the IP observers and then remove them all
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
func (re *recordEntry) notifyIPObservers() {
|
||||
numObservers := len(re.observers)
|
||||
if numObservers > 0 {
|
||||
Log.Debugf("[zonedb] Notifying %d observers of '%s'", numObservers, re.ip)
|
||||
for _, observer := range re.observers {
|
||||
observer()
|
||||
}
|
||||
re.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (re *recordEntry) TTL() int {
|
||||
// if we never set the TTL (eg, when using AddRecord()), return the standard value...
|
||||
if re.ttl == 0 {
|
||||
return int(DefaultLocalTTL)
|
||||
}
|
||||
return re.ttl
|
||||
}
|
||||
|
||||
// Check if the info in this record is still valid according to the TTL
|
||||
func (re *recordEntry) hasExpired(now time.Time) bool {
|
||||
return re.ttl > 0 && now.Sub(re.insTime).Seconds() > float64(re.ttl)
|
||||
}
|
||||
|
||||
func (re *recordEntry) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s", re.Record)
|
||||
lobs := len(re.observers)
|
||||
if lobs > 0 {
|
||||
fmt.Fprintf(&buf, "/OBS:%d", lobs)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// a name, with group of records by IPv4
|
||||
type name struct {
|
||||
name string
|
||||
ipv4 map[IPv4]*recordEntry // all the IPv4 records for this name
|
||||
observers []ZoneRecordObserver // the observers for this name
|
||||
lastRefreshTime time.Time // last time this name was updated by broadcasting a query
|
||||
lastAccessTime time.Time // last time a Lookup* used info from this name
|
||||
}
|
||||
|
||||
func newName(n string) *name {
|
||||
return &name{
|
||||
name: n,
|
||||
ipv4: make(map[IPv4]*recordEntry),
|
||||
observers: make([]ZoneRecordObserver, 0),
|
||||
}
|
||||
}
|
||||
func (n *name) len() int { return len(n.ipv4) }
|
||||
func (n *name) empty() bool { return n.len() == 0 }
|
||||
func (n *name) hasIP(ip net.IP) bool { return n.hasIPv4(ipToIPv4(ip)) }
|
||||
func (n *name) hasIPv4(ip IPv4) bool { _, found := n.ipv4[ip]; return found }
|
||||
|
||||
// Get all the entries for this name
|
||||
func (n *name) getAllEntries() (res []*recordEntry) {
|
||||
for _, entry := range n.ipv4 {
|
||||
res = append(res, entry)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the entry for an IP in this name
|
||||
func (n *name) getEntryForIP(ip IPv4) (res *recordEntry) {
|
||||
if entry, found := n.ipv4[ip]; found {
|
||||
res = entry
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to this name
|
||||
func (n *name) addIP(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
ne := newRecordEntry(zr, now)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
n.ipv4[ipv4] = ne
|
||||
n.notifyNameObservers()
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
// Delete by IP. Pass uninitialized value for wildcard
|
||||
func (n *name) deleteIP(ip net.IP) int {
|
||||
count := 0
|
||||
if !ip.Equal(net.IP{}) {
|
||||
ipv4 := ipToIPv4(ip)
|
||||
if ipv4V, found := n.ipv4[ipv4]; found {
|
||||
ipv4V.notifyIPObservers()
|
||||
delete(n.ipv4, ipv4)
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
for ipv4K, ipv4V := range n.ipv4 {
|
||||
ipv4V.notifyIPObservers()
|
||||
delete(n.ipv4, ipv4K)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Update the list of IPs for this name, adding new ones and removing old IPs...
|
||||
func (n *name) updateIPs(records []ZoneRecord, now time.Time) (added int, removed int) {
|
||||
ipsMap := make(map[IPv4]bool)
|
||||
|
||||
// add the new IPs
|
||||
for _, record := range records {
|
||||
ipv4 := ipToIPv4(record.IP())
|
||||
ipsMap[ipv4] = true
|
||||
if !n.hasIPv4(ipv4) {
|
||||
n.addIP(record, now)
|
||||
added++
|
||||
}
|
||||
}
|
||||
|
||||
// remove the old IPs
|
||||
for ipv4, ipRecord := range n.ipv4 {
|
||||
if _, isNew := ipsMap[ipv4]; !isNew {
|
||||
ipRecord.notifyIPObservers() // we must notify the observers before removing the IP record...
|
||||
delete(n.ipv4, ipv4)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
|
||||
// notify observers and update times
|
||||
if added > 0 || removed > 0 {
|
||||
n.lastRefreshTime = now
|
||||
n.notifyNameObservers()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a observer for this name
|
||||
func (n *name) addNameObserver(observer ZoneRecordObserver) {
|
||||
n.observers = append(n.observers, observer)
|
||||
}
|
||||
|
||||
// Notify and flush all the observers
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
func (n *name) notifyNameObservers() {
|
||||
numObservers := len(n.observers)
|
||||
if numObservers > 0 {
|
||||
Log.Debugf("[zonedb] Notifying %d observers of '%s'", numObservers, n.name)
|
||||
for _, observer := range n.observers {
|
||||
observer()
|
||||
}
|
||||
n.observers = make([]ZoneRecordObserver, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a set of IP records to a comma-separated string
|
||||
func (n *name) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, ip := range n.ipv4 {
|
||||
fmt.Fprintf(&buf, "%s, ", ip)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// all the names in a ident
|
||||
type nameSet struct {
|
||||
names map[string]*name
|
||||
}
|
||||
|
||||
func newNameSet() *nameSet { return &nameSet{names: make(map[string]*name)} }
|
||||
func (ns *nameSet) len() int { return len(ns.names) }
|
||||
func (ns *nameSet) empty() bool { return ns.len() == 0 }
|
||||
func (ns *nameSet) hasName(name string) bool {
|
||||
n, found := ns.names[name]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
// the name must have some valid IPs in order to be considered as "existent"
|
||||
return len(n.ipv4) > 0
|
||||
}
|
||||
|
||||
// Get the zone entries for a name
|
||||
// If the name does not exist and `create` is true, a new name will be created
|
||||
func (ns *nameSet) getName(name string, create bool) (n *name) {
|
||||
n, found := ns.names[name]
|
||||
if !found && create {
|
||||
newName := newName(name)
|
||||
ns.names[name] = newName
|
||||
return newName
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Get all the entries for a name
|
||||
func (ns *nameSet) getEntriesForName(name string) (res []*recordEntry) {
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
res = n.getAllEntries()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get all the entries for an IP, for all names
|
||||
func (ns *nameSet) getEntriesForIP(ip IPv4) (res []*recordEntry) {
|
||||
for _, name := range ns.names {
|
||||
entry := name.getEntryForIP(ip)
|
||||
if entry != nil {
|
||||
res = append(res, entry)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a new IPv4 to a name
|
||||
func (ns *nameSet) addIPToName(zr ZoneRecord, now time.Time) (*recordEntry, error) {
|
||||
n := ns.getName(zr.Name(), true)
|
||||
ipv4 := ipToIPv4(zr.IP())
|
||||
if n.hasIPv4(ipv4) {
|
||||
return n.getEntryForIP(ipv4), DuplicateError{}
|
||||
}
|
||||
ns.touchName(zr.Name(), now)
|
||||
return n.addIP(zr, now)
|
||||
}
|
||||
|
||||
// Delete by name and IP. Pass uninitialized values for wildcard
|
||||
func (ns nameSet) deleteNameIP(name string, ip net.IP) int {
|
||||
count := 0
|
||||
if name != "" {
|
||||
if nameV, found := ns.names[name]; found {
|
||||
count += nameV.deleteIP(ip)
|
||||
if nameV.empty() {
|
||||
delete(ns.names, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for nameK, nameV := range ns.names {
|
||||
count += nameV.deleteIP(ip)
|
||||
if nameV.empty() {
|
||||
delete(ns.names, nameK)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Get the name query time
|
||||
func (ns *nameSet) getNameLastAccess(n string) time.Time {
|
||||
if name, found := ns.names[n]; found {
|
||||
return name.lastAccessTime
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Touch the last access time for a name
|
||||
// The access time is saved only for locally-introduced records (otherwise it could
|
||||
// be lost when names are irrelevant...)
|
||||
func (ns *nameSet) touchName(name string, now time.Time) {
|
||||
Log.Debugf("[zonedb] Touching name %s", name)
|
||||
n := ns.getName(name, false)
|
||||
if n != nil {
|
||||
n.lastAccessTime = now
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *nameSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for _, name := range ns.names {
|
||||
fmt.Fprintf(&buf, "%s, ", name)
|
||||
}
|
||||
l := buf.Len()
|
||||
if l > 2 {
|
||||
buf.Truncate(l - 2)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Name sets, by ident name
|
||||
type identRecordSet map[string]*nameSet
|
||||
|
||||
func (irs identRecordSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
for ident, nameset := range irs {
|
||||
fmt.Fprintf(&buf, "%.12s: %s\n", ident, nameset)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
// Invariant on updateRequests: they are in ascending order of time.
|
||||
type refreshRequest struct {
|
||||
name string
|
||||
time time.Time
|
||||
}
|
||||
|
||||
type ZoneConfig struct {
|
||||
// The local domain
|
||||
Domain string
|
||||
// The interface where mDNS operates
|
||||
Iface *net.Interface
|
||||
// Refresh interval for local names in the zone database (disabled by default) (in seconds)
|
||||
RefreshInterval int
|
||||
// Forget about remote info if nobody asks for it in this time (in seconds)
|
||||
RelevantTime int
|
||||
// TTL returnined in local asnwers (with mDNS)
|
||||
LocalTTL int
|
||||
// Force a specific mDNS client
|
||||
MDNSClient ZoneMDNSClient
|
||||
// Force a specific mDNS server
|
||||
MDNSServer ZoneMDNSServer
|
||||
// For a specific clock provider
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// Zone database
|
||||
// The zone database contains the locally known information about the zone.
|
||||
// The zone database uses some other mechanisms (eg, mDNS) for finding unknown
|
||||
// information or for keeping current information up to date.
|
||||
type ZoneDb struct {
|
||||
mx sync.RWMutex
|
||||
idents identRecordSet
|
||||
|
||||
mdnsCli ZoneMDNSClient
|
||||
mdnsSrv ZoneMDNSServer
|
||||
iface *net.Interface
|
||||
domain string // the local domain
|
||||
relevantLimit time.Duration // if no one asks for something in this time, it is not relevant a anymore...
|
||||
clock clock.Clock
|
||||
|
||||
refreshScheds *SchedQueue
|
||||
refreshCloseChan chan bool
|
||||
refreshInterval time.Duration
|
||||
}
|
||||
|
||||
// Create a new zone database
|
||||
func NewZoneDb(config ZoneConfig) (zone *ZoneDb, err error) {
|
||||
zone = &ZoneDb{
|
||||
domain: config.Domain,
|
||||
idents: make(identRecordSet),
|
||||
mdnsCli: config.MDNSClient,
|
||||
mdnsSrv: config.MDNSServer,
|
||||
iface: config.Iface,
|
||||
clock: config.Clock,
|
||||
relevantLimit: time.Duration(DefaultRelevantTime) * time.Second,
|
||||
refreshCloseChan: make(chan bool),
|
||||
}
|
||||
|
||||
// fix the default configuration parameters
|
||||
if zone.clock == nil {
|
||||
zone.clock = clock.New()
|
||||
}
|
||||
if len(zone.domain) == 0 {
|
||||
zone.domain = DefaultLocalDomain
|
||||
}
|
||||
if config.RefreshInterval > 0 {
|
||||
zone.refreshInterval = time.Duration(config.RefreshInterval) * time.Second
|
||||
}
|
||||
if config.RelevantTime > 0 {
|
||||
zone.relevantLimit = time.Duration(config.RelevantTime) * time.Second
|
||||
}
|
||||
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.refreshScheds = NewSchedQueue(zone.clock)
|
||||
}
|
||||
|
||||
// Create the mDNS client and server
|
||||
if zone.mdnsCli == nil {
|
||||
if zone.mdnsCli, err = NewMDNSClient(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if zone.mdnsSrv == nil {
|
||||
mdnsTTL := DefaultLocalTTL
|
||||
if config.LocalTTL > 0 {
|
||||
mdnsTTL = config.LocalTTL
|
||||
}
|
||||
|
||||
if zone.mdnsSrv, err = NewMDNSServer(zone, false, mdnsTTL); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start the zone database
|
||||
func (zone *ZoneDb) Start() (err error) {
|
||||
if zone.iface != nil {
|
||||
Log.Infof("[zonedb] Using mDNS on %+v", zone.iface)
|
||||
} else {
|
||||
Log.Infof("[zonedb] Using mDNS on all interfaces")
|
||||
}
|
||||
|
||||
if err = zone.mdnsCli.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
if err = zone.mdnsSrv.Start(zone.iface); err != nil {
|
||||
return
|
||||
}
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.refreshScheds.Start()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a graceful shutdown of the zone database
|
||||
func (zone *ZoneDb) Stop() error {
|
||||
if zone.refreshInterval > 0 {
|
||||
Log.Debugf("[zonedb] Closing background updaters...")
|
||||
zone.refreshScheds.Stop()
|
||||
}
|
||||
|
||||
Log.Debugf("[zonedb] Exiting mDNS client and server...")
|
||||
zone.mdnsCli.Stop()
|
||||
zone.mdnsSrv.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain the domain where this database keeps information for
|
||||
func (zone *ZoneDb) Domain() string {
|
||||
return zone.domain
|
||||
}
|
||||
|
||||
// Return the status string
|
||||
func (zone *ZoneDb) Status() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintln(&buf, "Local domain", zone.domain)
|
||||
fmt.Fprintln(&buf, "Interface", zone.iface)
|
||||
fmt.Fprintf(&buf, "Zone database:\n%s", zone)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Add a record with `name` pointing to `ip`
|
||||
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
Log.Debugf("[zonedb] Adding record: '%s'/'%s'[%s]", ident, name, ip)
|
||||
record := Record{dns.Fqdn(name), ip, 0, 0, 0}
|
||||
_, err = zone.getNameSet(ident).addIPToName(record, zone.clock.Now())
|
||||
return
|
||||
}
|
||||
|
||||
// Delete matching records (uninitialised values act as wildcards)
|
||||
func (zone *ZoneDb) DeleteRecords(ident string, name string, ip net.IP) int {
|
||||
if name != "" {
|
||||
name = dns.Fqdn(name)
|
||||
}
|
||||
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
return zone.deleteIdentNameIP(ident, name, ip)
|
||||
}
|
||||
|
||||
// Observe a name.
|
||||
// The name must have at least one valid IP address.
|
||||
// Name observers are notified when
|
||||
// - an IP is added
|
||||
// - an IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this name.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *ZoneDb) ObserveName(name string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
name = dns.Fqdn(name)
|
||||
for _, nameset := range zone.idents {
|
||||
if n := nameset.getName(name, false); n != nil {
|
||||
n.addNameObserver(observer)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%s", name))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Observe a IP address.
|
||||
// The IP address must be exit in the database.
|
||||
// IP address observers are notified when
|
||||
// - the IP is removed
|
||||
// - the name is removed
|
||||
// The observer will be invoked on any change/removal that affects this IP.
|
||||
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
|
||||
// The observer should not try to lock the ZoneDB (you will get a deadlock)
|
||||
func (zone *ZoneDb) ObserveInaddr(inaddr string, observer ZoneRecordObserver) (err error) {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
cnt := 0
|
||||
revIP, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return newParseError("lookup address", inaddr)
|
||||
}
|
||||
for _, nameset := range zone.idents {
|
||||
for _, entry := range nameset.getEntriesForIP(revIP) {
|
||||
entry.addIPObserver(observer)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if cnt == 0 {
|
||||
err = LookupError(fmt.Sprintf("%+v/%+v", inaddr, revIP))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the string representation of a zone
|
||||
func (zone *ZoneDb) String() string {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
return zone.idents.String()
|
||||
}
|
||||
|
||||
// Notify that a container has died
|
||||
func (zone *ZoneDb) ContainerDied(ident string) error {
|
||||
Log.Infof("[zonedb] Container %s down. Removing records", ident)
|
||||
zone.DeleteRecords(ident, "", net.IP{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return true if a remote name is still relevant
|
||||
func (zone *ZoneDb) IsNameRelevant(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
lastAccess := time.Time{}
|
||||
for _, nameset := range zone.idents {
|
||||
t := nameset.getNameLastAccess(n)
|
||||
if !t.IsZero() && (lastAccess.IsZero() || t.After(lastAccess)) {
|
||||
lastAccess = t
|
||||
}
|
||||
}
|
||||
|
||||
return !lastAccess.IsZero() && zone.clock.Now().Sub(lastAccess) <= zone.relevantLimit
|
||||
}
|
||||
|
||||
// Returns true if the info we have for a remote name has expired. Local names are never expired
|
||||
// Returns false if we do not have info for this name from remote peers.
|
||||
func (zone *ZoneDb) IsNameExpired(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
now := zone.clock.Now()
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
entries := remoteNameSet.getEntriesForName(n)
|
||||
if entries != nil {
|
||||
for _, entry := range entries {
|
||||
if entry.hasExpired(now) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (zone *ZoneDb) HasNameLocalInfo(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
for _, nameset := range zone.idents {
|
||||
if nameset != remoteNameSet {
|
||||
if nameset.hasName(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Return true if the we have obtained some information for a name from remote peers
|
||||
// We can have both local (eg, introduced through the HTTP API) and remote (eg, mDNS)
|
||||
// information for a name. This method only checks if there is remote information...
|
||||
func (zone *ZoneDb) HasNameRemoteInfo(n string) bool {
|
||||
zone.mx.Lock()
|
||||
defer zone.mx.Unlock()
|
||||
|
||||
remoteNameSet := zone.getNameSet(defaultRemoteIdent)
|
||||
if remoteNameSet != nil {
|
||||
return remoteNameSet.hasName(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get or create a name set
|
||||
func (zone *ZoneDb) getNameSet(ident string) *nameSet {
|
||||
ns, found := zone.idents[ident]
|
||||
if !found {
|
||||
ns = newNameSet()
|
||||
zone.idents[ident] = ns
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// Delete by ident, name and IP. Pass uninitialized value for wildcard
|
||||
func (zone *ZoneDb) deleteIdentNameIP(ident string, name string, ip net.IP) int {
|
||||
count := 0
|
||||
if ident != "" {
|
||||
if identV, found := zone.idents[ident]; found {
|
||||
count += identV.deleteNameIP(name, ip)
|
||||
if identV.empty() {
|
||||
delete(zone.idents, ident)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for identK, identV := range zone.idents {
|
||||
count += identV.deleteNameIP(name, ip)
|
||||
if identV.empty() {
|
||||
delete(zone.idents, identK)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
type uniqZoneRecordKey struct {
|
||||
name string
|
||||
ipv4 IPv4
|
||||
}
|
||||
|
||||
// A group of ZoneRecords where there are no duplicates (according to the name & IPv4)
|
||||
type uniqZoneRecords map[uniqZoneRecordKey]ZoneRecord
|
||||
|
||||
func newUniqZoneRecords() uniqZoneRecords {
|
||||
return make(uniqZoneRecords, 0)
|
||||
}
|
||||
|
||||
// Add a new ZoneRecord to the group
|
||||
func (uzr *uniqZoneRecords) add(zr ZoneRecord) {
|
||||
key := uniqZoneRecordKey{zr.Name(), ipToIPv4(zr.IP())}
|
||||
(*uzr)[key] = zr
|
||||
}
|
||||
|
||||
// Return the group as an slice
|
||||
func (uzr *uniqZoneRecords) toSlice() []ZoneRecord {
|
||||
res := make([]ZoneRecord, len(*uzr))
|
||||
i := 0
|
||||
for _, r := range *uzr {
|
||||
res[i] = r
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Lookup in the database for locally-introduced information
|
||||
func (zone *ZoneDb) lookup(target string, lfun func(ns *nameSet) []*recordEntry) (res []ZoneRecord, err error) {
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
if identName != defaultRemoteIdent {
|
||||
for _, ze := range lfun(nameset) {
|
||||
uniq.add(ze)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(uniq) == 0 {
|
||||
return nil, LookupError(target)
|
||||
}
|
||||
return uniq.toSlice(), nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name in the zone
|
||||
// The name can be resolved locally with the local database
|
||||
func (zone *ZoneDb) LookupName(name string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupName() is usually called from the mDNS server, so we do not touch the name
|
||||
name = dns.Fqdn(name)
|
||||
Log.Debugf("[zonedb] Looking for name '%s' in local database", name)
|
||||
return zone.lookup(name, func(ns *nameSet) []*recordEntry { return ns.getEntriesForName(name) })
|
||||
}
|
||||
|
||||
// Perform a lookup for a IP address in the zone
|
||||
// The address can be resolved locally with the local database
|
||||
func (zone *ZoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
|
||||
zone.mx.RLock()
|
||||
defer zone.mx.RUnlock()
|
||||
|
||||
// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
|
||||
revIPv4, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
Log.Debugf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
|
||||
return zone.lookup(inaddr, func(ns *nameSet) []*recordEntry { return ns.getEntriesForIP(revIPv4) })
|
||||
}
|
||||
|
||||
// Perform a domain lookup with mDNS
|
||||
func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []ZoneRecord, err error) {
|
||||
// no local results have been obtained in the local database: try with a mDNS query
|
||||
Log.Debugf("[zonedb] '%s' not in local database... trying with mDNS", target)
|
||||
lanswers, err := lfun(target)
|
||||
if err != nil {
|
||||
Log.Debugf("[zonedb] mDNS lookup error for '%s': %s", target, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the request has been successful, save the IP in the local database and return the corresponding ZoneRecord
|
||||
// (we do not get the remote ident in the mDNS reply, so we save it in a "remote" ident)
|
||||
Log.Debugf("[zonedb] adding '%s' (obtained with mDNS) to '%s'", lanswers, target)
|
||||
res = make([]ZoneRecord, len(lanswers))
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
remoteIdent := zone.getNameSet(defaultRemoteIdent)
|
||||
for _, answer := range lanswers {
|
||||
r, err := remoteIdent.addIPToName(answer, now)
|
||||
if err != nil {
|
||||
zone.mx.Unlock()
|
||||
Log.Warningf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
|
||||
return nil, err
|
||||
}
|
||||
uniq.add(r)
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
return uniq.toSlice(), nil
|
||||
}
|
||||
|
||||
// Perform a lookup for a name in the zone
|
||||
// The name can be resolved locally with the local database or with some other resolution method (eg, a mDNS query)
|
||||
func (zone *ZoneDb) DomainLookupName(name string) (res []ZoneRecord, err error) {
|
||||
name = dns.Fqdn(name)
|
||||
Log.Debugf("[zonedb] Looking for name '%s' in local(&remote) database", name)
|
||||
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
for _, ze := range nameset.getEntriesForName(name) {
|
||||
// filter the entries with expired TTL
|
||||
// locally introduced entries are never expired: they always have TTL=0
|
||||
if ze.hasExpired(now) {
|
||||
Log.Debugf("[zonedb] '%s': expired entry '%s' ignored: removing", name, ze)
|
||||
nameset.deleteNameIP(name, net.IP{})
|
||||
} else {
|
||||
uniq.add(ze)
|
||||
}
|
||||
}
|
||||
if identName != defaultRemoteIdent {
|
||||
nameset.touchName(name, now)
|
||||
}
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
if len(uniq) > 0 {
|
||||
Log.Debugf("[zonedb] '%s' resolved in local database", name)
|
||||
res = uniq.toSlice()
|
||||
} else {
|
||||
res, err = zone.domainLookup(name, zone.mdnsCli.LookupName)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
zone.startUpdatingName(name)
|
||||
return res, nil
|
||||
}
|
||||
return nil, LookupError(name)
|
||||
}
|
||||
|
||||
// Perform a lookup for a IP address in the zone
|
||||
// The address can be resolved either with the local database or
|
||||
// with some other resolution method (eg, a mDNS query)
|
||||
func (zone *ZoneDb) DomainLookupInaddr(inaddr string) (res []ZoneRecord, err error) {
|
||||
revIPv4, err := raddrToIPv4(inaddr)
|
||||
if err != nil {
|
||||
return nil, newParseError("lookup address", inaddr)
|
||||
}
|
||||
|
||||
Log.Debugf("[zonedb] Looking for address in local(&remote) database: '%s' (%s)", revIPv4, inaddr)
|
||||
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
uniq := newUniqZoneRecords()
|
||||
for identName, nameset := range zone.idents {
|
||||
for _, ze := range nameset.getEntriesForIP(revIPv4) {
|
||||
// filter the entries with expired TTL
|
||||
// locally introduced entries are never expired: they always have TTL=0
|
||||
if ze.hasExpired(now) {
|
||||
Log.Debugf("[zonedb] '%s': expired entry '%s' ignored: removing", revIPv4, ze)
|
||||
nameset.deleteNameIP("", revIPv4.toNetIP())
|
||||
} else {
|
||||
uniq.add(ze)
|
||||
if identName != defaultRemoteIdent {
|
||||
nameset.touchName(ze.Name(), now)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
zone.mx.Unlock()
|
||||
|
||||
if len(uniq) > 0 {
|
||||
Log.Debugf("[zonedb] '%s' resolved in local database", inaddr)
|
||||
res = uniq.toSlice()
|
||||
} else {
|
||||
res, err = zone.domainLookup(inaddr, zone.mdnsCli.LookupInaddr)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
// note: even for reverse addresses, we perform the background updates in the name, not in the IP
|
||||
// this simplifies the process and produces basically the same results...
|
||||
// note: we do not spend time trying to update names that did not return an initial response...
|
||||
for _, r := range res {
|
||||
zone.startUpdatingName(r.Name())
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
return nil, LookupError(inaddr)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Names updates try to find all the IPs for a given name with a mDNS query
|
||||
//
|
||||
// There are two types of names updates:
|
||||
//
|
||||
// - immediate updates.
|
||||
// After a `DomainLookup*()` for a name not in the database we will return the
|
||||
// first IP we can get with mDNS from other peers. Waiting for more responses would
|
||||
// mean more latency in the response to the client, so we send only one answer BUT
|
||||
// we also trigger an immediate update request for that name in order to get all
|
||||
// the other IPs we didn't wait for...
|
||||
//
|
||||
// - periodic updates
|
||||
// once we have obtained the first group of IPs for a name, we schedule a periodic
|
||||
// refresh for that name, so we keep the list of IPs for that name up to date.
|
||||
//
|
||||
// These names updates are repeated until either
|
||||
//
|
||||
// a) there is no interest in the name, determined by a global 'relevant time'
|
||||
// and the last time some local client asked about the name,
|
||||
// or
|
||||
// b) no peers answer one of our refresh requests (because the name has probably
|
||||
// disappeared from the network)
|
||||
//
|
||||
|
||||
// Check if we must start updating a name and, in that case, trigger a immediate update
|
||||
func (zone *ZoneDb) startUpdatingName(name string) {
|
||||
if zone.refreshInterval > 0 {
|
||||
zone.mx.Lock()
|
||||
|
||||
// check if we should enqueue a refresh request for this name
|
||||
n := zone.getNameSet(defaultRemoteIdent).getName(name, true)
|
||||
if n.lastRefreshTime.IsZero() {
|
||||
now := zone.clock.Now()
|
||||
n.lastRefreshTime = now
|
||||
zone.mx.Unlock()
|
||||
|
||||
Log.Debugf("[zonedb] Creating new immediate refresh request for '%s'", name)
|
||||
zone.refreshScheds.Add(func() time.Time { return zone.updater(name) }, now)
|
||||
} else {
|
||||
zone.mx.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the IPs we have for a name
|
||||
func (zone *ZoneDb) updater(name string) (nextTime time.Time) {
|
||||
deleteRemoteInfo := func() {
|
||||
zone.mx.Lock()
|
||||
zone.getNameSet(defaultRemoteIdent).deleteNameIP(name, net.IP{})
|
||||
zone.mx.Unlock()
|
||||
}
|
||||
|
||||
// if nobody has asked for this name for long time, just forget about it...
|
||||
if !zone.IsNameRelevant(name) || zone.IsNameExpired(name) {
|
||||
Log.Debugf("[zonedb] '%s' seem to be irrelevant now: removing any remote information", name)
|
||||
deleteRemoteInfo()
|
||||
return
|
||||
}
|
||||
|
||||
// perform the refresh for this name
|
||||
fullName := dns.Fqdn(name)
|
||||
startTime := zone.clock.Now()
|
||||
Log.Debugf("[zonedb] Refreshing name '%s' with mDNS...", fullName)
|
||||
res, _ := zone.mdnsCli.InsistentLookupName(fullName)
|
||||
if res != nil && len(res) > 0 {
|
||||
numIps := len(res)
|
||||
zone.mx.Lock()
|
||||
now := zone.clock.Now()
|
||||
added, removed := zone.getNameSet(defaultRemoteIdent).getName(name, true).updateIPs(res, now)
|
||||
zone.mx.Unlock()
|
||||
Log.Debugf("[zonedb] Obtained %d IPs for name '%s' with mDNS: %d added, %d removed",
|
||||
numIps, name, added, removed)
|
||||
|
||||
// once the name has been updated, we re-schedule the update
|
||||
nextTime = startTime.Add(zone.refreshInterval)
|
||||
Log.Debugf("[zonedb] Rescheduling update for '%s' in %s", name, nextTime.Sub(zone.clock.Now()))
|
||||
} else {
|
||||
Log.Debugf("[zonedb] nobody knows about '%s'... removing", name)
|
||||
deleteRemoteInfo()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
// Check that the refreshing mechanism works as expected
|
||||
func TestZoneRefresh(t *testing.T) {
|
||||
const (
|
||||
refreshInterval = 5
|
||||
relevantTime = 30
|
||||
)
|
||||
var (
|
||||
name = "mysql.weave.local."
|
||||
addr1 = "10.2.8.4"
|
||||
addr2 = "10.2.8.5"
|
||||
addr3 = "10.2.8.6"
|
||||
addr4 = "10.2.8.7"
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
Log.Infoln("TestZoneRefresh starting")
|
||||
|
||||
clk := newMockedClock()
|
||||
|
||||
// Create multiple zone databases, linked through mocked mDNS connections
|
||||
zc := ZoneConfig{
|
||||
RefreshInterval: refreshInterval,
|
||||
RelevantTime: relevantTime,
|
||||
Clock: clk,
|
||||
}
|
||||
dbs := newZoneDbsWithMockedMDns(2, zc)
|
||||
dbs.Start()
|
||||
defer dbs.Stop()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // allow for server to get going
|
||||
|
||||
Log.Debugf("Adding '%s' to Db #1", name)
|
||||
dbs[0].Zone.AddRecord("someident", name, net.ParseIP(addr1))
|
||||
|
||||
Log.Debugf("Checking that the name %s is relevant (as it has been locally inserted) and not remote", name)
|
||||
require.True(t, dbs[0].Zone.IsNameRelevant(name), "name relevant")
|
||||
require.True(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
|
||||
require.False(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
|
||||
|
||||
Log.Debugf("Asking for '%s' to Db #1: should get 1 IP from the local database...", name)
|
||||
res, err := dbs[0].Zone.DomainLookupName(name)
|
||||
require.NoError(t, err)
|
||||
Log.Debugf("Got: %s", res)
|
||||
t.Logf("Db #1 after the lookup:\n%s", dbs[0].Zone)
|
||||
require.Equal(t, 1, len(res), "lookup result")
|
||||
|
||||
clk.Forward(refreshInterval / 2)
|
||||
dbs.Flush()
|
||||
Log.Debugf("A couple of seconds later, we should still have one IP for that name")
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(res), "lookup result")
|
||||
|
||||
Log.Debugf("And then we add 2 IPs for that name at ZoneDb 2")
|
||||
clk.Forward(1)
|
||||
dbs.Flush()
|
||||
Log.Debugf("Adding 2 IPs to '%s' in Db #2", name)
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr2))
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr3))
|
||||
|
||||
Log.Debugf("Perform a lookup, to ensure the name will be updated in the background...")
|
||||
dbs[0].Zone.DomainLookupName(name)
|
||||
|
||||
Log.Debugf("Wait for a while, until a refresh is performed...")
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
Log.Debugf("A refresh should have been scheduled now: we should have 3 IPs:")
|
||||
Log.Debugf("the first (local) IP and the others obtained from zone2 with a mDNS query")
|
||||
Log.Debugf("Asking for '%s' again... we should have 3 IPs now", name)
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
Log.Debugf("Got: %s", res)
|
||||
t.Logf("Db #1 after the second lookup:\n%s", dbs[0].Zone)
|
||||
require.Equal(t, 3, len(res), "lookup result length")
|
||||
|
||||
Log.Debugf("We will not ask for `name` for a while, so it will become irrelevant and will be removed...")
|
||||
clk.Forward(refreshInterval + relevantTime + 1)
|
||||
dbs.Flush()
|
||||
|
||||
// the name should be irrelevant now, and all remote info should have been
|
||||
// removed from the zone database
|
||||
Log.Debugf("Name '%s' should not be in the remote database in ZoneDb 1", name)
|
||||
require.False(t, dbs[0].Zone.IsNameRelevant(name), "name still relevant after some inactivity time")
|
||||
require.True(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
|
||||
require.False(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
|
||||
|
||||
Log.Debugf("There is no remote info about this name at zone 1: a new IP appears remotely meanwhile...")
|
||||
clk.Forward(1)
|
||||
dbs.Flush()
|
||||
|
||||
Log.Debugf("Adding '%s' to Db #2", name)
|
||||
dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr4))
|
||||
|
||||
Log.Debugf("When we ask about this name again, we get 4 IPs (1 local, 3 remote)")
|
||||
Log.Debugf("Asking for '%s' again... the first lookup will return only the local results", name)
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
require.Equal(t, 1, len(res), "lookup result length")
|
||||
Log.Debugf("... but a second lookup should return all the results in the network")
|
||||
|
||||
clk.Forward(refreshInterval + 1)
|
||||
dbs.Flush()
|
||||
|
||||
res, err = dbs[0].Zone.DomainLookupName(name)
|
||||
Log.Debugf("Got: %s", res)
|
||||
require.Equal(t, 4, len(res), "lookup result length")
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func TestZone(t *testing.T) {
|
||||
var (
|
||||
container1 = "deadbeef"
|
||||
container2 = "cowjuice"
|
||||
name1 = "test1.weave."
|
||||
name1Addr1 = "10.9.2.1/24"
|
||||
name1Addr2 = "10.9.2.2/24"
|
||||
revName1Addr1 = "1.2.9.10.in-addr.arpa."
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
ip1, _, _ := net.ParseCIDR(name1Addr1)
|
||||
t.Logf("Adding '%s'/%s to '%s'", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add a few more records to make the job harder
|
||||
t.Logf("Adding 'adummy.weave.' to 'abcdef0123'")
|
||||
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.9.0.1"))
|
||||
require.NoError(t, err)
|
||||
t.Logf("Adding 'zdummy.weave.' to '0123abcdef'")
|
||||
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.9.0.2"))
|
||||
require.NoError(t, err)
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking if we can find the name '%s'", name1)
|
||||
foundIPs, err := zone.LookupName(name1)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !foundIPs[0].IP().Equal(ip1) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
t.Logf("Checking if we cannot find some silly name like 'something.wrong'")
|
||||
foundIPs, err = zone.LookupName("something.wrong")
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, fmt.Sprintf("unknown name: %+v", foundIPs))
|
||||
|
||||
ip2, _, _ := net.ParseCIDR(name1Addr2)
|
||||
t.Logf("Adding a second IP for '%s'/%s to '%s'", name1, ip2, container1)
|
||||
err = zone.AddRecord(container1, name1, ip2)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Checking if we can find both the old IP and the new IP for '%s'", name1)
|
||||
foundIPs, err = zone.LookupName(name1)
|
||||
require.NoError(t, err)
|
||||
if !(foundIPs[0].IP().Equal(ip1) || foundIPs[1].IP().Equal(ip1)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
if !(foundIPs[0].IP().Equal(ip2) || foundIPs[1].IP().Equal(ip2)) {
|
||||
t.Fatal("Unexpected result for", name1, foundIPs)
|
||||
}
|
||||
|
||||
t.Logf("Checking if we can find the address by IP '1.2.9.10.in-addr.arpa.'")
|
||||
foundNames, err := zone.LookupInaddr("1.2.9.10.in-addr.arpa.")
|
||||
require.NoError(t, err)
|
||||
|
||||
if foundNames[0].Name() != name1 {
|
||||
t.Fatal("Unexpected result for", ip1, foundNames)
|
||||
}
|
||||
|
||||
t.Logf("Checking we can not find an unknown address '30.20.10.1.in-addr.arpa.'")
|
||||
foundNames, err = zone.LookupInaddr("30.20.10.1.in-addr.arpa.")
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, fmt.Sprintf("unknown IP: %+v", foundNames))
|
||||
|
||||
t.Logf("Checking if adding again '%s'/%s in %s results in an error", name1, ip1, container1)
|
||||
err = zone.AddRecord(container1, name1, ip1)
|
||||
wt.AssertErrorType(t, (*DuplicateError)(nil), err, "duplicate add")
|
||||
|
||||
t.Logf("Adding '%s'/%s in %s too", name1, ip1, container2)
|
||||
err = zone.AddRecord(container2, name1, ip1)
|
||||
require.NoError(t, err)
|
||||
|
||||
name1Removed := 0
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #1 for '%s' notified.", name1); name1Removed++ })
|
||||
require.NoError(t, err)
|
||||
err = zone.ObserveName(name1, func() { t.Logf("Observer #2 for '%s' notified.", name1); name1Removed++ })
|
||||
require.NoError(t, err)
|
||||
err = zone.ObserveInaddr(revName1Addr1, func() { t.Logf("Observer #1 for '%s' notified.", revName1Addr1); name1Removed++ })
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Logf("Deleting the %s in %s", ip1, container1)
|
||||
count := zone.DeleteRecords(container1, "", ip1)
|
||||
require.Equal(t, 1, count, "delete failed")
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
|
||||
t.Logf("Checking %s's observers have been notified on removal", name1)
|
||||
if name1Removed < 3 {
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
t.Fatalf("Unexpected number (%d) of calls to observers", name1Removed)
|
||||
}
|
||||
|
||||
t.Logf("Checking %s can be found", name1)
|
||||
_, err = zone.LookupName(name1)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("Checking %s is not found after removing %s it in %s and %s in %s",
|
||||
name1, ip1, container2, ip2, container1)
|
||||
count = zone.DeleteRecords(container1, "", ip2)
|
||||
require.Equal(t, 1, count, "delete failed")
|
||||
count = zone.DeleteRecords(container2, "", ip1)
|
||||
require.Equal(t, 1, count, "delete failed")
|
||||
t.Logf("Zone database:\n%s", zone)
|
||||
_, err = zone.LookupName(name1)
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, "after deleting record")
|
||||
|
||||
t.Logf("Checking if removing an unknown record results in an error")
|
||||
count = zone.DeleteRecords(container1, "", net.ParseIP("0.0.0.0"))
|
||||
require.Equal(t, 0, count, "delete failed")
|
||||
}
|
||||
|
||||
func TestDeleteRecords(t *testing.T) {
|
||||
var (
|
||||
id = "foobar"
|
||||
name = "foo.weave."
|
||||
addr1 = "10.2.2.3/24"
|
||||
addr2 = "10.2.7.8/24"
|
||||
)
|
||||
|
||||
EnableDebugLogging(testing.Verbose())
|
||||
|
||||
zone, err := NewZoneDb(ZoneConfig{})
|
||||
require.NoError(t, err)
|
||||
err = zone.Start()
|
||||
require.NoError(t, err)
|
||||
defer zone.Stop()
|
||||
|
||||
for _, addr := range []string{addr1, addr2} {
|
||||
ip, _, _ := net.ParseCIDR(addr)
|
||||
err := zone.AddRecord(id, name, ip)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = zone.LookupName(name)
|
||||
require.NoError(t, err)
|
||||
|
||||
count := zone.DeleteRecords(id, "", net.IP{})
|
||||
require.Equal(t, 2, count, "wildcard delete failed")
|
||||
_, err = zone.LookupName(name)
|
||||
wt.AssertErrorType(t, (*LookupError)(nil), err, "after deleting records for ident")
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM scratch
|
||||
MAINTAINER Weaveworks Inc <help@weave.works>
|
||||
WORKDIR /home/weave
|
||||
ADD ./weavedns /home/weave/
|
||||
EXPOSE 53/udp
|
||||
ENTRYPOINT ["/home/weave/weavedns"]
|
||||
@@ -1,186 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/docker"
|
||||
weavedns "github.com/weaveworks/weave/nameserver"
|
||||
weavenet "github.com/weaveworks/weave/net"
|
||||
)
|
||||
|
||||
var version = "(unreleased version)"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
justVersion bool
|
||||
dockerCli *docker.Client
|
||||
ifaceName string
|
||||
apiPath string
|
||||
domain string
|
||||
dnsPort int
|
||||
httpIfaceName string
|
||||
httpPort int
|
||||
wait int
|
||||
ttl int
|
||||
negTTL int
|
||||
timeout int
|
||||
udpbuf int
|
||||
fallback string
|
||||
refreshInterval int
|
||||
relevantTime int
|
||||
maxAnswers int
|
||||
cacheLen int
|
||||
cacheDisabled bool
|
||||
watch bool
|
||||
logLevel string
|
||||
err error
|
||||
)
|
||||
|
||||
mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
|
||||
mflag.StringVar(&ifaceName, []string{"#iface", "-iface"}, "", "name of interface to use for multicast")
|
||||
mflag.StringVar(&apiPath, []string{"#api", "-api"}, "unix:///var/run/docker.sock", "path to Docker API socket")
|
||||
mflag.StringVar(&domain, []string{"#domain", "-domain"}, weavedns.DefaultLocalDomain, "local domain (ie, 'weave.local.')")
|
||||
mflag.IntVar(&wait, []string{"#wait", "-wait"}, -1, "number of seconds to wait for interfaces to come up (0=don't wait, -1=wait forever)")
|
||||
mflag.IntVar(&dnsPort, []string{"#dnsport", "#-dnsport", "-dns-port"}, weavedns.DefaultServerPort, "port to listen to DNS requests")
|
||||
mflag.StringVar(&httpIfaceName, []string{"#httpiface", "#-httpiface", "-http-iface"}, "", "interface on which to listen for HTTP requests (empty string means listen on all interfaces)")
|
||||
mflag.IntVar(&httpPort, []string{"#httpport", "#-httpport", "-http-port"}, weavedns.DefaultHTTPPort, "port to listen to HTTP requests")
|
||||
mflag.IntVar(&cacheLen, []string{"#cache", "-cache"}, weavedns.DefaultCacheLen, "cache length")
|
||||
mflag.IntVar(&ttl, []string{"#ttl", "-ttl"}, weavedns.DefaultLocalTTL, "TTL (in secs) for responses for local names")
|
||||
mflag.BoolVar(&watch, []string{"#watch", "-watch"}, true, "watch the docker socket for container events")
|
||||
mflag.StringVar(&logLevel, []string{"-log-level"}, "info", "logging level (debug, info, warning, error)")
|
||||
// advanced options
|
||||
mflag.IntVar(&negTTL, []string{"#neg-ttl", "-neg-ttl"}, 0, "negative TTL (in secs) for unanswered queries for local names (0=same value as --ttl")
|
||||
mflag.IntVar(&refreshInterval, []string{"#refresh", "-refresh"}, weavedns.DefaultRefreshInterval, "refresh interval (in secs) for local names (0=disable)")
|
||||
mflag.IntVar(&maxAnswers, []string{"#max-answers", "#-max-answers", "-dns-max-answers"}, weavedns.DefaultMaxAnswers, "maximum number of answers returned to clients (0=unlimited)")
|
||||
mflag.IntVar(&relevantTime, []string{"#relevant", "-relevant"}, weavedns.DefaultRelevantTime, "life time for info in the absence of queries (in secs)")
|
||||
mflag.IntVar(&udpbuf, []string{"#udpbuf", "#-udpbuf", "-dns-udpbuf"}, weavedns.DefaultUDPBuflen, "UDP buffer length for DNS")
|
||||
mflag.IntVar(&timeout, []string{"#timeout", "#-timeout", "-dns-timeout"}, weavedns.DefaultTimeout, "timeout for resolutions (in millisecs)")
|
||||
mflag.BoolVar(&cacheDisabled, []string{"#no-cache", "-no-cache"}, false, "disable the cache")
|
||||
mflag.StringVar(&fallback, []string{"#fallback", "#-fallback", "-dns-fallback"}, "", "force a fallback server (ie, '8.8.8.8:53') (instead of /etc/resolv.conf values)")
|
||||
mflag.Parse()
|
||||
|
||||
if justVersion {
|
||||
fmt.Printf("weave DNS %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
SetLogLevel(logLevel)
|
||||
Log.Infof("[main] WeaveDNS version %s", version) // first thing in log: the version
|
||||
|
||||
var iface *net.Interface
|
||||
if ifaceName != "" {
|
||||
var err error
|
||||
Log.Infoln("[main] Waiting for mDNS interface", ifaceName, "to come up")
|
||||
iface, err = weavenet.EnsureInterface(ifaceName, wait)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
} else {
|
||||
Log.Infoln("[main] Interface", ifaceName, "is up")
|
||||
}
|
||||
}
|
||||
|
||||
var httpIP string
|
||||
if httpIfaceName == "" {
|
||||
httpIP = "0.0.0.0"
|
||||
} else {
|
||||
Log.Infoln("[main] Waiting for HTTP interface", httpIfaceName, "to come up")
|
||||
httpIface, err := weavenet.EnsureInterface(httpIfaceName, wait)
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
Log.Infoln("[main] Interface", httpIfaceName, "is up")
|
||||
|
||||
addrs, err := httpIface.Addrs()
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
Log.Fatal("[main] No addresses on HTTP interface")
|
||||
}
|
||||
|
||||
ip, _, err := net.ParseCIDR(addrs[0].String())
|
||||
if err != nil {
|
||||
Log.Fatal(err)
|
||||
}
|
||||
|
||||
httpIP = ip.String()
|
||||
|
||||
}
|
||||
httpAddr := net.JoinHostPort(httpIP, strconv.Itoa(httpPort))
|
||||
|
||||
zoneConfig := weavedns.ZoneConfig{
|
||||
Domain: domain,
|
||||
Iface: iface,
|
||||
LocalTTL: ttl,
|
||||
RefreshInterval: refreshInterval,
|
||||
RelevantTime: relevantTime,
|
||||
}
|
||||
zone, err := weavedns.NewZoneDb(zoneConfig)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Unable to initialize the Zone database", err)
|
||||
}
|
||||
if err := zone.Start(); err != nil {
|
||||
Log.Fatal("[main] Unable to start the Zone database", err)
|
||||
}
|
||||
defer zone.Stop()
|
||||
|
||||
dockerCli, err = docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Unable to start docker client: ", err)
|
||||
}
|
||||
|
||||
if watch {
|
||||
err := dockerCli.AddObserver(zone)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Unable to start watcher", err)
|
||||
}
|
||||
}
|
||||
|
||||
srvConfig := weavedns.DNSServerConfig{
|
||||
Zone: zone,
|
||||
Port: dnsPort,
|
||||
CacheLen: cacheLen,
|
||||
LocalTTL: ttl,
|
||||
CacheNegLocalTTL: negTTL,
|
||||
MaxAnswers: maxAnswers,
|
||||
Timeout: timeout,
|
||||
UDPBufLen: udpbuf,
|
||||
CacheDisabled: cacheDisabled,
|
||||
}
|
||||
|
||||
if len(fallback) > 0 {
|
||||
fallbackHost, fallbackPort, err := net.SplitHostPort(fallback)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Could not parse fallback host and port", err)
|
||||
}
|
||||
srvConfig.UpstreamCfg = &dns.ClientConfig{Servers: []string{fallbackHost}, Port: fallbackPort}
|
||||
Log.Debugf("[main] DNS fallback at %s:%s", fallbackHost, fallbackPort)
|
||||
}
|
||||
|
||||
srv, err := weavedns.NewDNSServer(srvConfig)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Failed to initialize the WeaveDNS server", err)
|
||||
}
|
||||
|
||||
httpListener, err := net.Listen("tcp", httpAddr)
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Unable to create http listener: ", err)
|
||||
}
|
||||
Log.Infoln("[main] HTTP API listening on", httpAddr)
|
||||
|
||||
go SignalHandlerLoop(srv)
|
||||
go weavedns.ServeHTTP(httpListener, version, srv, dockerCli)
|
||||
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
Log.Fatal("[main] Failed to start the WeaveDNS server: ", err)
|
||||
}
|
||||
srv.ActivateAndServe()
|
||||
}
|
||||
Reference in new issue
Block a user