diff --git a/nameserver/cache.go b/nameserver/cache.go index 624236b8..32451725 100644 --- a/nameserver/cache.go +++ b/nameserver/cache.go @@ -22,6 +22,8 @@ var ( ) const ( + DefaultCacheNegLocalTTL = 30 // Default TTL for negative local resolutions + defPendingTimeout int = 5 // timeout for a resolution ) diff --git a/nameserver/cache_test.go b/nameserver/cache_test.go index 890bea9b..47830dfd 100644 --- a/nameserver/cache_test.go +++ b/nameserver/cache_test.go @@ -36,7 +36,7 @@ func TestCacheLength(t *testing.T) { ip := net.ParseIP(fmt.Sprintf("10.0.1.%d", i)) records := []ZoneRecord{Record{name, ip, 0, 0, 0}} - reply := makeAddressReply(questionMsg, question, records) + reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) reply.Answer[0].Header().Ttl = uint32(i) l.Put(questionMsg, reply, 0, 0) @@ -89,7 +89,7 @@ func TestCacheEntries(t *testing.T) { t.Logf("Inserting the reply") records := []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.1"), 0, 0, 0}} - reply1 := makeAddressReply(questionMsg, question, records) + reply1 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) l.Put(questionMsg, reply1, nullTTL, 0) t.Logf("Checking we can Get() the reply now") @@ -110,8 +110,8 @@ func TestCacheEntries(t *testing.T) { ttlGet2 := resp.Answer[0].Header().Ttl wt.AssertEqualInt(t, int(ttlGet1-ttlGet2), 1, "TTL difference") - clk.Add(time.Duration(localTTL) * time.Second) - t.Logf("Checking that a third Get(), after %d second, gets no result", localTTL) + clk.Add(time.Duration(DefaultLocalTTL) * time.Second) + t.Logf("Checking that a third Get(), after %d second, gets no result", DefaultLocalTTL) resp, err = l.Get(questionMsg, minUDPSize) wt.AssertNoErr(t, err) if resp != nil { @@ -121,7 +121,7 @@ func TestCacheEntries(t *testing.T) { t.Logf("Checking that an Remove() results in Get() returning nothing") records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}} - replyTemp := makeAddressReply(questionMsg, question, records) + replyTemp := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) l.Put(questionMsg, replyTemp, nullTTL, 0) lenBefore := l.Len() l.Remove(question) @@ -135,11 +135,11 @@ func TestCacheEntries(t *testing.T) { t.Logf("Inserting a two replies for the same query") records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.2"), 0, 0, 0}} - reply2 := makeAddressReply(questionMsg, question, records) + reply2 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) l.Put(questionMsg, reply2, nullTTL, 0) clk.Add(time.Duration(1) * time.Second) records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.1.3"), 0, 0, 0}} - reply3 := makeAddressReply(questionMsg, question, records) + reply3 := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) l.Put(questionMsg, reply3, nullTTL, 0) t.Logf("Checking we get the last one...") @@ -149,9 +149,9 @@ func TestCacheEntries(t *testing.T) { t.Logf("Received '%s'", resp.Answer[0]) wt.AssertType(t, resp.Answer[0], (*dns.A)(nil), "DNS record") wt.AssertEqualString(t, resp.Answer[0].(*dns.A).A.String(), "10.0.1.3", "IP address") - wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), int(localTTL), "TTL") + wt.AssertEqualInt(t, int(resp.Answer[0].Header().Ttl), int(DefaultLocalTTL), "TTL") - clk.Add(time.Duration(localTTL-1) * time.Second) + clk.Add(time.Duration(DefaultLocalTTL-1) * time.Second) resp, err = l.Get(questionMsg, minUDPSize) wt.AssertNoErr(t, err) wt.AssertTrue(t, resp != nil, "reponse from the Get()") @@ -162,7 +162,7 @@ func TestCacheEntries(t *testing.T) { t.Logf("Checking we get empty replies when they are expired...") lenBefore = l.Len() - clk.Add(time.Duration(localTTL) * time.Second) + clk.Add(time.Duration(DefaultLocalTTL) * time.Second) resp, err = l.Get(questionMsg, minUDPSize) wt.AssertNoErr(t, err) if resp != nil { @@ -183,7 +183,7 @@ func TestCacheEntries(t *testing.T) { t.Logf("Checking that an Remove() between Get() and Put() does not break things") records = []ZoneRecord{Record{"some.name", net.ParseIP("10.0.9.9"), 0, 0, 0}} - replyTemp2 := makeAddressReply(questionMsg2, question2, records) + replyTemp2 := makeAddressReply(questionMsg2, question2, records, DefaultLocalTTL) l.Remove(question2) l.Put(questionMsg2, replyTemp2, nullTTL, 0) resp, err = l.Get(questionMsg2, minUDPSize) @@ -201,8 +201,8 @@ func TestCacheEntries(t *testing.T) { wt.AssertNil(t, resp, "Get() response with CacheNoLocalReplies") wt.AssertNotNil(t, err, "Get() error with CacheNoLocalReplies") - clk.Add(time.Second * time.Duration(negLocalTTL+1)) - t.Logf("Checking that we get an expired response after %d seconds", negLocalTTL) + clk.Add(time.Second * time.Duration(DefaultCacheNegLocalTTL+1)) + t.Logf("Checking that we get an expired response after %d seconds", DefaultCacheNegLocalTTL) resp, err = l.Get(questionMsg3, minUDPSize) wt.AssertNil(t, resp, "expired Get() response with CacheNoLocalReplies") wt.AssertNil(t, err, "expired Get() error with CacheNoLocalReplies") diff --git a/nameserver/dns.go b/nameserver/dns.go index 8c08a2db..c232d45b 100644 --- a/nameserver/dns.go +++ b/nameserver/dns.go @@ -8,16 +8,17 @@ import ( ) const ( - localTTL uint32 = 30 // somewhat arbitrary; we don't expect anyone downstream to cache results - negLocalTTL = 30 // TTL for negative local resolutions - minUDPSize = 512 - maxUDPSize = 65535 + minUDPSize = 512 + maxUDPSize = 65535 ) -func makeHeader(r *dns.Msg, q *dns.Question) *dns.RR_Header { +func makeHeader(r *dns.Msg, q *dns.Question, ttl int) *dns.RR_Header { return &dns.RR_Header{ - Name: q.Name, Rrtype: q.Qtype, - Class: dns.ClassINET, Ttl: localTTL} + Name: q.Name, + Rrtype: q.Qtype, + Class: dns.ClassINET, + Ttl: uint32(ttl), + } } func makeReply(r *dns.Msg, as []dns.RR) *dns.Msg { @@ -36,11 +37,11 @@ func makeTruncatedReply(r *dns.Msg) *dns.Msg { return reply } -type DNSResponseBuilder func(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg +type DNSResponseBuilder func(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg -func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg { +func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord, ttl int) *dns.Msg { answers := make([]dns.RR, len(addrs)) - header := makeHeader(r, q) + header := makeHeader(r, q, ttl) count := 0 for _, addr := range addrs { ip := addr.IP() @@ -62,9 +63,9 @@ func makeAddressReply(r *dns.Msg, q *dns.Question, addrs []ZoneRecord) *dns.Msg return makeReply(r, answers[:count]) } -func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord) *dns.Msg { +func makePTRReply(r *dns.Msg, q *dns.Question, names []ZoneRecord, ttl int) *dns.Msg { answers := make([]dns.RR, len(names)) - header := makeHeader(r, q) + header := makeHeader(r, q, ttl) for i, name := range names { answers[i] = &dns.PTR{Hdr: *header, Ptr: name.Name()} } diff --git a/nameserver/dns_test.go b/nameserver/dns_test.go index 66eba2cb..21fd22c5 100644 --- a/nameserver/dns_test.go +++ b/nameserver/dns_test.go @@ -25,8 +25,8 @@ func TestPrune(t *testing.T) { Record{"name", net.ParseIP("10.0.1.4"), 0, 0, 0}, } - reply := makeAddressReply(questionMsg, question, records) - reply.Answer[0].Header().Ttl = localTTL + reply := makeAddressReply(questionMsg, question, records, DefaultLocalTTL) + reply.Answer[0].Header().Ttl = DefaultLocalTTL pruned := pruneAnswers(reply.Answer, 1) wt.AssertEqualInt(t, len(pruned), 1, "wrong number of answers") diff --git a/nameserver/mdns_server.go b/nameserver/mdns_server.go index 482d9244..d95a6c1b 100644 --- a/nameserver/mdns_server.go +++ b/nameserver/mdns_server.go @@ -13,13 +13,18 @@ type MDNSServer struct { srv *dns.Server zone Zone allowLocal bool + ttl int running bool } // Create a new mDNS server // Nothing will be done (including port bindings) until you `Start()` the server -func NewMDNSServer(zone Zone, local bool) (*MDNSServer, error) { - return &MDNSServer{zone: zone, allowLocal: local}, nil +func NewMDNSServer(zone Zone, local bool, ttl int) (*MDNSServer, error) { + return &MDNSServer{ + zone: zone, + allowLocal: local, + ttl: ttl, + }, nil } // Return true if testaddr is a UDP address with IP matching my local i/f @@ -66,7 +71,7 @@ func (s *MDNSServer) Start(ifi *net.Interface) (err error) { handleLocal := s.makeHandler(dns.TypeA, func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg { if ips, err := zone.LookupName(q.Name); err == nil { - return makeAddressReply(r, q, ips) + return makeAddressReply(r, q, ips, s.ttl) } return nil }) @@ -74,7 +79,7 @@ func (s *MDNSServer) Start(ifi *net.Interface) (err error) { handleReverse := s.makeHandler(dns.TypePTR, func(zone ZoneLookup, r *dns.Msg, q *dns.Question) *dns.Msg { if names, err := zone.LookupInaddr(q.Name); err == nil { - return makePTRReply(r, q, names) + return makePTRReply(r, q, names, s.ttl) } return nil }) diff --git a/nameserver/mdns_server_test.go b/nameserver/mdns_server_test.go index 35ece389..bd6723a8 100644 --- a/nameserver/mdns_server_test.go +++ b/nameserver/mdns_server_test.go @@ -21,7 +21,7 @@ func TestServerSimpleQuery(t *testing.T) { Info.Println("TestServerSimpleQuery starting") mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1, testRecord2}) - mdnsServer, err := NewMDNSServer(mzone, true) + mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL) wt.AssertNoErr(t, err) err = mdnsServer.Start(nil) wt.AssertNoErr(t, err) diff --git a/nameserver/mdns_test.go b/nameserver/mdns_test.go index 2baeb3ff..3127d3c4 100644 --- a/nameserver/mdns_test.go +++ b/nameserver/mdns_test.go @@ -18,7 +18,7 @@ func TestClientServerSimpleQuery(t *testing.T) { testInAddr1 := "1.2.2.10.in-addr.arpa." mzone := newMockedZoneWithRecords([]ZoneRecord{testRecord1}) - mdnsServer, err := NewMDNSServer(mzone, true) + mdnsServer, err := NewMDNSServer(mzone, true, DefaultLocalTTL) wt.AssertNoErr(t, err) err = mdnsServer.Start(nil) wt.AssertNoErr(t, err) @@ -94,21 +94,21 @@ func TestClientServerInsistentQuery(t *testing.T) { testInAddr2 := "2.2.2.10.in-addr.arpa." mzone1 := newMockedZoneWithRecords([]ZoneRecord{testRecord1}) - mdnsServer1, err := NewMDNSServer(mzone1, true) + mdnsServer1, err := NewMDNSServer(mzone1, true, DefaultLocalTTL) wt.AssertNoErr(t, err) err = mdnsServer1.Start(nil) wt.AssertNoErr(t, err) defer mdnsServer1.Stop() mzone2 := newMockedZoneWithRecords([]ZoneRecord{testRecord2}) - mdnsServer2, err := NewMDNSServer(mzone2, true) + mdnsServer2, err := NewMDNSServer(mzone2, true, DefaultLocalTTL) 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, true) + mdnsServer3, err := NewMDNSServer(mzone2, true, DefaultLocalTTL) wt.AssertNoErr(t, err) err = mdnsServer3.Start(nil) wt.AssertNoErr(t, err) diff --git a/nameserver/server.go b/nameserver/server.go index 224568ae..598e8171 100644 --- a/nameserver/server.go +++ b/nameserver/server.go @@ -16,6 +16,7 @@ import ( const ( DefaultServerPort = 53 // The default server port DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load + DefaultLocalTTL = 30 // default TTL for responses for local domain queries DefaultUDPBuflen = 4096 // bigger than the default 512 DefaultCacheLen = 8192 // default cache capacity DefaultTimeout = 5000 // default timeout for DNS resolutions (millisecs) @@ -41,6 +42,8 @@ type DNSServerConfig struct { UDPBufLen int // (Optional) force a specific cache Cache ZoneCache + // (Optional) TTL for local domain responses + LocalTTL int // (Optional) TTL for negative results in the local domain CacheNegLocalTTL int // (Optional) for a specific clock provider @@ -94,6 +97,7 @@ type DNSServer struct { cache ZoneCache cacheDisabled bool maxAnswers int + localTTL int negLocalTTL int timeout time.Duration readTimeout time.Duration @@ -114,7 +118,8 @@ func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) { readTimeout: DefaultTimeout * time.Millisecond, cacheDisabled: false, maxAnswers: DefaultMaxAnswers, - negLocalTTL: negLocalTTL, + localTTL: DefaultLocalTTL, + negLocalTTL: DefaultCacheNegLocalTTL, clock: config.Clock, } @@ -153,6 +158,9 @@ func NewDNSServer(config DNSServerConfig) (s *DNSServer, err error) { if config.MaxAnswers > 0 { s.maxAnswers = config.MaxAnswers } + if config.LocalTTL > 0 { + s.localTTL = config.LocalTTL + } if config.CacheNegLocalTTL > 0 { s.negLocalTTL = config.CacheNegLocalTTL } @@ -344,7 +352,7 @@ func (s *DNSServer) localHandler(proto dnsProtocol, kind string, qtype uint16, maybeCache(nil, s.negLocalTTL, CacheNoLocalReplies) fallback(w, r) } else { - m := msgBuilder(r, &q, answers) + m := msgBuilder(r, &q, answers, s.localTTL) m.Authoritative = true maybeCache(m, nullTTL, 0) m.Answer = pruneAnswers(shuffleAnswers(m.Answer), s.maxAnswers) diff --git a/nameserver/server_cache_test.go b/nameserver/server_cache_test.go index 3e3be2d9..74c922cb 100644 --- a/nameserver/server_cache_test.go +++ b/nameserver/server_cache_test.go @@ -17,7 +17,7 @@ func TestServerCacheRefresh(t *testing.T) { containerID = "somecontainer" testName1 = "first.weave.local." testName2 = "second.weave.local." - refreshInterval = int(localTTL) / 3 + refreshInterval = int(DefaultLocalTTL) / 3 ) InitDefaultLogging(testing.Verbose()) diff --git a/nameserver/server_test.go b/nameserver/server_test.go index 6330a52e..1af4c126 100644 --- a/nameserver/server_test.go +++ b/nameserver/server_test.go @@ -158,7 +158,7 @@ func TestTCPDNSServer(t *testing.T) { t.Logf("Fallback UDP server got asked: returning %d answers", numAnswers) q := req.Question[0] - m := makeAddressReply(req, &q, addrs) + m := makeAddressReply(req, &q, addrs, DefaultLocalTTL) mLen := m.Len() m.SetEdns0(uint16(maxLen), false) @@ -171,7 +171,7 @@ func TestTCPDNSServer(t *testing.T) { fallbackTCPHandler := func(w dns.ResponseWriter, req *dns.Msg) { t.Logf("Fallback TCP server got asked: returning %d answers", numAnswers) q := req.Question[0] - m := makeAddressReply(req, &q, addrs) + m := makeAddressReply(req, &q, addrs, DefaultLocalTTL) w.WriteMsg(m) } diff --git a/nameserver/zone.go b/nameserver/zone.go index 4fa921dc..4ad7cd06 100644 --- a/nameserver/zone.go +++ b/nameserver/zone.go @@ -17,10 +17,10 @@ const ( ) 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 + DefaultLocalDomain = "weave.local." // The default name used for the local domain + DefaultRefreshInterval = int(DefaultLocalTTL) // Period for background updates with mDNS + DefaultRelevantTime = 60 // When to forget info about remote info if nobody asks... + 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 @@ -126,7 +126,7 @@ func (re *recordEntry) notifyIPObservers() { 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 int(DefaultLocalTTL) } return re.ttl } @@ -429,6 +429,8 @@ type ZoneConfig struct { RelevantTime int // Max pending refresh requests MaxRefreshRequests int + // TTL returnined in local asnwers (with mDNS) + LocalTTL int // Force a specific mDNS client MDNSClient ZoneMDNSClient // Force a specific mDNS server @@ -505,7 +507,12 @@ func NewZoneDb(config ZoneConfig) (zone *ZoneDb, err error) { } } if zone.mdnsSrv == nil { - if zone.mdnsSrv, err = NewMDNSServer(zone, false); err != nil { + mdnsTTL := DefaultLocalTTL + if config.LocalTTL > 0 { + mdnsTTL = config.LocalTTL + } + + if zone.mdnsSrv, err = NewMDNSServer(zone, false, mdnsTTL); err != nil { return } } diff --git a/site/weavedns.md b/site/weavedns.md index 23684eab..8d5f7095 100644 --- a/site/weavedns.md +++ b/site/weavedns.md @@ -134,7 +134,7 @@ Notice how the ping reaches different addresses. WeaveDNS removes the addresses of any container that dies. This offers a simple way to implement redundancy. E.g. if in our example we stop one of the `pingme` containers and re-run the ping tests, eventually -(within ~30s at most, since that is the weaveDNS cache expiry time) we +(within ~30s at most, since that is the weaveDNS [cache expiry time](#ttl)) we will only be hitting the address of the container that is still alive. @@ -179,6 +179,25 @@ to the container args: $ weave launch-dns 10.2.254.1/24 --watch=false ``` +## Using a different TTL value + +By default, weaveDNS specifies a TTL of 30 seconds in any reply sent to +another peer. Peers will honor the TTL received and cache the answer +until it is considered invalid. + +However, you can force a different TTL value by launching weaveDNS with +the `--ttl` argument: + +```bash +$ weave launch-dns 10.2.254.1/24 --ttl=10 +``` + +This will shorten the lifespan of answers sent to other peers, +so you will be effectively reducing the probability of them having stale +information, but you will also be increasing their resolution times (as +their cache hit rate will be reduced) and the number of request this +weaveDNS instance will receive. + ## Configuring the domain search paths If you don't supply a domain search path (with `--dns-search=`), diff --git a/weavedns/main.go b/weavedns/main.go index 0fc48523..9b6ab270 100644 --- a/weavedns/main.go +++ b/weavedns/main.go @@ -24,6 +24,8 @@ func main() { dnsPort int httpPort int wait int + ttl int + negTTL int timeout int udpbuf int fallback string @@ -46,9 +48,11 @@ func main() { 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(&cacheLen, "cache", weavedns.DefaultCacheLen, "cache length") + flag.IntVar(&ttl, "ttl", weavedns.DefaultLocalTTL, "TTL (in secs) for responses for local names") 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(&negTTL, "neg-ttl", weavedns.DefaultCacheNegLocalTTL, "negative TTL (in secs) for unanswered queries for local names") 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)") @@ -82,6 +86,7 @@ func main() { zoneConfig := weavedns.ZoneConfig{ Domain: domain, Iface: iface, + LocalTTL: ttl, RefreshInterval: refreshInterval, RefreshWorkers: refreshWorkers, RelevantTime: relevantTime, @@ -104,13 +109,15 @@ func main() { } srvConfig := weavedns.DNSServerConfig{ - Zone: zone, - Port: dnsPort, - CacheLen: cacheLen, - MaxAnswers: maxAnswers, - Timeout: timeout, - UDPBufLen: udpbuf, - CacheDisabled: cacheDisabled, + Zone: zone, + Port: dnsPort, + CacheLen: cacheLen, + LocalTTL: ttl, + CacheNegLocalTTL: negTTL, + MaxAnswers: maxAnswers, + Timeout: timeout, + UDPBufLen: udpbuf, + CacheDisabled: cacheDisabled, } if len(fallback) > 0 {