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
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.- 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.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.
Fake IP and dynamic routing hook capabilities are planned for later
stages; 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!;
Status
Pre-0.1 stage: this crate has no stable API yet. 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 is in final
verification and has 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). Fake IP and dynamic
routing hooks are not implemented yet (stage 0.4/0.5). Types may change
without notice until the first stable release.