dnscrypt-proxy/dnscrypt-proxy/plugin_cache.go

160 lines
3.4 KiB
Go

package main
import (
"crypto/sha512"
"encoding/binary"
"errors"
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
)
type CachedResponse struct {
expiration time.Time
msg dns.Msg
}
type CachedResponses struct {
sync.RWMutex
cache *lru.ARCCache
}
var cachedResponses CachedResponses
type PluginCacheResponse struct {
cachedResponses *CachedResponses
}
func (plugin *PluginCacheResponse) Name() string {
return "cache_response"
}
func (plugin *PluginCacheResponse) Description() string {
return "DNS cache (writer)."
}
func (plugin *PluginCacheResponse) Init(proxy *Proxy) error {
return nil
}
func (plugin *PluginCacheResponse) Drop() error {
return nil
}
func (plugin *PluginCacheResponse) Reload() error {
return nil
}
func (plugin *PluginCacheResponse) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
plugin.cachedResponses = &cachedResponses
if msg.Rcode != dns.RcodeSuccess && msg.Rcode != dns.RcodeNameError && msg.Rcode != dns.RcodeNotAuth {
return nil
}
if msg.Truncated {
return nil
}
cacheKey, err := computeCacheKey(pluginsState, msg)
if err != nil {
return err
}
ttl := getMinTTL(msg, pluginsState.cacheMinTTL, pluginsState.cacheMaxTTL, pluginsState.cacheNegMinTTL, pluginsState.cacheNegMaxTTL)
cachedResponse := CachedResponse{
expiration: time.Now().Add(ttl),
msg: *msg,
}
plugin.cachedResponses.Lock()
defer plugin.cachedResponses.Unlock()
if plugin.cachedResponses.cache == nil {
plugin.cachedResponses.cache, err = lru.NewARC(pluginsState.cacheSize)
if err != nil {
return err
}
}
plugin.cachedResponses.cache.Add(cacheKey, cachedResponse)
updateTTL(msg, cachedResponse.expiration)
return nil
}
type PluginCache struct {
cachedResponses *CachedResponses
}
func (plugin *PluginCache) Name() string {
return "cache"
}
func (plugin *PluginCache) Description() string {
return "DNS cache (reader)."
}
func (plugin *PluginCache) Init(proxy *Proxy) error {
return nil
}
func (plugin *PluginCache) Drop() error {
return nil
}
func (plugin *PluginCache) Reload() error {
return nil
}
func (plugin *PluginCache) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
plugin.cachedResponses = &cachedResponses
cacheKey, err := computeCacheKey(pluginsState, msg)
if err != nil {
return nil
}
plugin.cachedResponses.RLock()
defer plugin.cachedResponses.RUnlock()
if plugin.cachedResponses.cache == nil {
return nil
}
cachedAny, ok := plugin.cachedResponses.cache.Get(cacheKey)
if !ok {
return nil
}
cached := cachedAny.(CachedResponse)
if time.Now().After(cached.expiration) {
return nil
}
updateTTL(&cached.msg, cached.expiration)
synth := cached.msg
synth.Id = msg.Id
synth.Response = true
synth.Compress = true
synth.Question = msg.Question
pluginsState.synthResponse = &synth
pluginsState.action = PluginsActionSynth
pluginsState.cacheHit = true
return nil
}
func computeCacheKey(pluginsState *PluginsState, msg *dns.Msg) ([32]byte, error) {
questions := msg.Question
if len(questions) != 1 {
return [32]byte{}, errors.New("No question present")
}
question := questions[0]
h := sha512.New512_256()
var tmp [5]byte
binary.LittleEndian.PutUint16(tmp[0:2], question.Qtype)
binary.LittleEndian.PutUint16(tmp[2:4], question.Qclass)
if pluginsState.dnssec {
tmp[4] = 1
}
h.Write(tmp[:])
normalizedName := []byte(question.Name)
NormalizeName(&normalizedName)
h.Write(normalizedName)
var sum [32]byte
h.Sum(sum[:0])
return sum, nil
}