_resolver.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. # -*- test-case-name: twisted.internet.test.test_resolver -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. IPv6-aware hostname resolution.
  6. @see: L{IHostnameResolver}
  7. """
  8. from socket import (
  9. AF_INET,
  10. AF_INET6,
  11. AF_UNSPEC,
  12. SOCK_DGRAM,
  13. SOCK_STREAM,
  14. AddressFamily,
  15. SocketKind,
  16. gaierror,
  17. getaddrinfo,
  18. )
  19. from typing import (
  20. TYPE_CHECKING,
  21. Callable,
  22. List,
  23. NoReturn,
  24. Optional,
  25. Sequence,
  26. Tuple,
  27. Type,
  28. Union,
  29. )
  30. from zope.interface import implementer
  31. from twisted.internet._idna import _idnaBytes
  32. from twisted.internet.address import IPv4Address, IPv6Address
  33. from twisted.internet.defer import Deferred
  34. from twisted.internet.error import DNSLookupError
  35. from twisted.internet.interfaces import (
  36. IAddress,
  37. IHostnameResolver,
  38. IHostResolution,
  39. IReactorThreads,
  40. IResolutionReceiver,
  41. IResolverSimple,
  42. )
  43. from twisted.internet.threads import deferToThreadPool
  44. from twisted.logger import Logger
  45. from twisted.python.compat import nativeString
  46. if TYPE_CHECKING:
  47. from twisted.python.threadpool import ThreadPool
  48. @implementer(IHostResolution)
  49. class HostResolution:
  50. """
  51. The in-progress resolution of a given hostname.
  52. """
  53. def __init__(self, name: str):
  54. """
  55. Create a L{HostResolution} with the given name.
  56. """
  57. self.name = name
  58. def cancel(self) -> NoReturn:
  59. # IHostResolution.cancel
  60. raise NotImplementedError()
  61. _any = frozenset([IPv4Address, IPv6Address])
  62. _typesToAF = {
  63. frozenset([IPv4Address]): AF_INET,
  64. frozenset([IPv6Address]): AF_INET6,
  65. _any: AF_UNSPEC,
  66. }
  67. _afToType = {
  68. AF_INET: IPv4Address,
  69. AF_INET6: IPv6Address,
  70. }
  71. _transportToSocket = {
  72. "TCP": SOCK_STREAM,
  73. "UDP": SOCK_DGRAM,
  74. }
  75. _socktypeToType = {
  76. SOCK_STREAM: "TCP",
  77. SOCK_DGRAM: "UDP",
  78. }
  79. _GETADDRINFO_RESULT = List[
  80. Tuple[
  81. AddressFamily,
  82. SocketKind,
  83. int,
  84. str,
  85. Union[Tuple[str, int], Tuple[str, int, int, int]],
  86. ]
  87. ]
  88. @implementer(IHostnameResolver)
  89. class GAIResolver:
  90. """
  91. L{IHostnameResolver} implementation that resolves hostnames by calling
  92. L{getaddrinfo} in a thread.
  93. """
  94. def __init__(
  95. self,
  96. reactor: IReactorThreads,
  97. getThreadPool: Optional[Callable[[], "ThreadPool"]] = None,
  98. getaddrinfo: Callable[[str, int, int, int], _GETADDRINFO_RESULT] = getaddrinfo,
  99. ):
  100. """
  101. Create a L{GAIResolver}.
  102. @param reactor: the reactor to schedule result-delivery on
  103. @type reactor: L{IReactorThreads}
  104. @param getThreadPool: a function to retrieve the thread pool to use for
  105. scheduling name resolutions. If not supplied, the use the given
  106. C{reactor}'s thread pool.
  107. @type getThreadPool: 0-argument callable returning a
  108. L{twisted.python.threadpool.ThreadPool}
  109. @param getaddrinfo: a reference to the L{getaddrinfo} to use - mainly
  110. parameterized for testing.
  111. @type getaddrinfo: callable with the same signature as L{getaddrinfo}
  112. """
  113. self._reactor = reactor
  114. self._getThreadPool = (
  115. reactor.getThreadPool if getThreadPool is None else getThreadPool
  116. )
  117. self._getaddrinfo = getaddrinfo
  118. def resolveHostName(
  119. self,
  120. resolutionReceiver: IResolutionReceiver,
  121. hostName: str,
  122. portNumber: int = 0,
  123. addressTypes: Optional[Sequence[Type[IAddress]]] = None,
  124. transportSemantics: str = "TCP",
  125. ) -> IHostResolution:
  126. """
  127. See L{IHostnameResolver.resolveHostName}
  128. @param resolutionReceiver: see interface
  129. @param hostName: see interface
  130. @param portNumber: see interface
  131. @param addressTypes: see interface
  132. @param transportSemantics: see interface
  133. @return: see interface
  134. """
  135. pool = self._getThreadPool()
  136. addressFamily = _typesToAF[
  137. _any if addressTypes is None else frozenset(addressTypes)
  138. ]
  139. socketType = _transportToSocket[transportSemantics]
  140. def get() -> _GETADDRINFO_RESULT:
  141. try:
  142. return self._getaddrinfo(
  143. hostName, portNumber, addressFamily, socketType
  144. )
  145. except gaierror:
  146. return []
  147. d = deferToThreadPool(self._reactor, pool, get)
  148. resolution = HostResolution(hostName)
  149. resolutionReceiver.resolutionBegan(resolution)
  150. @d.addCallback
  151. def deliverResults(result: _GETADDRINFO_RESULT) -> None:
  152. for family, socktype, proto, cannoname, sockaddr in result:
  153. addrType = _afToType[family]
  154. resolutionReceiver.addressResolved(
  155. addrType(_socktypeToType.get(socktype, "TCP"), *sockaddr)
  156. )
  157. resolutionReceiver.resolutionComplete()
  158. return resolution
  159. @implementer(IHostnameResolver)
  160. class SimpleResolverComplexifier:
  161. """
  162. A converter from L{IResolverSimple} to L{IHostnameResolver}.
  163. """
  164. _log = Logger()
  165. def __init__(self, simpleResolver: IResolverSimple):
  166. """
  167. Construct a L{SimpleResolverComplexifier} with an L{IResolverSimple}.
  168. """
  169. self._simpleResolver = simpleResolver
  170. def resolveHostName(
  171. self,
  172. resolutionReceiver: IResolutionReceiver,
  173. hostName: str,
  174. portNumber: int = 0,
  175. addressTypes: Optional[Sequence[Type[IAddress]]] = None,
  176. transportSemantics: str = "TCP",
  177. ) -> IHostResolution:
  178. """
  179. See L{IHostnameResolver.resolveHostName}
  180. @param resolutionReceiver: see interface
  181. @param hostName: see interface
  182. @param portNumber: see interface
  183. @param addressTypes: see interface
  184. @param transportSemantics: see interface
  185. @return: see interface
  186. """
  187. # If it's str, we need to make sure that it's just ASCII.
  188. try:
  189. hostName_bytes = hostName.encode("ascii")
  190. except UnicodeEncodeError:
  191. # If it's not just ASCII, IDNA it. We don't want to give a Unicode
  192. # string with non-ASCII in it to Python 3, as if anyone passes that
  193. # to a Python 3 stdlib function, it will probably use the wrong
  194. # IDNA version and break absolutely everything
  195. hostName_bytes = _idnaBytes(hostName)
  196. # Make sure it's passed down as a native str, to maintain the interface
  197. hostName = nativeString(hostName_bytes)
  198. resolution = HostResolution(hostName)
  199. resolutionReceiver.resolutionBegan(resolution)
  200. (
  201. self._simpleResolver.getHostByName(hostName)
  202. .addCallback(
  203. lambda address: resolutionReceiver.addressResolved(
  204. IPv4Address("TCP", address, portNumber)
  205. )
  206. )
  207. .addErrback(
  208. lambda error: None
  209. if error.check(DNSLookupError)
  210. else self._log.failure(
  211. "while looking up {name} with {resolver}",
  212. error,
  213. name=hostName,
  214. resolver=self._simpleResolver,
  215. )
  216. )
  217. .addCallback(lambda nothing: resolutionReceiver.resolutionComplete())
  218. )
  219. return resolution
  220. @implementer(IResolutionReceiver)
  221. class FirstOneWins:
  222. """
  223. An L{IResolutionReceiver} which fires a L{Deferred} with its first result.
  224. """
  225. def __init__(self, deferred: "Deferred[str]"):
  226. """
  227. @param deferred: The L{Deferred} to fire when the first resolution
  228. result arrives.
  229. """
  230. self._deferred = deferred
  231. self._resolved = False
  232. def resolutionBegan(self, resolution: IHostResolution) -> None:
  233. """
  234. See L{IResolutionReceiver.resolutionBegan}
  235. @param resolution: See L{IResolutionReceiver.resolutionBegan}
  236. """
  237. self._resolution = resolution
  238. def addressResolved(self, address: IAddress) -> None:
  239. """
  240. See L{IResolutionReceiver.addressResolved}
  241. @param address: See L{IResolutionReceiver.addressResolved}
  242. """
  243. if self._resolved:
  244. return
  245. self._resolved = True
  246. # This is used by ComplexResolverSimplifier which specifies only results
  247. # of IPv4Address.
  248. assert isinstance(address, IPv4Address)
  249. self._deferred.callback(address.host)
  250. def resolutionComplete(self) -> None:
  251. """
  252. See L{IResolutionReceiver.resolutionComplete}
  253. """
  254. if self._resolved:
  255. return
  256. self._deferred.errback(DNSLookupError(self._resolution.name))
  257. @implementer(IResolverSimple)
  258. class ComplexResolverSimplifier:
  259. """
  260. A converter from L{IHostnameResolver} to L{IResolverSimple}
  261. """
  262. def __init__(self, nameResolver: IHostnameResolver):
  263. """
  264. Create a L{ComplexResolverSimplifier} with an L{IHostnameResolver}.
  265. @param nameResolver: The L{IHostnameResolver} to use.
  266. """
  267. self._nameResolver = nameResolver
  268. def getHostByName(self, name: str, timeouts: Sequence[int] = ()) -> "Deferred[str]":
  269. """
  270. See L{IResolverSimple.getHostByName}
  271. @param name: see L{IResolverSimple.getHostByName}
  272. @param timeouts: see L{IResolverSimple.getHostByName}
  273. @return: see L{IResolverSimple.getHostByName}
  274. """
  275. result: "Deferred[str]" = Deferred()
  276. self._nameResolver.resolveHostName(FirstOneWins(result), name, 0, [IPv4Address])
  277. return result