diff --git a/nameserver/addrs.go b/nameserver/addrs.go
index bb4ac27c..0bdb0dca 100644
--- a/nameserver/addrs.go
+++ b/nameserver/addrs.go
@@ -25,16 +25,13 @@ type ZoneLookup interface {
LookupInaddr(inaddr string) ([]ZoneRecord, error)
}
-type ZoneLookupFunc func(target string) ([]ZoneRecord, error)
-
-type ZoneObservable interface {
+type ZoneObserver 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()
/////////////////////////////////////////////////////////////
diff --git a/nameserver/cache.go b/nameserver/cache.go
index 7a5cc0c1..644814f0 100644
--- a/nameserver/cache.go
+++ b/nameserver/cache.go
@@ -9,6 +9,7 @@ import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"math"
+ "math/rand"
"sync"
"time"
)
@@ -42,6 +43,21 @@ const (
CacheNoLocalReplies uint8 = 1 << iota // not found in local network (stored in the cache so we skip another local lookup or some time)
)
+// shuffleAnswers reorders answers for very basic load balancing
+func shuffleAnswers(answers []dns.RR) []dns.RR {
+ n := len(answers)
+ if n > 1 {
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ for i := 0; i < n; i++ {
+ r := i + rand.Intn(n-i)
+ answers[r], answers[i] = answers[i], answers[r]
+ }
+ }
+
+ return answers
+}
+
// a cache entry
type cacheEntry struct {
Status entryStatus // status of the entry
@@ -108,6 +124,9 @@ func (e *cacheEntry) getReply(request *dns.Msg, maxLen int, now time.Time) (*dns
reply.Rcode = e.reply.Rcode
reply.Authoritative = true
+ // shuffle the values, etc...
+ reply.Answer = shuffleAnswers(reply.Answer)
+
return reply, nil
}
diff --git a/nameserver/dns.go b/nameserver/dns.go
index 3f919993..904a9bf2 100644
--- a/nameserver/dns.go
+++ b/nameserver/dns.go
@@ -2,8 +2,6 @@ package nameserver
import (
"github.com/miekg/dns"
- "math/rand"
- "time"
)
const (
@@ -35,8 +33,6 @@ func makeTruncatedReply(r *dns.Msg) *dns.Msg {
return reply
}
-type DNSResponseBuilder func(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg
-
func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg {
answers := make([]dns.RR, len(addrs))
header := makeHeader(r, q)
@@ -88,28 +84,3 @@ func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int {
}
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 58e53784..00000000
--- a/nameserver/dns_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package nameserver
-
-import (
- "github.com/miekg/dns"
- . "github.com/weaveworks/weave/common"
- wt "github.com/weaveworks/weave/testing"
- "net"
- "testing"
-)
-
-// Check that we can prune an answer
-func TestPrune(t *testing.T) {
- InitDefaultLogging(testing.Verbose())
- Info.Println("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)
- reply.Answer[0].Header().Ttl = localTTL
-
- pruned := pruneAnswers(reply.Answer, 1)
- wt.AssertEqualInt(t, len(pruned), 1, "wrong number of answers")
-
- pruned = pruneAnswers(reply.Answer, 2)
- wt.AssertEqualInt(t, len(pruned), 2, "wrong number of answers")
-
- pruned = pruneAnswers(reply.Answer, 0)
- wt.AssertEqualInt(t, len(pruned), len(records), "wrong number of answers")
-}
diff --git a/nameserver/http_test.go b/nameserver/http_test.go
index f3cee7a9..5979d683 100644
--- a/nameserver/http_test.go
+++ b/nameserver/http_test.go
@@ -24,15 +24,10 @@ func TestHttp(t *testing.T) {
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{})
- wt.AssertNoErr(t, err)
- err = zone.Start()
- wt.AssertNoErr(t, err)
- defer zone.Stop()
+ zone := NewZoneDb(DefaultLocalDomain)
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
@@ -70,31 +65,6 @@ func TestHttp(t *testing.T) {
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "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]}})
- wt.AssertNoErr(t, err)
- wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "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)
- wt.AssertNoErr(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)
wt.AssertNoErr(t, err)
@@ -110,11 +80,6 @@ func TestHttp(t *testing.T) {
wt.AssertNoErr(t, err)
wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
- // Delete the second IP
- resp, err = genForm("DELETE", addrURL2, nil)
- wt.AssertNoErr(t, err)
- wt.AssertStatus(t, resp.StatusCode, http.StatusOK, "http response")
-
// Check that the address is gone
x, err = zone.LookupName(successTestName)
t.Logf("Got %s", x)
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_test.go b/nameserver/mdns_test.go
deleted file mode 100644
index dc0fe11f..00000000
--- a/nameserver/mdns_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package nameserver
-
-import (
- "github.com/miekg/dns"
- . "github.com/weaveworks/weave/common"
- wt "github.com/weaveworks/weave/testing"
- "net"
- "testing"
- "time"
-)
-
-// Check that we can use a regular mDNS server with a regular mDNS client
-func TestClientServerSimpleQuery(t *testing.T) {
- InitDefaultLogging(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)
- wt.AssertNoErr(t, err)
- err = mdnsServer.Start(nil)
- wt.AssertNoErr(t, err)
- defer mdnsServer.Stop()
-
- var receivedAddr net.IP
- var receivedName string
- receivedCount := 0
-
- mdnsCli, err := NewMDNSClient()
- wt.AssertNoErr(t, err)
- err = mdnsCli.Start(nil)
- wt.AssertNoErr(t, err)
-
- sendQuery := func(name string, querytype uint16) {
- receivedAddr = nil
- receivedName = ""
- receivedCount = 0
- Debug.Printf("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
-
- Debug.Printf("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())
- }
-
- Debug.Printf("Query: testfail.weave. dns.TypeA")
- sendQuery("testfail.weave.", dns.TypeA)
- if receivedCount != 0 {
- t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
- }
-
- Debug.Printf("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) {
- InitDefaultLogging(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)
- wt.AssertNoErr(t, err)
- err = mdnsServer1.Start(nil)
- wt.AssertNoErr(t, err)
- defer mdnsServer1.Stop()
-
- mzone2 := newMockedZoneWithRecords([]ZoneRecord{testRecord2})
- mdnsServer2, err := NewMDNSServer(mzone2)
- wt.AssertNoErr(t, err)
- err = mdnsServer2.Start(nil)
- wt.AssertNoErr(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)
- wt.AssertNoErr(t, err)
- err = mdnsServer3.Start(nil)
- wt.AssertNoErr(t, err)
- defer mdnsServer3.Stop()
-
- var receivedAddrs []ZoneRecord
- var receivedNames []ZoneRecord
- receivedCount := 0
-
- mdnsCli, err := NewMDNSClient()
- wt.AssertNoErr(t, err)
- err = mdnsCli.Start(nil)
- wt.AssertNoErr(t, err)
-
- sendQuery := func(name string, querytype uint16) {
- receivedAddrs = nil
- receivedNames = nil
- receivedCount = 0
- Debug.Printf("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
-
- Debug.Printf("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())
- }
-
- Debug.Printf("Query: testfail.weave. dns.TypeA")
- sendQuery("testfail.weave.", dns.TypeA)
- if receivedCount != 0 {
- t.Fatalf("Unexpected result count %d for testfail.weave", receivedCount)
- }
-
- Debug.Printf("Query: %s dns.TypePTR", testInAddr1)
- sendQuery(testInAddr1, dns.TypePTR)
- if receivedCount != 1 {
- t.Fatalf("Expected an answer to %s, got %d answers", testInAddr1, receivedCount)
- }
-
- Debug.Printf("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
index ca3c941d..5f3ba870 100644
--- a/nameserver/mocks_test.go
+++ b/nameserver/mocks_test.go
@@ -1,16 +1,9 @@
package nameserver
import (
- "fmt"
- "github.com/benbjohnson/clock"
- "github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
- wt "github.com/weaveworks/weave/testing"
"net"
- "strconv"
"sync"
- "testing"
- "time"
)
const (
@@ -96,403 +89,3 @@ func (mz *mockedZoneWithRecords) ObserveInaddr(inaddr string, observer ZoneRecor
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 += 1
- 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 += 1
- 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()
-
- res := make([]ZoneRecord, 0)
- mc.NumInsLookupsName += 1
- 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()
-
- res := make([]ZoneRecord, 0)
- mc.NumInsLookupsInaddr += 1
- 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)
-
- Debug.Printf("[test] Creating mocked mDNS server #%d", i)
- res[i].Server = newMockedMDNSServer(nil)
-
- Debug.Printf("[test] Creating mocked mDNS client #%d", i)
- res[i].Client = newMockedMDNSClient([]*mockedMDNSServer{})
-
- cfg := config
- cfg.MDNSClient = res[i].Client
- cfg.MDNSServer = res[i].Server
-
- Debug.Printf("[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 {
- Debug.Printf("[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()
- }
-}
-
-//////////////////////////////////////////////////////////////////
-
-// 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 += 1
- return nil, nil
-}
-func (c *mockedCache) Put(request *dns.Msg, reply *dns.Msg, ttl int, flags uint8) int {
- c.NumPuts += 1
- return 0
-}
-func (c *mockedCache) Remove(question *dns.Question) { c.NumRemovals += 1 }
-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) {
- Debug.Printf("[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()
- }()
-
- Debug.Printf("[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) {
- Debug.Printf("[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()
- }()
-
- Debug.Printf("[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()}
-}
-
-func (clk *mockedClock) Forward(secs int) {
- Debug.Printf(">>>>>>> Moving clock forward %d seconds - Time traveling >>>>>>>", secs)
- clk.Add(time.Duration(secs) * time.Second)
- Debug.Printf("<<<<<<< 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) {
- wt.AssertNotEqualInt(t, port, 0, "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.Fatalf("Error when querying DNS server at %s: %s", lstAddr, err)
- }
- wt.AssertNoErr(t, err)
- if minAnswers == 0 && maxAnswers == 0 {
- wt.AssertStatus(t, r.Rcode, expErr, "DNS response code")
- } else {
- wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
- }
- answers := len(r.Answer)
- if minAnswers >= 0 && answers < minAnswers {
- wt.Fatalf(t, "Number of answers >= %d", minAnswers)
- }
- if maxAnswers >= 0 && answers > maxAnswers {
- wt.Fatalf(t, "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)
- wt.AssertNoErr(t, err)
- wt.AssertNotNil(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)
- wt.AssertNoErr(t, err)
- wt.AssertNil(t, r, fmt.Sprintf("value in the cache: %s\n%s", desc, r))
-}
diff --git a/nameserver/server.go b/nameserver/server.go
index 79a4e802..3077c8f4 100644
--- a/nameserver/server.go
+++ b/nameserver/server.go
@@ -3,51 +3,37 @@ package nameserver
import (
"bytes"
"fmt"
- "github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
- "strconv"
"sync"
- "time"
)
const (
- DefaultServerPort = 53 // The default server port
- DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
- DefaultUDPBuflen = 4096 // bigger than the default 512
- DefaultCacheLen = 8192 // default cache capacity
- DefaultTimeout = 5 // default timeout for DNS resolutions
- DefaultMaxAnswers = 1 // default number of answers provided to users
+ DefaultLocalDomain = "weave.local." // The default name used for the local domain
+ DefaultServerPort = 53 // The default server port
+ DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
+ DefaultUDPBuflen = 4096 // bigger than the default 512
+ DefaultCacheLen = 8192 // default cache capacity
+ DefaultResolvWorkers = 8 // default number of resolution workers
+ DefaultTimeout = 5 // default timeout for DNS resolutions
)
type DNSServerConfig struct {
- // 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) local domain (ie, "weave.local.")
+ LocalDomain string
// (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 negative results in the local domain
- 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
@@ -68,66 +54,51 @@ func (proto dnsProtocol) String() string {
}
// get a new dns.Client for a protocol
-func (proto dnsProtocol) GetNewClient(bufsize int, timeout time.Duration) *dns.Client {
+func (proto dnsProtocol) GetNewClient(bufsize int) *dns.Client {
switch proto {
case protTCP:
- return &dns.Client{Net: "tcp", ReadTimeout: timeout}
+ return &dns.Client{Net: "tcp"}
case protUDP:
- return &dns.Client{Net: "udp", ReadTimeout: timeout, UDPSize: uint16(bufsize)}
+ return &dns.Client{Net: "udp", UDPSize: uint16(bufsize)}
}
return nil
}
-// a DNS server
type DNSServer struct {
- Zone Zone
- Upstream *dns.ClientConfig
+ Zone Zone
+ Iface *net.Interface
+ Upstream *dns.ClientConfig
+ udpSrv *dns.Server
+ tcpSrv *dns.Server
+ mdnsCli *MDNSClient
+ mdnsSrv *MDNSServer
+ cache *Cache
+ timeout int
+ udpBuf int
+ listenersWg *sync.WaitGroup
+
Domain string // the local domain
ListenAddr string // the address the server is listening at
-
- udpSrv *dns.Server
- tcpSrv *dns.Server
- pc net.PacketConn
- lst net.Listener
- cache ZoneCache
- cacheDisabled bool
- maxAnswers 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) {
+func NewDNSServer(config DNSServerConfig, zone Zone, iface *net.Interface) (s *DNSServer, err error) {
s = &DNSServer{
- Zone: config.Zone,
+ Zone: zone,
+ Iface: iface,
+ listenersWg: new(sync.WaitGroup),
+
Domain: DefaultLocalDomain,
- ListenAddr: fmt.Sprintf(":%d", config.Port),
-
- listenersWg: new(sync.WaitGroup),
- timeout: time.Duration(config.Timeout) * time.Millisecond,
- readTimeout: time.Duration(config.ListenReadTimeout) * time.Millisecond,
- cacheDisabled: false,
- maxAnswers: DefaultMaxAnswers,
- negLocalTTL: negLocalTTL,
- 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()
+ ListenAddr: fmt.Sprintf(":%d", DefaultServerPort),
}
// fill empty parameters with defaults...
+ if config.Port != 0 {
+ s.ListenAddr = fmt.Sprintf(":%d", config.Port)
+ }
+ if len(config.LocalDomain) > 0 {
+ s.Domain = config.LocalDomain
+ }
if config.UpstreamCfg != nil {
s.Upstream = config.UpstreamCfg
} else {
@@ -140,226 +111,207 @@ func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) {
}
}
if config.Timeout > 0 {
- s.timeout = time.Duration(config.Timeout) * time.Millisecond
+ s.timeout = config.Timeout
}
if config.UDPBufLen > 0 {
s.udpBuf = config.UDPBufLen
}
- if config.MaxAnswers > 0 {
- s.maxAnswers = config.MaxAnswers
+ s.mdnsCli, err = NewMDNSClient()
+ if err != nil {
+ return
}
- if config.CacheNegLocalTTL > 0 {
- s.negLocalTTL = config.CacheNegLocalTTL
+ s.mdnsSrv, err = NewMDNSServer(s.Zone)
+ if err != nil {
+ return
}
- if config.CacheDisabled {
- s.cacheDisabled = true
+ cacheLen := DefaultCacheLen
+ if config.CacheLen > 0 {
+ cacheLen = config.CacheLen
}
- 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
- }
- }
+ Debug.Printf("[dns] Initializing cache: %d entries", config.CacheLen)
+ s.cache, err = NewCache(cacheLen, nil)
+ if err != nil {
+ return
}
+ // create two DNS request multiplexerers, depending on the protocol used by clients
+ // (we use the same protocol for asking upstream servers)
+ mux := func(proto dnsProtocol) *dns.ServeMux {
+ m := dns.NewServeMux()
+ m.HandleFunc(s.Domain, s.queryHandler(proto))
+ m.HandleFunc(RDNSDomain, s.rdnsHandler(proto))
+ m.HandleFunc(".", s.notUsHandler(proto))
+ return m
+ }
+
+ s.udpSrv = &dns.Server{Addr: s.ListenAddr, Net: "udp", Handler: mux(protUDP)}
+ s.tcpSrv = &dns.Server{Addr: s.ListenAddr, Net: "tcp", Handler: mux(protTCP)}
+
return
}
// Start the DNS server
func (s *DNSServer) Start() error {
- Info.Printf("[dns] Upstream server(s): %+v", s.Upstream)
- if s.cacheDisabled {
- Info.Printf("[dns] Cache: disabled")
- } else {
- Info.Printf("[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}
+ Info.Printf("Using mDNS on %v", s.Iface)
+ err := s.mdnsCli.Start(s.Iface)
+ CheckFatal(err)
+ err = s.mdnsSrv.Start(s.Iface)
+ CheckFatal(err)
s.listenersWg.Add(2)
go func() {
defer s.listenersWg.Done()
- Info.Printf("[dns] Listening for DNS on %s (UDP)", s.ListenAddr)
- err := s.udpSrv.ActivateAndServe()
+ Debug.Printf("Listening for DNS on %s (UDP)", s.ListenAddr)
+ err = s.udpSrv.ListenAndServe()
CheckFatal(err)
- Debug.Printf("[dns] DNS UDP server exiting...")
+ Debug.Printf("DNS UDP server exiting...")
}()
go func() {
defer s.listenersWg.Done()
- Info.Printf("[dns] Listening for DNS on %s (TCP)", s.ListenAddr)
- err := s.tcpSrv.ActivateAndServe()
+ Debug.Printf("Listening for DNS on %s (TCP)", s.ListenAddr)
+ err = s.tcpSrv.ListenAndServe()
CheckFatal(err)
- Debug.Printf("[dns] DNS TCP server exiting...")
+ Debug.Printf("DNS TCP server exiting...")
}()
// Waiting for all goroutines to finish (otherwise they die as main routine dies)
s.listenersWg.Wait()
- Info.Printf("[dns] Server exiting...")
+ Info.Printf("WeaveDNS server exiting...")
return nil
}
// Return status string
func (s *DNSServer) Status() string {
var buf bytes.Buffer
+ fmt.Fprintln(&buf, "Local domain", s.Domain)
fmt.Fprintln(&buf, "Listen address", s.ListenAddr)
+ fmt.Fprintln(&buf, "mDNS interface", s.Iface)
fmt.Fprintln(&buf, "Fallback DNS config", s.Upstream)
+ fmt.Fprintf(&buf, "Zone database:\n%s", s.Zone)
return buf.String()
}
// Perform a graceful shutdown
func (s *DNSServer) Stop() error {
// Stop the listeners/handlers
- if s.tcpSrv != nil {
- if err := s.tcpSrv.Shutdown(); err != nil {
- return err
- }
- s.lst.Close()
- s.tcpSrv = nil
+ if err := s.tcpSrv.Shutdown(); err != nil {
+ return err
}
- if s.udpSrv != nil {
- if err := s.udpSrv.Shutdown(); err != nil {
- return err
- }
- s.pc.Close()
- s.udpSrv = nil
+ if err := s.udpSrv.Shutdown(); err != nil {
+ return err
}
s.listenersWg.Wait()
+
+ // shutdown the mDNS server
+ s.mdnsSrv.Stop()
+
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) {
+func (s *DNSServer) queryHandler(proto dnsProtocol) dns.HandlerFunc {
+ zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
+ ips, err := lookup.LookupName(q.Name)
+ if err != nil {
+ return nil, nil, err
+ }
+ return makeAddressReply(r, q, ips), ips, nil
+ }
+
+ fallback := func(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(makeDNSFailResponse(r))
}
+
+ return s.commonQueryHandler(proto, "Query", dns.TypeA, zoneLookup, fallback)
+}
+
+func (s *DNSServer) rdnsHandler(proto dnsProtocol) dns.HandlerFunc {
+ zoneLookup := func(lookup ZoneLookup, q *dns.Question, r *dns.Msg) (*dns.Msg, []ZoneRecord, error) {
+ names, err := lookup.LookupInaddr(q.Name)
+ if err != nil {
+ return nil, nil, err
+ }
+ return makePTRReply(r, q, names), names, nil
+ }
+
notUsHandler := s.notUsHandler(proto)
- notUsFallback := func(w dns.ResponseWriter, r *dns.Msg) {
+ fallback := func(w dns.ResponseWriter, r *dns.Msg) {
Info.Printf("[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
+ return s.commonQueryHandler(proto, "Reverse query", dns.TypePTR, zoneLookup, fallback)
}
-// 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 {
+func (s *DNSServer) commonQueryHandler(proto dnsProtocol, kind string, qtype uint16,
+ zoneLookup func(ZoneLookup, *dns.Question, *dns.Msg) (*dns.Msg, []ZoneRecord, error), fallback dns.HandlerFunc) dns.HandlerFunc {
return func(w dns.ResponseWriter, r *dns.Msg) {
q := r.Question[0]
Debug.Printf("[dns msgid %d] %s: %+v", r.MsgHdr.Id, kind, q)
maxLen := getMaxReplyLen(r, proto)
+ lookups := []ZoneLookup{s.Zone, s.mdnsCli}
- // 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) })
+ reply, err := s.cache.Get(r, maxLen)
+ if err != nil {
+ if err == errNoLocalReplies {
+ Debug.Printf("[dns msgid %d] Cached 'no local replies' - skipping local lookup", r.MsgHdr.Id)
+ lookups = []ZoneLookup{s.Zone}
+ } else {
+ Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err)
+ w.WriteMsg(makeDNSFailResponse(r))
+ return
}
}
-
- if !s.cacheDisabled {
- reply, err := s.cache.Get(r, maxLen)
- if err != nil {
- if err == errNoLocalReplies {
- Debug.Printf("[dns msgid %d] Cached no-local-replies", r.MsgHdr.Id)
- fallback(w, r)
- } else {
- Debug.Printf("[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)
- Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
- r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
- w.WriteMsg(reply)
- return
- }
+ if reply != nil {
+ Debug.Printf("[dns msgid %d] Returning reply from cache: %s/%d answers",
+ r.MsgHdr.Id, dns.RcodeToString[reply.MsgHdr.Rcode], len(reply.Answer))
+ w.WriteMsg(reply)
+ return
}
// catch unsupported queries
if q.Qtype != qtype {
Debug.Printf("[dns msgid %d] Unsupported query type %s", r.MsgHdr.Id, dns.TypeToString[q.Qtype])
m := makeDNSFailResponse(r)
- maybeCache(m, s.negLocalTTL, 0)
+ s.cache.Put(r, m, negLocalTTL, 0)
w.WriteMsg(m)
return
}
- if answers, err := lookup(q.Name); err != nil {
- Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
- r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
- maybeCache(nil, s.negLocalTTL, CacheNoLocalReplies)
- fallback(w, r)
- } else {
- m := msgBuilder(r, &q, answers)
- m.Authoritative = true
- maybeCache(m, nullTTL, 0)
- m.Answer = pruneAnswers(shuffleAnswers(m.Answer), s.maxAnswers)
- Debug.Printf("[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)
+ for _, lookup := range lookups {
+ if m, answers, err := zoneLookup(lookup, &q, r); err == nil {
+ m.Authoritative = true
+ Debug.Printf("[dns msgid %d] Caching response for type %s query for '%s': %s [code:%s]",
+ m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, answers, dns.RcodeToString[m.Rcode])
+ s.cache.Put(r, m, nullTTL, 0)
+ w.WriteMsg(m)
+ return
+ }
}
+
+ Info.Printf("[dns msgid %d] No results for type %s query for '%s' [caching no-local]",
+ r.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name)
+ s.cache.Put(r, nil, negLocalTTL, CacheNoLocalReplies)
+
+ fallback(w, r)
}
}
// When we receive a request for a name outside of our '.weave.local.'
// domain, ask the configured DNS server as a fallback.
func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
- dnsClient := proto.GetNewClient(DefaultUDPBuflen, s.timeout)
+ dnsClient := proto.GetNewClient(DefaultUDPBuflen)
return func(w dns.ResponseWriter, r *dns.Msg) {
+ maxLen := getMaxReplyLen(r, proto)
q := r.Question[0]
- // announce our max payload size as the max payload our client supports
- maxLen := getMaxReplyLen(r, proto)
+ // create a request where we announce our max payload size
rcopy := r
rcopy.SetEdns0(uint16(maxLen), false)
@@ -385,12 +337,3 @@ func (s *DNSServer) notUsHandler(proto dnsProtocol) dns.HandlerFunc {
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 cd16537a..00000000
--- a/nameserver/server_cache_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package nameserver
-
-import (
- "fmt"
- "github.com/miekg/dns"
- . "github.com/weaveworks/weave/common"
- wt "github.com/weaveworks/weave/testing"
- "net"
- "testing"
- "time"
-)
-
-// 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(localTTL) / 3
- )
-
- InitDefaultLogging(testing.Verbose())
- Info.Println("TestServerCacheRefresh starting")
- clk := newMockedClock()
-
- Debug.Printf("Creating 2 zone databases")
- zoneConfig := ZoneConfig{
- RefreshInterval: refreshInterval,
- Clock: clk,
- }
- dbs := newZoneDbsWithMockedMDns(2, zoneConfig)
- dbs.Start()
- defer dbs.Stop()
-
- Debug.Printf("Creating a cache")
- cache, err := NewCache(1024, clk)
- wt.AssertNoErr(t, err)
-
- Debug.Printf("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,
- })
- wt.AssertNoErr(t, err)
- go srv.Start()
- defer srv.Stop()
- time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
-
- testPort, err := srv.GetPort()
- wt.AssertNoErr(t, err)
- wt.AssertNotEqualInt(t, testPort, 0, "listen port")
-
- Debug.Printf("Adding an IPs to %s", testName1)
- dbs[1].Zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1"))
-
- // 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)
-
- Debug.Printf("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)
-
- // 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.DeleteRecord(containerID, net.ParseIP("10.2.2.1"))
- dbs[1].Zone.DeleteRecord(containerID, net.ParseIP("10.2.2.2"))
- clk.Forward(refreshInterval + 1)
-
- 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
index 8f1897d1..7e893c5b 100644
--- a/nameserver/server_test.go
+++ b/nameserver/server_test.go
@@ -18,6 +18,10 @@ const (
testUDPBufSize = 16384
)
+var (
+ testPort = 17625
+)
+
func setupForTest(t *testing.T) {
// fail early if we cannot find a default multicast interface
multicast, err := LinkLocalMulticastListener(nil)
@@ -25,29 +29,23 @@ func setupForTest(t *testing.T) {
t.Fatalf("Unable to create multicast listener: %s. No default multicast interface?", err)
}
multicast.Close()
+
+ testPort++
}
func TestUDPDNSServer(t *testing.T) {
setupForTest(t)
-
const (
+ containerID = "foobar"
successTestName = "test1.weave.local."
- failTestName = "fail.weave.local."
+ failTestName = "test2.weave.local."
nonLocalName = "weave.works."
testAddr1 = "10.2.2.1"
- containerID = "somecontainer"
)
testCIDR1 := testAddr1 + "/24"
- InitDefaultLogging(testing.Verbose())
- Info.Println("TestUDPDNSServer starting")
-
- zone, err := NewZoneDb(ZoneConfig{})
- wt.AssertNoErr(t, err)
- err = zone.Start()
- wt.AssertNoErr(t, err)
- defer zone.Stop()
-
+ InitDefaultLogging(true)
+ var zone = NewZoneDb(DefaultLocalDomain)
ip, _, _ := net.ParseCIDR(testCIDR1)
zone.AddRecord(containerID, successTestName, ip)
@@ -75,17 +73,15 @@ func TestUDPDNSServer(t *testing.T) {
}
// Run another DNS server for fallback
- fallback, err := newMockedFallback(fallbackHandler, nil)
+ s, fallbackAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackHandler)
wt.AssertNoErr(t, err)
- fallback.Start()
- defer fallback.Stop()
+ defer s.Shutdown()
- srv, err := NewDNSServer(DNSServerConfig{
- Zone: zone,
- UpstreamCfg: fallback.CliConfig,
- CacheDisabled: true,
- ListenReadTimeout: testSocketTimeout,
- })
+ _, fallbackPort, err := net.SplitHostPort(fallbackAddr)
+ wt.AssertNoErr(t, err)
+
+ config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
+ srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
@@ -93,33 +89,29 @@ func TestUDPDNSServer(t *testing.T) {
var r *dns.Msg
- testPort, err := srv.GetPort()
- wt.AssertNoErr(t, err)
- wt.AssertNotEqualInt(t, testPort, 0, "invalid listen port")
-
- _, r = assertExchange(t, successTestName, dns.TypeA, testPort, 1, 1, 0)
+ r = assertExchange(t, successTestName, dns.TypeA, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address")
- assertExchange(t, failTestName, dns.TypeA, testPort, 0, 0, dns.RcodeNameError)
+ assertExchange(t, failTestName, dns.TypeA, 0, 0, dns.RcodeNameError)
- _, r = assertExchange(t, testRDNSsuccess, dns.TypePTR, testPort, 1, 1, 0)
+ r = assertExchange(t, testRDNSsuccess, dns.TypePTR, 1, 1, 0)
wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record")
wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address")
- assertExchange(t, testRDNSfail, dns.TypePTR, testPort, 0, 0, dns.RcodeNameError)
+ assertExchange(t, testRDNSfail, dns.TypePTR, 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 should fail because we don't have information about MX records
+ assertExchange(t, successTestName, dns.TypeMX, 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)
+ assertExchange(t, nonLocalName, dns.TypeMX, 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, nonLocalName, dns.TypeANY, 5, -1, 0)
- assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0)
+ assertExchange(t, testRDNSnonlocal, dns.TypePTR, 1, -1, 0)
// Not testing MDNS functionality of server here (yet), since it
// needs two servers, each listening on its own address
@@ -127,28 +119,21 @@ func TestUDPDNSServer(t *testing.T) {
func TestTCPDNSServer(t *testing.T) {
setupForTest(t)
-
const (
numAnswers = 512
nonLocalName = "weave.works."
)
+ dnsAddr := fmt.Sprintf("localhost:%d", testPort)
- InitDefaultLogging(testing.Verbose())
- Info.Println("TestTCPDNSServer starting")
-
- zone, err := NewZoneDb(ZoneConfig{})
- wt.AssertNoErr(t, err)
- err = zone.Start()
- wt.AssertNoErr(t, err)
- defer zone.Stop()
+ InitDefaultLogging(true)
+ var zone = NewZoneDb(DefaultLocalDomain)
// 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}))
+ addrs = append(addrs, Record{"", net.IPv4(bs[0], bs[1], bs[2], bs[3]), 0, 0, 0})
}
// handler for the fallback server: it will just return a very long response
@@ -175,28 +160,27 @@ func TestTCPDNSServer(t *testing.T) {
}
t.Logf("Running a DNS fallback server with UDP")
- fallback, err := newMockedFallback(fallbackUDPHandler, fallbackTCPHandler)
+ us, fallbackUDPAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackUDPHandler)
wt.AssertNoErr(t, err)
- fallback.Start()
- defer fallback.Stop()
+ defer us.Shutdown()
- t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%s", fallback.Port)
- srv, err := NewDNSServer(DNSServerConfig{
- Zone: zone,
- UpstreamCfg: fallback.CliConfig,
- CacheDisabled: true,
- ListenReadTimeout: testSocketTimeout,
- })
+ _, fallbackPort, err := net.SplitHostPort(fallbackUDPAddr)
+ wt.AssertNoErr(t, err)
+
+ t.Logf("Starting another fallback server, with TCP, on the same port as the UDP server")
+ fallbackTCPAddr := fmt.Sprintf("127.0.0.1:%s", fallbackPort)
+ ts, fallbackTCPAddr, err := runLocalTCPServer(t, fallbackTCPAddr, fallbackTCPHandler)
+ wt.AssertNoErr(t, err)
+ defer ts.Shutdown()
+
+ t.Logf("Creating a WeaveDNS server instance, falling back to 127.0.0.1:%s", fallbackPort)
+ config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort}
+ srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil)
wt.AssertNoErr(t, err)
defer srv.Stop()
go srv.Start()
time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start
- testPort, err := srv.GetPort()
- wt.AssertNoErr(t, err)
- wt.AssertNotEqualInt(t, testPort, 0, "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
@@ -208,8 +192,8 @@ func TestTCPDNSServer(t *testing.T) {
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("Checking the fallback server at %s returns a truncated response with UDP", fallbackUDPAddr)
+ r, _, err := uc.Exchange(m, fallbackUDPAddr)
t.Logf("Got response from fallback server (UDP) with %d answers", len(r.Answer))
t.Logf("Response:\n%+v\n", r)
wt.AssertNoErr(t, err)
@@ -245,3 +229,73 @@ func TestTCPDNSServer(t *testing.T) {
wt.AssertFalse(t, r.MsgHdr.Truncated, "DNS truncated response flag")
wt.AssertEqualInt(t, len(r.Answer), numAnswers, "number of answers (UDP-long)")
}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// perform a DNS query and assert the reply code, number or answers, etc
+func assertExchange(t *testing.T, z string, ty uint16, minAnswers int, maxAnswers int, expErr int) *dns.Msg {
+ c := new(dns.Client)
+ c.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...
+
+ r, _, err := c.Exchange(m, fmt.Sprintf("127.0.0.1:%d", testPort))
+ t.Logf("Response:\n%+v\n", r)
+ wt.AssertNoErr(t, err)
+ if minAnswers == 0 && maxAnswers == 0 {
+ wt.AssertStatus(t, r.Rcode, expErr, "DNS response code")
+ } else {
+ wt.AssertStatus(t, r.Rcode, dns.RcodeSuccess, "DNS response code")
+ }
+ answers := len(r.Answer)
+ if minAnswers >= 0 && answers < minAnswers {
+ wt.Fatalf(t, "Number of answers >= %d", minAnswers)
+ }
+ if maxAnswers >= 0 && answers > maxAnswers {
+ wt.Fatalf(t, "Number of answers <= %d", maxAnswers)
+ }
+ return r
+}
+
+// run a UDP fallback server
+func runLocalUDPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
+ t.Logf("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: 100 * time.Millisecond}
+
+ go func() {
+ server.ActivateAndServe()
+ pc.Close()
+ }()
+
+ t.Logf("Fallback UDP server listening at %s", pc.LocalAddr())
+ return server, pc.LocalAddr().String(), nil
+}
+
+// run a TCP fallback server
+func runLocalTCPServer(t *testing.T, laddr string, handler dns.HandlerFunc) (*dns.Server, string, error) {
+ t.Logf("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: 100 * time.Millisecond}
+
+ go func() {
+ server.ActivateAndServe()
+ l.Close()
+ }()
+
+ t.Logf("Fallback TCP server listening at %s", l.Addr().String())
+ return server, l.Addr().String(), nil
+}
diff --git a/nameserver/zone.go b/nameserver/zone.go
index c797779c..ca23dab1 100644
--- a/nameserver/zone.go
+++ b/nameserver/zone.go
@@ -3,31 +3,41 @@ package nameserver
import (
"bytes"
"fmt"
- "github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
- "time"
)
const (
RDNSDomain = "in-addr.arpa."
)
-const (
- DefaultLocalDomain = "weave.local." // The default name used for the local domain
- DefaultRefreshInterval = int(localTTL) // Period for background updates with mDNS
- DefaultRelevantTime = 60 // When to forget info about remote info if nobody asks...
- DefaultNumUpdaters = 4 // Default number of background updaters
-
- defaultRefreshMailbox = 1000 // Number of on-the-fly background queries
- defaultRemoteIdent = "weave:remote" // Ident used for info obtained from mDNS
-)
-
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNSDomain) + 1
+type Zone interface {
+ AddRecord(ident string, name string, ip net.IP) error
+ DeleteRecord(ident string, ip net.IP) error
+ DeleteRecordsFor(ident string) error
+ Domain() string
+ ZoneLookup
+}
+
+type dbRecord struct {
+ Ident string
+ Name string
+ IP net.IP
+}
+
+// Very simple data structure for now, with linear searching.
+// TODO: make more sophisticated to improve performance.
+type ZoneDb struct {
+ mx sync.RWMutex
+ recs []dbRecord
+ domain string
+}
+
type LookupError string
func (ops LookupError) Error() string {
@@ -41,730 +51,103 @@ 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 a record in the local database
- DeleteRecord(ident string, ip net.IP) error
- // Delete all records for an ident in the local database
- DeleteRecordsFor(ident string) error
- // 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 NewZoneDb(domain string) *ZoneDb {
+ return &ZoneDb{
+ domain: domain,
}
}
-func (i *recordEntry) addIPObserver(zro ZoneRecordObserver) {
- i.observers = append(i.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 {
- Debug.Printf("[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(localTTL)
- }
- 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 an IP for this name
-func (n *name) deleteIP(ip IPv4) bool {
- if ipRecord, found := n.ipv4[ip]; found {
- ipRecord.notifyIPObservers()
- n.notifyNameObservers()
- delete(n.ipv4, ip)
- return true
- }
- return false
-}
-
-// 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 {
- Debug.Printf("[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 namesSet struct {
- names map[string]*name
-}
-
-func newNamesSet() *namesSet { return &namesSet{names: make(map[string]*name)} }
-func (ns *namesSet) len() int { return len(ns.names) }
-func (ns *namesSet) empty() bool { return ns.len() == 0 }
-func (ns *namesSet) hasName(name string) bool {
- if n, found := ns.names[name]; !found {
- return false
- } else {
- // 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 *namesSet) getName(name string, create bool) (n *name) {
- if n, found := ns.names[name]; !found && create {
- newName := newName(name)
- ns.names[name] = newName
- return newName
- } else {
- return n
- }
-}
-
-// Get all the entries for a name
-func (ns *namesSet) 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 *namesSet) 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 *namesSet) 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 a name in the names set
-func (ns *namesSet) deleteName(n string) error {
- if name, found := ns.names[n]; found {
- for _, ipRecord := range name.getAllEntries() {
- ipRecord.notifyIPObservers()
- }
- name.notifyNameObservers()
- delete(ns.names, n)
- return nil
- } else {
- return LookupError(fmt.Sprintf("%+v", name))
- }
-}
-
-// Delete an IPv4 from all names
-func (ns *namesSet) deleteIP(ip IPv4) error {
- cnt := 0
- for nkey, n := range ns.names {
- if n.deleteIP(ip) {
- cnt++
- }
- if n.empty() {
- delete(ns.names, nkey)
- }
- }
-
- // we must return an error if no records have been found and deleted
- if cnt == 0 {
- return LookupError(fmt.Sprintf("%+v", ip))
- }
- return nil
-}
-
-// Get the name query time
-func (ns *namesSet) 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 *namesSet) touchName(name string, now time.Time) {
- Debug.Printf("[zonedb] Touching name %s", name)
- n := ns.getName(name, false)
- if n != nil {
- n.lastAccessTime = now
- }
-}
-
-func (ns *namesSet) 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()
-}
-
-///////////////////////////////////////////////////////////////////
-
-// Names sets, by ident name
-type identRecordsSet map[string]*namesSet
-
-func (irs identRecordsSet) 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
- // Number of background updaters for names
- RefreshWorkers int
- // Forget about remote info if nobody asks for it in this time (in seconds)
- RelevantTime int
- // Max pending refresh requests
- MaxRefreshRequests 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 identRecordsSet
-
- 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
-
- refreshChan chan refreshRequest // channel for enqueing requests for updating names
- refreshSchedChan chan refreshRequest // channel for enqueing requests for updating names
- refreshCloseChan chan bool
- refreshWg sync.WaitGroup
- refreshInterval time.Duration
- refreshWorkers int
-}
-
-// Create a new zone database
-func NewZoneDb(config ZoneConfig) (zone *zoneDb, err error) {
- maxRefreshRequests := defaultRefreshMailbox
- if config.MaxRefreshRequests > 0 {
- maxRefreshRequests = config.MaxRefreshRequests
- }
-
- zone = &zoneDb{
- domain: config.Domain,
- idents: make(identRecordsSet),
- mdnsCli: config.MDNSClient,
- mdnsSrv: config.MDNSServer,
- iface: config.Iface,
- clock: config.Clock,
- relevantLimit: time.Duration(DefaultRelevantTime) * time.Second,
- refreshChan: make(chan refreshRequest, maxRefreshRequests),
- refreshSchedChan: make(chan refreshRequest, maxRefreshRequests),
- refreshCloseChan: make(chan bool),
- refreshWorkers: DefaultNumUpdaters,
- }
-
- // 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 config.RefreshWorkers > 0 {
- zone.refreshWorkers = config.RefreshWorkers
- }
-
- // Create the mDNS client and server
- if zone.mdnsCli == nil {
- if zone.mdnsCli, err = NewMDNSClient(); err != nil {
- return
- }
- }
- if zone.mdnsSrv == nil {
- if zone.mdnsSrv, err = NewMDNSServer(zone); err != nil {
- return
- }
- }
-
- return
-}
-
-// Start the zone database
-func (zone *zoneDb) Start() (err error) {
- if zone.iface != nil {
- Info.Printf("[zonedb] Using mDNS on %+v", zone.iface)
- } else {
- Info.Printf("[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
- }
-
- // Start some name refreshers...
- if zone.refreshInterval > 0 {
- Info.Printf("[zonedb] Starting %d background name updaters", zone.refreshWorkers)
- for i := 0; i < zone.refreshWorkers; i++ {
- zone.refreshWg.Add(1)
- go zone.updater(i)
- }
-
- zone.refreshWg.Add(1)
- go zone.periodicUpdater()
- } else {
- // TODO: the refresher also garbage collects irrelevant remote info... we should have an alternative mechanism
- // when we don't use refreshers.
- Warning.Printf("[zonedb] Remote info will not be garbage collected")
- }
-
- return
-}
-
-// Perform a graceful shutdown of the zone database
-func (zone *zoneDb) Stop() error {
- if zone.refreshInterval > 0 {
- Debug.Printf("[zonedb] Closing background updaters...")
- close(zone.refreshCloseChan)
- zone.refreshWg.Wait()
- }
-
- Debug.Printf("[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 {
+func (zone *ZoneDb) Domain() string {
return zone.domain
}
-// Return the status string
-func (zone *zoneDb) Status() string {
+func (zone *ZoneDb) indexOf(match func(dbRecord) bool) int {
+ for i, r := range zone.recs {
+ if match(r) {
+ return i
+ }
+ }
+ return -1
+}
+
+func (zone *ZoneDb) String() string {
+ zone.mx.RLock()
+ defer zone.mx.RUnlock()
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)
+ for _, r := range zone.recs {
+ fmt.Fprintf(&buf, "%.12s %s %v\n", r.Ident, r.IP, r.Name)
+ }
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()
- Debug.Printf("[zonedb] Adding record: '%s'/'%s'[%s]", ident, name, ip)
- record := Record{dns.Fqdn(name), ip, 0, 0, 0}
- _, err = zone.getNamesSet(ident).addIPToName(record, zone.clock.Now())
- return
-}
-
-// Delete all records for an ident
-func (zone *zoneDb) DeleteRecordsFor(ident string) (err error) {
- zone.mx.Lock()
- defer zone.mx.Unlock()
- Debug.Printf("[zonedb] Deleting records for ident '%s'", ident)
- zone.deleteIdent(ident)
- return
-}
-
-// Delete an IP in a ident
-func (zone *zoneDb) DeleteRecord(ident string, ip net.IP) (err error) {
- zone.mx.Lock()
- defer zone.mx.Unlock()
-
- Debug.Printf("[zonedb] Deleting record '%s'/[%s]", ident, ip)
- if _, found := zone.idents[ident]; found {
- err = zone.idents[ident].deleteIP(ipToIPv4(ip))
- if zone.idents[ident].empty() {
- zone.deleteIdent(ident)
- }
- } else {
- err = LookupError(ident)
- }
- return
-}
-
-// 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 += 1
- }
- }
- 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 += 1
- }
- }
- if cnt == 0 {
- err = LookupError(fmt.Sprintf("%+v/%+v", inaddr, revIP))
- }
- return
-}
-
-// Get the string representation of a zone
-func (zone *zoneDb) String() string {
+func (zone *ZoneDb) LookupName(name string) ([]ZoneRecord, error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
- return zone.idents.String()
+ for _, r := range zone.recs {
+ if r.Name == name {
+ return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
+ }
+ }
+ return nil, LookupError(name)
}
-// Notify that a container has died
-func (zone *zoneDb) ContainerDied(ident string) error {
+func (zone *ZoneDb) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
+ if revIP := net.ParseIP(inaddr[:len(inaddr)-rdnsDomainLen]); revIP != nil {
+ revIP4 := revIP.To4()
+ ip := []byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}
+ Debug.Printf("[zonedb] Looking for address: %+v", ip)
+ zone.mx.RLock()
+ defer zone.mx.RUnlock()
+ for _, r := range zone.recs {
+ if r.IP.Equal(ip) {
+ return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
+ }
+ }
+ return nil, LookupError(inaddr)
+ }
+ Warning.Printf("[zonedb] Asked to reverse lookup %s", inaddr)
+ return nil, LookupError(inaddr)
+}
+
+func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error {
+ zone.mx.Lock()
+ defer zone.mx.Unlock()
+ fqdn := dns.Fqdn(name)
+ pred := func(r dbRecord) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
+ if index := zone.indexOf(pred); index != -1 {
+ return DuplicateError{}
+ }
+ zone.recs = append(zone.recs, dbRecord{ident, fqdn, ip})
+ return nil
+}
+
+func (zone *ZoneDb) DeleteRecord(ident string, ip net.IP) error {
+ zone.mx.Lock()
+ defer zone.mx.Unlock()
+ pred := func(r dbRecord) bool { return r.Ident == ident && r.IP.Equal(ip) }
+ if index := zone.indexOf(pred); index != -1 {
+ zone.recs = append(zone.recs[:index], zone.recs[index+1:]...)
+ return nil
+ }
+ return LookupError(ident)
+}
+
+func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
+ zone.mx.Lock()
+ defer zone.mx.Unlock()
+ w := 0 // write index
+
+ for _, r := range zone.recs {
+ if r.Ident != ident {
+ zone.recs[w] = r
+ w++
+ }
+ }
+ zone.recs = zone.recs[:w]
+ return nil
+}
+
+func (zone *ZoneDb) ContainerDied(ident string) error {
Info.Printf("[zonedb] Container %s down. Removing records", ident)
return zone.DeleteRecordsFor(ident)
}
-
-// 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 {
- if t := nameset.getNameLastAccess(n); !t.IsZero() {
- if 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()
- remoteNamesSet := zone.getNamesSet(defaultRemoteIdent)
- entries := remoteNamesSet.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()
-
- remoteNamesSet := zone.getNamesSet(defaultRemoteIdent)
- for _, nameset := range zone.idents {
- if nameset != remoteNamesSet {
- 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()
-
- remoteNamesSet := zone.getNamesSet(defaultRemoteIdent)
- if remoteNamesSet != nil {
- return remoteNamesSet.hasName(n)
- }
- return false
-}
-
-// Get or create a names set
-func (zone *zoneDb) getNamesSet(ident string) *namesSet {
- ns, found := zone.idents[ident]
- if !found {
- ns = newNamesSet()
- zone.idents[ident] = ns
- }
- return ns
-}
-
-// Delete a ident
-func (zone *zoneDb) deleteIdent(ident string) {
- Info.Printf("[zonedb] Removing everything for ident '%s'", ident)
- if ns, found := zone.idents[ident]; found {
- for _, n := range ns.names {
- for _, entry := range n.getAllEntries() {
- entry.notifyIPObservers()
- }
- n.notifyNameObservers()
- }
- delete(zone.idents, ident)
- }
-}
diff --git a/nameserver/zone_lookup.go b/nameserver/zone_lookup.go
deleted file mode 100644
index b38971d3..00000000
--- a/nameserver/zone_lookup.go
+++ /dev/null
@@ -1,342 +0,0 @@
-package nameserver
-
-import (
- "github.com/miekg/dns"
- . "github.com/weaveworks/weave/common"
- "time"
-)
-
-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 *namesSet) []*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)
- } else {
- 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)
- Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
- return zone.lookup(name, func(ns *namesSet) []*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)
- }
- Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
- return zone.lookup(inaddr, func(ns *namesSet) []*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
- Debug.Printf("[zonedb] '%s' not in local database... trying with mDNS", target)
- lanswers, err := lfun(target)
- if err != nil {
- Debug.Printf("[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)
- Debug.Printf("[zonedb] adding '%s' (obtained with mDNS) to '%s'", lanswers, target)
- res = make([]ZoneRecord, len(lanswers))
- zone.mx.Lock()
- now := zone.clock.Now()
- uniq := newUniqZoneRecords()
- for _, answer := range lanswers {
- r, err := zone.getNamesSet(defaultRemoteIdent).addIPToName(answer, now)
- if err != nil {
- zone.mx.Unlock()
- Warning.Printf("[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)
- Debug.Printf("[zonedb] Looking for name '%s' in local(&remote) database", name)
-
- zone.mx.RLock()
- 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) {
- Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", name, ze)
- nameset.deleteName(name)
- } else {
- uniq.add(ze)
- }
- }
- if identName != defaultRemoteIdent {
- nameset.touchName(name, now)
- }
- }
- zone.mx.RUnlock()
-
- if len(uniq) > 0 {
- Debug.Printf("[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)
- }
-
- Debug.Printf("[zonedb] Looking for address in local(&remote) database: '%s' (%s)", revIPv4, inaddr)
-
- zone.mx.RLock()
- 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) {
- Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", revIPv4, ze)
- nameset.deleteIP(revIPv4)
- } else {
- uniq.add(ze)
- if identName != defaultRemoteIdent {
- nameset.touchName(ze.Name(), now)
- }
- }
- }
- }
- zone.mx.RUnlock()
-
- if len(uniq) > 0 {
- Debug.Printf("[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)
-//
-
-// TODO: for the sake of simplicity, we implement this mechanism with two channels: one for immediate
-// updates and another one for scheduled requests. We could use a heap of "next-time"s and a timer,
-// but that would require timers cancellations/updates/etc on insertions/removals/etc, and
-// it is probably not worth the trouble as we always use the same refresh period.
-// It could be useful if we move to a solution where we set update times from the responses TTLs,
-// but we currently use a fixed TTL (30secs), the same as the refresh period...
-// Anyway, maybe we will move to a gossip-based solution instead of doing this polling...
-
-// 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()
- defer zone.mx.Unlock()
-
- // check if we should enqueue a refresh request for this name
- n := zone.getNamesSet(defaultRemoteIdent).getName(name, true)
- if n.lastRefreshTime.IsZero() {
- now := zone.clock.Now()
- n.lastRefreshTime = now
-
- Debug.Printf("[zonedb] Creating new immediate refresh request for '%s'", name)
- zone.refreshChan <- refreshRequest{name: name, time: now}
- }
- }
-}
-
-// A worker for updating the list of IPs we have for a name
-func (zone *zoneDb) updater(num int) {
- defer zone.refreshWg.Done()
-
- Debug.Printf("[zonedb] Starting background updater #%d...", num)
- for {
- select {
- case <-zone.refreshCloseChan:
- Debug.Printf("[zonedb] Background updater #%d: interrupted while waiting for requests: exiting", num)
- return
-
- case request := <-zone.refreshChan:
- // if nobody has asked for this name for long time, just forget about it...
- // this will eventually garbage collect the `refreshChan` and all remote info in absence of activity
- if !zone.IsNameRelevant(request.name) || zone.IsNameExpired(request.name) {
- Debug.Printf("[zonedb] '%s' seem to be irrelevant now: removing any remote information", request.name)
- zone.mx.Lock()
- zone.getNamesSet(defaultRemoteIdent).deleteName(request.name)
- zone.mx.Unlock()
- continue
- }
-
- // perform the refresh for this name
- name := dns.Fqdn(request.name)
- Debug.Printf("[zonedb] Refreshing name '%s' with mDNS...", name)
- res, _ := zone.mdnsCli.InsistentLookupName(request.name)
- if res != nil && len(res) > 0 {
- numIps := len(res)
- zone.mx.Lock()
- now := zone.clock.Now()
- added, removed := zone.getNamesSet(defaultRemoteIdent).getName(name, true).updateIPs(res, now)
- zone.mx.Unlock()
- Debug.Printf("[zonedb] Obtained %d IPs for name '%s' with mDNS: %d added, %d removed",
- numIps, name, added, removed)
-
- // once the name has been updated, we insert the request (back) in the periodic requests channel
- now = zone.clock.Now()
- request.time = now.Add(zone.refreshInterval)
- Debug.Printf("[zonedb] Rescheduling update for '%s' in %.2f secs",
- request.name, zone.refreshInterval.Seconds())
- zone.refreshSchedChan <- request
- } else {
- Debug.Printf("[zonedb] nobody knows about '%s'... removing", name)
- zone.mx.Lock()
- zone.getNamesSet(defaultRemoteIdent).deleteName(request.name)
- zone.mx.Unlock()
- }
- }
- }
-}
-
-// The periodic updater
-// Consume requests from the `refreshSchedChan`, where requests with increasing scheduling time are enqueued
-// for refreshing names...
-func (zone *zoneDb) periodicUpdater() {
- defer zone.refreshWg.Done()
- for {
- select {
- case <-zone.refreshCloseChan:
- Debug.Printf("[zonedb] Periodic updater: interrupted while waiting for requests: exiting")
- return
-
- case request := <-zone.refreshSchedChan:
- now := zone.clock.Now()
- // we can sleep until the update time has arrived, as requests are sorted by scheduled time (new request
- // in this channel will always be scheduled later than the last item in the channel), so we can
- // safely suspend this goroutine until then...
- if request.time.After(now) {
- ddiff := time.Duration(request.time.Sub(now).Nanoseconds())
- timer := zone.clock.Timer(ddiff)
- Debug.Printf("[zonedb] Periodic updater: new request for %s: sleeping for %.2f secs...",
- request.name, ddiff.Seconds())
- select {
- case <-zone.refreshCloseChan:
- Debug.Printf("[zonedb] Periodic updater: interrupted while sleeping: exiting")
- return
- case <-timer.C:
- }
- }
-
- // once the time has arrived, we insert the request in the immediate refresh channel
- zone.refreshChan <- request
- }
- }
-}
diff --git a/nameserver/zone_lookup_test.go b/nameserver/zone_lookup_test.go
deleted file mode 100644
index 6c09ab6b..00000000
--- a/nameserver/zone_lookup_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package nameserver
-
-import (
- . "github.com/weaveworks/weave/common"
- wt "github.com/weaveworks/weave/testing"
- "net"
- "testing"
- "time"
-)
-
-// 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"
- )
-
- InitDefaultLogging(testing.Verbose())
- Info.Println("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
-
- Debug.Printf("Adding '%s' to Db #1", name)
- dbs[0].Zone.AddRecord("someident", name, net.ParseIP(addr1))
-
- Debug.Printf("Checking that the name %s is relevant (as it has been locally inserted) and not remote", name)
- wt.AssertTrue(t, dbs[0].Zone.IsNameRelevant(name), "name relevant")
- wt.AssertTrue(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
- wt.AssertFalse(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
-
- Debug.Printf("Asking for '%s' to Db #1: should get 1 IP from the local database...", name)
- res, err := dbs[0].Zone.DomainLookupName(name)
- wt.AssertNoErr(t, err)
- Debug.Printf("Got: %s", res)
- t.Logf("Db #1 after the lookup:\n%s", dbs[0].Zone)
- wt.AssertEqualInt(t, len(res), 1, "lookup result")
-
- clk.Forward(refreshInterval / 2)
- Debug.Printf("A couple of seconds later, we should still have one IP for that name")
- res, err = dbs[0].Zone.DomainLookupName(name)
- wt.AssertNoErr(t, err)
- wt.AssertEqualInt(t, len(res), 1, "lookup result")
-
- Debug.Printf("And then we add 2 IPs for that name at ZoneDb 2")
- clk.Forward(1)
- Debug.Printf("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))
-
- Debug.Printf("Perform a lookup, to ensure the name will be updated in the background...")
- dbs[0].Zone.DomainLookupName(name)
-
- Debug.Printf("Wait for a while, until a refresh is performed...")
- clk.Forward(refreshInterval + 1)
-
- Debug.Printf("A refresh should have been scheduled now: we should have 3 IPs:")
- Debug.Printf("the first (local) IP and the others obtained from zone2 with a mDNS query")
- Debug.Printf("Asking for '%s' again... we should have 3 IPs now", name)
- res, err = dbs[0].Zone.DomainLookupName(name)
- Debug.Printf("Got: %s", res)
- t.Logf("Db #1 after the second lookup:\n%s", dbs[0].Zone)
- wt.AssertEqualInt(t, len(res), 3, "lookup result length")
-
- Debug.Printf("We will not ask for `name` for a while, so it will become irrelevant and will be removed...")
- clk.Forward(relevantTime + refreshInterval + 1)
-
- // the name should be irrelevant now, and all remote info should have been
- // removed from the zone database
- Debug.Printf("Name '%s' should not be in the remote database in ZoneDb 1", name)
- wt.AssertFalse(t, dbs[0].Zone.IsNameRelevant(name), "name still relevant after some inactivity time")
- wt.AssertTrue(t, dbs[0].Zone.HasNameLocalInfo(name), "local name info")
- wt.AssertFalse(t, dbs[0].Zone.HasNameRemoteInfo(name), "remote name info")
-
- Debug.Printf("There is no remote info about this name at zone 1: a new IP appears remotely meanwhile...")
- clk.Forward(1)
- Debug.Printf("Adding '%s' to Db #2", name)
- dbs[1].Zone.AddRecord("someident", name, net.ParseIP(addr4))
-
- Debug.Printf("When we ask about this name again, we get 4 IPs (1 local, 3 remote)")
- clk.Forward(1)
- Debug.Printf("Asking for '%s' again... the first lookup will return only the local results", name)
- res, err = dbs[0].Zone.DomainLookupName(name)
- wt.AssertEqualInt(t, len(res), 1, "lookup result length")
- Debug.Printf("... but a second lookup should return all the results in the network")
- clk.Forward(1)
- res, err = dbs[0].Zone.DomainLookupName(name)
- Debug.Printf("Got: %s", res)
- wt.AssertEqualInt(t, len(res), 4, "lookup result length") // TODO: this fails 1% of the runs... !!??
-}
diff --git a/nameserver/zone_test.go b/nameserver/zone_test.go
index 968b4b7d..a807464f 100644
--- a/nameserver/zone_test.go
+++ b/nameserver/zone_test.go
@@ -1,8 +1,6 @@
package nameserver
import (
- "fmt"
- . "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
@@ -10,118 +8,60 @@ import (
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."
+ containerID = "deadbeef"
+ otherContainerID = "cowjuice"
+ successTestName = "test1.weave."
+ testAddr1 = "10.2.2.1/24"
)
- InitDefaultLogging(testing.Verbose())
+ var zone = NewZoneDb(DefaultLocalDomain)
- zone, err := NewZoneDb(ZoneConfig{})
- wt.AssertNoErr(t, err)
- err = zone.Start()
- wt.AssertNoErr(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)
+ ip, _, _ := net.ParseCIDR(testAddr1)
+ err := zone.AddRecord(containerID, successTestName, ip)
wt.AssertNoErr(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"))
+ err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.2.0.1"))
wt.AssertNoErr(t, err)
- t.Logf("Adding 'zdummy.weave.' to '0123abcdef'")
- err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.9.0.2"))
- wt.AssertNoErr(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)
+ err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.2.0.2"))
wt.AssertNoErr(t, err)
- if !foundIPs[0].IP().Equal(ip1) {
- t.Fatal("Unexpected result for", name1, foundIPs)
+ // Check that the address is now there.
+ foundIP, err := zone.LookupName(successTestName)
+ wt.AssertNoErr(t, err)
+
+ if !foundIP[0].IP().Equal(ip) {
+ t.Fatal("Unexpected result for", successTestName, foundIP)
}
- t.Logf("Checking if we cannot find some silly name like 'something.wrong'")
- foundIPs, err = zone.LookupName("something.wrong")
- wt.AssertErrorType(t, err, (*LookupError)(nil), 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)
+ // See if we can find the address by IP.
+ foundName, err := zone.LookupInaddr("1.2.2.10.in-addr.arpa.")
wt.AssertNoErr(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)
- wt.AssertNoErr(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)
+ if foundName[0].Name() != successTestName {
+ t.Fatal("Unexpected result for", ip, foundName)
}
- 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.")
- wt.AssertNoErr(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, err, (*LookupError)(nil), 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)
+ err = zone.AddRecord(containerID, successTestName, ip)
wt.AssertErrorType(t, err, (*DuplicateError)(nil), "duplicate add")
- t.Logf("Adding '%s'/%s in %s too", name1, ip1, container2)
- err = zone.AddRecord(container2, name1, ip1)
+ err = zone.AddRecord(otherContainerID, successTestName, ip)
+ // Delete the record for the original container
+ err = zone.DeleteRecord(containerID, ip)
wt.AssertNoErr(t, err)
- name1Removed := 0
- err = zone.ObserveName(name1, func() { t.Logf("Observer #1 for '%s' notified.", name1); name1Removed++ })
- wt.AssertNoErr(t, err)
- err = zone.ObserveName(name1, func() { t.Logf("Observer #2 for '%s' notified.", name1); name1Removed++ })
- wt.AssertNoErr(t, err)
- err = zone.ObserveInaddr(revName1Addr1, func() { t.Logf("Observer #1 for '%s' notified.", revName1Addr1); name1Removed++ })
+ _, err = zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
- t.Logf("Zone database:\n%s", zone)
- t.Logf("Deleting the %s in %s", ip1, container1)
- err = zone.DeleteRecord(container1, ip1)
- wt.AssertNoErr(t, err)
- 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)
+ err = zone.DeleteRecord(otherContainerID, ip)
wt.AssertNoErr(t, err)
- t.Logf("Checking %s is not found after removing %s it in %s and %s in %s",
- name1, ip1, container2, ip2, container1)
- err = zone.DeleteRecord(container1, ip2)
- err = zone.DeleteRecord(container2, ip1)
- wt.AssertNoErr(t, err)
- t.Logf("Zone database:\n%s", zone)
- _, err = zone.LookupName(name1)
+ // Check that the address is not there now.
+ _, err = zone.LookupName(successTestName)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
- t.Logf("Checking if removing an unknown record results in an error")
- err = zone.DeleteRecord(container1, net.ParseIP("0.0.0.0"))
+ // Delete a record that isn't there
+ err = zone.DeleteRecord(containerID, net.ParseIP("0.0.0.0"))
wt.AssertErrorType(t, err, (*LookupError)(nil), "when deleting record that doesn't exist")
}
@@ -132,22 +72,14 @@ func TestDeleteFor(t *testing.T) {
addr1 = "10.2.2.3/24"
addr2 = "10.2.7.8/24"
)
-
- InitDefaultLogging(testing.Verbose())
-
- zone, err := NewZoneDb(ZoneConfig{})
- wt.AssertNoErr(t, err)
- err = zone.Start()
- wt.AssertNoErr(t, err)
- defer zone.Stop()
-
+ zone := NewZoneDb(DefaultLocalDomain)
for _, addr := range []string{addr1, addr2} {
ip, _, _ := net.ParseCIDR(addr)
err := zone.AddRecord(id, name, ip)
wt.AssertNoErr(t, err)
}
- _, err = zone.LookupName(name)
+ _, err := zone.LookupName(name)
wt.AssertNoErr(t, err)
err = zone.DeleteRecordsFor(id)
diff --git a/site/weavedns.md b/site/weavedns.md
index d13009b7..db736eac 100644
--- a/site/weavedns.md
+++ b/site/weavedns.md
@@ -44,9 +44,10 @@ $ docker attach $shell1
```
Note that it is permissible to register multiple containers with the
-same name: weaveDNS picks one address at random on each request. Since
-weaveDNS removes any container that dies, this is a simple way to
-implement redundancy.
+same name. weaveDNS picks one address to return when asked for the
+name. Since weaveDNS removes any container that dies, this is a simple
+way to implement redundancy. In the current implementation it does
+not attempt to do load-balancing.
Each weaveDNS container started with `launch-dns` needs to be given
its own, unique, IP address, in a subnet that is a) common to all
@@ -102,9 +103,6 @@ The inverse operation can be carried out using the `dns-remove` command:
$ weave dns-remove 10.2.1.27 $shell2
```
-When queried about a name with multiple IPs, weaveDNS returns a random
-result from the set of IPs available.
-
## Hot-swapping service containers
If you would like to deploy a new version of a service, keep the old
diff --git a/test/210_dns_multicast_test.sh b/test/210_dns_multicast_test.sh
index ca959c1a..097ef027 100755
--- a/test/210_dns_multicast_test.sh
+++ b/test/210_dns_multicast_test.sh
@@ -3,9 +3,7 @@
. ./config.sh
C1=10.2.3.78
-C2_1=10.2.3.34
-C2_2=10.2.3.35
-C2_3=10.2.3.36
+C2=10.2.3.34
UNIVERSE=10.2.4.0/24
NAME2=seetwo.weave.local
NAME4=seefour.weave.local
@@ -15,19 +13,16 @@ start_suite "Resolve names across hosts (with and without IPAM)"
weave_on $HOST1 launch -iprange $UNIVERSE
weave_on $HOST2 launch -iprange $UNIVERSE $HOST1
-weave_on $HOST1 launch-dns 10.2.254.1/24 --no-cache
-weave_on $HOST2 launch-dns 10.2.254.2/24 --no-cache
+weave_on $HOST1 launch-dns 10.2.254.1/24
+weave_on $HOST2 launch-dns 10.2.254.2/24
-start_container $HOST2 $C2_1/24 --name=c2 -h $NAME2
-start_container $HOST2 --name=c4 -h $NAME4
-start_container_with_dns $HOST1 $C1/24 --name=c1
-start_container_with_dns $HOST1 --name=c3
+start_container $HOST2 $C2/24 --name=c2 -h $NAME2
+start_container $HOST2 --name=c4 -h $NAME4
+start_container_with_dns $HOST1 $C1/24 --name=c1
+start_container_with_dns $HOST1 --name=c3
C4=$(container_ip $HOST2 c4)
-tell_dns PUT $HOST2 c2 $NAME $C2_2 $NAME2
-tell_dns PUT $HOST2 c2 $NAME $C2_3 $NAME2
-
-assert_dns_record $HOST1 c1 $NAME2 $C2_1 $C2_2 $C2_3
+assert_dns_record $HOST1 c1 $NAME2 $C2
assert_dns_record $HOST1 c3 $NAME4 $C4
assert_raises "exec_on $HOST1 c1 getent hosts 8.8.8.8 | grep google"
diff --git a/test/config.sh b/test/config.sh
index c8bdcf06..8e74c5ab 100644
--- a/test/config.sh
+++ b/test/config.sh
@@ -99,25 +99,6 @@ exec_on() {
docker -H tcp://$host:$DOCKER_PORT exec $container "$@"
}
-# get a container IP:
-get_container_ip() {
- docker -H tcp://$1:2375 inspect --format '{{ .NetworkSettings.IPAddress }}' $2
-}
-
-tell_dns() {
- method=$1
- host=$2
- container=$3
- name=$4
- ip=$5
-
- dns=$(get_container_ip $host weavedns)
- [[ -z "$DEBUG" ]] || greyly echo "$method'ing $name=$ip at $host/$dns"
- run_on $host \
- curl --connect-timeout 3 -s -X $method \
- "http://$dns:6785/name/$container/$ip" -d fqdn=$name
-}
-
start_container() {
host=$1
shift 1
@@ -136,20 +117,8 @@ container_ip() {
# assert_dns_record
assert_dns_record() {
- host=$1
- container=$2
- name=$3
- shift 3
- exp_ips_regex=$(echo $@ | sed -r 's/ /\\\|/g')
-
- [[ -z "$DEBUG" ]] || greyly echo "Checking wether $name exists at $host"
- got_ip=$(exec_on $host $container getent hosts $name | tr -s ' ')
- assert "echo \"$got_ip\" | grep -q \"$exp_ips_regex\" && echo found" "found"
-
- [[ -z "$DEBUG" ]] || greyly echo "Checking wether the IPs \"$ips\" exists at $host"
- for ip ; do
- assert "exec_on $host $container getent hosts $ip | tr -s ' '" "$ip $name"
- done
+ assert "exec_on $1 $2 getent hosts $3 | tr -s ' '" "$4 $3"
+ assert "exec_on $1 $2 getent hosts $4 | tr -s ' '" "$4 $3"
}
start_suite() {
diff --git a/weavedns/main.go b/weavedns/main.go
index db38cbcd..6a888896 100644
--- a/weavedns/main.go
+++ b/weavedns/main.go
@@ -15,24 +15,19 @@ var version = "(unreleased version)"
func main() {
var (
- justVersion bool
- ifaceName string
- apiPath string
- domain string
- dnsPort int
- httpPort int
- wait int
- timeout int
- udpbuf int
- refreshInterval int
- refreshWorkers int
- relevantTime int
- maxAnswers int
- cacheLen int
- cacheDisabled bool
- watch bool
- debug bool
- err error
+ justVersion bool
+ ifaceName string
+ apiPath string
+ domain string
+ dnsPort int
+ httpPort int
+ wait int
+ timeout int
+ udpbuf int
+ cacheLen int
+ watch bool
+ debug bool
+ err error
)
flag.BoolVar(&justVersion, "version", false, "print version and exit")
@@ -42,18 +37,11 @@ func main() {
flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up")
flag.IntVar(&dnsPort, "dnsport", weavedns.DefaultServerPort, "port to listen to DNS requests")
flag.IntVar(&httpPort, "httpport", 6785, "port to listen to HTTP requests")
+ flag.IntVar(&timeout, "timeout", weavedns.DefaultTimeout, "timeout for resolutions")
+ flag.IntVar(&udpbuf, "udpbuf", weavedns.DefaultUDPBuflen, "UDP buffer length")
flag.IntVar(&cacheLen, "cache", weavedns.DefaultCacheLen, "cache length")
flag.BoolVar(&watch, "watch", true, "watch the docker socket for container events")
flag.BoolVar(&debug, "debug", false, "output debugging info to stderr")
- // advanced options
- flag.IntVar(&refreshInterval, "refresh", weavedns.DefaultRefreshInterval, "refresh interval (in secs) for local names (0=disable)")
- flag.IntVar(&refreshWorkers, "refresh-workers", weavedns.DefaultNumUpdaters, "default number of background updaters")
- flag.IntVar(&maxAnswers, "max-answers", weavedns.DefaultMaxAnswers, "maximum number of answers returned to clients (0=unlimited)")
- flag.IntVar(&relevantTime, "relevant", weavedns.DefaultRelevantTime, "life time for info in the absence of queries (in secs)")
- flag.IntVar(&udpbuf, "udpbuf", weavedns.DefaultUDPBuflen, "UDP buffer length")
- flag.IntVar(&timeout, "timeout", weavedns.DefaultTimeout, "timeout for resolutions (in millisecs)")
- flag.BoolVar(&cacheDisabled, "no-cache", false, "disable the cache")
-
flag.Parse()
if justVersion {
@@ -62,7 +50,16 @@ func main() {
}
InitDefaultLogging(debug)
- Info.Printf("[main] WeaveDNS version %s\n", version) // first thing in log: the version
+ Info.Printf("WeaveDNS version %s\n", version) // first thing in log: the version
+
+ var zone = weavedns.NewZoneDb(domain)
+
+ if watch {
+ err := updater.Start(apiPath, zone)
+ if err != nil {
+ Error.Fatal("Unable to start watcher", err)
+ }
+ }
var iface *net.Interface
if ifaceName != "" {
@@ -76,44 +73,19 @@ func main() {
}
}
- zoneConfig := weavedns.ZoneConfig{
- Domain: domain,
- Iface: iface,
- RefreshInterval: refreshInterval,
- RefreshWorkers: refreshWorkers,
- RelevantTime: relevantTime,
- }
- zone, err := weavedns.NewZoneDb(zoneConfig)
- if err != nil {
- Error.Fatal("[main] Unable to initialize the Zone database", err)
- }
- err = zone.Start()
- if err != nil {
- Error.Fatal("[main] Unable to start the Zone database", err)
- }
- defer zone.Stop()
-
- if watch {
- err := updater.Start(apiPath, zone)
- if err != nil {
- Error.Fatal("[main] Unable to start watcher", err)
- }
- }
-
srvConfig := weavedns.DNSServerConfig{
- Zone: zone,
- Port: dnsPort,
- CacheLen: cacheLen,
- MaxAnswers: maxAnswers,
- Timeout: timeout,
- UDPBufLen: udpbuf,
- CacheDisabled: cacheDisabled,
+ Port: dnsPort,
+ CacheLen: cacheLen,
+ LocalDomain: domain,
+ Timeout: timeout,
+ UDPBufLen: udpbuf,
}
- srv, err := weavedns.NewDNSServer(srvConfig)
+ srv, err := weavedns.NewDNSServer(srvConfig, zone, iface)
if err != nil {
- Error.Fatal("[main] Failed to initialize the WeaveDNS server", err)
+ Error.Fatal("Failed to initialize the WeaveDNS server", err)
}
+ Info.Println("Upstream", srv.Upstream)
go SignalHandlerLoop(srv)
go weavedns.ListenHTTP(version, srv, domain, zone, httpPort)