mirror of
https://github.com/AdguardTeam/AdGuardDNS.git
synced 2025-02-20 11:23:36 +08:00
110 lines
2.2 KiB
Go
110 lines
2.2 KiB
Go
package dnsdb
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
|
|
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
|
|
"github.com/AdguardTeam/golibs/container"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// buffer contains the approximate statistics for DNS answers. It saves data
|
|
// until it reaches maxSize, upon which it can only increase the hits of the
|
|
// previous records.
|
|
type buffer struct {
|
|
// mu protects entries.
|
|
mu *sync.Mutex
|
|
|
|
// entries is the data of the statistics.
|
|
entries map[recordKey]*recordValue
|
|
|
|
// maxSize is the maximum length of entries.
|
|
maxSize int
|
|
}
|
|
|
|
// add increments the records for all answers.
|
|
func (b *buffer) add(target string, answers []dns.RR, qt dnsmsg.RRType, rc dnsmsg.RCode) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
// Do nothing if the buffer is already full.
|
|
l := len(b.entries)
|
|
if l >= b.maxSize {
|
|
return
|
|
}
|
|
|
|
key := recordKey{
|
|
target: target,
|
|
qt: qt,
|
|
}
|
|
|
|
prev, ok := b.entries[key]
|
|
if ok {
|
|
prev.hits++
|
|
|
|
// Note, that only the first set of answers is stored in the buffer.
|
|
// If a more detailed response is needed, maps.Copy can be used to
|
|
// achieve that.
|
|
|
|
return
|
|
}
|
|
|
|
b.entries[key] = &recordValue{
|
|
answers: toAnswerSet(answers, rc),
|
|
hits: 1,
|
|
}
|
|
|
|
metrics.DNSDBBufferSize.Set(float64(l + 1))
|
|
}
|
|
|
|
// all returns buffered records.
|
|
func (b *buffer) all() (records []*record) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
|
|
for key, val := range b.entries {
|
|
if val.answers.Len() == 0 {
|
|
records = append(records, &record{
|
|
target: key.target,
|
|
hits: val.hits,
|
|
rrType: key.qt,
|
|
})
|
|
|
|
continue
|
|
}
|
|
|
|
val.answers.Range(func(a recordAnswer) (cont bool) {
|
|
records = append(records, &record{
|
|
target: key.target,
|
|
answer: a.value,
|
|
hits: val.hits,
|
|
rrType: a.rrType,
|
|
rcode: a.rcode,
|
|
})
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
return records
|
|
}
|
|
|
|
// toAnswerSet converts a slice of [dns.RR] to a set that can easier be
|
|
// serialized to a csv.
|
|
func toAnswerSet(answers []dns.RR, rc dnsmsg.RCode) (answerSet *container.MapSet[recordAnswer]) {
|
|
answerSet = container.NewMapSet[recordAnswer]()
|
|
for _, a := range answers {
|
|
ansStr := answerString(a)
|
|
if ansStr != "" {
|
|
answerSet.Add(recordAnswer{
|
|
value: ansStr,
|
|
rrType: a.Header().Rrtype,
|
|
rcode: rc,
|
|
})
|
|
}
|
|
}
|
|
|
|
return answerSet
|
|
}
|