* cache: add sharded cache implementation Add Cache impl and a few tests. This cache is 256-way sharded, mainly so each shard has it's own lock. The main cache structure is a readonly jump plane into the right shard. This should remove the single lock contention on the main lock and provide more concurrent throughput - Obviously this hasn't been tested or measured. The key into the cache was made a uint32 (hash.fnv) and the hashing op is not using strings.ToLower anymore remove any GC in that code path. * here too * Minimum shard size * typos * blurp * small cleanups no defer * typo * Add freq based on Johns idea * cherry-pick conflict resolv * typo * update from early code review from john * add prefetch to the cache * mw/cache: add prefetch * remove println * remove comment * Fix tests * Test prefetch in setup * Add start of cache * try add diff cache options * Add hacky testcase * not needed * allow the use of a percentage for prefetch If the TTL falls below xx% do a prefetch, if the record was popular. Some other fixes and correctly prefetch only popular records.
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
// Package dnssec implements a middleware that signs responses on-the-fly using
|
|
// NSEC black lies.
|
|
package dnssec
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/middleware"
|
|
"github.com/coredns/coredns/middleware/pkg/cache"
|
|
"github.com/coredns/coredns/middleware/pkg/response"
|
|
"github.com/coredns/coredns/middleware/pkg/singleflight"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// Dnssec signs the reply on-the-fly.
|
|
type Dnssec struct {
|
|
Next middleware.Handler
|
|
|
|
zones []string
|
|
keys []*DNSKEY
|
|
inflight *singleflight.Group
|
|
cache *cache.Cache
|
|
}
|
|
|
|
// New returns a new Dnssec.
|
|
func New(zones []string, keys []*DNSKEY, next middleware.Handler, c *cache.Cache) Dnssec {
|
|
return Dnssec{Next: next,
|
|
zones: zones,
|
|
keys: keys,
|
|
cache: c,
|
|
inflight: new(singleflight.Group),
|
|
}
|
|
}
|
|
|
|
// Sign signs the message in state. it takes care of negative or nodata responses. It
|
|
// uses NSEC black lies for authenticated denial of existence. Signatures
|
|
// creates will be cached for a short while. By default we sign for 8 days,
|
|
// starting 3 hours ago.
|
|
func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg {
|
|
req := state.Req
|
|
|
|
mt, _ := response.Typify(req, time.Now().UTC()) // TODO(miek): need opt record here?
|
|
if mt == response.Delegation {
|
|
// TODO(miek): uh, signing DS record?!?!
|
|
return req
|
|
}
|
|
|
|
incep, expir := incepExpir(now)
|
|
|
|
if mt == response.NameError {
|
|
if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 {
|
|
return req
|
|
}
|
|
|
|
ttl := req.Ns[0].Header().Ttl
|
|
|
|
if sigs, err := d.sign(req.Ns, zone, ttl, incep, expir); err == nil {
|
|
req.Ns = append(req.Ns, sigs...)
|
|
}
|
|
if sigs, err := d.nsec(state.Name(), zone, ttl, incep, expir); err == nil {
|
|
req.Ns = append(req.Ns, sigs...)
|
|
}
|
|
if len(req.Ns) > 1 { // actually added nsec and sigs, reset the rcode
|
|
req.Rcode = dns.RcodeSuccess
|
|
}
|
|
return req
|
|
}
|
|
|
|
for _, r := range rrSets(req.Answer) {
|
|
ttl := r[0].Header().Ttl
|
|
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
|
|
req.Answer = append(req.Answer, sigs...)
|
|
}
|
|
}
|
|
for _, r := range rrSets(req.Ns) {
|
|
ttl := r[0].Header().Ttl
|
|
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
|
|
req.Ns = append(req.Ns, sigs...)
|
|
}
|
|
}
|
|
for _, r := range rrSets(req.Extra) {
|
|
ttl := r[0].Header().Ttl
|
|
if sigs, err := d.sign(r, zone, ttl, incep, expir); err == nil {
|
|
req.Extra = append(sigs, req.Extra...) // prepend to leave OPT alone
|
|
}
|
|
}
|
|
return req
|
|
}
|
|
|
|
func (d Dnssec) sign(rrs []dns.RR, signerName string, ttl, incep, expir uint32) ([]dns.RR, error) {
|
|
k := hash(rrs)
|
|
sgs, ok := d.get(k)
|
|
if ok {
|
|
return sgs, nil
|
|
}
|
|
|
|
sigs, err := d.inflight.Do(k, func() (interface{}, error) {
|
|
sigs := make([]dns.RR, len(d.keys))
|
|
var e error
|
|
for i, k := range d.keys {
|
|
sig := k.newRRSIG(signerName, ttl, incep, expir)
|
|
e = sig.Sign(k.s, rrs)
|
|
sigs[i] = sig
|
|
}
|
|
d.set(k, sigs)
|
|
return sigs, e
|
|
})
|
|
return sigs.([]dns.RR), err
|
|
}
|
|
|
|
func (d Dnssec) set(key uint32, sigs []dns.RR) {
|
|
d.cache.Add(key, sigs)
|
|
}
|
|
|
|
func (d Dnssec) get(key uint32) ([]dns.RR, bool) {
|
|
if s, ok := d.cache.Get(key); ok {
|
|
cacheHits.Inc()
|
|
return s.([]dns.RR), true
|
|
}
|
|
cacheMisses.Inc()
|
|
return nil, false
|
|
}
|
|
|
|
func incepExpir(now time.Time) (uint32, uint32) {
|
|
incep := uint32(now.Add(-3 * time.Hour).Unix()) // -(2+1) hours, be sure to catch daylight saving time and such
|
|
expir := uint32(now.Add(eightDays).Unix()) // sign for 8 days
|
|
return incep, expir
|
|
}
|
|
|
|
const (
|
|
eightDays = 8 * 24 * time.Hour
|
|
defaultCap = 10000 // default capacity of the cache.
|
|
)
|