# checker-resolver-propagation Worldwide DNS propagation checker for [happyDomain](https://www.happydomain.org/). Probes a curated catalog of public recursive resolvers (Cloudflare, Google, Quad9, OpenDNS, Yandex, regional ISPs, …) across multiple transports (UDP, TCP, DoT, DoH) and regions, then compares their answers to the zone's authoritative nameservers to detect propagation gaps, regional splits, SOA serial drift, stale caches, DNSSEC validation failures, SERVFAIL/NXDOMAIN inconsistencies, and resolver filtering. ## Usage ### Standalone HTTP server ```bash # Build and run make ./checker-resolver-propagation -listen :8080 ``` The server exposes: - `GET /health`: health check - `POST /collect`: collect propagation observations (happyDomain external checker protocol) - `POST /evaluate`: run the evaluation rules against an observation - `POST /report`: extract metrics / HTML report from an observation ### Docker ```bash make docker docker run -p 8080:8080 happydomain/checker-resolver-propagation ``` ### happyDomain plugin ```bash make plugin # produces checker-resolver-propagation.so, loadable by happyDomain as a Go plugin ``` The plugin exposes a `NewCheckerPlugin` symbol returning the checker definition and observation provider, which happyDomain registers in its global registries at load time. ### Versioning The binary, plugin, and Docker image embed a version string overridable at build time: ```bash make CHECKER_VERSION=1.2.3 make plugin CHECKER_VERSION=1.2.3 make docker CHECKER_VERSION=1.2.3 ``` ### happyDomain remote endpoint Set the `endpoint` admin option for the resolver-propagation checker to the URL of the running checker-resolver-propagation server (e.g., `http://checker-resolver-propagation:8080`). happyDomain will delegate observation collection to this endpoint. This checker applies to **service**-level checks and is restricted to the `abstract.Origin` and `abstract.NSOnlyOrigin` services (the zone apex / NS configuration). ## Options | Id | Type | Default | Description | |-----------------------|--------|-------------------------------|------------------------------------------------------------------------------------------------------------------------| | `recordTypes` | string | `SOA,NS,A,AAAA,MX,TXT,CAA` | Comma-separated list of RR types to probe at the apex (and at each `subdomains` entry). | | `subdomains` | string | `www` | Comma-separated list of owner names to probe in addition to the apex (e.g. `www,mail,@`). Empty = apex only. | | `includeFiltered` | bool | `false` | Probe filtering resolvers (malware/family/adblock). Their answers routinely diverge by design. | | `region` | string | `all` | Restrict to a region: `all`, `global`, `na`, `eu`, `asia`, `ru`, `me`. | | `transports` | string | `udp` | Comma-separated transports to probe: `udp`, `tcp`, `dot`, `doh`. Encrypted transports are only used where published. | | `resolverAllowlist` | string | | Comma-separated resolver IDs or IPs to probe exclusively (e.g. `cloudflare,google,9.9.9.9`). Empty = catalog selection.| | `latencyThresholdMs` | uint | `500` | Resolvers averaging above this value emit an info finding. | | `runTimeoutSeconds` | uint | `30` | Hard wall-clock budget for one propagation run. Slower resolvers report as unreachable. | ## Rules Each rule emits a finding code. Severity can be affected by the options above. | Code | Default severity | Condition | |-------------------------------|------------------|-----------| | `rprop_no_resolvers` | critical | The current option set selects no resolver from the catalog. | | `rprop_all_resolvers_down` | critical | Every selected resolver failed to answer (likely no DNS connectivity from the checker host). | | `rprop_resolver_unreachable` | warning | An individual resolver failed to answer within the run budget. | | `rprop_resolver_high_latency` | info | A resolver's average response time exceeds `latencyThresholdMs`. | | `rprop_resolver_filtered_hit` | info | A filtered resolver returned a different answer than the consensus (typical blocklist behaviour). Only when `includeFiltered` is enabled. | | `rprop_partial_propagation` | warning | Public resolvers disagree on the answer for a probed RRset. | | `rprop_answer_drift` | critical | The public consensus differs from the answer served by the zone's authoritative nameservers. | | `rprop_unexpected_nxdomain` | critical | Some resolvers return NXDOMAIN while others return NOERROR for the same RRset. | | `rprop_unexpected_servfail` | critical | A resolver returns SERVFAIL (usually a DNSSEC or reachability failure). | | `rprop_regional_split` | warning | Every resolver of a region agrees on an answer that differs from the global consensus. | | `rprop_serial_drift` | warning | Unfiltered resolvers disagree on the SOA serial. | | `rprop_stale_cache` | info | A resolver still serves an SOA serial below the one last observed by happyDomain. | | `rprop_dnssec_failure` | critical | A validating resolver fails to validate the zone's DNSSEC chain (returns SERVFAIL with AD/CD semantics). | | `rprop_dnssec_not_validated` | info | A validating resolver answered without setting AD on a signed zone. | ## License Licensed under the **MIT License** (see `LICENSE`).