diff --git a/Makefile b/Makefile index 0784ee8e..dca84405 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/common/sched_queue.go b/common/sched_queue.go deleted file mode 100644 index cbfc9eb3..00000000 --- a/common/sched_queue.go +++ /dev/null @@ -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 -} diff --git a/common/sched_queue_test.go b/common/sched_queue_test.go deleted file mode 100644 index d9eb6830..00000000 --- a/common/sched_queue_test.go +++ /dev/null @@ -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") -} diff --git a/nameserver/addrs.go b/nameserver/addrs.go deleted file mode 100644 index fb3deb66..00000000 --- a/nameserver/addrs.go +++ /dev/null @@ -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 -} diff --git a/nameserver/addrs_test.go b/nameserver/addrs_test.go deleted file mode 100644 index 00d1ba7d..00000000 --- a/nameserver/addrs_test.go +++ /dev/null @@ -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") -} diff --git a/nameserver/cache.go b/nameserver/cache.go deleted file mode 100644 index fd0be604..00000000 --- a/nameserver/cache.go +++ /dev/null @@ -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() -} diff --git a/nameserver/cache_test.go b/nameserver/cache_test.go deleted file mode 100644 index d6e29c25..00000000 --- a/nameserver/cache_test.go +++ /dev/null @@ -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") -} diff --git a/nameserver/dns.go b/nameserver/dns.go deleted file mode 100644 index c232d45b..00000000 --- a/nameserver/dns.go +++ /dev/null @@ -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 -} diff --git a/nameserver/dns_test.go b/nameserver/dns_test.go deleted file mode 100644 index 3e001e6e..00000000 --- a/nameserver/dns_test.go +++ /dev/null @@ -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") -} diff --git a/nameserver/http.go b/nameserver/http.go deleted file mode 100644 index 02e1d380..00000000 --- a/nameserver/http.go +++ /dev/null @@ -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) - } -} diff --git a/nameserver/http_test.go b/nameserver/http_test.go deleted file mode 100644 index 18e26b1b..00000000 --- a/nameserver/http_test.go +++ /dev/null @@ -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 -} diff --git a/nameserver/mdns.go b/nameserver/mdns.go deleted file mode 100644 index e476fb4f..00000000 --- a/nameserver/mdns.go +++ /dev/null @@ -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) -} diff --git a/nameserver/mdns_client.go b/nameserver/mdns_client.go deleted file mode 100644 index 0be13fbd..00000000 --- a/nameserver/mdns_client.go +++ /dev/null @@ -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) - } - } -} diff --git a/nameserver/mdns_client_test.go b/nameserver/mdns_client_test.go deleted file mode 100644 index edeb1bf1..00000000 --- a/nameserver/mdns_client_test.go +++ /dev/null @@ -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) - } -} diff --git a/nameserver/mdns_lookup.go b/nameserver/mdns_lookup.go deleted file mode 100644 index db051bb4..00000000 --- a/nameserver/mdns_lookup.go +++ /dev/null @@ -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) -} diff --git a/nameserver/mdns_server.go b/nameserver/mdns_server.go deleted file mode 100644 index df51f6f8..00000000 --- a/nameserver/mdns_server.go +++ /dev/null @@ -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 -} diff --git a/nameserver/mdns_server_test.go b/nameserver/mdns_server_test.go deleted file mode 100644 index 5022af21..00000000 --- a/nameserver/mdns_server_test.go +++ /dev/null @@ -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) - } -} diff --git a/nameserver/mdns_test.go b/nameserver/mdns_test.go deleted file mode 100644 index 6acf1ac8..00000000 --- a/nameserver/mdns_test.go +++ /dev/null @@ -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) - } -} diff --git a/nameserver/mocks_test.go b/nameserver/mocks_test.go deleted file mode 100644 index 58bb5e04..00000000 --- a/nameserver/mocks_test.go +++ /dev/null @@ -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)) -} diff --git a/nameserver/server.go b/nameserver/server.go deleted file mode 100644 index 6f36aaf4..00000000 --- a/nameserver/server.go +++ /dev/null @@ -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) -} diff --git a/nameserver/server_cache_test.go b/nameserver/server_cache_test.go deleted file mode 100644 index 394710d6..00000000 --- a/nameserver/server_cache_test.go +++ /dev/null @@ -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") - -} diff --git a/nameserver/server_test.go b/nameserver/server_test.go deleted file mode 100644 index 9c4a1976..00000000 --- a/nameserver/server_test.go +++ /dev/null @@ -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)") -} diff --git a/nameserver/zone.go b/nameserver/zone.go deleted file mode 100644 index e6598932..00000000 --- a/nameserver/zone.go +++ /dev/null @@ -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 -} diff --git a/nameserver/zone_lookup.go b/nameserver/zone_lookup.go deleted file mode 100644 index 6e515b62..00000000 --- a/nameserver/zone_lookup.go +++ /dev/null @@ -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 -} diff --git a/nameserver/zone_lookup_test.go b/nameserver/zone_lookup_test.go deleted file mode 100644 index 31789133..00000000 --- a/nameserver/zone_lookup_test.go +++ /dev/null @@ -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") -} diff --git a/nameserver/zone_test.go b/nameserver/zone_test.go deleted file mode 100644 index 2a3a1467..00000000 --- a/nameserver/zone_test.go +++ /dev/null @@ -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") -} diff --git a/prog/weavedns/Dockerfile b/prog/weavedns/Dockerfile deleted file mode 100644 index 883b34b8..00000000 --- a/prog/weavedns/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM scratch -MAINTAINER Weaveworks Inc -WORKDIR /home/weave -ADD ./weavedns /home/weave/ -EXPOSE 53/udp -ENTRYPOINT ["/home/weave/weavedns"] diff --git a/prog/weavedns/main.go b/prog/weavedns/main.go deleted file mode 100644 index c8217656..00000000 --- a/prog/weavedns/main.go +++ /dev/null @@ -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() -}