You are here: Home > Publications > RIPE Labs > Stéphane Bortzmeyer > Quad9, a Public DNS Resolver - with Security

Quad9, a Public DNS Resolver - with Security

Stéphane Bortzmeyer — 21 Nov 2017
Quad9 is a public DNS resolver, with promises of better privacy, and a DNS-over-TLS access.

 

Last week, the new DNS resolver Quad9 has been announced. It is a public DNS resolver with the additional benefit that it is accessible in a secure way over TLS (RFC 7858).

There are plenty of public DNS resolvers. The best known one is Google Public DNS, but there are many others, each of them with different policies and technical features. The fact that so many users blindly use Google Public DNS, despite the huge amount of data that Google already collects about us, is worrisome. But there is also a technical problem, common to most public resolvers: the link to them is not secure. This allows hijackings, as seen in Turkey, as well as third-party monitoring.

The new Quad9 service on the other hand is operated by the not-for-profit Packet Clearing House (PCH), which manages large parts of the DNS infrastructure, and it allows access to the DNS over TLS. This makes it very difficult for third parties to listen in. And it makes it possible to authenticate the resolver (I have not yet tested this yet, Quad9 does not seem to distribute its public keys in an authenticated way, but they plan to do so).

Note that Quad9 claims not to store your IP address. Also note that their resolver is sometimes lying: it does not (deliberately) provide an answer for domain names that are considered to be related to harmful activities like malware distribution. You can have a non-lying resolver by using other addresses, but then you lose DNSSEC. In that case Quad9 uses an indication of the client's network (see RFC 7871), a bad privacy practice. Hopefully we will soon have an address for non-false responses, with DNSSEC and without indication of the customer's network.

Now, let's move on to practice on a Unix machine. The IPv4 address of Quad9, as the name implies, is 9.9.9.9. Its IPv6 address is 2620:fe::fe (see the FAQ). First, let's start with classic UDP access:

% dig +nodnssec @9.9.9.9 AAAA irtf.org   

; <<>> DiG 9.10.3-P4-Ubuntu <<>> +nodnssec @9.9.9.9 AAAA irtf.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11544
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;irtf.org.		IN AAAA

;; ANSWER SECTION:
irtf.org.		1325 IN	AAAA 2001:1900:3001:11::2c

;; Query time: 4 msec
;; SERVER: 9.9.9.9#53(9.9.9.9)
;; WHEN: Thu Nov 16 09:49:41 +08 2017
;; MSG SIZE  rcvd: 65

This shows that Quad9 validates with DNSSEC (the answer contains the AD - Authentic Data - bit).

If the domain is on the blacklist of Quad9 (thanks to Xavier Claude for having found such a name to test), the resolver answers with NXDOMAIN (No Such Domain - this domain does not exist):

%  dig @9.9.9.9  www.hjaoopoa.top

; <<>> DiG 9.10.3-P4-Debian <<>> @9.9.9.9 www.hjaoopoa.top
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1143
;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;www.hjaoopoa.top.	IN A

;; Query time: 17 msec
;; SERVER: 9.9.9.9#53(9.9.9.9)
;; WHEN: Sat Nov 18 20:30:41 CET 2017
;; MSG SIZE  rcvd: 45

(With a non-lying resolver, we would have gotten the NOERROR return code and the IP address 54.213.138.248.)

Now let's test the important benefit of this service, DNS over TLS. It's TLS so we can go with openssl:

% openssl s_client -connect \[2620:fe::fe\]:853 -showcerts
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = dns.quad9.net
verify return:1
---
Certificate chain
 0 s:/CN=dns.quad9.net
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
-----BEGIN CERTIFICATE-----
...
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
...
Server certificate
subject=/CN=dns.quad9.net
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
...
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
...
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
...

We see that Quad9 responds well in TLS, and that it has a Let's Encrypt certificate.

Next, let's test the getdns_query programme distributed with getdns:

