diff options
author | Bob Halley <halley@dnspython.org> | 2021-11-17 06:15:09 -0800 |
---|---|---|
committer | Bob Halley <halley@dnspython.org> | 2021-11-17 06:15:09 -0800 |
commit | 7c98b8e7bccc5d40a87bad351c775706c5848208 (patch) | |
tree | 7518d240a5ddf7a6d8dea6bdbf654fde30b84684 | |
parent | bebf01c04e9fd197f6c50dd64f9a261f313d325f (diff) | |
download | dnspython-7c98b8e7bccc5d40a87bad351c775706c5848208.tar.gz |
initial implemention of async https query
-rw-r--r-- | dns/asyncquery.py | 88 | ||||
-rw-r--r-- | dns/query.py | 4 |
2 files changed, 89 insertions, 3 deletions
diff --git a/dns/asyncquery.py b/dns/asyncquery.py index deeff27..58766e3 100644 --- a/dns/asyncquery.py +++ b/dns/asyncquery.py @@ -17,9 +17,11 @@ """Talk to a DNS server.""" +import base64 import socket import struct import time +import urllib import dns.asyncbackend import dns.exception @@ -31,8 +33,10 @@ import dns.rdataclass import dns.rdatatype from dns.query import _compute_times, _matches_destination, BadResponse, ssl, \ - UDPMode + UDPMode, have_doh, _have_httpx, _have_http2, NoDOH +if _have_httpx: + import httpx # for brevity _lltuple = dns.inet.low_level_address_tuple @@ -354,6 +358,88 @@ async def tls(q, where, timeout=None, port=853, source=None, source_port=0, if not sock and s: await s.close() +async def https(q, where, timeout=None, port=443, source=None, source_port=0, + one_rr_per_rrset=False, ignore_trailing=False, client=None, + path='/dns-query', post=True, verify=True, backend=None): + """Return the response obtained after sending a query via DNS-over-HTTPS. + + *client*, a ``httpx.AsyncClient``. If provided, the client to use for + the query. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.https()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + if not _have_httpx: + raise NoDOH('httpx is not available.') # pragma: no cover + + _httpx_ok = True + + wire = q.to_wire() + try: + af = dns.inet.af_for_address(where) + except ValueError: + af = None + transport = None + headers = { + "accept": "application/dns-message" + } + if af is not None: + if af == socket.AF_INET: + url = 'https://{}:{}{}'.format(where, port, path) + elif af == socket.AF_INET6: + url = 'https://[{}]:{}{}'.format(where, port, path) + else: + url = where + if source is not None: + transport = httpx.AsyncHTTPTransport(local_address=source[0]) + + # After 3.6 is no longer supported, this can use an AsyncExitStack + client_to_close = None + try: + if not client: + client = httpx.AsyncClient(http1=True, http2=_have_http2, + verify=verify, transport=transport) + client_to_close = client + + # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH + # GET and POST examples + if post: + headers.update({ + "content-type": "application/dns-message", + "content-length": str(len(wire)) + }) + response = await client.post(url, headers=headers, content=wire, + timeout=timeout) + else: + wire = base64.urlsafe_b64encode(wire).rstrip(b"=") + wire = wire.decode() # httpx does a repr() if we give it bytes + response = await client.get(url, headers=headers, timeout=timeout, + params={"dns": wire}) + finally: + if client_to_close: + await client.aclose() + + # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH + # status codes + if response.status_code < 200 or response.status_code > 299: + raise ValueError('{} responded with status code {}' + '\nResponse body: {}'.format(where, + response.status_code, + response.content)) + r = dns.message.from_wire(response.content, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) + r.time = response.elapsed + if not q.is_response(r): + raise BadResponse + return r + async def inbound_xfr(where, txn_manager, query=None, port=53, timeout=None, lifetime=None, source=None, source_port=0, udp_mode=UDPMode.NEVER, backend=None): diff --git a/dns/query.py b/dns/query.py index 314d8d8..fbf76d8 100644 --- a/dns/query.py +++ b/dns/query.py @@ -274,8 +274,8 @@ def https(q, where, timeout=None, port=443, source=None, source_port=0, *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the received message. - *session*, a ``requests.session.Session``. If provided, the session to use - to send the queries. + *session*, an ``httpx.Client`` or ``requests.session.Session``. If + provided, the client/session to use to send the queries. *path*, a ``str``. If *where* is an IP address, then *path* will be used to construct the URL to send the DNS query to. |