Cache (#126)
* Add middleware/cache Add a caching middleware that caches nxdomain, nodata and successful responses. It differentiates between DNSSEC on normal DNS replies. Each reply is compress and scrubbed so it will fit the specific client asking for it. * first simple test, less exporting of stuff * more * Add middleware/cache Add a caching middleware that caches nxdomain, nodata and successful responses. It differentiates between DNSSEC on normal DNS replies. Each reply is compressed and scrubbed so it will fit the specific client asking for it. The TTL is decremented with the time spend in the cache. There is syntax that allows you to cap the TTL for all records, no matter what. This allows for a shortlived cache, just to absorb query peaks. +Tests * cache test infrastructure * Testing
This commit is contained in:
parent
4e3c82bec5
commit
10db2a80df
12 changed files with 566 additions and 12 deletions
|
@ -57,6 +57,7 @@ var directiveOrder = []directive{
|
|||
{"chaos", setup.Chaos},
|
||||
{"rewrite", setup.Rewrite},
|
||||
{"loadbalance", setup.Loadbalance},
|
||||
{"cache", setup.Cache},
|
||||
{"file", setup.File},
|
||||
{"secondary", setup.Secondary},
|
||||
{"etcd", setup.Etcd},
|
||||
|
|
54
core/setup/cache.go
Normal file
54
core/setup/cache.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
"github.com/miekg/coredns/middleware/cache"
|
||||
)
|
||||
|
||||
// Cache sets up the root file path of the server.
|
||||
func Cache(c *Controller) (middleware.Middleware, error) {
|
||||
ttl, zones, err := cacheParse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return cache.NewCache(ttl, zones, next)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func cacheParse(c *Controller) (int, []string, error) {
|
||||
var (
|
||||
err error
|
||||
ttl int
|
||||
)
|
||||
|
||||
for c.Next() {
|
||||
if c.Val() == "cache" {
|
||||
// cache [ttl] [zones..]
|
||||
|
||||
origins := []string{c.ServerBlockHosts[c.ServerBlockHostIndex]}
|
||||
args := c.RemainingArgs()
|
||||
if len(args) > 0 {
|
||||
origins = args
|
||||
// first args may be just a number, then it is the ttl, if not it is a zone
|
||||
t := origins[0]
|
||||
ttl, err = strconv.Atoi(t)
|
||||
if err == nil {
|
||||
origins = origins[1:]
|
||||
if len(origins) == 0 {
|
||||
// There was *only* the ttl, revert back to server block
|
||||
origins = []string{c.ServerBlockHosts[c.ServerBlockHostIndex]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, _ := range origins {
|
||||
origins[i] = middleware.Host(origins[i]).Normalize()
|
||||
}
|
||||
return ttl, origins, nil
|
||||
}
|
||||
}
|
||||
return 0, nil, nil
|
||||
}
|
|
@ -40,7 +40,7 @@ func fileParse(c *Controller) (file.Zones, error) {
|
|||
names := []string{}
|
||||
for c.Next() {
|
||||
if c.Val() == "file" {
|
||||
// file db.file [origin]
|
||||
// file db.file [zones...]
|
||||
if !c.NextArg() {
|
||||
return file.Zones{}, c.ArgErr()
|
||||
}
|
||||
|
@ -83,7 +83,6 @@ func fileParse(c *Controller) (file.Zones, error) {
|
|||
}
|
||||
z[origin].NoReload = noReload
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,12 @@ import (
|
|||
"github.com/miekg/coredns/middleware/loadbalance"
|
||||
)
|
||||
|
||||
// Root sets up the root file path of the server.
|
||||
// Loadbalance sets up the root file path of the server.
|
||||
func Loadbalance(c *Controller) (middleware.Middleware, error) {
|
||||
for c.Next() {
|
||||
// and choosing the correct balancer
|
||||
// TODO(miek): block and option parsing
|
||||
}
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return loadbalance.RoundRobin{Next: next}
|
||||
}, nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
29
middleware/cache/README.md
vendored
Normal file
29
middleware/cache/README.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# cache
|
||||
|
||||
`cache` enables a frontend cache.
|
||||
|
||||
## Syntax
|
||||
|
||||
~~~
|
||||
cache [ttl] [zones...]
|
||||
~~~
|
||||
|
||||
* `ttl` max TTL in seconds, if not specified the TTL of the reply (SOA minimum or minimum TTL in the
|
||||
answer section) will be used.
|
||||
* `zones` zones it should should cache for. If empty the zones from the configuration block are used.
|
||||
|
||||
|
||||
Each element in the cache is cached according to its TTL, for the negative cache the SOA's MinTTL
|
||||
value is used.
|
||||
|
||||
A cache mostly makes sense with a middleware that is potentially slow, i.e. a proxy that retrieves
|
||||
answer, or to minimize backend queries for middleware like etcd. Using a cache with the file
|
||||
middleware essentially doubles the memory load with no concealable increase of query speed.
|
||||
|
||||
## Examples
|
||||
|
||||
~~~
|
||||
cache
|
||||
~~~
|
||||
|
||||
Enable caching for all zones.
|
196
middleware/cache/cache.go
vendored
Normal file
196
middleware/cache/cache.go
vendored
Normal file
|
@ -0,0 +1,196 @@
|
|||
package cache
|
||||
|
||||
/*
|
||||
The idea behind this implementation is as follows. We have a cache that is index
|
||||
by a couple different keys, which allows use to have:
|
||||
|
||||
- negative cache: qname only for NXDOMAIN responses
|
||||
- negative cache: qname + qtype for NODATA responses
|
||||
- positive cache: qname + qtype for succesful responses.
|
||||
|
||||
We track DNSSEC responses separately, i.e. under a different cache key.
|
||||
Each Item stored contains the message split up in the different sections
|
||||
and a few bits of the msg header.
|
||||
|
||||
For instance an NXDOMAIN for blaat.miek.nl will create the
|
||||
following negative cache entry (do signal state of DO (do off, DO on)).
|
||||
|
||||
ncache: do <blaat.miek.nl>
|
||||
Item:
|
||||
Ns: <miek.nl> SOA RR
|
||||
|
||||
If found a return packet is assembled and returned to the client. Taking size and EDNS0
|
||||
constraints into account.
|
||||
|
||||
We also need to track if the answer received was an authoritative answer, ad bit and other
|
||||
setting, for this we also store a few header bits.
|
||||
|
||||
For the positive cache we use the same idea. Truncated responses are never stored.
|
||||
*/
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
gcache "github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
// Cache is middleware that looks up responses in a cache and caches replies.
|
||||
type Cache struct {
|
||||
Next middleware.Handler
|
||||
Zones []string
|
||||
cache *gcache.Cache
|
||||
cap time.Duration
|
||||
}
|
||||
|
||||
func NewCache(ttl int, zones []string, next middleware.Handler) Cache {
|
||||
return Cache{Next: next, Zones: zones, cache: gcache.New(defaultDuration, purgeDuration), cap: time.Duration(ttl) * time.Second}
|
||||
}
|
||||
|
||||
type messageType int
|
||||
|
||||
const (
|
||||
success messageType = iota
|
||||
nameError // NXDOMAIN in header, SOA in auth.
|
||||
noData // NOERROR in header, SOA in auth.
|
||||
otherError // Don't cache these.
|
||||
)
|
||||
|
||||
// classify classifies a message, it returns the MessageType.
|
||||
func classify(m *dns.Msg) (messageType, *dns.OPT) {
|
||||
opt := m.IsEdns0()
|
||||
soa := false
|
||||
if m.Rcode == dns.RcodeSuccess {
|
||||
return success, opt
|
||||
}
|
||||
for _, r := range m.Ns {
|
||||
if r.Header().Rrtype == dns.TypeSOA {
|
||||
soa = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check length of different section, and drop stuff that is just to large.
|
||||
if soa && m.Rcode == dns.RcodeSuccess {
|
||||
return noData, opt
|
||||
}
|
||||
if soa && m.Rcode == dns.RcodeNameError {
|
||||
return nameError, opt
|
||||
}
|
||||
|
||||
return otherError, opt
|
||||
}
|
||||
|
||||
func cacheKey(m *dns.Msg, t messageType, do bool) string {
|
||||
if m.Truncated {
|
||||
return ""
|
||||
}
|
||||
|
||||
qtype := m.Question[0].Qtype
|
||||
qname := middleware.Name(m.Question[0].Name).Normalize()
|
||||
switch t {
|
||||
case success:
|
||||
return successKey(qname, qtype, do)
|
||||
case nameError:
|
||||
return nameErrorKey(qname, do)
|
||||
case noData:
|
||||
return noDataKey(qname, qtype, do)
|
||||
case otherError:
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CachingResponseWriter struct {
|
||||
dns.ResponseWriter
|
||||
cache *gcache.Cache
|
||||
cap time.Duration
|
||||
}
|
||||
|
||||
func NewCachingResponseWriter(w dns.ResponseWriter, cache *gcache.Cache, cap time.Duration) *CachingResponseWriter {
|
||||
return &CachingResponseWriter{w, cache, cap}
|
||||
}
|
||||
|
||||
func (c *CachingResponseWriter) WriteMsg(res *dns.Msg) error {
|
||||
do := false
|
||||
mt, opt := classify(res)
|
||||
if opt != nil {
|
||||
do = opt.Do()
|
||||
}
|
||||
|
||||
key := cacheKey(res, mt, do)
|
||||
c.Set(res, key, mt)
|
||||
|
||||
if c.cap != 0 {
|
||||
setCap(res, uint32(c.cap.Seconds()))
|
||||
}
|
||||
|
||||
return c.ResponseWriter.WriteMsg(res)
|
||||
}
|
||||
|
||||
func (c *CachingResponseWriter) Set(m *dns.Msg, key string, mt messageType) {
|
||||
if key == "" {
|
||||
// logger the log? TODO(miek)
|
||||
return
|
||||
}
|
||||
|
||||
duration := c.cap
|
||||
switch mt {
|
||||
case success:
|
||||
if c.cap == 0 {
|
||||
duration = minTtl(m.Answer, mt)
|
||||
}
|
||||
i := newItem(m, duration)
|
||||
|
||||
c.cache.Set(key, i, duration)
|
||||
case nameError, noData:
|
||||
if c.cap == 0 {
|
||||
duration = minTtl(m.Ns, mt)
|
||||
}
|
||||
i := newItem(m, duration)
|
||||
|
||||
c.cache.Set(key, i, duration)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CachingResponseWriter) Write(buf []byte) (int, error) {
|
||||
log.Printf("[WARNING] Caching called with Write: not caching reply")
|
||||
n, err := c.ResponseWriter.Write(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *CachingResponseWriter) Hijack() {
|
||||
c.ResponseWriter.Hijack()
|
||||
return
|
||||
}
|
||||
|
||||
func minTtl(rrs []dns.RR, mt messageType) time.Duration {
|
||||
if mt != success && mt != nameError && mt != noData {
|
||||
return 0
|
||||
}
|
||||
|
||||
minTtl := maxTtl
|
||||
for _, r := range rrs {
|
||||
switch mt {
|
||||
case nameError, noData:
|
||||
if r.Header().Rrtype == dns.TypeSOA {
|
||||
return time.Duration(r.(*dns.SOA).Minttl) * time.Second
|
||||
}
|
||||
case success:
|
||||
if r.Header().Ttl < minTtl {
|
||||
minTtl = r.Header().Ttl
|
||||
}
|
||||
}
|
||||
}
|
||||
return time.Duration(minTtl) * time.Second
|
||||
}
|
||||
|
||||
const (
|
||||
purgeDuration = 1 * time.Minute
|
||||
defaultDuration = 20 * time.Minute
|
||||
baseTtl = 5 // minimum ttl that we will allow
|
||||
maxTtl uint32 = 2 * 3600
|
||||
)
|
112
middleware/cache/cache_test.go
vendored
Normal file
112
middleware/cache/cache_test.go
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
"github.com/miekg/coredns/middleware/test"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type cacheTestCase struct {
|
||||
test.Case
|
||||
in test.Case
|
||||
AuthenticatedData bool
|
||||
Authoritative bool
|
||||
RecursionAvailable bool
|
||||
Truncated bool
|
||||
}
|
||||
|
||||
var cacheTestCases = []cacheTestCase{
|
||||
{
|
||||
RecursionAvailable: true, AuthenticatedData: true, Authoritative: true,
|
||||
Case: test.Case{
|
||||
Qname: "miek.nl.", Qtype: dns.TypeMX,
|
||||
Answer: []dns.RR{
|
||||
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."),
|
||||
},
|
||||
},
|
||||
in: test.Case{
|
||||
Qname: "miek.nl.", Qtype: dns.TypeMX,
|
||||
Answer: []dns.RR{
|
||||
test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 10 aspmx2.googlemail.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 10 aspmx3.googlemail.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 5 alt1.aspmx.l.google.com."),
|
||||
test.MX("miek.nl. 1800 IN MX 5 alt2.aspmx.l.google.com."),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Truncated: true,
|
||||
Case: test.Case{
|
||||
Qname: "miek.nl.", Qtype: dns.TypeMX,
|
||||
Answer: []dns.RR{test.MX("miek.nl. 1800 IN MX 1 aspmx.l.google.com.")},
|
||||
},
|
||||
in: test.Case{},
|
||||
},
|
||||
}
|
||||
|
||||
func cacheMsg(m *dns.Msg, tc cacheTestCase) *dns.Msg {
|
||||
m.RecursionAvailable = tc.RecursionAvailable
|
||||
m.AuthenticatedData = tc.AuthenticatedData
|
||||
m.Authoritative = tc.Authoritative
|
||||
m.Truncated = tc.Truncated
|
||||
m.Answer = tc.in.Answer
|
||||
m.Ns = tc.in.Ns
|
||||
// m.Extra = tc.in.Extra , not the OPT record!
|
||||
return m
|
||||
}
|
||||
|
||||
func newTestCache() (Cache, *CachingResponseWriter) {
|
||||
c := NewCache(0, []string{"."}, nil)
|
||||
crr := NewCachingResponseWriter(nil, c.cache, time.Duration(0))
|
||||
return c, crr
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
c, crr := newTestCache()
|
||||
|
||||
for _, tc := range cacheTestCases {
|
||||
m := tc.in.Msg()
|
||||
m = cacheMsg(m, tc)
|
||||
do := tc.in.Do
|
||||
|
||||
mt, _ := classify(m)
|
||||
key := cacheKey(m, mt, do)
|
||||
crr.Set(m, key, mt)
|
||||
|
||||
name := middleware.Name(m.Question[0].Name).Normalize()
|
||||
qtype := m.Question[0].Qtype
|
||||
i, ok := c.Get(name, qtype, do)
|
||||
if !ok && !m.Truncated {
|
||||
t.Errorf("Truncated message should not have been cached")
|
||||
}
|
||||
|
||||
if ok {
|
||||
resp := i.toMsg(m)
|
||||
|
||||
if !test.Header(t, tc.Case, resp) {
|
||||
t.Logf("%v\n", resp)
|
||||
continue
|
||||
}
|
||||
|
||||
if !test.Section(t, tc.Case, test.Answer, resp.Answer) {
|
||||
t.Logf("%v\n", resp)
|
||||
}
|
||||
if !test.Section(t, tc.Case, test.Ns, resp.Ns) {
|
||||
t.Logf("%v\n", resp)
|
||||
|
||||
}
|
||||
if !test.Section(t, tc.Case, test.Extra, resp.Extra) {
|
||||
t.Logf("%v\n", resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
middleware/cache/handler.go
vendored
Normal file
44
middleware/cache/handler.go
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"github.com/miekg/coredns/middleware"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ServeDNS implements the middleware.Handler interface.
|
||||
func (c Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
state := middleware.State{W: w, Req: r}
|
||||
|
||||
qname := state.Name()
|
||||
qtype := state.QType()
|
||||
zone := middleware.Zones(c.Zones).Matches(qname)
|
||||
if zone == "" {
|
||||
return c.Next.ServeDNS(ctx, w, r)
|
||||
}
|
||||
|
||||
do := state.Do() // might need more from OPT record?
|
||||
|
||||
if i, ok := c.Get(qname, qtype, do); ok {
|
||||
resp := i.toMsg(r)
|
||||
state.SizeAndDo(resp)
|
||||
w.WriteMsg(resp)
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
crr := NewCachingResponseWriter(w, c.cache, c.cap)
|
||||
return c.Next.ServeDNS(ctx, crr, r)
|
||||
}
|
||||
|
||||
func (c Cache) Get(qname string, qtype uint16, do bool) (*item, bool) {
|
||||
nxdomain := nameErrorKey(qname, do)
|
||||
if i, ok := c.cache.Get(nxdomain); ok {
|
||||
return i.(*item), true
|
||||
}
|
||||
|
||||
successOrNoData := successKey(qname, qtype, do)
|
||||
if i, ok := c.cache.Get(successOrNoData); ok {
|
||||
return i.(*item), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
98
middleware/cache/item.go
vendored
Normal file
98
middleware/cache/item.go
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type item struct {
|
||||
Authoritative bool
|
||||
AuthenticatedData bool
|
||||
RecursionAvailable bool
|
||||
Answer []dns.RR
|
||||
Ns []dns.RR
|
||||
Extra []dns.RR
|
||||
|
||||
origTtl uint32
|
||||
stored time.Time
|
||||
}
|
||||
|
||||
func newItem(m *dns.Msg, d time.Duration) *item {
|
||||
i := new(item)
|
||||
i.Authoritative = m.Authoritative
|
||||
i.AuthenticatedData = m.AuthenticatedData
|
||||
i.RecursionAvailable = m.RecursionAvailable
|
||||
i.Answer = m.Answer
|
||||
i.Ns = m.Ns
|
||||
i.Extra = make([]dns.RR, len(m.Extra))
|
||||
// Don't copy OPT record as these are hop-by-hop.
|
||||
j := 0
|
||||
for _, e := range m.Extra {
|
||||
if e.Header().Rrtype == dns.TypeOPT {
|
||||
continue
|
||||
}
|
||||
i.Extra[j] = e
|
||||
j++
|
||||
}
|
||||
i.Extra = i.Extra[:j]
|
||||
|
||||
i.origTtl = uint32(d.Seconds())
|
||||
i.stored = time.Now().UTC()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// toMsg turns i into a message, it tailers to reply to m.
|
||||
func (i *item) toMsg(m *dns.Msg) *dns.Msg {
|
||||
m1 := new(dns.Msg)
|
||||
m1.SetReply(m)
|
||||
m1.Authoritative = i.Authoritative
|
||||
m1.AuthenticatedData = i.AuthenticatedData
|
||||
m1.RecursionAvailable = i.RecursionAvailable
|
||||
m1.Compress = true
|
||||
|
||||
m1.Answer = i.Answer
|
||||
m1.Ns = i.Ns
|
||||
m1.Extra = i.Extra
|
||||
|
||||
ttl := int(i.origTtl) - int(time.Now().UTC().Sub(i.stored).Seconds())
|
||||
if ttl < baseTtl {
|
||||
ttl = baseTtl
|
||||
}
|
||||
setCap(m1, uint32(ttl))
|
||||
return m1
|
||||
}
|
||||
|
||||
// setCap sets the ttl on all RRs in all sections.
|
||||
func setCap(m *dns.Msg, ttl uint32) {
|
||||
for _, r := range m.Answer {
|
||||
r.Header().Ttl = uint32(ttl)
|
||||
}
|
||||
for _, r := range m.Ns {
|
||||
r.Header().Ttl = uint32(ttl)
|
||||
}
|
||||
for _, r := range m.Extra {
|
||||
r.Header().Ttl = uint32(ttl)
|
||||
}
|
||||
}
|
||||
|
||||
// nodataKey returns a caching key for NODATA responses.
|
||||
func noDataKey(qname string, qtype uint16, do bool) string {
|
||||
if do {
|
||||
return "1" + qname + ".." + strconv.Itoa(int(qtype))
|
||||
}
|
||||
return "0" + qname + ".." + strconv.Itoa(int(qtype))
|
||||
}
|
||||
|
||||
// nameErrorKey returns a caching key for NXDOMAIN responses.
|
||||
func nameErrorKey(qname string, do bool) string {
|
||||
if do {
|
||||
return "1" + qname
|
||||
}
|
||||
return "0" + qname
|
||||
}
|
||||
|
||||
// successKey returns a caching key for successfull answers.
|
||||
func successKey(qname string, qtype uint16, do bool) string { return noDataKey(qname, qtype, do) }
|
25
middleware/cache/item_test.go
vendored
Normal file
25
middleware/cache/item_test.go
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
if noDataKey("miek.nl.", dns.TypeMX, false) != "0miek.nl...15" {
|
||||
t.Errorf("failed to create correct key")
|
||||
}
|
||||
if noDataKey("miek.nl.", dns.TypeMX, true) != "1miek.nl...15" {
|
||||
t.Errorf("failed to create correct key")
|
||||
}
|
||||
if nameErrorKey("miek.nl.", false) != "0miek.nl." {
|
||||
t.Errorf("failed to create correct key")
|
||||
}
|
||||
if nameErrorKey("miek.nl.", true) != "1miek.nl." {
|
||||
t.Errorf("failed to create correct key")
|
||||
}
|
||||
if noDataKey("miek.nl.", dns.TypeMX, false) != successKey("miek.nl.", dns.TypeMX, false) {
|
||||
t.Errorf("nameErrorKey and successKey should be the same")
|
||||
}
|
||||
}
|
|
@ -349,15 +349,13 @@ func (e Etcd) SOA(zone string, state middleware.State) *dns.SOA {
|
|||
Mbox: "hostmaster." + zone,
|
||||
Ns: "ns.dns." + zone,
|
||||
Serial: uint32(time.Now().Unix()),
|
||||
Refresh: 14400,
|
||||
Retry: 3600,
|
||||
Expire: 604800,
|
||||
Refresh: 7200,
|
||||
Retry: 1800,
|
||||
Expire: 86400,
|
||||
Minttl: 60,
|
||||
}
|
||||
}
|
||||
|
||||
// NS returns the NS records from etcd.
|
||||
|
||||
// TODO(miek): DNSKEY and friends... intercepted by the DNSSEC middleware?
|
||||
|
||||
func isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool {
|
||||
|
|
|
@ -3,6 +3,7 @@ package loadbalance
|
|||
|
||||
import (
|
||||
"github.com/miekg/coredns/middleware"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
@ -12,7 +13,7 @@ type RoundRobin struct {
|
|||
Next middleware.Handler
|
||||
}
|
||||
|
||||
// ServeHTTP implements the middleware.Handler interface.
|
||||
// ServeDNS implements the middleware.Handler interface.
|
||||
func (rr RoundRobin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
wrr := NewRoundRobinResponseWriter(w)
|
||||
return rr.Next.ServeDNS(ctx, wrr, r)
|
||||
|
|
Loading…
Add table
Reference in a new issue