caresresolver.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import pycares # type: ignore
  2. import socket
  3. from tornado.concurrent import Future
  4. from tornado import gen
  5. from tornado.ioloop import IOLoop
  6. from tornado.netutil import Resolver, is_valid_ip
  7. import typing
  8. if typing.TYPE_CHECKING:
  9. from typing import Generator, Any, List, Tuple, Dict # noqa: F401
  10. class CaresResolver(Resolver):
  11. """Name resolver based on the c-ares library.
  12. This is a non-blocking and non-threaded resolver. It may not produce
  13. the same results as the system resolver, but can be used for non-blocking
  14. resolution when threads cannot be used.
  15. c-ares fails to resolve some names when ``family`` is ``AF_UNSPEC``,
  16. so it is only recommended for use in ``AF_INET`` (i.e. IPv4). This is
  17. the default for ``tornado.simple_httpclient``, but other libraries
  18. may default to ``AF_UNSPEC``.
  19. .. versionchanged:: 5.0
  20. The ``io_loop`` argument (deprecated since version 4.1) has been removed.
  21. """
  22. def initialize(self) -> None:
  23. self.io_loop = IOLoop.current()
  24. self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)
  25. self.fds = {} # type: Dict[int, int]
  26. def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None:
  27. state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0)
  28. if not state:
  29. self.io_loop.remove_handler(fd)
  30. del self.fds[fd]
  31. elif fd in self.fds:
  32. self.io_loop.update_handler(fd, state)
  33. self.fds[fd] = state
  34. else:
  35. self.io_loop.add_handler(fd, self._handle_events, state)
  36. self.fds[fd] = state
  37. def _handle_events(self, fd: int, events: int) -> None:
  38. read_fd = pycares.ARES_SOCKET_BAD
  39. write_fd = pycares.ARES_SOCKET_BAD
  40. if events & IOLoop.READ:
  41. read_fd = fd
  42. if events & IOLoop.WRITE:
  43. write_fd = fd
  44. self.channel.process_fd(read_fd, write_fd)
  45. @gen.coroutine
  46. def resolve(
  47. self, host: str, port: int, family: int = 0
  48. ) -> "Generator[Any, Any, List[Tuple[int, Any]]]":
  49. if is_valid_ip(host):
  50. addresses = [host]
  51. else:
  52. # gethostbyname doesn't take callback as a kwarg
  53. fut = Future() # type: Future[Tuple[Any, Any]]
  54. self.channel.gethostbyname(
  55. host, family, lambda result, error: fut.set_result((result, error))
  56. )
  57. result, error = yield fut
  58. if error:
  59. raise IOError(
  60. "C-Ares returned error %s: %s while resolving %s"
  61. % (error, pycares.errno.strerror(error), host)
  62. )
  63. addresses = result.addresses
  64. addrinfo = []
  65. for address in addresses:
  66. if "." in address:
  67. address_family = socket.AF_INET
  68. elif ":" in address:
  69. address_family = socket.AF_INET6
  70. else:
  71. address_family = socket.AF_UNSPEC
  72. if family != socket.AF_UNSPEC and family != address_family:
  73. raise IOError(
  74. "Requested socket family %d but got %d" % (family, address_family)
  75. )
  76. addrinfo.append((typing.cast(int, address_family), (address, port)))
  77. return addrinfo