mirror of https://github.com/0xERR0R/blocky.git
feat: caching for empty DNS responses (#700)
This commit is contained in:
parent
2037e862dc
commit
3e95b12eed
|
@ -23,6 +23,8 @@ const (
|
|||
IPVersionV6
|
||||
)
|
||||
|
||||
var ErrInvalidIPVersion = fmt.Errorf("not a valid IPVersion, try [%s]", strings.Join(_IPVersionNames, ", "))
|
||||
|
||||
const _IPVersionName = "dualv4v6"
|
||||
|
||||
var _IPVersionNames = []string{
|
||||
|
@ -63,7 +65,7 @@ func ParseIPVersion(name string) (IPVersion, error) {
|
|||
if x, ok := _IPVersionValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return IPVersion(0), fmt.Errorf("%s is not a valid IPVersion, try [%s]", name, strings.Join(_IPVersionNames, ", "))
|
||||
return IPVersion(0), fmt.Errorf("%s is %w", name, ErrInvalidIPVersion)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -94,6 +96,8 @@ const (
|
|||
NetProtocolHttps
|
||||
)
|
||||
|
||||
var ErrInvalidNetProtocol = fmt.Errorf("not a valid NetProtocol, try [%s]", strings.Join(_NetProtocolNames, ", "))
|
||||
|
||||
const _NetProtocolName = "tcp+udptcp-tlshttps"
|
||||
|
||||
var _NetProtocolNames = []string{
|
||||
|
@ -134,7 +138,7 @@ func ParseNetProtocol(name string) (NetProtocol, error) {
|
|||
if x, ok := _NetProtocolValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return NetProtocol(0), fmt.Errorf("%s is not a valid NetProtocol, try [%s]", name, strings.Join(_NetProtocolNames, ", "))
|
||||
return NetProtocol(0), fmt.Errorf("%s is %w", name, ErrInvalidNetProtocol)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -174,6 +178,8 @@ const (
|
|||
QueryLogTypeCsvClient
|
||||
)
|
||||
|
||||
var ErrInvalidQueryLogType = fmt.Errorf("not a valid QueryLogType, try [%s]", strings.Join(_QueryLogTypeNames, ", "))
|
||||
|
||||
const _QueryLogTypeName = "consolenonemysqlpostgresqlcsvcsv-client"
|
||||
|
||||
var _QueryLogTypeNames = []string{
|
||||
|
@ -223,7 +229,7 @@ func ParseQueryLogType(name string) (QueryLogType, error) {
|
|||
if x, ok := _QueryLogTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return QueryLogType(0), fmt.Errorf("%s is not a valid QueryLogType, try [%s]", name, strings.Join(_QueryLogTypeNames, ", "))
|
||||
return QueryLogType(0), fmt.Errorf("%s is %w", name, ErrInvalidQueryLogType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -254,6 +260,8 @@ const (
|
|||
StartStrategyTypeFast
|
||||
)
|
||||
|
||||
var ErrInvalidStartStrategyType = fmt.Errorf("not a valid StartStrategyType, try [%s]", strings.Join(_StartStrategyTypeNames, ", "))
|
||||
|
||||
const _StartStrategyTypeName = "blockingfailOnErrorfast"
|
||||
|
||||
var _StartStrategyTypeNames = []string{
|
||||
|
@ -294,7 +302,7 @@ func ParseStartStrategyType(name string) (StartStrategyType, error) {
|
|||
if x, ok := _StartStrategyTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return StartStrategyType(0), fmt.Errorf("%s is not a valid StartStrategyType, try [%s]", name, strings.Join(_StartStrategyTypeNames, ", "))
|
||||
return StartStrategyType(0), fmt.Errorf("%s is %w", name, ErrInvalidStartStrategyType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -140,6 +140,9 @@ caching:
|
|||
# Max number of domains to be kept in cache for prefetching (soft limit). Useful on systems with limited amount of RAM.
|
||||
# Default (0): unlimited
|
||||
prefetchMaxItemsCount: 0
|
||||
# Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results.
|
||||
# Default: 30m
|
||||
cacheTimeNegative: 30m
|
||||
|
||||
# optional: configuration of client name resolution
|
||||
clientLookup:
|
||||
|
|
|
@ -514,7 +514,7 @@ With following parameters you can tune the caching behavior:
|
|||
| caching.prefetchExpires | duration format | no | 2h | Prefetch track time window |
|
||||
| caching.prefetchThreshold | int | no | 5 | Name queries threshold for prefetch |
|
||||
| caching.prefetchMaxItemsCount | int | no | 0 (unlimited) | Max number of domains to be kept in cache for prefetching (soft limit). Default (0): unlimited. Useful on systems with limited amount of RAM. |
|
||||
| caching.cacheTimeNegative | duration format | no | 30m | Time how long negative results are cached. A value of -1 will disable caching for negative results. |
|
||||
| caching.cacheTimeNegative | duration format | no | 30m | Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results. |
|
||||
|
||||
!!! example
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
ListCacheTypeWhitelist
|
||||
)
|
||||
|
||||
var ErrInvalidListCacheType = fmt.Errorf("not a valid ListCacheType, try [%s]", strings.Join(_ListCacheTypeNames, ", "))
|
||||
|
||||
const _ListCacheTypeName = "blacklistwhitelist"
|
||||
|
||||
var _ListCacheTypeNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseListCacheType(name string) (ListCacheType, error) {
|
|||
if x, ok := _ListCacheTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return ListCacheType(0), fmt.Errorf("%s is not a valid ListCacheType, try [%s]", name, strings.Join(_ListCacheTypeNames, ", "))
|
||||
return ListCacheType(0), fmt.Errorf("%s is %w", name, ErrInvalidListCacheType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
FormatTypeJson
|
||||
)
|
||||
|
||||
var ErrInvalidFormatType = fmt.Errorf("not a valid FormatType, try [%s]", strings.Join(_FormatTypeNames, ", "))
|
||||
|
||||
const _FormatTypeName = "textjson"
|
||||
|
||||
var _FormatTypeNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseFormatType(name string) (FormatType, error) {
|
|||
if x, ok := _FormatTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return FormatType(0), fmt.Errorf("%s is not a valid FormatType, try [%s]", name, strings.Join(_FormatTypeNames, ", "))
|
||||
return FormatType(0), fmt.Errorf("%s is %w", name, ErrInvalidFormatType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -91,6 +93,8 @@ const (
|
|||
LevelFatal
|
||||
)
|
||||
|
||||
var ErrInvalidLevel = fmt.Errorf("not a valid Level, try [%s]", strings.Join(_LevelNames, ", "))
|
||||
|
||||
const _LevelName = "infotracedebugwarnerrorfatal"
|
||||
|
||||
var _LevelNames = []string{
|
||||
|
@ -140,7 +144,7 @@ func ParseLevel(name string) (Level, error) {
|
|||
if x, ok := _LevelValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return Level(0), fmt.Errorf("%s is not a valid Level, try [%s]", name, strings.Join(_LevelNames, ", "))
|
||||
return Level(0), fmt.Errorf("%s is %w", name, ErrInvalidLevel)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
RequestProtocolUDP
|
||||
)
|
||||
|
||||
var ErrInvalidRequestProtocol = fmt.Errorf("not a valid RequestProtocol, try [%s]", strings.Join(_RequestProtocolNames, ", "))
|
||||
|
||||
const _RequestProtocolName = "TCPUDP"
|
||||
|
||||
var _RequestProtocolNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseRequestProtocol(name string) (RequestProtocol, error) {
|
|||
if x, ok := _RequestProtocolValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return RequestProtocol(0), fmt.Errorf("%s is not a valid RequestProtocol, try [%s]", name, strings.Join(_RequestProtocolNames, ", "))
|
||||
return RequestProtocol(0), fmt.Errorf("%s is %w", name, ErrInvalidRequestProtocol)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -106,6 +108,8 @@ const (
|
|||
ResponseTypeSPECIAL
|
||||
)
|
||||
|
||||
var ErrInvalidResponseType = fmt.Errorf("not a valid ResponseType, try [%s]", strings.Join(_ResponseTypeNames, ", "))
|
||||
|
||||
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTEREDNOTFQDNSPECIAL"
|
||||
|
||||
var _ResponseTypeNames = []string{
|
||||
|
@ -164,7 +168,7 @@ func ParseResponseType(name string) (ResponseType, error) {
|
|||
if x, ok := _ResponseTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return ResponseType(0), fmt.Errorf("%s is not a valid ResponseType, try [%s]", name, strings.Join(_ResponseTypeNames, ", "))
|
||||
return ResponseType(0), fmt.Errorf("%s is %w", name, ErrInvalidResponseType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -112,7 +112,7 @@ func (r *CachingResolver) onExpired(cacheKey string) (val interface{}, ttl time.
|
|||
if response.Res.Rcode == dns.RcodeSuccess {
|
||||
evt.Bus().Publish(evt.CachingDomainPrefetched, domainName)
|
||||
|
||||
return cacheValue{response.Res.Answer, true}, time.Duration(r.adjustTTLs(response.Res.Answer)) * time.Second
|
||||
return cacheValue{response.Res.Answer, true}, r.adjustTTLs(response.Res.Answer)
|
||||
}
|
||||
} else {
|
||||
util.LogOnError(fmt.Sprintf("can't prefetch '%s' ", domainName), err)
|
||||
|
@ -232,7 +232,7 @@ func (r *CachingResolver) putInCache(cacheKey string, response *model.Response,
|
|||
|
||||
if response.Res.Rcode == dns.RcodeSuccess {
|
||||
// put value into cache
|
||||
r.resultCache.Put(cacheKey, cacheValue{answer, prefetch}, time.Duration(r.adjustTTLs(answer))*time.Second)
|
||||
r.resultCache.Put(cacheKey, cacheValue{answer, prefetch}, r.adjustTTLs(answer))
|
||||
} else if response.Res.Rcode == dns.RcodeNameError {
|
||||
if r.cacheTimeNegative > 0 {
|
||||
// put return code if NXDOMAIN
|
||||
|
@ -249,7 +249,16 @@ func (r *CachingResolver) putInCache(cacheKey string, response *model.Response,
|
|||
}
|
||||
}
|
||||
|
||||
func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL uint32) {
|
||||
// adjustTTLs calculates and returns the max TTL (considers also the min and max cache time)
|
||||
// for all records from answer or a negative cache time for empty answer
|
||||
// adjust the TTL in the answer header accordingly
|
||||
func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL time.Duration) {
|
||||
var max uint32
|
||||
|
||||
if len(answer) == 0 {
|
||||
return r.cacheTimeNegative
|
||||
}
|
||||
|
||||
for _, a := range answer {
|
||||
// if TTL < mitTTL -> adjust the value, set minTTL
|
||||
if r.minCacheTimeSec > 0 {
|
||||
|
@ -264,10 +273,10 @@ func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL uint32) {
|
|||
}
|
||||
}
|
||||
|
||||
if maxTTL < a.Header().Ttl {
|
||||
maxTTL = a.Header().Ttl
|
||||
if max < a.Header().Ttl {
|
||||
max = a.Header().Ttl
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return time.Duration(max) * time.Second
|
||||
}
|
||||
|
|
|
@ -355,6 +355,7 @@ var _ = Describe("CachingResolver", func() {
|
|||
})
|
||||
|
||||
Describe("Negative cache (caching if upstream resolver returns NXDOMAIN)", func() {
|
||||
Context("Caching if upstream resolver returns NXDOMAIN", func() {
|
||||
When("Upstream resolver returns NXDOMAIN with caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeNameError
|
||||
|
@ -410,11 +411,42 @@ var _ = Describe("CachingResolver", func() {
|
|||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Context("Caching if upstream resolver returns empty result", func() {
|
||||
When("Upstream resolver returns empty result with caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeSuccess
|
||||
mockAnswer.Answer = make([]dns.RR, 0)
|
||||
})
|
||||
|
||||
It("response should be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeCACHED))
|
||||
g.Expect(resp.Reason).Should(Equal("CACHED"))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
// still one call to resolver
|
||||
g.Expect(m.Calls).Should(HaveLen(1))
|
||||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Not A / AAAA queries should also cached", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Not A / AAAA queries should also be cached", func() {
|
||||
When("MX query will be performed", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer, _ = util.NewMsgWithAnswer("google.de.", 180, dns.Type(dns.TypeMX), "10 alt1.aspmx.l.google.com.")
|
||||
|
|
Loading…
Reference in New Issue