% getdns_query @9.9.9.9 -s -l L www.afnic.fr AAAA
{
  "answer_type": GETDNS_NAMETYPE_DNS,
  "canonical_name": <bindata for lb01-1.nic.fr.>,
  "just_address_answers":
  [
    {
      "address_data": <bindata for 2001:67c:2218:30::24>,
      "address_type": <bindata of "IPv6">
    }
...

Yes, getdns_query is very talkative. The -l L option tells it to use DNS over TLS.

I've also used tshark to check that we are actually using TLS:

% tshark -n -i wlp2s0  -d tcp.port==853,ssl host 9.9.9.9 
Capturing on 'wlp2s0'
1 0.000000000 31.133.136.116 → 9.9.9.9      TCP 74 37874 → 853 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=233018174 TSecr=0 WS=128
2 0.002518390      9.9.9.9 → 31.133.136.116 TCP 74 853 → 37874 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=873811762 TSecr=233018174 WS=256
3 0.002551638 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=233018175 TSecr=873811762
4 0.002642065 31.133.136.116 → 9.9.9.9      SSL 371 Client Hello
5 0.022008585      9.9.9.9 → 31.133.136.116 TLSv1.2 1514 Server Hello
6 0.022042645 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=306 Ack=1449 Win=32128 Len=0 TSval=233018180 TSecr=873811781
7 0.022050371      9.9.9.9 → 31.133.136.116 TLSv1.2 108 [TCP Previous segment not captured] , Ignored Unknown Record
8 0.022054712 31.133.136.116 → 9.9.9.9      TCP 78 [TCP Window Update] 37874 → 853 [ACK] Seq=306 Ack=1449 Win=35072 Len=0 TSval=233018180 TSecr=873811781 SLE=2897 SRE=2939
9 0.022667110      9.9.9.9 → 31.133.136.116 TCP 1514 [TCP Out-Of-Order] 853 → 37874 [ACK] Seq=1449 Ack=306 Win=30208 Len=1448 TSval=873811781 TSecr=233018175
10 0.022679278 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=306 Ack=2939 Win=37888 Len=0 TSval=233018180 TSecr=873811781
11 0.023537602 31.133.136.116 → 9.9.9.9      TLSv1.2 192 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
12 0.037713598      9.9.9.9 → 31.133.136.116 TLSv1.2 117 Change Cipher Spec, Encrypted Handshake Message
13 0.037888417 31.133.136.116 → 9.9.9.9      TLSv1.2 225 Application Data
14 0.093441153      9.9.9.9 → 31.133.136.116 TCP 66 853 → 37874 [ACK] Seq=2990 Ack=591 Win=31232 Len=0 TSval=873811853 TSecr=233018184
15 0.742375719      9.9.9.9 → 31.133.136.116 TLSv1.2 178 Application Data
...

The -d tcp.port == 853, ssl is there to tell tshark to interpret everything that goes through port 853 (DNS-over-TLS) as TLS. We can see the TLS dialogue, but obviously not the DNS queries and answers since everything is encrypted.

Now that these tests have been going well, let's use Quad9 for true name resolution. We will use Stubby to talk to Quad9. The Stubby configuration file looks like this:

listen_addresses:
  - 0::1@8053

dns_transport_list:
  - GETDNS_TRANSPORT_TLS

upstream_recursive_servers:
# Quad9
   - address_data: 9.9.9.9
     tls_auth_name: "dns.quad9.net"
   - address_data: 2620:fe::fe
     tls_auth_name: "dns.quad9.net"

Stubby is told to listen to the local address ::1 on port 8053, and to forward DNS queries over TLS to 9.9.9.9 or to 2620:fe::fe. Then we launch Stubby:

% stubby
[12:28:10.942595] STUBBY: Read config from file /usr/local/etc/stubby/stubby.yml
[12:28:10.942842] STUBBY: Starting DAEMON....

And we can test it, using dig to query the specified address and port:

% dig @::1 -p 8053 A www.catstuff.com 

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @::1 -p 8053 A www.catstuff.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20910
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 65535
;; QUESTION SECTION:
;www.catstuff.com.	IN A

;; ANSWER SECTION:
www.catstuff.com.	600 IN A 216.157.88.24

;; Query time: 974 msec
;; SERVER: ::1#8053(::1)
;; WHEN: Thu Nov 16 20:29:26 +08 2017
;; MSG SIZE  rcvd: 77

We can then check with tshark or tcpdump that Stubby does speak with Quad9, and that it is using TLS.

Stubby has the advantage of managing TCP well, especially by reusing connections (it would be very expensive to establish a TCP connection for each DNS query, especially over TLS). But it does not cache the answers, which can be annoying if you're far away from the Quad9 server. In that case, it's best to add a real resolver, for instance Unbound. It is configured as follows:

server:
   interface: 127.0.0.1
   do-not-query-localhost:  no
forward-zone:
  name: "."
    forward-addr: ::1@8053

With this configuration, Unbound will listen on 127.0.0.1 (on the default port, 53, the DNS port) and relay the queries for which it does not already have an answer in its cache to Stubby (::1, port 8053). So, let's ask Unbound:

% dig @127.0.0.1 A mastodon.gougere.fr

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 A mastodon.gougere.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40668
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;mastodon.gougere.fr.	IN A

;; ANSWER SECTION:
mastodon.gougere.fr.	600 IN A 185.167.17.10

;; Query time: 2662 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Nov 16 20:36:09 +08 2017
;; MSG SIZE  rcvd: 64

Unbound has a memory (the cache) so if we send the query again, the answer will arrive much faster and we will see the TTL (here 600 seconds) decreased.

If you find that all this is very complicated to install or configure, you can use a Docker image, contributed by Juzam (also available on GitHub).

Note that it is not easy to find an IPv4 address that is so easy to remember as 9.9.9.9. DNSDB shows that this address has been used a lot before arriving at PCH, and partially for activities that some may find questionable. This IP address is blacklisted in several places. If that does not work for you, talk to your provider, or try Quad9 over IPv6. We can see this imperfect connectivity by testing with RIPE Atlas and the atlas-resolve programme, comparing Quad9 and Google Public DNS:

% atlas-resolve -r 200 -e 9.9.9.9 --nsid -t AAAA irtf.org
Nameserver 9.9.9.9
[ERROR: SERVFAIL] : 1 occurrences
[TIMEOUT] : 9 occurrences
[2001:1900:3001:11::2c] : 177 occurrences
Test #10205081 done at 2017-11-16T01:41:40Z

% atlas-resolve -r 200 -e 8.8.8.8 -g 10205081 --nsid -t AAAA irtf.org
Nameserver 8.8.8.8
[TIMEOUT] : 1 occurrences
[2001:1900:3001:11::2c] : 186 occurrences
Test #10205089 done at 2017-11-16T01:46:38Z

 The situation is better with IPv6, this time on par with Google (the second IPv6 test):

% atlas-resolve -6 -r 200 -e 2620:fe::fe -t AAAA irtf.org
Nameserver 2620:fe::fe
[NETWORK PROBLEM WITH RESOLVER] : 2 occurrences
[TIMEOUT(S)] : 5 occurrences
[2001:1900:3001:11::2c] : 191 occurrences
Test #10268718 done at 2017-11-20T15:15:32Z

% atlas-resolve -6 -r 200 -e 2001:4860:4860::8888 -t AAAA irtf.org
Nameserver 2001:4860:4860::8888
[TIMEOUT(S)] : 6 occurrences
[2001:1900:3001:11::2c] : 190 occurrences
Test #10268732 done at 2017-11-20T15:19:27Z

Many thanks to Sara Dickinson for her technical help.

 

This article was first published in French on my personal blog.

 

 

6 Comments

GAURAV KANSAL says:
24 Nov, 2017 08:39 PM
DNSDB link mentioned is only for account holders. Can we get the information about previous usage of 9.9.9.9 from any other source, which don't need an account ?
Stéphane Bortzmeyer says:
27 Nov, 2017 11:16 AM
DNSDB is subscription-only. But there are some gratis "passive DNS" databases such as https://passivedns.cn/ or https://www.circl.lu/services/passive-dns/
Robert Edmonds says:
27 Nov, 2017 10:10 PM
Re:

> DNSDB shows that this address has been used a lot before arriving at PCH, and partially for activities that some may find questionable.

Of course, address records in the DNS do not require any authorization from the number resource holder, so for "round number" IP addresses like 8.8.8.8, 9.9.9.9, etc. a lot of weird A-records pointing to those IP addresses would be expected. For instance, there are quite a few "questionable" records in DNSDB pointing at the resolver that I use, 10.10.10.10 :-)
OLF says:
28 Nov, 2017 07:16 AM
With the stubby config above I only receive a FORMERR

$ dig @::1 -p 8053 A www.catstuff.com

; <<>> DiG 9.8.3-P1 <<>> @::1 -p 8053 A www.catstuff.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: FORMERR, id: 39788
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.catstuff.com. IN A

;; Query time: 115 msec
;; SERVER: ::1#8053(::1)
;; WHEN: Tue Nov 28 07:06:35 2017
;; MSG SIZE rcvd: 45
Stéphane Bortzmeyer says:
28 Nov, 2017 10:58 AM
This is apparently because a known ECS bug https://github.com/getdnsapi/getdns/issues/357 already fixed in the code repository.
OLF says:
28 Nov, 2017 08:20 PM
Thank you. The version available via brew is too old. After building the latest version it is working fine.
Add comment

You can add a comment by filling out the form below. Comments are moderated so they won't appear immediately. If you have a RIPE NCC Access account, we would like you to log in.