def _discoverAuthority(self, query, servers, timeout, queriesLeft): """ Issue a query to a server and follow a delegation if necessary. @param query: The query to issue. @type query: L{dns.Query} @param servers: The servers which might have an answer for this query. @type servers: L{list} of L{tuple} of L{str} and L{int} @param timeout: A C{tuple} of C{int} giving the timeout to use for this query. @param queriesLeft: A C{int} giving the number of queries which may yet be attempted to answer this query before the attempt will be abandoned. @return: A L{Deferred} which fires with a three-tuple of lists of L{twisted.names.dns.RRHeader} giving the response, or with a L{Failure} if there is a timeout or response error. """ # Stop now if we've hit the query limit. if queriesLeft <= 0: return Failure( error.ResolverError("Query limit reached without result")) d = self._query(query, servers, timeout, False) d.addCallback( self._discoveredAuthority, query, timeout, queriesLeft - 1) return d
def _discoveredAuthority(self, response, query, timeout, queriesLeft): """ Interpret the response to a query, checking for error codes and following delegations if necessary. @param response: The L{Message} received in response to issuing C{query}. @type response: L{Message} @param query: The L{dns.Query} which was issued. @type query: L{dns.Query}. @param timeout: The timeout to use if another query is indicated by this response. @type timeout: L{tuple} of L{int} @param queriesLeft: A C{int} giving the number of queries which may yet be attempted to answer this query before the attempt will be abandoned. @return: A L{Failure} indicating a response error, a three-tuple of lists of L{twisted.names.dns.RRHeader} giving the response to C{query} or a L{Deferred} which will fire with one of those. """ if response.rCode != dns.OK: return Failure(self.exceptionForCode(response.rCode)(response)) # Turn the answers into a structure that's a little easier to work with. records = {} for answer in response.answers: records.setdefault(answer.name, []).append(answer) def findAnswerOrCName(name, type, cls): cname = None for record in records.get(name, []): if record.cls == cls: if record.type == type: return record elif record.type == dns.CNAME: cname = record # If there were any CNAME records, return the last one. There's # only supposed to be zero or one, though. return cname seen = set() name = query.name record = None while True: seen.add(name) previous = record record = findAnswerOrCName(name, query.type, query.cls) if record is None: if name == query.name: # If there's no answer for the original name, then this may # be a delegation. Code below handles it. break else: # Try to resolve the CNAME with another query. d = self._discoverAuthority( dns.Query(str(name), query.type, query.cls), self._roots(), timeout, queriesLeft) # We also want to include the CNAME in the ultimate result, # otherwise this will be pretty confusing. def cbResolved(results): answers, authority, additional = results answers.insert(0, previous) return (answers, authority, additional) d.addCallback(cbResolved) return d elif record.type == query.type: return ( response.answers, response.authority, response.additional) else: # It's a CNAME record. Try to resolve it from the records # in this response with another iteration around the loop. if record.payload.name in seen: raise error.ResolverError("Cycle in CNAME processing") name = record.payload.name # Build a map to use to convert NS names into IP addresses. addresses = {} for rr in response.additional: if rr.type == dns.A: addresses[rr.name.name] = rr.payload.dottedQuad() hints = [] traps = [] for rr in response.authority: if rr.type == dns.NS: ns = rr.payload.name.name if ns in addresses: hints.append((addresses[ns], dns.PORT)) else: traps.append(ns) if hints: return self._discoverAuthority( query, hints, timeout, queriesLeft) elif traps: d = self.lookupAddress(traps[0], timeout) def getOneAddress(results): answers, authority, additional = results return answers[0].payload.dottedQuad() d.addCallback(getOneAddress) d.addCallback( lambda hint: self._discoverAuthority( query, [(hint, dns.PORT)], timeout, queriesLeft - 1)) return d else: return Failure(error.ResolverError( "Stuck at response without answers or delegation"))
def _discoveredAuthority(self, response, query, timeout, queriesLeft): """ Interpret the response to a query, following CNAMES if necessary. @param response: The L{Message} received in response to issuing C{query}. @type response: L{Message} @param query: The L{dns.Query} which was issued. @type query: L{dns.Query}. @param timeout: The timeout to use if another query is indicated by this response. @type timeout: L{tuple} of L{int} @param queriesLeft: A C{int} giving the number of queries which may yet be attempted to answer this query before the attempt will be abandoned. @return: A L{Failure} indicating a response error, a three-tuple of lists of L{twisted.names.dns.RRHeader} giving the response to C{query} or a L{Deferred} which will fire with one of those. """ answers, authority, additional = response if not answers: return Failure(error.DomainError()) # Turn the answers into a structure that's a little easier to work with. records = {} for answer in answers: records.setdefault(answer.name, []).append(answer) def findAnswerOrCName(name, type, cls): cname = None for record in records.get(name, []): if record.cls == cls: if record.type == type: return record elif record.type == dns.CNAME: cname = record # If there were any CNAME records, return the last one. There's # only supposed to be zero or one, though. return cname seen = set() name = query.name record = None while True: seen.add(name) previous = record record = findAnswerOrCName(name, query.type, query.cls) if record is None: if name == query.name: # If there's no answer for the original name, then this may # be a delegation. Code below handles it. break else: # Try to resolve the CNAME with another query. d = self._discoverAuthority( dns.Query(str(name), query.type, query.cls), timeout, queriesLeft) # We also want to include the CNAME in the ultimate result, # otherwise this will be pretty confusing. def cbResolved(results): answers, authority, additional = results answers.insert(0, previous) return (answers, authority, additional) d.addCallback(cbResolved) return d elif record.type == query.type: return ( answers, authority, additional) else: # It's a CNAME record. Try to resolve it from the records # in this response with another iteration around the loop. if record.payload.name in seen: raise error.ResolverError("Cycle in CNAME processing") name = record.payload.name return Failure(error.DomainError())