checker-alias/checker/types.go
Pierre-Olivier Mercier da6def100c
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
checker: report transient apex-lookup failures as Unknown, not Crit
apexLookupRule mapped every findApex failure to Crit, including transport
and resolver faults like "lookup nemunai.re on 127.0.0.11:53: server
misbehaving" — a flaky recursive resolver, not a broken delegation. That
made the check flap into Crit whenever the resolver hiccuped, the same
class of false negative the chain path already fixed.

Mark apex-lookup failures that stem from a transport/resolver fault
(resolveZoneNSAddrs net errors, recursiveExchange transport errors, and
SERVFAIL/REFUSED seen during the SOA walk) as transient via a typed
error, surface it as ApexLookupTransient, and have apexLookupRule report
Unknown for those. Definitive failures (NXDOMAIN-only walk, no resolvable
NS) still drive Crit.
2026-06-18 10:29:30 +09:00

111 lines
4 KiB
Go

package checker
import (
"encoding/json"
"github.com/miekg/dns"
)
const ObservationKeyAlias = "alias"
type AliasKind string
const (
KindCNAME AliasKind = "CNAME"
KindDNAME AliasKind = "DNAME"
KindALIAS AliasKind = "ALIAS" // provider-flattened apex alias, no real RR on the wire
KindTarget AliasKind = "TARGET"
)
type ChainHop struct {
Owner string `json:"owner"`
Kind AliasKind `json:"kind"`
Target string `json:"target,omitempty"`
TTL uint32 `json:"ttl,omitempty"`
Server string `json:"server,omitempty"`
Synthesized bool `json:"synthesized,omitempty"` // CNAME synthesized from DNAME
}
type CoexistingRRset struct {
Type string `json:"type"`
TTL uint32 `json:"ttl,omitempty"`
}
type TerminationReason string
const (
TermOK TerminationReason = "ok"
TermLoop TerminationReason = "loop"
TermTooLong TerminationReason = "too_long"
TermQueryErr TerminationReason = "query_error"
TermRcode TerminationReason = "rcode"
)
// ChainTermination is always populated after a walk; rules key off Reason.
type ChainTermination struct {
Reason TerminationReason `json:"reason"`
Subject string `json:"subject,omitempty"`
Detail string `json:"detail,omitempty"`
Rcode string `json:"rcode,omitempty"` // only with TermRcode
// Transient is meaningful with TermQueryErr: true when the query could not be
// completed because of a transport/resolver fault (could not observe), false
// when it stems from definitive evidence such as a target with no locatable apex.
Transient bool `json:"transient,omitempty"`
}
// AliasData carries raw facts only; judgement is delegated to the rules.
type AliasData struct {
Owner string `json:"owner"`
// Apex is empty iff the apex lookup failed; ApexLookupError explains why and
// ApexLookupTransient is true when the failure was a transport/resolver fault
// (could not observe) rather than definitive evidence the apex is missing.
Apex string `json:"apex,omitempty"`
ApexLookupError string `json:"apex_lookup_error,omitempty"`
ApexLookupTransient bool `json:"apex_lookup_transient,omitempty"`
AuthServers []string `json:"auth_servers,omitempty"`
Chain []ChainHop `json:"chain,omitempty"`
ChainTerminated ChainTermination `json:"chain_terminated"`
// FinalTarget is the last name in the chain, equal to Owner when there is
// no indirection.
FinalTarget string `json:"final_target,omitempty"`
FinalA []string `json:"final_a,omitempty"`
FinalAAAA []string `json:"final_aaaa,omitempty"`
FinalRcode string `json:"final_rcode,omitempty"`
// Coexisting is populated only when Owner has a CNAME.
Coexisting []CoexistingRRset `json:"coexisting,omitempty"`
// DNAMECoexistence maps each DNAME owner (from DNAMESubstitutions) to its sibling RRsets.
DNAMECoexistence map[string][]CoexistingRRset `json:"dname_coexistence,omitempty"`
OwnerIsApex bool `json:"owner_is_apex,omitempty"`
OwnerHasCNAME bool `json:"owner_has_cname,omitempty"`
// Apex* fields are populated only when OwnerIsApex.
ApexHasA bool `json:"apex_has_a,omitempty"`
ApexHasAAAA bool `json:"apex_has_aaaa,omitempty"`
ApexHasCNAME bool `json:"apex_has_cname,omitempty"`
ApexFlattening bool `json:"apex_flattening,omitempty"`
ZoneSigned bool `json:"zone_signed,omitempty"`
// CNAMESigCheckDone gates CNAMESigned: a false here means we never probed
// (zone unsigned or no CNAME), so CNAMESigned must not be interpreted.
CNAMESigCheckDone bool `json:"cname_sig_check_done,omitempty"`
CNAMESigned bool `json:"cname_signed,omitempty"`
DNAMESubstitutions []ChainHop `json:"dname_substitutions,omitempty"`
}
// cnameService mirrors happyDomain's svcs.CNAME / svcs.SpecialCNAME wire shape.
type cnameService struct {
Record *dns.CNAME `json:"cname"`
}
// serviceMessage mirrors happyDomain's ServiceMessage envelope.
type serviceMessage struct {
Type string `json:"_svctype"`
Domain string `json:"_domain"`
Service json.RawMessage `json:"Service"`
}