dns-lattice
Programmable, embeddable DNS resolver/server engine for Rust: split DNS, TTL-aware caching, Fake IP, dynamic route selection, structured observability, and UDP/TCP/DoT/DoH/DoQ transports.
This is the recommended application-facing crate in the DNS Lattice workspace. It re-exports the protocol/model and shared error layers through canonical domain modules and contains the resolver/server runtime implementation.
Installation
Baseline UDP/TCP:
[]
= "1.0"
= { = "1", = ["rt-multi-thread", "macros"] }
Encrypted DNS transports are opt-in:
[]
= { = "1.0", = ["dot", "doh", "doq"] }
Features are independent and default-off:
dot— DNS-over-TLS;doh— DNS-over-HTTPS over HTTP/1.1, HTTP/2, and HTTP/3;doq— DNS-over-QUIC.
MSRV: Rust 1.93.
Public surface
Use canonical domain modules; flat root aliases are intentionally not exposed.
dns_lattice::core— sharedError/Result;dns_lattice::model— DNS messages, records, names, domain matcher, split-DNS policy, upstream-group identifiers;dns_lattice::engine—Resolver/ResolverBuilder;dns_lattice::upstream—UpstreamBackendand outbound transports;dns_lattice::server—Server/ServerBuilderand inbound listeners;dns_lattice::fakeip— synthetic-address pool, policy, TTL, snapshots;dns_lattice::hooks— dynamic route-selection hook;dns_lattice::observability— structured resolver event sink.
Resolver pipeline
For ordinary queries the resolver executes:
static split-DNS candidate
→ optional RouteHook
→ validate effective upstream group
→ cache scoped to that group
→ ordered upstream failover
→ answer
Fake IP is a terminal path before ordinary routing/cache/upstreams when the
configured FakeIpPolicy selects the query.
Cache identity
The in-memory answer cache respects DNS TTLs and negative caching. Ordinary cache identity includes the effective upstream group. Equal DNS questions routed to different groups cannot share an answer.
Quick start
use ;
use ;
# async
Resolver owns routing/cache/failover. Server owns inbound listening and
protocol framing. UpstreamBackend implementations own outbound transport
execution.
Dynamic route hook
ResolverBuilder::route_hook accepts one caller-owned hooks::RouteHook.
The hook receives the first DNS question and tentative static group:
RouteDecision::Use(group)selects a registered, nonempty group;RouteDecision::Abstainpreserves the static candidate.
A hook error, unknown selected group, or empty selected group returns a resolver error without cache/upstream fallback. Hooks are selection-only and receive no resolver/backend handles, client transport metadata, or OS/network side-effect authority. Hook implementations own timeout, retry, cancellation cleanup, and external integration.
use async_trait;
use ;
;
Do not re-enter the same resolver from its route hook.
Fake IP
FakeIpPool provides deterministic concurrent synthetic-address state with:
- optional inclusive IPv4 and IPv6 ranges;
- deterministic domain → address allocation/reuse;
- reverse lookup of active mappings;
- per-family LRU eviction when a range is full;
- required whole-second TTL and expiry;
- caller-owned process-local in-memory snapshot/restore.
ResolverBuilder::fake_ip(pool, policy) makes synthesis explicit:
- matching IN A/AAAA → local synthetic response;
- selected but disabled family → local NODATA;
- canonical PTR inside a configured range → active mapping or NXDOMAIN.
These answers bypass the ordinary answer cache and upstreams, and their DNS TTL never exceeds the mapping's remaining lifetime.
The crate deliberately does not serialize snapshots or provide durable Fake IP persistence.
Observability
ResolverBuilder::observability_sink accepts an optional
observability::ObservabilitySink. Events cover query receipt, Fake IP
terminal behavior, route/hook decisions, cache hit/miss, upstream attempts and
outcomes, timeouts, and terminal failures.
The sink is synchronous and non-authoritative:
- events are immutable and bounded;
- callbacks cannot modify routing, cache state, retries, or answers;
- callbacks receive no resolver/backend handles;
- resolver locks are released before callbacks run;
- callback panics are isolated from resolver correctness;
- the crate does not require a logging/tracing framework or own a background telemetry queue.
Upstream transports
UpstreamBackend is async. Backends registered for one upstream group are
tried in registration order. Timeout/transport/TLS failures can fall over to
the next backend. If all fail, the last error is returned.
| Transport | Feature | Notes |
|---|---|---|
| UDP | default | Falls back to TCP when TC=1 |
| TCP | default | RFC 1035 framed DNS |
| DoT | dot |
rustls / tokio-rustls |
| DoH HTTP/1.1 + HTTP/2 | doh |
hyper / hyper-rustls |
| DoH HTTP/3 | doh |
h3 / quinn, ALPN h3, TLS 1.3 |
| DoQ | doq |
quinn, ALPN doq, TLS 1.3 |
Encrypted features are default-off so applications using only UDP/TCP do not inherit TLS/HTTP/QUIC dependency weight.
Inbound server
ServerBuilder embeds a shared Arc<Resolver> and supports:
- UDP/TCP in the baseline build;
dot_addrwithdotfor DoT;doh_addrwithdohfor HTTP/1.1/HTTP/2 DoH;doh3_addrwithdohfor HTTP/3 DoH;doq_addrwithdoqfor DoQ.
The host provides TLS/QUIC server configuration and certificate material. Binding privileged ports, configuring the OS resolver, and provisioning certificates remain host responsibilities.
Platform and validation contract
The supported surface is validated on Linux, Windows, and macOS. CI runs the workspace format/lint/check/test/doc gates and strict facade check/test/rustdoc for:
--no-default-features
dot
doh
doq
--all-features
CI also lists workspace package contents and runs the hermetic release automation regression. Validation does not publish crates.
Safety and responsibility boundaries
This crate performs ordinary socket/TLS/QUIC networking but does not mutate OS DNS configuration or manage TUN/TAP devices. Those responsibilities belong to the host application or sibling Lattice components.
Route hooks and observability sinks do not receive privileged runtime handles from DNS Lattice. Applications that intentionally perform side effects from their own hook/sink implementations are responsible for those effects.
Status
Stable 1.x releases are published on crates.io. Stages 0.0 through 1.0
are complete: Fake IP, dynamic route hooks, structured observability,
cross-platform feature validation, deterministic hardening coverage,
package/release regression checks, full rustdoc coverage, and the
public-API freeze audit are all done.
Within the 1.x line, ordinary SemVer now applies: additive changes are
minor releases, fixes are patch releases, and a breaking change requires an
explicit major version bump.
Repository: https://github.com/F000NKKK/dns-lattice
License: MPL-2.0.