Add empty-error for detecting unusual empty responses

This commit is contained in:
Anuskuss
2025-03-24 23:33:45 +01:00
committed by GitHub
parent f3f16a6ac5
commit 9e94273084
6 changed files with 30 additions and 8 deletions

View File

@@ -111,6 +111,7 @@ type group struct {
// Failover/Failback options
ResetAfter int `toml:"reset-after"` // Time in seconds after which to reset resolvers in fail-back and random groups, default 0 (reset immediately).
ServfailError bool `toml:"servfail-error"` // If true, SERVFAIL responses are considered errors and cause failover etc.
EmptyError bool `toml:"empty-error"` // If true, empty responses that are unusual (such as NOERROR with no records instead of NXDOMAIN) are considered errors and cause failover etc.
// Cache options
Backend *cacheBackend

View File

@@ -370,12 +370,14 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
case "fail-rotate":
opt := rdns.FailRotateOptions{
ServfailError: g.ServfailError,
EmptyError: g.EmptyError,
}
resolvers[id] = rdns.NewFailRotate(id, opt, gr...)
case "fail-back":
opt := rdns.FailBackOptions{
ResetAfter: time.Duration(time.Duration(g.ResetAfter) * time.Second),
ServfailError: g.ServfailError,
EmptyError: g.EmptyError,
}
resolvers[id] = rdns.NewFailBack(id, opt, gr...)
case "fastest":
@@ -384,6 +386,7 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
opt := rdns.RandomOptions{
ResetAfter: time.Duration(time.Duration(g.ResetAfter) * time.Second),
ServfailError: g.ServfailError,
EmptyError: g.EmptyError,
}
resolvers[id] = rdns.NewRandom(id, opt, gr...)
case "blocklist":

View File

@@ -519,6 +519,7 @@ Options:
- `resolvers` - An array of upstream resolvers or modifiers.
- `servfail-error` - If `true`, a SERVFAIL response from an upstream resolver is considered a failure triggering a switch to the next resolver. This can happen when DNSSEC validation fails for example. Default `false`.
- `empty-error` - If `true`, a unusual empty reponse (such as NOERROR with no records instead of NXDOMAIN) from an upstream resolver is considered a failure triggering a switch to the next resolver. Default `false`.
#### Examples
@@ -539,8 +540,9 @@ Fail-Back groups are instantiated with `type = "fail-back"` in the groups sectio
Options:
- `resolvers` - An array of upstream resolvers or modifiers. The first in the array is the preferred resolver.
- `reset-after` - Time in seconds before switching from an alternative resolver back to the preferred resolver (first in the list), default 60. Note: This is not a timeout argument. After a failure of the preferred resolver, this defines the amount of time to use alternative/failover resolvers before switching back to the preferred. You can have as many resolvers in the array as the time limit allows.
- `reset-after` - Time in seconds before switching from an alternative resolver back to the preferred resolver (first in the list), default 0 (meaning it will switch after a single request). Note: This is not a timeout argument. After a failure of the preferred resolver, this defines the amount of time to use alternative/failover resolvers before switching back to the preferred. You can have as many resolvers in the array as the time limit allows.
- `servfail-error` - If `true`, a SERVFAIL response from an upstream resolver is considered a failure triggering a failover. This can happen when DNSSEC validation fails for example. Default `false`.
- `empty-error` - If `true`, a unusual empty reponse (such as NOERROR with no records instead of NXDOMAIN) from an upstream resolver is considered a failure triggering a switch to the next resolver. Default `false`.
#### Examples
@@ -561,8 +563,9 @@ Random groups are instantiated with `type = "random"` in the groups section of t
Options:
- `resolvers` - An array of upstream resolvers or modifiers.
- `reset-after` - Time in seconds to disable a failed resolver, default 60.
- `reset-after` - Time in seconds to disable a failed resolver, default 0 (disabled only for a single request).
- `servfail-error` - If `true`, a SERVFAIL response from an upstream resolver is considered a failure which will take the resolver temporarily out of the group. This can happen when DNSSEC validation fails for example. Default `false`.
- `empty-error` - If `true`, a unusual empty reponse (such as NOERROR with no records instead of NXDOMAIN) from an upstream resolver is considered a failure triggering a switch to the next resolver. Default `false`.
#### Examples

View File

@@ -36,6 +36,10 @@ type FailBackOptions struct {
// Determines if a SERVFAIL returned by a resolver should be considered an
// error response and trigger a failover.
ServfailError bool
// Determines if an empty reponse (that isn't NXDOMAIN) returned by a resolver
// should be considered an error respone and trigger a failover.
EmptyError bool
}
var _ Resolver = &FailBack{}
@@ -155,5 +159,7 @@ func (r *FailBack) startResetTimer() chan struct{} {
// Returns true is the response is considered successful given the options.
func (r *FailBack) isSuccessResponse(a *dns.Msg) bool {
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure)
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure) &&
!(r.opt.EmptyError && a.Rcode == dns.RcodeSuccess && len(a.Answer) == 0 ||
a.Rcode == dns.RcodeNameError && len(a.Answer) == 1 && a.Answer[0].Header().Rrtype == dns.TypeCNAME)
}

View File

@@ -27,6 +27,10 @@ type FailRotateOptions struct {
// Determines if a SERVFAIL returned by a resolver should be considered an
// error response and trigger a failover.
ServfailError bool
// Determines if an empty reponse (that isn't NXDOMAIN) returned by a resolver
// should be considered an error respone and trigger a failover.
EmptyError bool
}
var _ Resolver = &FailRotate{}
@@ -94,5 +98,7 @@ func (r *FailRotate) errorFrom(i int) {
// Returns true is the response is considered successful given the options.
func (r *FailRotate) isSuccessResponse(a *dns.Msg) bool {
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure)
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure) &&
!(r.opt.EmptyError && a.Rcode == dns.RcodeSuccess && len(a.Answer) == 0 ||
a.Rcode == dns.RcodeNameError && len(a.Answer) == 1 && a.Answer[0].Header().Rrtype == dns.TypeCNAME)
}

View File

@@ -30,14 +30,15 @@ type RandomOptions struct {
// Determines if a SERVFAIL returned by a resolver should be considered an
// error response and cause the resolver to be removed from the group temporarily.
ServfailError bool
// Determines if an empty reponse (that isn't NXDOMAIN) returned by a resolver
// should be considered an error respone and trigger a failover.
EmptyError bool
}
// NewRandom returns a new instance of a random resolver group.
func NewRandom(id string, opt RandomOptions, resolvers ...Resolver) *Random {
rand.Seed(time.Now().UnixNano())
if opt.ResetAfter == 0 {
opt.ResetAfter = time.Minute
}
return &Random{
id: id,
resolvers: resolvers,
@@ -121,5 +122,7 @@ func (r *Random) reactivateLater(resolver Resolver) {
// Returns true is the response is considered successful given the options.
func (r *Random) isSuccessResponse(a *dns.Msg) bool {
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure)
return a == nil || !(r.opt.ServfailError && a.Rcode == dns.RcodeServerFailure) &&
!(r.opt.EmptyError && a.Rcode == dns.RcodeSuccess && len(a.Answer) == 0 ||
a.Rcode == dns.RcodeNameError && len(a.Answer) == 1 && a.Answer[0].Header().Rrtype == dns.TypeCNAME)
}