dns-lattice
Programmable Rust DNS control plane for the Lattice networking stack: split DNS, Fake IP, address pools, and dynamic routing hooks.
What it provides
For new code, prefer the canonical domain modules:
dns_lattice::model— DNS messages, names, matcher, and split-DNS policy;dns_lattice::engine—Resolverquery orchestration, cache, and upstream failover;dns_lattice::upstream— the outbound backend trait and transports;dns_lattice::server— inbound listener construction and lifecycle;dns_lattice::fakeip— synthetic-address pool, policy, and snapshots.
The existing flat root aliases remain compatible.
dns-lattice-model's DNS message model (Message,Header,Question,ResourceRecord,RData), zone/domain matcher (DomainPattern,DomainMatcher), and split-DNS policy types (SplitDnsPolicy), re-exported through this facade crate.dns-lattice-core'sError/Resultpair.- A synchronous, concurrent
fakeipmodule (FakeIpPool,FakeIpPoolBuilder): configure one or both inclusive synthetic IPv4/IPv6 ranges, allocate or reuse one address per DNS name, and reverse-resolve an address to its currently assigned name. Allocation uses a family-salted deterministic hash and circular probing; a full family evicts its LRU mapping. Mappings have a required whole-second TTL and caller-owned, process-local in-memory snapshots can restore their live entries and LRU order. The pool itself performs no socket I/O or durable persistence. - An in-process resolver entry point (
Resolver,ResolverBuilder): route one query through aSplitDnsPolicyto an upstream group, then try that group's registered backends in registration order — a backend failing with a timeout/transport/TLS error falls over to the next backend in the group, and the first success is cached in memory (TTL-respecting, with RFC 2308 negative caching) so a repeated query is served without re-querying the backend. Once every backend in the group has failed, the last attempted backend's error is returned as-is.ResolverBuilder::fake_ipexplicitly adds local Fake IP behavior: matching IN A/AAAA questions allocate or reuse a synthetic address, while canonical IN PTR questions in the configured ranges return a live mapping or NXDOMAIN. A selected but disabled A/AAAA family returns local NODATA. These answers bypass the ordinary cache and upstreams and use the mapping's remaining lifetime as their DNS TTL.Resolver::resolveisasync fnand must be called from inside atokioruntime. - A public, async
upstreammodule (UpstreamBackendtrait,UdpBackend,TcpBackend): baseline UDP and TCP upstream transports, no EDNS0/OPT support yet (UdpBackendfalls back to a TCP query when a response'sTCbit is set). - Three opt-in, default-off Cargo features adding encrypted upstream
transports to the same
upstreammodule:dot(DotBackend/DotBackendConfig, DNS-over-TLS, RFC 7858, overrustls/tokio-rustls),doh(DohBackend/DohBackendConfig/DohMethod, DNS-over-HTTPS, RFC 8484, GET and POST wire formats, overhyper/hyper-rustls), anddoq(DoqBackend/DoqBackendConfig, DNS-over-QUIC, RFC 9250, overquinn, with TLS 1.3 embedded in QUIC viarustls).doqopens a fresh QUIC connection per query in this stage — no connection pooling/reuse yet. - A public, async
servermodule (Server,ServerBuilder): an embeddable inbound UDP/TCP DNS listener over a sharedArc<Resolver>.ServerBuilder::new(resolver)plusudp_addr/tcp_addrconfigure one or more listen addresses,bindperforms the actual socket binds, andserve/serve_untilrun the UDP receive loop and TCP accept loop concurrently — onetokiotask per received UDP datagram, one per accepted TCP connection (looping over multiple RFC 1035 §4.2.2 length-prefixed queries per connection). Oversized UDP answers are truncated withTC=1set at the existing 512-byte boundary; aResolver::resolveerror is answered with a synthesizedRcode::ServFailresponse instead of being dropped or crashing the listener. Behind the default-offdotCargo feature,ServerBuilder::dot_addr(addr, tls_config)adds an inbound DNS-over-TLS (RFC 7858) listener: it TLS-accepts each connection viatokio_rustls::TlsAcceptor(caller-suppliedrustls::ServerConfig) and then reuses the exact same length-prefixed read/write loop as the plain TCP listener. Behind the default-offdoqCargo feature,ServerBuilder::doq_addr(addr, server_config)adds an inbound DNS-over-QUIC (RFC 9250) listener: aquinn::Endpointin server mode (ALPNdoq) answers one query per accepted bidirectional stream, reusing the same framing helpers asDoqBackend's client side. Behind the default-offdohCargo feature,ServerBuilder::doh_addr(addr, tls_config, config)adds an inbound DNS-over-HTTPS (RFC 8484) listener: it TLS-accepts each TCP connection likedot_addrover TLS 1.2 or 1.3, then serves the ALPN-negotiated HTTP/1.1 or HTTP/2 protocol viahyper_util's protocol-detecting server builder, parsing RFC 8484 GET (?dns=base64url query parameter) and POST (application/dns-messagebody) requests. A dual-protocol deployment configuresh2andhttp/1.1in itsrustls::ServerConfigALPN list.config(aDohListenerConfig, defaulting to the/dns-querypath) selects which URI path the listener answers; any other path gets HTTP 404, an unsupported method or undecodable request gets HTTP 400, and everything else is answered HTTP 200 with anapplication/dns-messagebody — including a synthesizedRcode::ServFailon a resolver error, matching every other transport's error policy.ServerBuilder::doh3_addr(addr, quinn_config, config)separately binds HTTP/3 over QUIC/UDP with ALPNh3and TLS 1.3. Keepdoh_addrfor HTTP/1.1/HTTP/2 legacy TCP clients on TLS 1.2 or 1.3.
Dynamic routing hooks are planned for a later stage; see ROADMAP.md in the
repository root.
Feature/platform constraints
- Default build: no Cargo features enabled. Carries no TLS/HTTP dependency
weight — only
dns-lattice-core,dns-lattice-model,async-trait, andtokio(withnet/time/rt/macros/io-utilonly). dotfeature: addsrustls,rustls-pki-types,tokio-rustls, andwebpki-rootsas dependencies. Independent ofdoh; enable only this feature to useDotBackendwithout pulling in an HTTP client.dohfeature: addsrustls,rustls-pki-types,tokio-rustls,hyper,hyper-util,hyper-rustls,http,http-body-util,bytes, andbase64as dependencies. Independent ofdot; enable only this feature to useDohBackendwithout pulling in raw TLS-over-TCP framing you don't use directly.doqfeature: addsrustls,rustls-pki-types,webpki-roots, andquinnas dependencies. Independent ofdot/doh; enable only this feature to useDoqBackend/ServerBuilder::doq_addrwithout pulling intokio-rustls/hyper.quinn'srustlscrypto-provider feature is set torustls-aws-lc-rs, matching the workspacerustlsdependency's ownaws-lc-rsfeature (enabled directly onrustlsitself, not left to arrive only transitively viadoh/doq— every TLS handshake needs a process-levelCryptoProvidereven with justdotenabled on its own).dot,doh, anddoqall userustls(pure-Rust TLS, no OpenSSL/ platform-TLS dependency) uniformly on Linux, Windows, and macOS — no platform-specific behavior. Cross-platform: nocfg-gated logic in any backend.- None of the three features require elevated privileges; all perform ordinary outbound TLS/HTTPS/QUIC client connections.
Usage
use ;
let policy = builder
.rule
.build;
let name = from_ascii.unwrap;
assert_eq!;
use ;
use ;
let pool = builder
.ipv4_range
.ttl
.build?;
let address = pool.allocate_ipv4?;
assert_eq!;
let snapshot = pool.snapshot;
let restored = restore?;
assert_eq!;
# Ok::
use Arc;
use ;
# let pool = new;
let fake_ip_policy = builder
.rule
.build;
let resolver = builder
.fake_ip
.build;
# let _ = resolver;
# Ok::
Status
Version 0.4.0 is published and this crate remains pre-1.0: its public API
may change before the first stable release. Stage 0.1 (core model)
landed the DNS message/matcher/policy model above; stage 0.2 landed the
resolver's construct/resolve lifecycle, static split-DNS routing, and its
in-memory TTL/negative-caching answer cache; stage 0.3 landed
the public async upstream trait, baseline UDP/TCP backends, the opt-in
dot/doh/doq encrypted-transport backends described above, failover
across a group's registered backends, and the server module's
embeddable inbound UDP/TCP listener (Server/ServerBuilder) plus its
opt-in dot/doh/doq-gated inbound DoT/DoH/DoQ listeners
(ServerBuilder::dot_addr/doh_addr/doq_addr). Published Stage 0.4 adds
the opt-in Fake IP resolver behavior above, TTL expiry, and caller-owned
process-local snapshot/restore; it does not add durable persistence. Dynamic
routing hooks are not implemented yet. Types may change without notice until
the first stable release.