def test_lookupChecksClass(self): """ If a response includes a record with a class different from the one in the query, it is ignored and lookup continues until a record with the right class is found. """ badClass = Record_A('10.0.0.1') badClass.CLASS = HS servers = { ('1.1.2.3', 53): { ('foo.example.com', A): { 'answers': [('foo.example.com', badClass)], 'authority': [('foo.example.com', Record_NS('ns1.example.com'))], 'additional': [('ns1.example.com', Record_A('10.0.0.2'))], }, }, ('10.0.0.2', 53): { ('foo.example.com', A): { 'answers': [('foo.example.com', Record_A('10.0.0.3'))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress('foo.example.com') d.addCallback(lambda (ans, auth, add): ans[0].payload) d.addCallback(self.assertEquals, Record_A('10.0.0.3')) return d
def test_missingGlue(self): """ If an intermediate response includes no glue records for the authorities, separate queries are made to find those addresses. """ servers = { ("1.1.2.3", 53): { (b"foo.example.com", A): { "authority": [(b"foo.example.com", Record_NS(b"ns1.example.org"))], # Conspicuous lack of an additional section naming ns1.example.com }, (b"ns1.example.org", A): { "answers": [(b"ns1.example.org", Record_A("10.0.0.1"))], }, }, ("10.0.0.1", 53): { (b"foo.example.com", A): { "answers": [(b"foo.example.com", Record_A("10.0.0.2"))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b"foo.example.com") d.addCallback(getOneAddress) d.addCallback(self.assertEqual, "10.0.0.2") return d
def test_lookupAddress(self): """ L{root.Resolver.lookupAddress} looks up the I{A} records for the specified hostname by first querying one of the root servers the resolver was created with and then following the authority delegations until a result is received. """ servers = { ("1.1.2.3", 53): { (b"foo.example.com", A): { "authority": [(b"foo.example.com", Record_NS(b"ns1.example.com"))], "additional": [(b"ns1.example.com", Record_A("34.55.89.144"))], }, }, ("34.55.89.144", 53): { (b"foo.example.com", A): { "answers": [(b"foo.example.com", Record_A("10.0.0.1"))], } }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b"foo.example.com") d.addCallback(getOneAddress) d.addCallback(self.assertEqual, "10.0.0.1") return d
def test_missingGlue(self): """ If an intermediate response includes no glue records for the authorities, separate queries are made to find those addresses. """ servers = { ('1.1.2.3', 53): { (b'foo.example.com', A): { 'authority': [(b'foo.example.com', Record_NS(b'ns1.example.org'))], # Conspicuous lack of an additional section naming ns1.example.com }, (b'ns1.example.org', A): { 'answers': [(b'ns1.example.org', Record_A('10.0.0.1'))], }, }, ('10.0.0.1', 53): { (b'foo.example.com', A): { 'answers': [(b'foo.example.com', Record_A('10.0.0.2'))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b'foo.example.com') d.addCallback(getOneAddress) d.addCallback(self.assertEqual, '10.0.0.2') return d
def test_lookupAddress(self): """ L{root.Resolver.lookupAddress} looks up the I{A} records for the specified hostname by first querying one of the root servers the resolver was created with and then following the authority delegations until a result is received. """ servers = { ('1.1.2.3', 53): { (b'foo.example.com', A): { 'authority': [(b'foo.example.com', Record_NS(b'ns1.example.com'))], 'additional': [(b'ns1.example.com', Record_A('34.55.89.144'))], }, }, ('34.55.89.144', 53): { (b'foo.example.com', A): { 'answers': [(b'foo.example.com', Record_A('10.0.0.1'))], } }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b'foo.example.com') d.addCallback(getOneAddress) d.addCallback(self.assertEqual, '10.0.0.1') return d
def test_followCanonicalName(self): """ If no record of the requested type is included in a response, but a I{CNAME} record for the query name is included, queries are made to resolve the value of the I{CNAME}. """ servers = { ('1.1.2.3', 53): { ('example.com', A): { 'answers': [('example.com', Record_CNAME('example.net'))], }, ('example.net', A): { 'answers': [('example.net', Record_A('10.0.0.5'))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress('example.com') d.addCallback(lambda (ans, auth, add): ans) d.addCallback(self.assertEquals, [ RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')), RRHeader('example.net', A, payload=Record_A('10.0.0.5')) ]) return d
def test_followCanonicalName(self): """ If no record of the requested type is included in a response, but a I{CNAME} record for the query name is included, queries are made to resolve the value of the I{CNAME}. """ servers = { ("1.1.2.3", 53): { (b"example.com", A): { "answers": [(b"example.com", Record_CNAME(b"example.net"))], }, (b"example.net", A): { "answers": [(b"example.net", Record_A("10.0.0.5"))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b"example.com") d.addCallback(lambda results: results[0]) # Get the answer section d.addCallback( self.assertEqual, [ RRHeader(b"example.com", CNAME, payload=Record_CNAME(b"example.net")), RRHeader(b"example.net", A, payload=Record_A("10.0.0.5")), ], ) return d
def test_returnCanonicalName(self): """ If a I{CNAME} record is encountered as the answer to a query for another record type, that record is returned as the answer. """ servers = { ("1.1.2.3", 53): { (b"example.com", A): { "answers": [ (b"example.com", Record_CNAME(b"example.net")), (b"example.net", Record_A("10.0.0.7")), ], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b"example.com") d.addCallback(lambda results: results[0]) # Get the answer section d.addCallback( self.assertEqual, [ RRHeader(b"example.com", CNAME, payload=Record_CNAME(b"example.net")), RRHeader(b"example.net", A, payload=Record_A("10.0.0.7")), ], ) return d
def resolved(results): answers, authority, additional = results self.assertEqual((RRHeader(b"multiple", A, IN, self.ttl, Record_A("1.1.1.3", self.ttl)), RRHeader(b"multiple", A, IN, self.ttl, Record_A("1.1.1.4", self.ttl))), answers)
def test_lookupAddress(self): """ L{hosts.Resolver.lookupAddress} returns a L{Deferred} which fires with A records from the hosts file. """ d = self.resolver.lookupAddress(b'multiple') answers, authority, additional = self.successResultOf(d) self.assertEqual((RRHeader(b"multiple", A, IN, self.ttl, Record_A("1.1.1.3", self.ttl)), RRHeader(b"multiple", A, IN, self.ttl, Record_A("1.1.1.4", self.ttl))), answers)
def test_boundedQueries(self): """ L{Resolver.lookupAddress} won't issue more queries following delegations than the limit passed to its initializer. """ servers = { ("1.1.2.3", 53): { # First query - force it to start over with a name lookup of # ns1.example.com (b"example.com", A): { "authority": [(b"example.com", Record_NS(b"ns1.example.com"))], }, # Second query - let it resume the original lookup with the # address of the nameserver handling the delegation. (b"ns1.example.com", A): { "answers": [(b"ns1.example.com", Record_A("10.0.0.2"))], }, }, ("10.0.0.2", 53): { # Third query - let it jump straight to asking the # delegation server by including its address here (different # case from the first query). (b"example.com", A): { "authority": [(b"example.com", Record_NS(b"ns2.example.com"))], "additional": [(b"ns2.example.com", Record_A("10.0.0.3"))], }, }, ("10.0.0.3", 53): { # Fourth query - give it the answer, we're done. (b"example.com", A): { "answers": [(b"example.com", Record_A("10.0.0.4"))], }, }, } # Make two resolvers. One which is allowed to make 3 queries # maximum, and so will fail, and on which may make 4, and so should # succeed. failer = self._getResolver(servers, 3) failD = self.assertFailure(failer.lookupAddress(b"example.com"), ResolverError) succeeder = self._getResolver(servers, 4) succeedD = succeeder.lookupAddress(b"example.com") succeedD.addCallback(getOnePayload) succeedD.addCallback(self.assertEqual, Record_A("10.0.0.4")) return gatherResults([failD, succeedD])
def test_infer_search_domains(resolver): """ ``LocalResolver`` uses a number of DNS queries sent to it as probes to infer the search domains configured for the client. """ probe = u"hellotelepresence" counter = count(0) for search in [u".foo", u".foo.bar", u".alternate"]: for i in range(3): name = u"{}{}{}".format( probe, next(counter), search, ).encode("ascii") rrheader = RRHeader( name=name, payload=Record_A(address=b"127.0.0.1"), ) expected = ([rrheader], [], []) result = resolver.query(Query(name)) assert expected == result for search in [u".foo", u".foo.bar", u".alternate"]: mangled = (u"example.com" + search).encode("ascii").split(b".") assert [b"example", b"com"] == resolver._strip_search_suffix(mangled)
def addSubdomains(host, zone, subs): """ Add the given subdomain mapping to the given zone list. """ for (ip, hosts) in subs.items(): for sub in hosts: zone.append((sub + host, Record_A(ip, ttl="5M")))
def test_lookupMalformed(self): """ L{hosts.Resolver.lookupAddress} returns a L{Deferred} which fires with the valid addresses from the hosts file, ignoring any entries that aren't valid IP addresses. """ d = self.resolver.lookupAddress(b"malformed") [answer], authority, additional = self.successResultOf(d) self.assertEqual( RRHeader(b"malformed", A, IN, self.ttl, Record_A("1.1.1.5", self.ttl)), answer, )
def test_lookupChecksClass(self): """ If a response includes a record with a class different from the one in the query, it is ignored and lookup continues until a record with the right class is found. """ badClass = Record_A('10.0.0.1') badClass.CLASS = HS servers = { ('1.1.2.3', 53): { ('foo.example.com', A): { 'answers': [('foo.example.com', badClass)], 'authority': [('foo.example.com', Record_NS('ns1.example.com'))], 'additional': [('ns1.example.com', Record_A('10.0.0.2'))], }, }, ('10.0.0.2', 53): { ('foo.example.com', A): { 'answers': [('foo.example.com', Record_A('10.0.0.3'))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress('foo.example.com') d.addCallback(getOnePayload) d.addCallback(self.assertEqual, Record_A('10.0.0.3')) return d
def test_lookupChecksClass(self): """ If a response includes a record with a class different from the one in the query, it is ignored and lookup continues until a record with the right class is found. """ badClass = Record_A("10.0.0.1") badClass.CLASS = HS servers = { ("1.1.2.3", 53): { (b"foo.example.com", A): { "answers": [(b"foo.example.com", badClass)], "authority": [(b"foo.example.com", Record_NS(b"ns1.example.com"))], "additional": [(b"ns1.example.com", Record_A("10.0.0.2"))], }, }, ("10.0.0.2", 53): { (b"foo.example.com", A): { "answers": [(b"foo.example.com", Record_A("10.0.0.3"))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress(b"foo.example.com") d.addCallback(getOnePayload) d.addCallback(self.assertEqual, Record_A("10.0.0.3")) return d
def nameservers(host, *addresses): """ Return NS records and A record glue for the given host. """ if not addresses: addresses = [dornkirk, ns2] records = [] for i, addr in enumerate(addresses): records.extend([ (host, Record_NS('ns%d.twistedmatrix.com' % (i + 1, ), ttl='1H')), ('ns%d.twistedmatrix.com' % (i + 1, ), Record_A(addr, ttl='1H')) ]) return records
def test_filteredQuery(self): """ L{Resolver._query} accepts a L{Query} instance and an address, issues the query, and returns a L{Deferred} which fires with the response to the query. If a true value is passed for the C{filter} parameter, the result is a three-tuple of lists of records. """ answer, authority, additional = self._queryTest(True) self.assertEqual(answer, [ RRHeader(b"foo.example.com", payload=Record_A("5.8.13.21", ttl=0)) ]) self.assertEqual(authority, []) self.assertEqual(additional, [])
def test_returnCanonicalName(self): """ If a I{CNAME} record is encountered as the answer to a query for another record type, that record is returned as the answer. """ servers = { ('1.1.2.3', 53): { ('example.com', A): { 'answers': [('example.com', Record_CNAME('example.net')), ('example.net', Record_A('10.0.0.7'))], }, }, } resolver = self._getResolver(servers) d = resolver.lookupAddress('example.com') d.addCallback(lambda (ans, auth, add): ans) d.addCallback(self.assertEquals, [ RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')), RRHeader('example.net', A, payload=Record_A('10.0.0.7')) ]) return d
def test_unfilteredQuery(self): """ Similar to L{test_filteredQuery}, but for the case where a false value is passed for the C{filter} parameter. In this case, the result is a L{Message} instance. """ message = self._queryTest(False) self.assertIsInstance(message, Message) self.assertEqual(message.queries, []) self.assertEqual(message.answers, [ RRHeader(b'foo.example.com', payload=Record_A('5.8.13.21', ttl=0)) ]) self.assertEqual(message.authority, []) self.assertEqual(message.additional, [])
def test_lookupAddress(self): """ L{SecondaryAuthority.lookupAddress} returns a L{Deferred} that fires with the I{A} records the authority has cached from the primary. """ secondary = SecondaryAuthority.fromServerAddressAndDomain( ("192.168.1.2", 1234), b"example.com") secondary._reactor = reactor = MemoryReactorClock() secondary.transfer() host, port, factory, timeout, bindAddress = reactor.tcpClients.pop(0) proto = factory.buildProtocol((host, port)) transport = StringTransport() proto.makeConnection(transport) query = Message(answer=1, auth=1) query.decode(BytesIO(transport.value()[2:])) # Generate a response with some data we can check. soa = Record_SOA( mname=b"ns1.example.com", rname="admin.example.com", serial=123456, refresh=3600, minimum=4800, expire=7200, retry=9600, ttl=12000, ) a = Record_A(b"192.168.1.2", ttl=0) answer = Message(id=query.id, answer=1, auth=1) answer.answers.extend([ RRHeader(b"example.com", type=SOA, payload=soa), RRHeader(b"example.com", payload=a), RRHeader(b"example.com", type=SOA, payload=soa), ]) data = answer.toStr() proto.dataReceived(pack("!H", len(data)) + data) result = self.successResultOf(secondary.lookupAddress("example.com")) self.assertEqual( ([RRHeader(b"example.com", payload=a, auth=True)], [], []), result)
def _queryTest(self, filter): """ Invoke L{Resolver._query} and verify that it sends the correct DNS query. Deliver a canned response to the query and return whatever the L{Deferred} returned by L{Resolver._query} fires with. @param filter: The value to pass for the C{filter} parameter to L{Resolver._query}. """ reactor = MemoryReactor() resolver = Resolver([], reactor=reactor) d = resolver._query( Query(b'foo.example.com', A, IN), [('1.1.2.3', 1053)], (30,), filter) # A UDP port should have been started. portNumber, transport = reactor.udpPorts.popitem() # And a DNS packet sent. [(packet, address)] = transport._sentPackets message = Message() message.fromStr(packet) # It should be a query with the parameters used above. self.assertEqual(message.queries, [Query(b'foo.example.com', A, IN)]) self.assertEqual(message.answers, []) self.assertEqual(message.authority, []) self.assertEqual(message.additional, []) response = [] d.addCallback(response.append) self.assertEqual(response, []) # Once a reply is received, the Deferred should fire. del message.queries[:] message.answer = 1 message.answers.append(RRHeader( b'foo.example.com', payload=Record_A('5.8.13.21'))) transport._protocol.datagramReceived( message.toStr(), ('1.1.2.3', 1053)) return response[0]
def resolved((results, authority, additional)): self.assertEqual((RRHeader("mixed", A, IN, self.ttl, Record_A("1.1.1.2", self.ttl)), ), results)
def resolved((results, authority, additional)): self.assertEqual((RRHeader("multiple", A, IN, self.ttl, Record_A("1.1.1.3", self.ttl)), RRHeader("multiple", A, IN, self.ttl, Record_A("1.1.1.4", self.ttl))), results)
def resolved(results): answers, authority, additional = results self.assertEqual( (RRHeader(b"mixed", A, IN, self.ttl, Record_A("1.1.1.2", self.ttl)),), answers)