root.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. # -*- test-case-name: twisted.names.test.test_rootresolve -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Resolver implementation for querying successive authoritative servers to
  6. lookup a record, starting from the root nameservers.
  7. @author: Jp Calderone
  8. todo::
  9. robustify it
  10. documentation
  11. """
  12. from twisted.python.failure import Failure
  13. from twisted.internet import defer
  14. from twisted.names import dns, common, error
  15. class _DummyController:
  16. """
  17. A do-nothing DNS controller. This is useful when all messages received
  18. will be responses to previously issued queries. Anything else received
  19. will be ignored.
  20. """
  21. def messageReceived(self, *args):
  22. pass
  23. class Resolver(common.ResolverBase):
  24. """
  25. L{Resolver} implements recursive lookup starting from a specified list of
  26. root servers.
  27. @ivar hints: See C{hints} parameter of L{__init__}
  28. @ivar _maximumQueries: See C{maximumQueries} parameter of L{__init__}
  29. @ivar _reactor: See C{reactor} parameter of L{__init__}
  30. @ivar _resolverFactory: See C{resolverFactory} parameter of L{__init__}
  31. """
  32. def __init__(self, hints, maximumQueries=10,
  33. reactor=None, resolverFactory=None):
  34. """
  35. @param hints: A L{list} of L{str} giving the dotted quad
  36. representation of IP addresses of root servers at which to
  37. begin resolving names.
  38. @type hints: L{list} of L{str}
  39. @param maximumQueries: An optional L{int} giving the maximum
  40. number of queries which will be attempted to resolve a
  41. single name.
  42. @type maximumQueries: L{int}
  43. @param reactor: An optional L{IReactorTime} and L{IReactorUDP}
  44. provider to use to bind UDP ports and manage timeouts.
  45. @type reactor: L{IReactorTime} and L{IReactorUDP} provider
  46. @param resolverFactory: An optional callable which accepts C{reactor}
  47. and C{servers} arguments and returns an instance that provides a
  48. C{queryUDP} method. Defaults to L{twisted.names.client.Resolver}.
  49. @type resolverFactory: callable
  50. """
  51. common.ResolverBase.__init__(self)
  52. self.hints = hints
  53. self._maximumQueries = maximumQueries
  54. self._reactor = reactor
  55. if resolverFactory is None:
  56. from twisted.names.client import Resolver as resolverFactory
  57. self._resolverFactory = resolverFactory
  58. def _roots(self):
  59. """
  60. Return a list of two-tuples representing the addresses of the root
  61. servers, as defined by C{self.hints}.
  62. """
  63. return [(ip, dns.PORT) for ip in self.hints]
  64. def _query(self, query, servers, timeout, filter):
  65. """
  66. Issue one query and return a L{Deferred} which fires with its response.
  67. @param query: The query to issue.
  68. @type query: L{dns.Query}
  69. @param servers: The servers which might have an answer for this
  70. query.
  71. @type servers: L{list} of L{tuple} of L{str} and L{int}
  72. @param timeout: A timeout on how long to wait for the response.
  73. @type timeout: L{tuple} of L{int}
  74. @param filter: A flag indicating whether to filter the results. If
  75. C{True}, the returned L{Deferred} will fire with a three-tuple of
  76. lists of L{twisted.names.dns.RRHeader} (like the return value of
  77. the I{lookup*} methods of L{IResolver}. IF C{False}, the result
  78. will be a L{Message} instance.
  79. @type filter: L{bool}
  80. @return: A L{Deferred} which fires with the response or a timeout
  81. error.
  82. @rtype: L{Deferred}
  83. """
  84. r = self._resolverFactory(servers=servers, reactor=self._reactor)
  85. d = r.queryUDP([query], timeout)
  86. if filter:
  87. d.addCallback(r.filterAnswers)
  88. return d
  89. def _lookup(self, name, cls, type, timeout):
  90. """
  91. Implement name lookup by recursively discovering the authoritative
  92. server for the name and then asking it, starting at one of the servers
  93. in C{self.hints}.
  94. """
  95. if timeout is None:
  96. # A series of timeouts for semi-exponential backoff, summing to an
  97. # arbitrary total of 60 seconds.
  98. timeout = (1, 3, 11, 45)
  99. return self._discoverAuthority(
  100. dns.Query(name, type, cls), self._roots(), timeout,
  101. self._maximumQueries)
  102. def _discoverAuthority(self, query, servers, timeout, queriesLeft):
  103. """
  104. Issue a query to a server and follow a delegation if necessary.
  105. @param query: The query to issue.
  106. @type query: L{dns.Query}
  107. @param servers: The servers which might have an answer for this
  108. query.
  109. @type servers: L{list} of L{tuple} of L{str} and L{int}
  110. @param timeout: A C{tuple} of C{int} giving the timeout to use for this
  111. query.
  112. @param queriesLeft: A C{int} giving the number of queries which may
  113. yet be attempted to answer this query before the attempt will be
  114. abandoned.
  115. @return: A L{Deferred} which fires with a three-tuple of lists of
  116. L{twisted.names.dns.RRHeader} giving the response, or with a
  117. L{Failure} if there is a timeout or response error.
  118. """
  119. # Stop now if we've hit the query limit.
  120. if queriesLeft <= 0:
  121. return Failure(
  122. error.ResolverError("Query limit reached without result"))
  123. d = self._query(query, servers, timeout, False)
  124. d.addCallback(
  125. self._discoveredAuthority, query, timeout, queriesLeft - 1)
  126. return d
  127. def _discoveredAuthority(self, response, query, timeout, queriesLeft):
  128. """
  129. Interpret the response to a query, checking for error codes and
  130. following delegations if necessary.
  131. @param response: The L{Message} received in response to issuing C{query}.
  132. @type response: L{Message}
  133. @param query: The L{dns.Query} which was issued.
  134. @type query: L{dns.Query}.
  135. @param timeout: The timeout to use if another query is indicated by
  136. this response.
  137. @type timeout: L{tuple} of L{int}
  138. @param queriesLeft: A C{int} giving the number of queries which may
  139. yet be attempted to answer this query before the attempt will be
  140. abandoned.
  141. @return: A L{Failure} indicating a response error, a three-tuple of
  142. lists of L{twisted.names.dns.RRHeader} giving the response to
  143. C{query} or a L{Deferred} which will fire with one of those.
  144. """
  145. if response.rCode != dns.OK:
  146. return Failure(self.exceptionForCode(response.rCode)(response))
  147. # Turn the answers into a structure that's a little easier to work with.
  148. records = {}
  149. for answer in response.answers:
  150. records.setdefault(answer.name, []).append(answer)
  151. def findAnswerOrCName(name, type, cls):
  152. cname = None
  153. for record in records.get(name, []):
  154. if record.cls == cls:
  155. if record.type == type:
  156. return record
  157. elif record.type == dns.CNAME:
  158. cname = record
  159. # If there were any CNAME records, return the last one. There's
  160. # only supposed to be zero or one, though.
  161. return cname
  162. seen = set()
  163. name = query.name
  164. record = None
  165. while True:
  166. seen.add(name)
  167. previous = record
  168. record = findAnswerOrCName(name, query.type, query.cls)
  169. if record is None:
  170. if name == query.name:
  171. # If there's no answer for the original name, then this may
  172. # be a delegation. Code below handles it.
  173. break
  174. else:
  175. # Try to resolve the CNAME with another query.
  176. d = self._discoverAuthority(
  177. dns.Query(str(name), query.type, query.cls),
  178. self._roots(), timeout, queriesLeft)
  179. # We also want to include the CNAME in the ultimate result,
  180. # otherwise this will be pretty confusing.
  181. def cbResolved(results):
  182. answers, authority, additional = results
  183. answers.insert(0, previous)
  184. return (answers, authority, additional)
  185. d.addCallback(cbResolved)
  186. return d
  187. elif record.type == query.type:
  188. return (
  189. response.answers,
  190. response.authority,
  191. response.additional)
  192. else:
  193. # It's a CNAME record. Try to resolve it from the records
  194. # in this response with another iteration around the loop.
  195. if record.payload.name in seen:
  196. raise error.ResolverError("Cycle in CNAME processing")
  197. name = record.payload.name
  198. # Build a map to use to convert NS names into IP addresses.
  199. addresses = {}
  200. for rr in response.additional:
  201. if rr.type == dns.A:
  202. addresses[rr.name.name] = rr.payload.dottedQuad()
  203. hints = []
  204. traps = []
  205. for rr in response.authority:
  206. if rr.type == dns.NS:
  207. ns = rr.payload.name.name
  208. if ns in addresses:
  209. hints.append((addresses[ns], dns.PORT))
  210. else:
  211. traps.append(ns)
  212. if hints:
  213. return self._discoverAuthority(
  214. query, hints, timeout, queriesLeft)
  215. elif traps:
  216. d = self.lookupAddress(traps[0], timeout)
  217. def getOneAddress(results):
  218. answers, authority, additional = results
  219. return answers[0].payload.dottedQuad()
  220. d.addCallback(getOneAddress)
  221. d.addCallback(
  222. lambda hint: self._discoverAuthority(
  223. query, [(hint, dns.PORT)], timeout, queriesLeft - 1))
  224. return d
  225. else:
  226. return Failure(error.ResolverError(
  227. "Stuck at response without answers or delegation"))
  228. def makePlaceholder(deferred, name):
  229. def placeholder(*args, **kw):
  230. deferred.addCallback(lambda r: getattr(r, name)(*args, **kw))
  231. return deferred
  232. return placeholder
  233. class DeferredResolver:
  234. def __init__(self, resolverDeferred):
  235. self.waiting = []
  236. resolverDeferred.addCallback(self.gotRealResolver)
  237. def gotRealResolver(self, resolver):
  238. w = self.waiting
  239. self.__dict__ = resolver.__dict__
  240. self.__class__ = resolver.__class__
  241. for d in w:
  242. d.callback(resolver)
  243. def __getattr__(self, name):
  244. if name.startswith('lookup') or name in ('getHostByName', 'query'):
  245. self.waiting.append(defer.Deferred())
  246. return makePlaceholder(self.waiting[-1], name)
  247. raise AttributeError(name)
  248. def bootstrap(resolver, resolverFactory=None):
  249. """
  250. Lookup the root nameserver addresses using the given resolver
  251. Return a Resolver which will eventually become a C{root.Resolver}
  252. instance that has references to all the root servers that we were able
  253. to look up.
  254. @param resolver: The resolver instance which will be used to
  255. lookup the root nameserver addresses.
  256. @type resolver: L{twisted.internet.interfaces.IResolverSimple}
  257. @param resolverFactory: An optional callable which returns a
  258. resolver instance. It will passed as the C{resolverFactory}
  259. argument to L{Resolver.__init__}.
  260. @type resolverFactory: callable
  261. @return: A L{DeferredResolver} which will be dynamically replaced
  262. with L{Resolver} when the root nameservers have been looked up.
  263. """
  264. domains = [chr(ord('a') + i) for i in range(13)]
  265. L = [resolver.getHostByName('%s.root-servers.net' % d) for d in domains]
  266. d = defer.DeferredList(L, consumeErrors=True)
  267. def buildResolver(res):
  268. return Resolver(
  269. hints=[e[1] for e in res if e[0]],
  270. resolverFactory=resolverFactory)
  271. d.addCallback(buildResolver)
  272. return DeferredResolver(d)