Initial commit
This commit is contained in:
commit
2d98ed1b5d
33 changed files with 4644 additions and 0 deletions
139
checker/rules_resolvers.go
Normal file
139
checker/rules_resolvers.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// resolverSelectionRule flags an empty selection (nothing to probe).
|
||||
type resolverSelectionRule struct{}
|
||||
|
||||
func (r *resolverSelectionRule) Name() string { return "resolver_propagation.selection" }
|
||||
func (r *resolverSelectionRule) Description() string {
|
||||
return "Checks that the current option set selects at least one public resolver."
|
||||
}
|
||||
func (r *resolverSelectionRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
if len(data.Resolvers) == 0 {
|
||||
return []sdk.CheckState{critState(CodeNoResolvers, data.Zone,
|
||||
"no resolvers match the current selection (region / filtered / allowlist), loosen the region filter or reset the allowlist")}
|
||||
}
|
||||
return []sdk.CheckState{passState("resolver_propagation.selection.ok",
|
||||
fmt.Sprintf("%d resolver(s) selected for probing", len(data.Resolvers)))}
|
||||
}
|
||||
|
||||
// resolversReachableRule flags the "no resolver answered" case.
|
||||
type resolversReachableRule struct{}
|
||||
|
||||
func (r *resolversReachableRule) Name() string { return "resolver_propagation.reachable" }
|
||||
func (r *resolversReachableRule) Description() string {
|
||||
return "Checks that at least one selected resolver answered a query (detects a checker host with no DNS connectivity)."
|
||||
}
|
||||
func (r *resolversReachableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
if len(data.Resolvers) == 0 {
|
||||
return []sdk.CheckState{{Status: sdk.StatusUnknown, Code: "resolver_propagation.reachable.skipped",
|
||||
Message: "no resolver in selection"}}
|
||||
}
|
||||
for _, rv := range data.Resolvers {
|
||||
if rv.Reachable {
|
||||
return []sdk.CheckState{passState("resolver_propagation.reachable.ok",
|
||||
fmt.Sprintf("%d/%d resolver(s) answered at least one query",
|
||||
data.Stats.ReachableResolvers, data.Stats.TotalResolvers))}
|
||||
}
|
||||
}
|
||||
return []sdk.CheckState{critState(CodeAllResolversDown, data.Zone,
|
||||
"no public resolver answered, the checker host may be offline, or DNS traffic is blocked on its network")}
|
||||
}
|
||||
|
||||
// resolverLatencyRule flags resolvers with high average latency.
|
||||
type resolverLatencyRule struct{}
|
||||
|
||||
func (r *resolverLatencyRule) Name() string { return "resolver_propagation.latency" }
|
||||
func (r *resolverLatencyRule) Description() string {
|
||||
return "Flags resolvers whose average response time exceeds the configured threshold."
|
||||
}
|
||||
func (r *resolverLatencyRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
threshold := int64(sdk.GetIntOption(opts, "latencyThresholdMs", 500))
|
||||
|
||||
var states []sdk.CheckState
|
||||
for _, rv := range data.Resolvers {
|
||||
if !rv.Reachable {
|
||||
states = append(states, warnState(CodeResolverUnreachable, rv.ID,
|
||||
fmt.Sprintf("resolver %s (%s, %s) did not answer any query", rv.Name, rv.IP, rv.Transport)))
|
||||
continue
|
||||
}
|
||||
var total, n int64
|
||||
for _, p := range rv.Probes {
|
||||
if p.Error != "" {
|
||||
continue
|
||||
}
|
||||
total += p.LatencyMs
|
||||
n++
|
||||
}
|
||||
if n > 0 {
|
||||
avg := total / n
|
||||
if avg > threshold {
|
||||
states = append(states, infoState(CodeResolverHighLatency, rv.ID,
|
||||
fmt.Sprintf("%s answered in %d ms on average (threshold %d ms)", rv.Name, avg, threshold)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(states) == 0 {
|
||||
return []sdk.CheckState{passState("resolver_propagation.latency.ok",
|
||||
"All reachable resolvers respond within the latency threshold.")}
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
// filteredHitRule notes when a filtered resolver returns a different answer
|
||||
// than the consensus (i.e. a likely blocklist hit).
|
||||
type filteredHitRule struct{}
|
||||
|
||||
func (r *filteredHitRule) Name() string { return "resolver_propagation.filtered_hit" }
|
||||
func (r *filteredHitRule) Description() string {
|
||||
return "Reports filtered resolvers returning a different answer than the consensus (typical blocklist behaviour)."
|
||||
}
|
||||
func (r *filteredHitRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errSt := loadData(ctx, obs)
|
||||
if errSt != nil {
|
||||
return []sdk.CheckState{*errSt}
|
||||
}
|
||||
var states []sdk.CheckState
|
||||
for _, rv := range data.Resolvers {
|
||||
if !rv.Filtered {
|
||||
continue
|
||||
}
|
||||
for key, p := range rv.Probes {
|
||||
if p == nil || p.Error != "" || p.Rcode != "NOERROR" {
|
||||
continue
|
||||
}
|
||||
rv2 := data.RRsets[key]
|
||||
if rv2 == nil || rv2.ConsensusSig == "" {
|
||||
continue
|
||||
}
|
||||
if p.Signature != rv2.ConsensusSig {
|
||||
states = append(states, infoState(CodeResolverFilteredHit, rv.ID+" "+key,
|
||||
fmt.Sprintf("%s (filtered) returned a different answer than the consensus for %s, likely a blocklist hit", rv.Name, key)))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(states) == 0 {
|
||||
return []sdk.CheckState{passState("resolver_propagation.filtered_hit.ok",
|
||||
"No filtered resolver deviates from the consensus (no blocklist hit detected).")}
|
||||
}
|
||||
return states
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue