asyncquery.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """Talk to a DNS server."""
  17. import base64
  18. import socket
  19. import struct
  20. import time
  21. import dns.asyncbackend
  22. import dns.exception
  23. import dns.inet
  24. import dns.name
  25. import dns.message
  26. import dns.rcode
  27. import dns.rdataclass
  28. import dns.rdatatype
  29. from dns.query import _compute_times, _matches_destination, BadResponse, ssl, \
  30. UDPMode, _have_httpx, _have_http2, NoDOH
  31. if _have_httpx:
  32. import httpx
  33. # for brevity
  34. _lltuple = dns.inet.low_level_address_tuple
  35. def _source_tuple(af, address, port):
  36. # Make a high level source tuple, or return None if address and port
  37. # are both None
  38. if address or port:
  39. if address is None:
  40. if af == socket.AF_INET:
  41. address = '0.0.0.0'
  42. elif af == socket.AF_INET6:
  43. address = '::'
  44. else:
  45. raise NotImplementedError(f'unknown address family {af}')
  46. return (address, port)
  47. else:
  48. return None
  49. def _timeout(expiration, now=None):
  50. if expiration:
  51. if not now:
  52. now = time.time()
  53. return max(expiration - now, 0)
  54. else:
  55. return None
  56. async def send_udp(sock, what, destination, expiration=None):
  57. """Send a DNS message to the specified UDP socket.
  58. *sock*, a ``dns.asyncbackend.DatagramSocket``.
  59. *what*, a ``bytes`` or ``dns.message.Message``, the message to send.
  60. *destination*, a destination tuple appropriate for the address family
  61. of the socket, specifying where to send the query.
  62. *expiration*, a ``float`` or ``None``, the absolute time at which
  63. a timeout exception should be raised. If ``None``, no timeout will
  64. occur.
  65. Returns an ``(int, float)`` tuple of bytes sent and the sent time.
  66. """
  67. if isinstance(what, dns.message.Message):
  68. what = what.to_wire()
  69. sent_time = time.time()
  70. n = await sock.sendto(what, destination, _timeout(expiration, sent_time))
  71. return (n, sent_time)
  72. async def receive_udp(sock, destination=None, expiration=None,
  73. ignore_unexpected=False, one_rr_per_rrset=False,
  74. keyring=None, request_mac=b'', ignore_trailing=False,
  75. raise_on_truncation=False):
  76. """Read a DNS message from a UDP socket.
  77. *sock*, a ``dns.asyncbackend.DatagramSocket``.
  78. See :py:func:`dns.query.receive_udp()` for the documentation of the other
  79. parameters, exceptions, and return type of this method.
  80. """
  81. wire = b''
  82. while 1:
  83. (wire, from_address) = await sock.recvfrom(65535, _timeout(expiration))
  84. if _matches_destination(sock.family, from_address, destination,
  85. ignore_unexpected):
  86. break
  87. received_time = time.time()
  88. r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
  89. one_rr_per_rrset=one_rr_per_rrset,
  90. ignore_trailing=ignore_trailing,
  91. raise_on_truncation=raise_on_truncation)
  92. return (r, received_time, from_address)
  93. async def udp(q, where, timeout=None, port=53, source=None, source_port=0,
  94. ignore_unexpected=False, one_rr_per_rrset=False,
  95. ignore_trailing=False, raise_on_truncation=False, sock=None,
  96. backend=None):
  97. """Return the response obtained after sending a query via UDP.
  98. *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
  99. the socket to use for the query. If ``None``, the default, a
  100. socket is created. Note that if a socket is provided, the
  101. *source*, *source_port*, and *backend* are ignored.
  102. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  103. the default, then dnspython will use the default backend.
  104. See :py:func:`dns.query.udp()` for the documentation of the other
  105. parameters, exceptions, and return type of this method.
  106. """
  107. wire = q.to_wire()
  108. (begin_time, expiration) = _compute_times(timeout)
  109. s = None
  110. # After 3.6 is no longer supported, this can use an AsyncExitStack.
  111. try:
  112. af = dns.inet.af_for_address(where)
  113. destination = _lltuple((where, port), af)
  114. if sock:
  115. s = sock
  116. else:
  117. if not backend:
  118. backend = dns.asyncbackend.get_default_backend()
  119. stuple = _source_tuple(af, source, source_port)
  120. if backend.datagram_connection_required():
  121. dtuple = (where, port)
  122. else:
  123. dtuple = None
  124. s = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple,
  125. dtuple)
  126. await send_udp(s, wire, destination, expiration)
  127. (r, received_time, _) = await receive_udp(s, destination, expiration,
  128. ignore_unexpected,
  129. one_rr_per_rrset,
  130. q.keyring, q.mac,
  131. ignore_trailing,
  132. raise_on_truncation)
  133. r.time = received_time - begin_time
  134. if not q.is_response(r):
  135. raise BadResponse
  136. return r
  137. finally:
  138. if not sock and s:
  139. await s.close()
  140. async def udp_with_fallback(q, where, timeout=None, port=53, source=None,
  141. source_port=0, ignore_unexpected=False,
  142. one_rr_per_rrset=False, ignore_trailing=False,
  143. udp_sock=None, tcp_sock=None, backend=None):
  144. """Return the response to the query, trying UDP first and falling back
  145. to TCP if UDP results in a truncated response.
  146. *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
  147. the socket to use for the UDP query. If ``None``, the default, a
  148. socket is created. Note that if a socket is provided the *source*,
  149. *source_port*, and *backend* are ignored for the UDP query.
  150. *tcp_sock*, a ``dns.asyncbackend.StreamSocket``, or ``None``, the
  151. socket to use for the TCP query. If ``None``, the default, a
  152. socket is created. Note that if a socket is provided *where*,
  153. *source*, *source_port*, and *backend* are ignored for the TCP query.
  154. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  155. the default, then dnspython will use the default backend.
  156. See :py:func:`dns.query.udp_with_fallback()` for the documentation
  157. of the other parameters, exceptions, and return type of this
  158. method.
  159. """
  160. try:
  161. response = await udp(q, where, timeout, port, source, source_port,
  162. ignore_unexpected, one_rr_per_rrset,
  163. ignore_trailing, True, udp_sock, backend)
  164. return (response, False)
  165. except dns.message.Truncated:
  166. response = await tcp(q, where, timeout, port, source, source_port,
  167. one_rr_per_rrset, ignore_trailing, tcp_sock,
  168. backend)
  169. return (response, True)
  170. async def send_tcp(sock, what, expiration=None):
  171. """Send a DNS message to the specified TCP socket.
  172. *sock*, a ``dns.asyncbackend.StreamSocket``.
  173. See :py:func:`dns.query.send_tcp()` for the documentation of the other
  174. parameters, exceptions, and return type of this method.
  175. """
  176. if isinstance(what, dns.message.Message):
  177. what = what.to_wire()
  178. l = len(what)
  179. # copying the wire into tcpmsg is inefficient, but lets us
  180. # avoid writev() or doing a short write that would get pushed
  181. # onto the net
  182. tcpmsg = struct.pack("!H", l) + what
  183. sent_time = time.time()
  184. await sock.sendall(tcpmsg, _timeout(expiration, sent_time))
  185. return (len(tcpmsg), sent_time)
  186. async def _read_exactly(sock, count, expiration):
  187. """Read the specified number of bytes from stream. Keep trying until we
  188. either get the desired amount, or we hit EOF.
  189. """
  190. s = b''
  191. while count > 0:
  192. n = await sock.recv(count, _timeout(expiration))
  193. if n == b'':
  194. raise EOFError
  195. count = count - len(n)
  196. s = s + n
  197. return s
  198. async def receive_tcp(sock, expiration=None, one_rr_per_rrset=False,
  199. keyring=None, request_mac=b'', ignore_trailing=False):
  200. """Read a DNS message from a TCP socket.
  201. *sock*, a ``dns.asyncbackend.StreamSocket``.
  202. See :py:func:`dns.query.receive_tcp()` for the documentation of the other
  203. parameters, exceptions, and return type of this method.
  204. """
  205. ldata = await _read_exactly(sock, 2, expiration)
  206. (l,) = struct.unpack("!H", ldata)
  207. wire = await _read_exactly(sock, l, expiration)
  208. received_time = time.time()
  209. r = dns.message.from_wire(wire, keyring=keyring, request_mac=request_mac,
  210. one_rr_per_rrset=one_rr_per_rrset,
  211. ignore_trailing=ignore_trailing)
  212. return (r, received_time)
  213. async def tcp(q, where, timeout=None, port=53, source=None, source_port=0,
  214. one_rr_per_rrset=False, ignore_trailing=False, sock=None,
  215. backend=None):
  216. """Return the response obtained after sending a query via TCP.
  217. *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the
  218. socket to use for the query. If ``None``, the default, a socket
  219. is created. Note that if a socket is provided
  220. *where*, *port*, *source*, *source_port*, and *backend* are ignored.
  221. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  222. the default, then dnspython will use the default backend.
  223. See :py:func:`dns.query.tcp()` for the documentation of the other
  224. parameters, exceptions, and return type of this method.
  225. """
  226. wire = q.to_wire()
  227. (begin_time, expiration) = _compute_times(timeout)
  228. s = None
  229. # After 3.6 is no longer supported, this can use an AsyncExitStack.
  230. try:
  231. if sock:
  232. # Verify that the socket is connected, as if it's not connected,
  233. # it's not writable, and the polling in send_tcp() will time out or
  234. # hang forever.
  235. await sock.getpeername()
  236. s = sock
  237. else:
  238. # These are simple (address, port) pairs, not
  239. # family-dependent tuples you pass to lowlevel socket
  240. # code.
  241. af = dns.inet.af_for_address(where)
  242. stuple = _source_tuple(af, source, source_port)
  243. dtuple = (where, port)
  244. if not backend:
  245. backend = dns.asyncbackend.get_default_backend()
  246. s = await backend.make_socket(af, socket.SOCK_STREAM, 0, stuple,
  247. dtuple, timeout)
  248. await send_tcp(s, wire, expiration)
  249. (r, received_time) = await receive_tcp(s, expiration, one_rr_per_rrset,
  250. q.keyring, q.mac,
  251. ignore_trailing)
  252. r.time = received_time - begin_time
  253. if not q.is_response(r):
  254. raise BadResponse
  255. return r
  256. finally:
  257. if not sock and s:
  258. await s.close()
  259. async def tls(q, where, timeout=None, port=853, source=None, source_port=0,
  260. one_rr_per_rrset=False, ignore_trailing=False, sock=None,
  261. backend=None, ssl_context=None, server_hostname=None):
  262. """Return the response obtained after sending a query via TLS.
  263. *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket
  264. to use for the query. If ``None``, the default, a socket is
  265. created. Note that if a socket is provided, it must be a
  266. connected SSL stream socket, and *where*, *port*,
  267. *source*, *source_port*, *backend*, *ssl_context*, and *server_hostname*
  268. are ignored.
  269. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  270. the default, then dnspython will use the default backend.
  271. See :py:func:`dns.query.tls()` for the documentation of the other
  272. parameters, exceptions, and return type of this method.
  273. """
  274. # After 3.6 is no longer supported, this can use an AsyncExitStack.
  275. (begin_time, expiration) = _compute_times(timeout)
  276. if not sock:
  277. if ssl_context is None:
  278. ssl_context = ssl.create_default_context()
  279. if server_hostname is None:
  280. ssl_context.check_hostname = False
  281. else:
  282. ssl_context = None
  283. server_hostname = None
  284. af = dns.inet.af_for_address(where)
  285. stuple = _source_tuple(af, source, source_port)
  286. dtuple = (where, port)
  287. if not backend:
  288. backend = dns.asyncbackend.get_default_backend()
  289. s = await backend.make_socket(af, socket.SOCK_STREAM, 0, stuple,
  290. dtuple, timeout, ssl_context,
  291. server_hostname)
  292. else:
  293. s = sock
  294. try:
  295. timeout = _timeout(expiration)
  296. response = await tcp(q, where, timeout, port, source, source_port,
  297. one_rr_per_rrset, ignore_trailing, s, backend)
  298. end_time = time.time()
  299. response.time = end_time - begin_time
  300. return response
  301. finally:
  302. if not sock and s:
  303. await s.close()
  304. async def https(q, where, timeout=None, port=443, source=None, source_port=0,
  305. one_rr_per_rrset=False, ignore_trailing=False, client=None,
  306. path='/dns-query', post=True, verify=True):
  307. """Return the response obtained after sending a query via DNS-over-HTTPS.
  308. *client*, a ``httpx.AsyncClient``. If provided, the client to use for
  309. the query.
  310. Unlike the other dnspython async functions, a backend cannot be provided
  311. in this function because httpx always auto-detects the async backend.
  312. See :py:func:`dns.query.https()` for the documentation of the other
  313. parameters, exceptions, and return type of this method.
  314. """
  315. if not _have_httpx:
  316. raise NoDOH('httpx is not available.') # pragma: no cover
  317. wire = q.to_wire()
  318. try:
  319. af = dns.inet.af_for_address(where)
  320. except ValueError:
  321. af = None
  322. transport = None
  323. headers = {
  324. "accept": "application/dns-message"
  325. }
  326. if af is not None:
  327. if af == socket.AF_INET:
  328. url = 'https://{}:{}{}'.format(where, port, path)
  329. elif af == socket.AF_INET6:
  330. url = 'https://[{}]:{}{}'.format(where, port, path)
  331. else:
  332. url = where
  333. if source is not None:
  334. transport = httpx.AsyncHTTPTransport(local_address=source[0])
  335. # After 3.6 is no longer supported, this can use an AsyncExitStack
  336. client_to_close = None
  337. try:
  338. if not client:
  339. client = httpx.AsyncClient(http1=True, http2=_have_http2,
  340. verify=verify, transport=transport)
  341. client_to_close = client
  342. # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH
  343. # GET and POST examples
  344. if post:
  345. headers.update({
  346. "content-type": "application/dns-message",
  347. "content-length": str(len(wire))
  348. })
  349. response = await client.post(url, headers=headers, content=wire,
  350. timeout=timeout)
  351. else:
  352. wire = base64.urlsafe_b64encode(wire).rstrip(b"=")
  353. wire = wire.decode() # httpx does a repr() if we give it bytes
  354. response = await client.get(url, headers=headers, timeout=timeout,
  355. params={"dns": wire})
  356. finally:
  357. if client_to_close:
  358. await client.aclose()
  359. # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH
  360. # status codes
  361. if response.status_code < 200 or response.status_code > 299:
  362. raise ValueError('{} responded with status code {}'
  363. '\nResponse body: {}'.format(where,
  364. response.status_code,
  365. response.content))
  366. r = dns.message.from_wire(response.content,
  367. keyring=q.keyring,
  368. request_mac=q.request_mac,
  369. one_rr_per_rrset=one_rr_per_rrset,
  370. ignore_trailing=ignore_trailing)
  371. r.time = response.elapsed
  372. if not q.is_response(r):
  373. raise BadResponse
  374. return r
  375. async def inbound_xfr(where, txn_manager, query=None,
  376. port=53, timeout=None, lifetime=None, source=None,
  377. source_port=0, udp_mode=UDPMode.NEVER, backend=None):
  378. """Conduct an inbound transfer and apply it via a transaction from the
  379. txn_manager.
  380. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  381. the default, then dnspython will use the default backend.
  382. See :py:func:`dns.query.inbound_xfr()` for the documentation of
  383. the other parameters, exceptions, and return type of this method.
  384. """
  385. if query is None:
  386. (query, serial) = dns.xfr.make_query(txn_manager)
  387. else:
  388. serial = dns.xfr.extract_serial_from_query(query)
  389. rdtype = query.question[0].rdtype
  390. is_ixfr = rdtype == dns.rdatatype.IXFR
  391. origin = txn_manager.from_wire_origin()
  392. wire = query.to_wire()
  393. af = dns.inet.af_for_address(where)
  394. stuple = _source_tuple(af, source, source_port)
  395. dtuple = (where, port)
  396. (_, expiration) = _compute_times(lifetime)
  397. retry = True
  398. while retry:
  399. retry = False
  400. if is_ixfr and udp_mode != UDPMode.NEVER:
  401. sock_type = socket.SOCK_DGRAM
  402. is_udp = True
  403. else:
  404. sock_type = socket.SOCK_STREAM
  405. is_udp = False
  406. if not backend:
  407. backend = dns.asyncbackend.get_default_backend()
  408. s = await backend.make_socket(af, sock_type, 0, stuple, dtuple,
  409. _timeout(expiration))
  410. async with s:
  411. if is_udp:
  412. await s.sendto(wire, dtuple, _timeout(expiration))
  413. else:
  414. tcpmsg = struct.pack("!H", len(wire)) + wire
  415. await s.sendall(tcpmsg, expiration)
  416. with dns.xfr.Inbound(txn_manager, rdtype, serial,
  417. is_udp) as inbound:
  418. done = False
  419. tsig_ctx = None
  420. while not done:
  421. (_, mexpiration) = _compute_times(timeout)
  422. if mexpiration is None or \
  423. (expiration is not None and mexpiration > expiration):
  424. mexpiration = expiration
  425. if is_udp:
  426. destination = _lltuple((where, port), af)
  427. while True:
  428. timeout = _timeout(mexpiration)
  429. (rwire, from_address) = await s.recvfrom(65535,
  430. timeout)
  431. if _matches_destination(af, from_address,
  432. destination, True):
  433. break
  434. else:
  435. ldata = await _read_exactly(s, 2, mexpiration)
  436. (l,) = struct.unpack("!H", ldata)
  437. rwire = await _read_exactly(s, l, mexpiration)
  438. is_ixfr = (rdtype == dns.rdatatype.IXFR)
  439. r = dns.message.from_wire(rwire, keyring=query.keyring,
  440. request_mac=query.mac, xfr=True,
  441. origin=origin, tsig_ctx=tsig_ctx,
  442. multi=(not is_udp),
  443. one_rr_per_rrset=is_ixfr)
  444. try:
  445. done = inbound.process_message(r)
  446. except dns.xfr.UseTCP:
  447. assert is_udp # should not happen if we used TCP!
  448. if udp_mode == UDPMode.ONLY:
  449. raise
  450. done = True
  451. retry = True
  452. udp_mode = UDPMode.NEVER
  453. continue
  454. tsig_ctx = r.tsig_ctx
  455. if not retry and query.keyring and not r.had_tsig:
  456. raise dns.exception.FormError("missing TSIG")