Releases: AZBrandCanada/Unified-DNS
Releases · AZBrandCanada/Unified-DNS
Release list
v1.1.1
[1.1.1] — 2026-09-18
This release is a large correctness and feature expansion. It fixes a set of
DNSSEC validation bugs that caused certain well-signed zones to be rejected,
adds two new transport protocols, introduces a bounded caching engine with
single-flight coalescing and background prefetch, adds an optional
geo-aware GSLB layer with an authenticated multi-node peer mesh, and adds a
JSON metrics endpoint.
Users upgrading from v1.0.x should read the Migration Notes section at
the bottom of this entry.
Added — Transports
- DNS-over-QUIC (DoQ), per RFC 9250. Binds to UDP/853 with ALPN
doq,
uses 2-octet length prefix framing, and returns RFC 9250 application reset
codes (0x2forDOQ_PROTOCOL_ERROR,0x4forDOQ_EXCESSIVE_LOAD). - DNS-over-HTTP/3 (DoH3), HTTP/3 over QUIC. Binds to UDP/443 with ALPN
h3, and serves the same/dns-query,/health, and/metricsroutes
as DoH. - Reverse-proxy DoH mode via
DOH_NO_TLS=1, allowing Nginx, Caddy, or
Envoy to terminate TLS and forward plain HTTP to the resolver.
Added — Caching Engine
- Bounded answer cache using W-TinyLFU admission and eviction. Capacity
is configurable viaCACHE_MAX_ENTRIES(default 500,000). Reads are
lock-free and sharded; unrelated keys do not contend. - Single-flight coalescing on the cache-miss path. Concurrent identical
{qname}:{qtype}:INmisses collapse into a single upstream resolution.
Unrelated queries remain fully parallel. - Background prefetch for hot records nearing expiration. A dedicated
loop considers only records with at leastCACHE_PREFETCH_MIN_HITS
hits, refreshes them in the lastCACHE_PREFETCH_THRESHOLD_PCTpercent
of their TTL, applies exponential backoff after failures, and never
invalidates a still-valid entry. - Cache-disabled test mode via
CACHE_ENABLED=0, allowing A/B
comparison between cached and uncached behavior. Single-flight remains
active in this mode. - Negative entry tagging (
EntryKind::Negative) to allow per-class
metrics and future per-class eviction policies. - Hit tracker (
HIT_TRACKER_MAX_ENTRIES, default 100,000) that scores
record popularity for prefetch decisions.
Added — Root Zone Lifecycle
- In-process root zone manager. Loads from a local JSON cache if
present, parses the local text file if not, and downloads from IANA in
the background if neither exists. Startup is never blocked on a network
fetch. - Periodic in-process refresh (
ROOT_ZONE_REFRESH_HOURS, default 168)
that atomically replaces all root-zone-sourced delegations without
touching dynamically-learned ones. No service restart required. - Root-zone-sourced delegation tagging (
DelegationSource::RootZone)
so that refreshes only replace entries the root zone manager owns. - Atomicity: if a refresh fails at any stage, the previous known-good
root zone remains in use. - Status reporting via
/metricswith explicitValid,
StaleButUsable,TooOld, andMissingstates.
Added — Metrics and Observability
- JSON
/metricsendpoint on both DoH and DoH3 with counters for
cache hits, misses, insertions, evictions, stale serves, negative cache,
single-flight leaders and coalesced requests, prefetch attempts and
successes, and root zone status. /healthendpoint returning a minimal liveness payload.
Added — Geo-Aware GSLB
- Authoritative GSLB layer that can answer a configured set of names
(e.g.dns.example.com) with region-appropriate A/AAAA records.
Decisions are computed per-request and never enter the recursive cache. - Unbounded node configuration via
NODE<N>_*environment variables.
Nodes are discovered by walkingNODE1,NODE2, … until an empty
NODE<N>_NAME. No hardcoded upper bound. - GeoIP lookup using a local MaxMind GeoLite2-City database. The
database path is configurable viaGEOIP_DATABASE. - EDNS Client Subnet (ECS) support (RFC 7871). When the upstream
resolver includes an ECS option, the subnet is used for the GeoIP lookup
in preference to the raw source IP. - Weighted node scoring combining geographic distance, measured
health-check RTT, and per-node health state. Nodes missing geo or
latency data degrade gracefully. - Local UDP health checks on a configurable interval
(GEO_HEALTH_INTERVAL), with a three-state machine
(Unknown/Healthy/Degraded/Unhealthy) and hysteresis. - Multi-IP failover via
GEO_IP_FAILOVER_IP. Setting it to 2 or more
returns that many A/AAAA records, ordered best-first and excluding any
node the health system has marked unavailable. This enables TCP-layer
client failover without requiring a DNS re-resolution. - Health-filter fallback: if every node is excluded by the health
system, the resolver returns all configured nodes rather than SERVFAIL
or a single node.
Added — Peer Mesh (Cross-Node Health)
- Authenticated inter-node heartbeat mesh for multi-node GSLB
deployments. Each node periodically POSTs a signed JSON payload to every
other node's/internal/peer-heartbeatendpoint. - Three-layer security: source-IP allowlist, HMAC-SHA256 signature
over the raw body, and a 60-second timestamp replay window. - Graceful exclusion semantics: a peer is excluded from GSLB selection
when its last heartbeat is older than5 × GEO_PEER_HEARTBEAT_INTERVAL,
or when it explicitly reportshealthy=false. The local node never
excludes itself. - Self-exclusion fix: the local node is not subject to peer mesh
exclusion regardless of heartbeat state. - Health-fallback tolerance: when all peers are excluded, the resolver
returns all configured nodes rather than failing closed.
Added — DNSSEC
- Manual TBS (To-Be-Signed) construction that does not rely on the
protocol library's built-in RRSIG serializer, which contains known
correctness issues in the pinned dependency version. - RFC 4034 §6.2 canonical name lowercasing, including names embedded
in RDATA (SOAmname/rname, CNAME, NS, MX, SRV, PTR). - Escaped-dot label handling for SOA
rnamefields like
disa\.tinker\.ie\.list\.dci-ii-dns.mail.mil.— labels are emitted by
iterating raw bytes rather than re-parsing the ASCII presentation form. - Empty non-terminal detection. Names like
go.jpthat exist only as
ancestors of other names are correctly distinguished from real insecure
delegations, based on the NS bit in the parent's NSEC/NSEC3 type bitmap. - NSEC3 Opt-Out handling per RFC 5155 §8.7. Opt-Out proofs are honored
for DS queries (proving insecure delegation) but not accepted as generic
proof that arbitrary records do not exist inside a signed zone. When an
opt-out NSEC3 covers the next-closer name for a non-DS query, the
response is treated as Insecure rather than Secure. - Wildcard RRSIG downgrade in NSEC3 zones. Wildcard answers in NSEC3
opt-out zones are conservatively downgraded from Secure to
InsecureUnknown because full RFC 5155 §8.8 wildcard proof validation is
not yet implemented. - Case-insensitive DNS name comparison (RFC 4343) throughout the
validator, chain builder, negative validation, and TBS construction.
Authoritative servers that return mixed-case owner names within a single
RRset no longer cause record-set filters to drop half the RRset.
Added — Cryptographic Backends
- Pure-Rust RSA/SHA-1 fallback for algorithm 5 and algorithm 7 DNSKEYs
whose modulus is below 2,048 bits. The optimizedringbackend rejects
these keys; the pure-Rustrsacrate does not. This restores validation
for legacy zones such asuk.com,eu.com,us.com,co.com,
de.com,uk.net,cmu.edu, and several*.miland*.go.jpzones. - Algorithm 7 (RSASHA1-NSEC3-SHA1) is now correctly mapped to the same
crypto path as algorithm 5, per RFC 5155 §2. The NSEC3 distinction only
affects the algorithm identifier and the denial-of-existence records, not
the RSA/SHA-1 signature computation. - Post-quantum ML-DSA-44 / DNSSEC Algorithm 18 verification continues
to be supported via the RustCryptoml-dsabackend.
Fixed — DNSSEC Validation
- Truncated TBS construction — the previous release used the protocol
library'sTBS::from_rrsig, which under the pinned version emits only
the RRSIG RDATA prefix and drops the RRset records entirely, producing a
26-byte TBS instead of ~61 bytes. Every signature verification failed
regardless of algorithm or key size. This affected all zones signed with
algorithm 7. - Uppercase owner names — authoritative servers returning
CMU.EDU.orAPPS.MIL.no longer cause signature verification to fail
because the canonical data is now lowercased per RFC 4034 §6.2. - SOA with escaped dots — SOA
rnamefields containing escaped dots
(common in.mil) no longer produce invalid wire encoding. - Empty non-terminals —
go.jpand similar names no longer terminate
the trust chain walk prematurely. - NSEC3 opt-out — wildcard and NXDOMAIN answers in opt-out zones no
longer produce a falseSecureverdict.
Fixed — Caching
- Thundering herd on cache miss — concurrent identical misses now
coalesce into a single upstream resolution. - Unbounded cache growth — the answer cache is now bounded by
W-TinyLFU admission rather than growing without limit. - Negative entries are now tagged and tracked separately, enabling
future per-class policies and per-class metrics.
Fixed — GSLB
- Self-exclusion bug — the local node was previously treated the same
as peers by the mesh exclusion logic, causing it to exclude itself when
no heartbeat had been received for its own name. It is now excluded from
pee...
v1.1.0
Included
- Iterative recursive resolution from the IANA root servers
- DNSSEC validation from the root trust anchors down to leaf RRSIGs
- RSA/SHA-256, RSA/SHA-512, ECDSA P-256, ECDSA P-384, and Ed25519
signature verification viaring - ML-DSA-44 (post-quantum DNSSEC Algorithm 18) verification via RustCrypto
- Plain DNS over UDP and TCP (port 53)
- DNS-over-TLS (RFC 7858, port 853)
- DNS-over-HTTPS (RFC 8484, port 443) with GET and POST support
- NSEC and NSEC3 authenticated denial of existence
- RFC 8767 stale-while-revalidate caching
- RFC 1982 DNSSEC timestamp arithmetic
- Per-subnet rate limiting (IPv4 /24, IPv6 /64)
- UDP duplicate-domain Response Rate Limiting with a TC=1 challenge
- SSRF protection via bailiwick and glue validation
- Reverse-proxy mode for DoH
- Self-signed development certificate generation
- Unprivileged port fallback (5053/8853/8443)
- Systemd unit example