Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

root.py 12KB

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