dns-update
Async Rust library for dynamic DNS updates across RFC 2136 and 70+ cloud, registrar, and self-hosted DNS provider APIs.
Repository Health
Technical Analysis
dns-update is an async Rust crate for programmatically updating DNS records, whether over the standards-based RFC 2136 protocol with TSIG authentication or through the REST and XML APIs of more than 70 cloud, registrar, and self-hosted DNS providers, including Cloudflare, AWS Route53, Azure DNS, Google Cloud DNS, DigitalOcean, and Hetzner. Every backend is exposed through the same DnsUpdater enum, so switching providers means changing one constructor call rather than rewriting integration code.
The library’s core operations, set_rrset, add_to_rrset, and remove_from_rrset, act on the full record set at a given name and type rather than on individual records, and are documented as idempotent. That makes it well suited to use cases like ACME DNS-01 challenge automation and dynamic IP DDNS clients that run repeatedly without accumulating duplicate or stale records.
What You Get
- A single DnsUpdater enum with a dedicated new_<provider> constructor for each of 70+ backends, from RFC 2136 TSIG to Cloudflare, Route53, Azure, Google Cloud DNS, and niche registrars like Porkbun and Namecheap
- Idempotent RRSet-oriented operations (set_rrset, add_to_rrset, remove_from_rrset) that manage the full record set at a name/type pair instead of requiring callers to diff and track individual record IDs
- Typed DnsRecord variants for A, AAAA, CNAME, NS, MX, TXT, SRV, TLSA, and CAA records, including structured CAARecord and TLSARecord encodings shared across every provider
- A pluggable crypto backend (aws-lc-rs by default, or ring) so consumers can align with whichever TLS stack the rest of their application already uses
- Shared HTTP client, JWT, and utility modules that every provider reuses for auth headers, retries on 429, TXT chunking, and FQDN handling
Common Use Cases
- Automating ACME DNS-01 challenge TXT records during certificate issuance without depending on a single DNS host
- Building dynamic DNS (DDNS) clients that keep A/AAAA records in sync with a changing public IP
- Writing infrastructure tooling that provisions or tears down DNS records across multiple provider accounts through one interface
- Self-hosted services (the crate is maintained by Stalwart Labs for its mail server) that need to publish or rotate DNS records like SPF, DKIM, and DMARC TXT records at runtime
Under The Hood
Architecture The DnsUpdater enum in src/lib.rs has one variant per backend, each wrapping a provider struct defined in its own src/providers/<name>.rs file. src/update.rs holds the new_<provider> constructors plus the three top-level async methods, set_rrset, add_to_rrset, and remove_from_rrset, which match over every variant and delegate to that provider’s own implementation. Providers share common building blocks: src/http.rs’s HttpClientBuilder/HttpClient wraps reqwest with header injection, timeout handling, and status-to-Error mapping; src/crypto.rs and src/jwt.rs supply HMAC/SHA/RSA/JWT signing gated on the ring vs aws-lc-rs feature; src/utils.rs centralizes FQDN handling, TXT chunking, and CAA/TLSA encoding reused across providers; src/bind.rs renders BIND zone files as a separate, non-dispatch output path. Adding a new backend is a documented recipe in the repo’s own CLAUDE.md: one provider file, a providers/mod.rs registration, a DnsUpdater variant, three match arms in update.rs, and a mirrored test file.
Tech Stack The crate targets Rust’s 2024 edition and builds on tokio (rt, net features) and reqwest 0.13 (http2, gzip, deflate, json), with the TLS and crypto backend selected via Cargo features, aws-lc-rs by default or ring as an alternative. hickory-net and hickory-proto implement the RFC 2136 DNS wire protocol and TSIG signing; quick-xml handles XML-RPC style provider APIs such as Route53; serde, serde_json, and serde_urlencoded cover JSON and form encoding; chrono, base64, and hex round out the small dependency set. Dev-dependencies mockito and httpmock (with an https feature) back the per-provider HTTP mock test suite. CI, defined in .github/workflows/test.yml, runs cargo fmt —check plus cargo test under both the aws-lc-rs and default ring feature sets, though the workflow currently triggers only manually rather than on every push or pull request.
Code Quality src/tests/ mirrors src/providers/ one to one, with 72 test files exercising each provider against mockito-mocked HTTP endpoints, plus ignored integration tests gated behind environment variables for hitting real provider APIs. Error handling is centralized through a single Error enum (Protocol, Parse, Client, Response, Api, Serialize, Unauthorized, NotFound, BadRequest, Unsupported) that every provider maps into rather than inventing its own variants. The project’s own CLAUDE.md documents an unusual but consistently applied convention of no inline comments, with rationale expected to live in commit messages instead, and funnels FQDN and record-encoding logic through shared utils rather than duplicating it per provider. No clippy or coverage step is visible in CI beyond formatting and tests.
API Design The public surface is a single non_exhaustive DnsUpdater enum with one new_<provider> constructor per backend, all returning a uniform Result<Self>, and three RRSet-oriented async methods, set_rrset, add_to_rrset, and remove_from_rrset, documented as idempotent and operating on the full record set at (name, type) rather than individual records. That spares callers from hand-managing record IDs or diffing existing state themselves, and provider selection is a single function call rather than a trait object or generic parameter. Every constructor carries a doc comment naming the exact API it wraps, and an IntoFqdn trait lets callers pass either &str or String hostnames interchangeably.