From: Bob Halley Date: Mon, 13 Jul 2020 13:31:24 +0000 (-0700) Subject: Add DoH JSON example. X-Git-Tag: v2.0.0~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a7e71aa9b0ebf43c8f997adc66b45069506afa6e;p=thirdparty%2Fdnspython.git Add DoH JSON example. --- diff --git a/examples/doh-json.py b/examples/doh-json.py new file mode 100755 index 00000000..8cfe1b0c --- /dev/null +++ b/examples/doh-json.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +import copy +import json +import requests + +import dns.flags +import dns.message +import dns.resolver +import dns.rdataclass +import dns.rdatatype + +# This shows how to convert to/from dnspython's message object and the +# DNS-over-HTTPS (DoH) JSON form used by Google and Cloudflare, and +# described here: +# +# https://developers.google.com/speed/public-dns/docs/doh/json +# +# There's no need to do this for DoH as dnspython supports the +# standard RFC 8484 protocol which all DoH providers implement. The +# conversion to/from JSON is useful, however, so we show a way to do +# it. +# +# "simple" below means "simple python data types", i.e. things made of +# combinations of dictionaries, lists, strings, and numbers. + +def make_rr(simple, rdata): + csimple = copy.copy(simple) + csimple['data'] = rdata.to_text() + return csimple + +def flatten_rrset(rrs): + simple = { + 'name': str(rrs.name), + 'type': rrs.rdtype, + } + if len(rrs) > 0: + simple['TTL'] = rrs.ttl + return [make_rr(simple, rdata) for rdata in rrs] + else: + return [simple] + +def to_doh_simple(message): + simple = { + 'Status': message.rcode() + } + for f in dns.flags.Flag: + if f != dns.flags.Flag.AA and f != dns.flags.Flag.QR: + # DoH JSON doesn't need AA and omits it. DoH JSON is only + # used in replies so the QR flag is implied. + simple[f.name] = (message.flags & f) != 0 + for i, s in enumerate(message.sections): + k = dns.message.MessageSection.to_text(i).title() + simple[k] = [] + for rrs in s: + simple[k].extend(flatten_rrset(rrs)) + # we don't encode the ecs_client_subnet field + return simple + +def from_doh_simple(simple, add_qr=False): + message = dns.message.QueryMessage() + flags = 0 + for f in dns.flags.Flag: + if simple.get(f.name, False): + flags |= f + if add_qr: # QR is implied + flags |= dns.flags.QR + message.flags = flags + message.set_rcode(simple.get('Status', 0)) + for i, sn in enumerate(dns.message.MessageSection): + rr_list = simple.get(sn.name.title(), []) + for rr in rr_list: + rdtype = dns.rdatatype.RdataType(rr['type']) + rrs = message.find_rrset(i, dns.name.from_text(rr['name']), + dns.rdataclass.IN, rdtype, + create=True) + if 'data' in rr: + rrs.add(dns.rdata.from_text(dns.rdataclass.IN, rdtype, + rr['data']), rr.get('TTL', 0)) + # we don't decode the ecs_client_subnet field + return message + + +a = dns.resolver.resolve('www.dnspython.org', 'a') +p = to_doh_simple(a.response) +print(json.dumps(p, indent=4)) +response = requests.get('https://dns.google/resolve?', verify=True, + params={'name': 'www.dnspython.org', + 'type': 1}) +p = json.loads(response.text) +m = from_doh_simple(p, True) +print(m)