Performance of DNS-over-TLS

Craig Younkins
5 min readFeb 5, 2022
Photo by oatsy40. CC BY 2.0. No changes made.

Traditional DNS is over UDP, a connectionless protocol with no setup. DNS-over-TLS (DoT) wraps DNS requests in a TLS connection, which itself goes over a TCP connection. Transport Layer Security (TLS) is the successor to Secure Sockets Layer (SSL), and is what secures most of today’s web browsing traffic.

In the context of the home or small business, using DNS-over-TLS with the local forwarding resolver on your router creates a secure connection between your router and the public recursive resolver such as Google’s Public DNS or Cloudflare DNS. The public resolver is authenticated with a signed TLS/SSL certificate and an encrypted tunnel is made.

Benefits of DNS-over-TLS

DNS-over-TLS is limited in scope compared to the end-to-end authentication provided by DNSSEC, but it is far easier to deploy incrementally and provides privacy benefits that DNSSEC does not.

From a security perspective, DNS-over-TLS prevents ISPs from injecting DNS responses with false information, such as to send you to a search page with ads for mistyped domains. It raises the bar for such man-in-the-middle attacks to requiring a faked TLS/SSL certificate - likely within reach for governments, but few others.

From a privacy perspective, DNS-over-TLS hides your DNS traffic from ISPs and mass surveillance sites between your router and the public resolver. However, connecting to the IP address retrieved via DNS gives wire snoopers a lot of information even if DNS is encrypted.

Performance

DNS is latency sensitive — it’s frequently in the critical path for web browsing — so it’s important that additional security has minimal impact on performance.

I tested resolution latency with and without DNS-over-TLS for the most popular forwarding resolvers:

  • dnsmasq, used in many consumer-grade routers, OpenWrt, and Pi-hole
  • unbound, used in pfSense
  • knot-resolver, used by Cloudflare for their public resolver (in recursive mode)

dnsmasq has no support for DNS-over-TLS by itself, but is commonly paired with stubby for this use case.

I’ll use the same framework that I used to analyze DNSSEC performance, this time with the top 2000 domains queried sequentially. Full project details can be found here: https://github.com/cyounkins/dns-forwarder-benchmark

No difference in median response time for unbound and knot-resolver, and a tiny increase for stubby!

But why is there no penalty? A traditional UDP DNS request requires 1 round-trip. In DNS-over-TLS, initiating a TLS connection requires 1 round-trip for the TCP connection, and a second round-trip for TLS v1.3. Then a third round-trip can be used for the actual DNS request and response.

The above data contradicts stubby's documentation that claims “[Unbound] opens a separate connection for every DNS query”. While that may have been true in older versions of Unbound, the above data clearly shows it’s no longer the case.

Since the framework records all network activity to and from the upstream server, we can process the log and count the number of opened TLS connections.

Over 2000 queries

The forwarding resolvers are only paying the TCP + TLS initialization penalty a small portion of the time. While unbound creates more than the others, it doesn’t create a new connection for every request.

Since the resolvers are only intermittently paying the connection penalty, using the median may not give us the best understanding of the data. Arithmetic mean is more affected by outliers, and the 95th percentile gives us a look at the long tail:

unbound and knot-resolver look great with virtually no change, but adding stubby to dnsmasq moderately affects long tail performance.

Public Resolver Differences

The above data were produced using Google Public DNS as the upstream, but upstream implementation differences could cause different performance. For example, if the upstream only allowed 1 DNS request per TLS connection, performance would be as bad as local unbound doing the same.

Qualys’ SSL Report indicates some differences in session resumption, and I observed differences in the TLS socket timeout. These will affect the time it takes to set up a TLS connection and how frequently the client has to do it.

Using my test framework I found that using Cloudflare upstream resulted in different numbers of opened TLS connections — Google appears to restrict the maximum queries per connection to around 200, whereas Cloudflare allowed all 2000 queries on a single TLS connection.

But… none of these upstream differences seem to matter. In my benchmarking, using Cloudflare produced very similar results to Google with no significant differences between UDP and DNS-over-TLS configurations.

I see no reason to not enable DNS-over-TLS. It provides some security and privacy benefits with virtually no performance impact, and can be used in conjunction with DNSSEC if you so choose.

To start using DNS-over-TLS, enable it in unbound, knot-resolver, or point dnsmasq to a local stubby daemon. For Pi-hole, you’ll probably want to set up unbound or stubby and point Pi-hole to that.

Notes

Performance of DNS-over-HTTPS should be approximately the same but wasn’t tested.

You may be interested in my analysis of the Performance of DNSSEC.

--

--

Craig Younkins

Hacker, entrepreneur, and quantified self nerd. cyounkins at gmail.