From cfcc08a914d1a4f578de0eb81c69607a56acd710 Mon Sep 17 00:00:00 2001 From: Alvaro Saurin Date: Mon, 18 May 2015 17:25:13 +0200 Subject: [PATCH] Limit the number of answers we provide to users. --- nameserver/dns.go | 10 ++++++++- nameserver/dns_test.go | 38 +++++++++++++++++++++++++++++++++ nameserver/server.go | 19 +++++++++++------ nameserver/server_cache_test.go | 1 + site/weavedns.md | 10 +++++---- weavedns/main.go | 3 +++ 6 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 nameserver/dns_test.go diff --git a/nameserver/dns.go b/nameserver/dns.go index be0a7fd0..3f919993 100644 --- a/nameserver/dns.go +++ b/nameserver/dns.go @@ -89,7 +89,6 @@ func getMaxReplyLen(r *dns.Msg, proto dnsProtocol) int { return maxLen } - func init() { rand.Seed(time.Now().UTC().UnixNano()) } @@ -105,3 +104,12 @@ func shuffleAnswers(answers []dns.RR) []dns.RR { 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 new file mode 100644 index 00000000..58e53784 --- /dev/null +++ b/nameserver/dns_test.go @@ -0,0 +1,38 @@ +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/server.go b/nameserver/server.go index 6fd244c9..79a4e802 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -18,6 +18,7 @@ const ( 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 ) type DNSServerConfig struct { @@ -45,6 +46,8 @@ type DNSServerConfig struct { Clock clock.Clock // (Optional) Listening socket read timeout (in milliseconds) ListenReadTimeout int + // Maximum number of answers provided to users + MaxAnswers int } type dnsProtocol uint8 @@ -88,6 +91,7 @@ type DNSServer struct { lst net.Listener cache ZoneCache cacheDisabled bool + maxAnswers int negLocalTTL int timeout time.Duration readTimeout time.Duration @@ -107,6 +111,7 @@ func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) { timeout: time.Duration(config.Timeout) * time.Millisecond, readTimeout: time.Duration(config.ListenReadTimeout) * time.Millisecond, cacheDisabled: false, + maxAnswers: DefaultMaxAnswers, negLocalTTL: negLocalTTL, clock: config.Clock, } @@ -140,6 +145,9 @@ func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) { if config.UDPBufLen > 0 { s.udpBuf = config.UDPBufLen } + if config.MaxAnswers > 0 { + s.maxAnswers = config.MaxAnswers + } if config.CacheNegLocalTTL > 0 { s.negLocalTTL = config.CacheNegLocalTTL } @@ -301,17 +309,16 @@ func (s *DNSServer) localHandler(proto dnsProtocol, kind string, qtype uint16, if err == errNoLocalReplies { Debug.Printf("[dns msgid %d] Cached no-local-replies", r.MsgHdr.Id) fallback(w, r) - return } else { Debug.Printf("[dns msgid %d] Error from cache: %s", r.MsgHdr.Id, err) w.WriteMsg(makeDNSFailResponse(r)) - return } + 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)) - reply.Answer = shuffleAnswers(reply.Answer) w.WriteMsg(reply) return } @@ -334,10 +341,10 @@ func (s *DNSServer) localHandler(proto dnsProtocol, kind string, qtype uint16, } else { m := msgBuilder(r, &q, answers) m.Authoritative = true - m.Answer = shuffleAnswers(m.Answer) - Debug.Printf("[dns msgid %d] Sending response: %s/%s:%s [code:%s]", - m.MsgHdr.Id, dns.TypeToString[q.Qtype], q.Name, answers, dns.RcodeToString[m.Rcode]) 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) } } diff --git a/nameserver/server_cache_test.go b/nameserver/server_cache_test.go index c06eae1c..cd16537a 100644 --- a/nameserver/server_cache_test.go +++ b/nameserver/server_cache_test.go @@ -42,6 +42,7 @@ func TestServerCacheRefresh(t *testing.T) { Cache: cache, Clock: clk, ListenReadTimeout: testSocketTimeout, + MaxAnswers: 4, }) wt.AssertNoErr(t, err) go srv.Start() diff --git a/site/weavedns.md b/site/weavedns.md index db736eac..d13009b7 100644 --- a/site/weavedns.md +++ b/site/weavedns.md @@ -44,10 +44,9 @@ $ docker attach $shell1 ``` Note that it is permissible to register multiple containers with the -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. +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. 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 @@ -103,6 +102,9 @@ 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/weavedns/main.go b/weavedns/main.go index 2199d204..db38cbcd 100644 --- a/weavedns/main.go +++ b/weavedns/main.go @@ -27,6 +27,7 @@ func main() { refreshInterval int refreshWorkers int relevantTime int + maxAnswers int cacheLen int cacheDisabled bool watch bool @@ -47,6 +48,7 @@ func main() { // 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)") @@ -102,6 +104,7 @@ func main() { Zone: zone, Port: dnsPort, CacheLen: cacheLen, + MaxAnswers: maxAnswers, Timeout: timeout, UDPBufLen: udpbuf, CacheDisabled: cacheDisabled,