dnscrypt-proxy/dnscrypt-proxy/plugin_cache.go

165 lines
3.4 KiB
Go

package main
import (
"crypto/sha512"
"encoding/binary"
"sync"
"time"
"github.com/miekg/dns"
sieve "github.com/opencoff/go-sieve"
)
const StaleResponseTTL = 30 * time.Second
type CachedResponse struct {
expiration time.Time
msg dns.Msg
}
type CachedResponses struct {
sync.RWMutex
cache *sieve.Sieve[[32]byte, CachedResponse]
}
var cachedResponses CachedResponses
func computeCacheKey(pluginsState *PluginsState, msg *dns.Msg) [32]byte {
question := msg.Question[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[:])
normalizedRawQName := []byte(question.Name)
NormalizeRawQName(&normalizedRawQName)
h.Write(normalizedRawQName)
var sum [32]byte
h.Sum(sum[:0])
return sum
}
// ---
type PluginCache struct{}
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 {
cacheKey := computeCacheKey(pluginsState, msg)
cachedResponses.RLock()
if cachedResponses.cache == nil {
cachedResponses.RUnlock()
return nil
}
cached, ok := cachedResponses.cache.Get(cacheKey)
if !ok {
cachedResponses.RUnlock()
return nil
}
expiration := cached.expiration
synth := cached.msg.Copy()
cachedResponses.RUnlock()
synth.Id = msg.Id
synth.Response = true
synth.Compress = true
synth.Question = msg.Question
if time.Now().After(expiration) {
expiration2 := time.Now().Add(StaleResponseTTL)
updateTTL(synth, expiration2)
pluginsState.sessionData["stale"] = synth
return nil
}
updateTTL(synth, expiration)
pluginsState.synthResponse = synth
pluginsState.action = PluginsActionSynth
pluginsState.cacheHit = true
return nil
}
// ---
type PluginCacheResponse struct{}
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 {
if msg.Rcode != dns.RcodeSuccess && msg.Rcode != dns.RcodeNameError && msg.Rcode != dns.RcodeNotAuth {
return nil
}
if msg.Truncated {
return nil
}
cacheKey := computeCacheKey(pluginsState, msg)
ttl := getMinTTL(
msg,
pluginsState.cacheMinTTL,
pluginsState.cacheMaxTTL,
pluginsState.cacheNegMinTTL,
pluginsState.cacheNegMaxTTL,
)
cachedResponse := CachedResponse{
expiration: time.Now().Add(ttl),
msg: *msg,
}
cachedResponses.Lock()
if cachedResponses.cache == nil {
var err error
cachedResponses.cache = sieve.New[[32]byte, CachedResponse](pluginsState.cacheSize)
if cachedResponses.cache == nil {
cachedResponses.Unlock()
return err
}
}
cachedResponses.cache.Add(cacheKey, cachedResponse)
cachedResponses.Unlock()
updateTTL(msg, cachedResponse.expiration)
return nil
}