In a previous post, I tried different solutions for tunnelling DNS over TLS. One of those solutions was using a dedicated DNS-over-UDP fake service replying to all queries with the truncate flag set: this was causing the stub resolvers to retry the query using a TCP-based virtual-circuit. This solution is interesting because it is dead simple (it fits in a few line of codes) but it is clearly a hack. Here, I'm using a dedicated DNS forwarder aggregating all the incoming DNS-over-UDP requests over a single persistent TCP virtual-circuit.
The differents solutions presented in the previous post on the resolver side:
using unbound for TLS encapulation (but unbound does not check the remote certificate);
using a dedicated TLS encapulation tool such as stunnel and forcing the stub resolvers to use TCP;
using unbound for caching and UDP/TCP conversion and stunnel for TLS initiation.
The last solution is using this protocol stack:
DNSSEC valid. TLS Init. cache verify TLS [DNS]<->[DNS ]<------------------------>[DNS] [ ] [ ]<---->[ |TLS]<---------->[TLS] [TCP]<->[TCP ]<---->[TCP ]<---------->[TCP] [IP ]<->[IP ]<---->[IP ]<---------->[IP ] Stub R. Forwarder TLS Init. Internet Recursive (unbound) (stunnel)
However, each DNS request is using a new TCP connection between the stub resolver and unbound. Between unbound and stunnel, each each incoming TCP connection stream is encapsulated in a new TLS connection. This is very inefficient and the resulting DNS service is not very robust.
Performance considerations for DNS over TLS are summarized in the TLS for DNS: Initiation and Performance Considerations draft (emphasis mine):
Latency: Compared to UDP, DNS-over-TCP requires an additional round-trip-time […]. The TLS handshake adds another two RTTs of latency.
State: The use of connection-oriented TCP requires keeping additional state in both kernels and applications. […]
Processing: […] slightly higher CPU usage.
Number of connections: clients SHOULD minimize creation of new TCP connections. Use of a local DNS request aggregator (a particular type of forwarder) allows a single active DNS-over-TLS connection from any given client computer to its server.
DNS aggregation over a persistent TCP connection
In order to fix this problem, I wrote a prototype DNS forwarder which aggregates all the local UDP-based DNS messages over a persistent TCP stream:
all incoming datagram DNS requests are multiplexed over a shared persistent TCP connection;
when the answer is received it is forwarded to the correct UDP endpoint (identified using the identification field).
This service can then be coupled with a TLS initiator (
stunnel) which encapsulates the persistent DNS stream over TCP.
In the future, the tool might have an option to talk TLS natively. My excuse for not adding builtin support for TLS was that it gives you the freedom of choosing which TLS implementation you would like to use (OpenSSL with
socat, GnuTLS with
gnutls-serv and a tool such as
faucet, NSS but I do not know a suitable tool using this library, etc).
VC encap. DNSSEC valid. TLS Mux. cache TLS verify [DNS]<->[DNS ]<->[DNS ]<---------------------->[DNS] [TLS]<-------->[TLS] [UDP]<->[UDP|TCP]<->[TCP ]<---->[TCP ]<-------->[TCP] [IP ]<->[IP ]<->[IP ]<---->[IP ]<-------->[IP ] Client Aggregator Forwarder TLS Init. Internet Recursive (dnsfwd) (unbound) (stunnel)
The resulting DNS service is much more robust.
Warning: This software is currently a prototype. Use at your own risk.