The weekend of 17-18 March 2018, I participated to the IETF 101
DoH (
So, DNS-over-HTTPS. This technique allows a stub resolver to
talk to a DNS resolver over a secure transport. Let's see if we can implement the draft and
make this implementation work with other implementations. My
personal idea was to modify the excellent getdns library to add DoH as a
possible transport (DNS-over-TLS is already there). But it was too
complicated for me and, moreover, Willem Toorop decided to
refactor the code, to make easier to add new transports, so getdns
was too "in flux" for me. (Willem worked on it during the
hackathon.) Instead, I developed first a server in
DoH requires (I know, the actual rules are more complicated
than a simple requirement)
Like many HTTP development frameworks for Python, Quart allows
you to define code to be run in response to some HTTP methods, for
a given path in the
@app.route('/hello')
async def hello():
return 'Hello\n'
The
@app.route('/dns', methods=['POST'])
async def index():
ct = request.headers.get('content-type')
if ct != "application/dns-udpwireformat":
abort(415)
data = await request.get_data()
r = bytes(data)
message = dns.message.from_wire(r)
# get the DNS response from the DNS message, see later…
return (response
{'Content-Type': 'application/dns-udpwireformat'})
Here, we handle only POST requests, we check the
How do we get the answer to a specific DNS request? We simply
give it to our local resolver, with dnspython:
resolver = "::1"
raw = dns.query.udp(message, resolver)
response = raw.to_wire()
The biggest goal of DoH is privacy, so we need to activate
tls_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
tls_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION
tls_context.set_alpn_protocols(['h2', 'http/1.1'])
app.run(host=bind, port=port, ssl=tls_context)
(We accept HTTP/1.1, also, because we're tolerant.)
To get a
tls_context.load_cert_chain(certfile='le-cert.pem', keyfile='le-key.pem')
Putting every together, we have the complete code
% ./quart-doh.py -c -r ::1
Obviously, this is not a successful hackathon if you don't
discover at
least one bug in the library. Note it was fixed by the author
even before the end of the event.
Having a server is nice but there were not many DoH clients to
test it (some were developed during the hackathon). I then
developed a client in Python, still with dnspython for the DNS
part, but using pycurl for
HTTP/2. The DNS request is built from a name entered by the user
(note that the DNS query type, here, is fixed and set to ANY):
message = dns.message.make_query(queryname, dns.rdatatype.ANY)
message.id = 0 # DoH requests that
We use pycurl to establish a HTTP/2 connection:
c = pycurl.Curl()
c.setopt(c.URL, url) # url is the URL of the DoH server
data = message.to_wire()
c.setopt(pycurl.POST, True)
c.setopt(pycurl.POSTFIELDS, data)
c.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-udpwireformat"])
c.setopt(c.WRITEDATA, buffer)
c.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2)
c.perform()
The
We then get the answer in the
body = buffer.getvalue()
response = dns.message.from_wire(body)
The complete code is
% ./doh-client.py https://dns.dnsoverhttps.net/dns-query gitlab.com
...
;ANSWER
gitlab.com. 300 IN A 52.167.219.168
...
I also developed a
getdns_convert_fqdn_to_dns_name (session_data->qname, dns_name_wire_fmt);
getdns_dict_set_bindata (dict, "qname", *dns_name_wire_fmt);
getdns_dict_set_int (dict, "qtype", GETDNS_RRTYPE_A);
getdns_dict_set_dict (qdict, "question", dict);
getdns_dict_set_int (rdict, "rd", 1);
getdns_dict_set_dict (qdict, "header", rdict);
Yes, building getdns data structures is a pain. In the end, all
that was necessary was (as displayed by
,
"qtype": GETDNS_RRTYPE_A
}
}
]]>
We then put it in DNS wire format with
When getting the answer, getdns allows us to search info with
the JSON pointer (
The complete code is
% ./doh-nghttp https://dns.dnsoverhttps.net/dns-query gitlab.com
The address is 52.167.219.168
The
What were the lessons learned during the hackathon? I let you see that in the presentation I gave at the DoH working group afterwards. For the other code developed during the hackathon, see the notes taken during the hackathon.
Other reports:
Many thanks to Charles Eckel for organising this wonderful event, to the other people working on DoH at the same time, making this both a fun and useful experience, and to the authors of the very good libraries I used, Quart, nghttp2, getdns and pycurl.