mirror of
https://github.com/folbricht/routedns.git
synced 2026-01-06 01:30:00 -06:00
New cache-harden-below-nxdomain option (#132)
* New cache-harden-below-nxdomain option * Wire in the config
This commit is contained in:
27
cache.go
27
cache.go
@@ -5,6 +5,7 @@ import (
|
||||
"expvar"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -48,6 +49,12 @@ type CacheOptions struct {
|
||||
// Allows control over the order of answer RRs in cached responses. Default is to keep
|
||||
// the order if nil.
|
||||
ShuffleAnswerFunc AnswerShuffleFunc
|
||||
|
||||
// If enabled, will return NXDOMAIN for every name query under another name that is
|
||||
// already cached as NXDOMAIN. For example, if example.com is in the cache with
|
||||
// NXDOMAIN, a query for www.example.com will also immediately return NXDOMAIN.
|
||||
// See RFC8020.
|
||||
HardenBelowNXDOMAIN bool
|
||||
}
|
||||
|
||||
// NewCache returns a new instance of a Cache resolver.
|
||||
@@ -128,6 +135,26 @@ func (r *Cache) answerFromCache(q *dns.Msg) (*dns.Msg, bool) {
|
||||
}
|
||||
r.mu.Unlock()
|
||||
|
||||
// We couldn't find it in the cache, but a parent domain may already be with NXDOMAIN.
|
||||
// Return that instead if enabled.
|
||||
if answer == nil && r.HardenBelowNXDOMAIN {
|
||||
name := q.Question[0].Name
|
||||
newQ := q.Copy()
|
||||
fragments := strings.Split(name, ".")
|
||||
r.mu.Lock()
|
||||
for i := 1; i < len(fragments)-1; i++ {
|
||||
newQ.Question[0].Name = strings.Join(fragments[i:], ".")
|
||||
if a := r.lru.get(newQ); a != nil {
|
||||
if a.Rcode == dns.RcodeNameError {
|
||||
r.mu.Unlock()
|
||||
return nxdomain(q), true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// Return a cache-miss if there's no answer record in the map
|
||||
if answer == nil {
|
||||
return nil, false
|
||||
|
||||
@@ -99,6 +99,39 @@ func TestCacheNXDOMAIN(t *testing.T) {
|
||||
require.Equal(t, 1, r.HitCount())
|
||||
}
|
||||
|
||||
func TestCacheHardenBelowNXDOMAIN(t *testing.T) {
|
||||
var ci ClientInfo
|
||||
q := new(dns.Msg)
|
||||
r := &TestResolver{
|
||||
ResolveFunc: func(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
|
||||
a := new(dns.Msg)
|
||||
a.SetReply(q)
|
||||
a.SetRcode(q, dns.RcodeNameError)
|
||||
return a, nil
|
||||
},
|
||||
}
|
||||
|
||||
opt := CacheOptions{
|
||||
GCPeriod: time.Minute,
|
||||
HardenBelowNXDOMAIN: true,
|
||||
}
|
||||
c := NewCache("test-cache", r, opt)
|
||||
|
||||
// Cache an NXDOMAIN for the parent domain
|
||||
q.SetQuestion("test.com.", dns.TypeA)
|
||||
_, err := c.Resolve(q, ci)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, r.HitCount())
|
||||
|
||||
// A sub-domain query should also return NXDOMAIN based on the cached
|
||||
// record for the parent if HardenBelowNXDOMAIN is enabled.
|
||||
q.SetQuestion("not.exist.test.com.", dns.TypeA)
|
||||
a, err := c.Resolve(q, ci)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, r.HitCount())
|
||||
require.Equal(t, dns.RcodeNameError, a.Rcode)
|
||||
}
|
||||
|
||||
func TestRoundRobinShuffle(t *testing.T) {
|
||||
msg := &dns.Msg{
|
||||
Answer: []dns.RR{
|
||||
|
||||
@@ -70,9 +70,10 @@ type group struct {
|
||||
EDNS0Data []byte `toml:"edns0-data"` // EDNS0 modifier option data
|
||||
|
||||
// Cache options
|
||||
CacheSize int `toml:"cache-size"` // Max number of items to keep in the cache. Default 0 == unlimited
|
||||
CacheNegativeTTL uint32 `toml:"cache-negative-ttl"` // TTL to apply to negative responses, default 60.
|
||||
CacheAnswerShuffle string `toml:"cache-answer-shuffle"` // Algorithm to use for modifying the response order of cached items
|
||||
CacheSize int `toml:"cache-size"` // Max number of items to keep in the cache. Default 0 == unlimited
|
||||
CacheNegativeTTL uint32 `toml:"cache-negative-ttl"` // TTL to apply to negative responses, default 60.
|
||||
CacheAnswerShuffle string `toml:"cache-answer-shuffle"` // Algorithm to use for modifying the response order of cached items
|
||||
CacheHardenBelowNXDOMAIN bool `toml:"cache-harden-below-nxdomain"` // Return NXDOMAIN if an NXDOMAIN is cached for a parent domain
|
||||
|
||||
// Blocklist options
|
||||
Blocklist []string // Blocklist rules, only used by "blocklist" type
|
||||
|
||||
@@ -428,10 +428,11 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
|
||||
return fmt.Errorf("unsupported shuffle function %q", g.CacheAnswerShuffle)
|
||||
}
|
||||
opt := rdns.CacheOptions{
|
||||
GCPeriod: time.Duration(g.GCPeriod) * time.Second,
|
||||
Capacity: g.CacheSize,
|
||||
NegativeTTL: g.CacheNegativeTTL,
|
||||
ShuffleAnswerFunc: shuffleFunc,
|
||||
GCPeriod: time.Duration(g.GCPeriod) * time.Second,
|
||||
Capacity: g.CacheSize,
|
||||
NegativeTTL: g.CacheNegativeTTL,
|
||||
ShuffleAnswerFunc: shuffleFunc,
|
||||
HardenBelowNXDOMAIN: g.CacheHardenBelowNXDOMAIN,
|
||||
}
|
||||
resolvers[id] = rdns.NewCache(id, gr[0], opt)
|
||||
case "response-blocklist-ip", "response-blocklist-cidr": // "response-blocklist-cidr" has been retired/renamed to "response-blocklist-ip"
|
||||
|
||||
@@ -293,6 +293,7 @@ Options:
|
||||
- `cache-size` - Max number of responses to cache. Defaults to 0 which means no limit. Optional
|
||||
- `cache-negative-ttl` - TTL (in seconds) to apply to responses without a SOA. Default: 60. Optional
|
||||
- `cache-answer-shuffle` - Specifies a method for changing the order of cached A/AAAA answer records. Possible values `random` or `round-robin`. Defaults to static responses if not set.
|
||||
- `cache-harden-below-nxdomain` - Return NXDOMAIN for sudomain queries if the parent domain has a cached NXDOMAIN. See [RFC8020](https://tools.ietf.org/html/rfc8020).
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
Reference in New Issue
Block a user