103 lines
2.9 KiB
Python
Executable File
103 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import copy
|
|
import json
|
|
import httpx
|
|
|
|
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 = httpx.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)
|