Hello everyone.
We have been working on improving Chute’s DNS system. Today I would like to introduce our implementation of DNS-over-TLS (DoT), a feature that significantly enhances DNS privacy and security.
What is DNS-over-TLS?
DNS-over-TLS, defined in RFC 7858, encrypts DNS queries between the client and the recursive resolver. Instead of sending DNS packets as plaintext UDP datagrams — visible to every network observer — DoT wraps DNS messages inside a TLS tunnel over TCP, typically on port 853.
Without DoT, your ISP or any network middlebox can see every domain you resolve. With DoT, only your chosen resolver knows what you are querying. It solves two problems at once: confidentiality (nobody can spy on your DNS traffic) and integrity (nobody can tamper with DNS responses).
How We Designed the DNS System
Chute runs a local DNS proxy on the device. Applications send DNS queries to our local UDP listener, and the DNS engine fans those queries out to upstream resolvers. We now support five upstream protocols through a unified API:
| Protocol | Transport | Port |
|---|---|---|
| Plain DNS | UDP | 53 |
| DNS-over-TLS | TCP + TLS | 853 |
| DNS-over-QUIC | QUIC | 853 |
| DNS-over-HTTPS | HTTP/2 | 443 |
| DNS-over-HTTP/3 | HTTP/3 | 443 |
Rather than having separate code paths for each DNS transport, they all share a common interface. The DNS engine does not care which protocol is used; it simply races queries across all configured upstreams and returns the first valid response.
DoT Configuration
You can enable DoT by adding a dot line in your configuration file:
1 | dot: dns.quad9.net, dns.cloudflare.com |
You can also assign a per-domain DoT resolver:
1 | example.internal = dot:dns.quad9.net |
And for servers behind CDNs where the TLS certificate does not match the IP:
1 | dns.example.com:resolver.example.com |
The second part is used as the TLS SNI, while the first part is the actual connect address.
The Framing Protocol
RFC 7858 specifies a simple two-octet length-prefixed framing over the TLS stream. Each DNS message must be preceded by a 2-byte big-endian length:
1 | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
Our DoT implementation alternates between reading exactly 2 bytes (the length prefix) and reading exactly N bytes (the DNS message body). After delivering a response, it immediately starts reading the next length prefix. This two-phase read eliminates the need for complex buffering — the socket layer handles partial reads transparently.
TLS and Connection Reuse
We use Apple’s Network.framework for TLS. Each DoT connection is pooled by host:port in a dictionary, so subsequent queries reuse the existing TLS session without a new handshake. Certificate verification is always enabled — if the resolver’s certificate fails validation, the query fails. There is no downgrade path.
When the network changes, we disconnect all cached sockets and force fresh connections. TLS sessions cannot survive network interface switches, and attempting session resumption across different IPs is unreliable in practice.
Multi-Protocol Fan-Out
DoT does not replace plain DNS — it runs alongside it. Queries go out to all configured DNS/DoT/DoH/DoQ/DoH3 servers in parallel, and whichever returns first wins. The result is cached. This means DoT adds privacy without compromising speed; if the DoT resolver is fast, it wins the race. If it is slow, plain DNS or DoH serves as a fallback.
With DoT, Chute closes one of the last plaintext leaks on the modern internet. Combined with a trusted resolver like Quad9 or Cloudflare, your DNS queries are now encrypted end-to-end.
If you have questions or suggestions, please reach out via Support.
Thanks.
Chute Devs