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 }