DNS over HTTPS with cloudflared

In last weeks post I went over using Unbound to encrypt DNS traffic. The main downside to this was uncached DNS queries had a fairly high latency. In this post I'm going to look at an alternative approach using cloudflared.

Installing cloudflared

The cloudflared daemon is fairly straightforward to install, on CentOS it can be installed with yum:

yum install https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.rpm

Note: the RPM download link above was taken from CloudFlare's download page.

Once the cloudflared package is installed, you should be able to run cloudflared --version:

$ cloudflared --version
cloudflared version 2018.5.6 (built 2018-05-23-1637 UTC)

Running cloudflared

cloudflared is primarily used to connect to Argo Tunnel Servers, however it can also be used to proxy DNS traffic over HTTPS. This is done with the proxy-dns command:

$ cloudflared proxy-dns
WARN[0000] Cannot determine default configuration path. No file [config.yml config.yaml] in [~/.cloudflared ~/.cloudflare-warp ~/cloudflare-warp /usr/local/etc/cloudflared /etc/cloudflared]
INFO[0000] Adding DNS upstream                           url="https://1.1.1.1/dns-query"
INFO[0000] Adding DNS upstream                           url="https://1.0.0.1/dns-query"
INFO[0000] Starting DNS over HTTPS proxy server          addr="dns://localhost:53"
INFO[0000] Starting metrics server                       addr="127.0.0.1:37593"

There are also several options that can be used to control the DNS proxy. For example if you wanted to disable auto updates, and use Google as the upstream DNS you could use the following command:

cloudflared --no-autoupdate proxy-dns \
  --upstream https://dns.google.com/experimental?ct

Note: at the time of writing DNS over HTTPS is still a draft standard (draft-ietf-doh-over-https-07); and shouldn't be confused with Google's DNS-over-HTTPS API.

Daemon metrics

Once cloudflared is running, as well as providing a DNS endpoint, it also provides metrics over HTTP:

$ curl  127.0.0.1:34197/metrics |grep cpu_seconds
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.02

Configuring systemd

Unfortunately the cloudflared package only includes the binary; therefore it will not start at boot. This can be fixed by creating a custom unit file with the following contents:

[Unit]
Description=CloudFlare DNS proxy
Documentation=https://developers.cloudflare.com/1.1.1.1/dns-over-https/cloudflared-proxy/
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/cloudflared --no-autoupdate  proxy-dns
ProtectSystem=strict

[Install]
WantedBy=multi-user.target

Save the unit file as /etc/systemd/system/cloudflared-proxy-dns.service, and then run the following commands to enable and start the service:

systemctl daemon-reload
systemctl enable cloudflared-proxy-dns.service
systemctl start cloudflared-proxy-dns.service

Query latency

Like Unbound, cloudflared has to initially establish a TLS connection which introduces noticeable latency:

$ time host example.com 127.0.0.1
Using domain server:
Name: 127.0.0.1
Address: 127.0.0.1#53
Aliases:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

real    0m0.546s
user    0m0.004s
sys     0m0.002s

However unlike Unbound, the TLS connection is kept open, consequently subsequent queries are significantly faster because the TLS connection is reused:

$ time host example.org 127.0.0.1
Using domain server:
Name: 127.0.0.1
Address: 127.0.0.1#53
Aliases:

example.org has address 93.184.216.34
example.org has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

real    0m0.282s
user    0m0.002s
sys     0m0.005s