checker-alias/checker/types.go
Pierre-Olivier Mercier c5c13960d5
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
checker: add dname_coexistence rule and refactor sibling probing
Extract querySiblings from observeCoexistence so both CNAME and DNAME
coexistence checks share the same parallel RRset scan. Add
observeDNAMECoexistence (called from Collect) that populates
AliasData.DNAMECoexistence for each DNAME node in DNAMESubstitutions.
Add the dname_coexistence rule (RFC 6672 §2.3) that flags any sibling
RRsets at a DNAME owner as CRIT, with matching tests.
2026-05-16 21:36:20 +08:00

104 lines
3.5 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
}
// 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.
Apex string `json:"apex,omitempty"`
ApexLookupError string `json:"apex_lookup_error,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"`
}