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 } 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 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 }