Initial commit
This commit is contained in:
commit
6c66afc85f
25 changed files with 3049 additions and 0 deletions
127
checker/types.go
Normal file
127
checker/types.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Cap on per-NS error list so a flaky server with many addresses cannot
|
||||
// bloat the JSON observation payload.
|
||||
const maxNSResultErrors = 16
|
||||
|
||||
const ObservationKey = "authoritative-consistency"
|
||||
|
||||
type Severity string
|
||||
|
||||
const (
|
||||
SeverityInfo Severity = "info"
|
||||
SeverityWarn Severity = "warn"
|
||||
SeverityCrit Severity = "crit"
|
||||
)
|
||||
|
||||
// Stable identifiers; the UI keys translations and remediation docs off them.
|
||||
const (
|
||||
CodeSerialDrift = "authoritative_consistency_serial_drift"
|
||||
CodeSerialStaleVsSaved = "authoritative_consistency_serial_stale_vs_saved"
|
||||
CodeSerialAheadOfSaved = "authoritative_consistency_serial_ahead_of_saved"
|
||||
CodeNSUnreachable = "authoritative_consistency_ns_unreachable"
|
||||
CodeNSUDPFailed = "authoritative_consistency_ns_udp_failed"
|
||||
CodeNSTCPFailed = "authoritative_consistency_ns_tcp_failed"
|
||||
CodeNSUnresolvable = "authoritative_consistency_ns_unresolvable"
|
||||
CodeLame = "authoritative_consistency_lame"
|
||||
CodeNoSOA = "authoritative_consistency_no_soa"
|
||||
CodeNSRRsetDrift = "authoritative_consistency_ns_rrset_drift"
|
||||
CodeNSRRsetMismatchConfig = "authoritative_consistency_ns_rrset_mismatch_config"
|
||||
CodeParentDrift = "authoritative_consistency_parent_drift"
|
||||
CodeParentQueryFailed = "authoritative_consistency_parent_query_failed"
|
||||
CodeSOAFieldsDrift = "authoritative_consistency_soa_fields_drift"
|
||||
CodeSlowNS = "authoritative_consistency_slow_ns"
|
||||
CodeEDNSUnsupported = "authoritative_consistency_edns_unsupported"
|
||||
CodeTooFewNS = "authoritative_consistency_too_few_ns"
|
||||
CodeNoNS = "authoritative_consistency_no_ns"
|
||||
)
|
||||
|
||||
type Finding struct {
|
||||
Code string `json:"code"`
|
||||
Severity Severity `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
// Server is empty for zone-wide findings.
|
||||
Server string `json:"server,omitempty"`
|
||||
// Addr disambiguates IPv4/IPv6 issues on a multi-homed NS.
|
||||
Addr string `json:"addr,omitempty"`
|
||||
}
|
||||
|
||||
type NSResult struct {
|
||||
// Name is FQDN, lowercase.
|
||||
Name string `json:"name"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
ResolveError string `json:"resolve_error,omitempty"`
|
||||
UDPReachable bool `json:"udp_reachable"`
|
||||
TCPReachable bool `json:"tcp_reachable"`
|
||||
Authoritative bool `json:"authoritative"`
|
||||
// Zero when not reachable or the answer carries no SOA.
|
||||
Serial uint32 `json:"serial,omitempty"`
|
||||
// Full SOA RR kept for per-field comparison in the report.
|
||||
SOA *dns.SOA `json:"soa,omitempty"`
|
||||
NSRRset []string `json:"ns_rrset,omitempty"`
|
||||
EDNSSupported bool `json:"edns_supported"`
|
||||
// Zero when not reachable.
|
||||
LatencyMs int64 `json:"latency_ms,omitempty"`
|
||||
// Capped at maxNSResultErrors; appendError is the only intended writer.
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
suppressedErrors int
|
||||
}
|
||||
|
||||
// Dedupes identical messages and caps the list with a sentinel summary.
|
||||
func (n *NSResult) appendError(format string, args ...any) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
for _, e := range n.Errors {
|
||||
if e == msg {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(n.Errors) >= maxNSResultErrors {
|
||||
n.suppressedErrors++
|
||||
sentinel := fmt.Sprintf("(%d more error(s) suppressed)", n.suppressedErrors)
|
||||
if last := len(n.Errors) - 1; last >= 0 && len(n.Errors[last]) > 0 && n.Errors[last][0] == '(' {
|
||||
n.Errors[last] = sentinel
|
||||
return
|
||||
}
|
||||
n.Errors = append(n.Errors, sentinel)
|
||||
return
|
||||
}
|
||||
n.Errors = append(n.Errors, msg)
|
||||
}
|
||||
|
||||
type ObservationData struct {
|
||||
Zone string `json:"zone"`
|
||||
// HasSOA distinguishes Origin from NSOnlyOrigin and gates SOA-based rules.
|
||||
HasSOA bool `json:"has_soa"`
|
||||
// Zero when the service is an NSOnlyOrigin.
|
||||
DeclaredSerial uint32 `json:"declared_serial,omitempty"`
|
||||
DeclaredNS []string `json:"declared_ns,omitempty"`
|
||||
// Empty when parent discovery is disabled or failed (see ParentQueryError).
|
||||
ParentNS []string `json:"parent_ns,omitempty"`
|
||||
ParentQueryError string `json:"parent_query_error,omitempty"`
|
||||
// Union of DeclaredNS and ParentNS, de-duplicated.
|
||||
Probed []string `json:"probed,omitempty"`
|
||||
Results map[string]*NSResult `json:"results,omitempty"`
|
||||
Findings []Finding `json:"findings"`
|
||||
}
|
||||
|
||||
// Local mirror of happyDomain's services/abstract.Origin. Duplicated on
|
||||
// purpose to avoid pulling the entire happyDomain server module just to
|
||||
// decode the payload; miekg/dns marshals dns.SOA/dns.NS in the same shape.
|
||||
type originService struct {
|
||||
SOA *dns.SOA `json:"soa,omitempty"`
|
||||
NameServers []*dns.NS `json:"ns"`
|
||||
}
|
||||
|
||||
// Local mirror of happyDomain's ServiceMessage envelope.
|
||||
type serviceMessage struct {
|
||||
Type string `json:"_svctype"`
|
||||
Domain string `json:"_domain"`
|
||||
Service json.RawMessage `json:"Service"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue