diff -Nru twisted-names-11.0.0/debian/changelog twisted-names-12.1.0/debian/changelog --- twisted-names-11.0.0/debian/changelog 2011-04-18 20:49:39.000000000 +0000 +++ twisted-names-12.1.0/debian/changelog 2012-06-19 23:03:47.000000000 +0000 @@ -1,3 +1,23 @@ +twisted-names (12.1.0-1~ppa1~oneiric1) oneiric; urgency=low + + * New upstream version. + * Rebuild package for use in PPA. + + -- Jessica McKellar Tue, 19 Jun 2012 19:02:56 -0400 + +twisted-names (12.0.0-1~ppa1~oneiric1) oneiric; urgency=low + + * New upstream version. + * Rebuild package for use in PPA. + + -- Jessica McKellar Wed, 15 Feb 2012 20:35:30 -0500 + +twisted-names (11.1.0-1) unstable; urgency=low + + * New upstream release. + + -- Matthias Klose Wed, 21 Dec 2011 12:56:22 +0100 + twisted-names (11.0.0-1) unstable; urgency=low * New upstream release. diff -Nru twisted-names-11.0.0/debian/compat twisted-names-12.1.0/debian/compat --- twisted-names-11.0.0/debian/compat 2010-07-21 08:36:12.000000000 +0000 +++ twisted-names-12.1.0/debian/compat 2012-06-19 23:02:40.000000000 +0000 @@ -1 +1 @@ -5 +7 diff -Nru twisted-names-11.0.0/debian/control twisted-names-12.1.0/debian/control --- twisted-names-11.0.0/debian/control 2011-04-18 20:50:41.000000000 +0000 +++ twisted-names-12.1.0/debian/control 2012-06-19 23:04:12.000000000 +0000 @@ -3,17 +3,16 @@ Priority: optional Maintainer: Matthias Klose Uploaders: Free Ekanayaka -Build-Depends: debhelper (>= 5.0.37.1), python-all (>= 2.6.5-9~), python-twisted-core (>= 11.0), patch +Build-Depends: debhelper (>=7.0.50~), python-all (>= 2.6.6-3~), python-twisted-core (>= 12.1), patch XS-Python-Version: all -Standards-Version: 3.9.1 +Standards-Version: 3.9.2 Package: python-twisted-names Architecture: all -Depends: ${python:Depends}, python-twisted-core (>= 11.0), ${misc:Depends} +Depends: ${python:Depends}, python-twisted-core (>= 12.1), ${misc:Depends} Conflicts: python2.3-twisted-names, python2.4-twisted-names Replaces: python2.3-twisted-names, python2.4-twisted-names -XB-Python-Version: ${python:Versions} -Description: A DNS protocol implementation with client and server +Description: DNS protocol implementation with client and server Twisted Names is both a domain name server as well as a client resolver library. Twisted Names comes with an "out of the box" nameserver which can read most BIND-syntax zone files as well as a diff -Nru twisted-names-11.0.0/debian/rules twisted-names-12.1.0/debian/rules --- twisted-names-11.0.0/debian/rules 2010-07-21 08:38:20.000000000 +0000 +++ twisted-names-12.1.0/debian/rules 2012-06-19 23:02:40.000000000 +0000 @@ -7,6 +7,8 @@ VER := $(shell /usr/bin/python -c 'import sys; print sys.version[:3]') build: build-stamp +build-arch: build-stamp +build-indep: build-stamp build-stamp: $(PYVERS:%=build-python%) touch $@ build-python%: @@ -25,7 +27,7 @@ install-prereq: build-stamp dh_testdir dh_testroot - dh_clean -k + dh_prep install-python%: install-prereq : # python-twisted-names diff -Nru twisted-names-11.0.0/debian/watch twisted-names-12.1.0/debian/watch --- twisted-names-11.0.0/debian/watch 2011-02-24 11:08:22.000000000 +0000 +++ twisted-names-12.1.0/debian/watch 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -version=3 -http://tmrc.mit.edu/mirror/twisted/Names/(\d+\.\d)/ TwistedNames-([\d\.]*)\.tar\.bz2 diff -Nru twisted-names-11.0.0/doc/examples/dns-service.py twisted-names-12.1.0/doc/examples/dns-service.py --- twisted-names-11.0.0/doc/examples/dns-service.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/doc/examples/dns-service.py 2012-04-02 15:26:04.000000000 +0000 @@ -5,6 +5,15 @@ """ Sample app to lookup SRV records in DNS. +To run this script: +$ python dns-service.py +where, +service = the symbolic name of the desired service. +proto = the transport protocol of the desired service; this is usually either TCP or UDP. +domain = the domain name for which this record is valid. +e.g.: +$ python dns-service.py sip udp yahoo.com +$ python dns-service.py xmpp-client tcp gmail.com """ from twisted.names import client diff -Nru twisted-names-11.0.0/doc/examples/gethostbyname.py twisted-names-12.1.0/doc/examples/gethostbyname.py --- twisted-names-11.0.0/doc/examples/gethostbyname.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/doc/examples/gethostbyname.py 2012-04-02 15:26:04.000000000 +0000 @@ -3,6 +3,13 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +""" +Returns the IP address for a given hostname. +To run this script: +$ python gethostbyname.py +e.g.: +$ python gethostbyname.py www.google.com +""" import sys from twisted.names import client from twisted.internet import reactor diff -Nru twisted-names-11.0.0/doc/examples/index.html twisted-names-12.1.0/doc/examples/index.html --- twisted-names-11.0.0/doc/examples/index.html 2011-04-02 01:47:24.000000000 +0000 +++ twisted-names-12.1.0/doc/examples/index.html 2012-06-04 08:46:25.000000000 +0000 @@ -12,13 +12,13 @@

DNS (Twisted Names)

Index

- Version: 11.0.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.0.0/doc/examples/testdns.py twisted-names-12.1.0/doc/examples/testdns.py --- twisted-names-11.0.0/doc/examples/testdns.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/doc/examples/testdns.py 2012-04-02 15:26:04.000000000 +0000 @@ -3,6 +3,16 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +""" +Prints the results of an Address record lookup, Mail-Exchanger record lookup, +and Nameserver record lookup for the given hostname for a given hostname. + +To run this script: +$ python testdns.py +e.g.: +$ python testdns.py www.google.com +""" + import sys from twisted.names import client from twisted.internet import reactor diff -Nru twisted-names-11.0.0/doc/howto/index.html twisted-names-12.1.0/doc/howto/index.html --- twisted-names-11.0.0/doc/howto/index.html 2011-04-02 01:47:24.000000000 +0000 +++ twisted-names-12.1.0/doc/howto/index.html 2012-06-04 08:46:25.000000000 +0000 @@ -17,6 +17,6 @@

Index

- Version: 11.0.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.0.0/doc/howto/names.html twisted-names-12.1.0/doc/howto/names.html --- twisted-names-11.0.0/doc/howto/names.html 2011-04-02 01:47:24.000000000 +0000 +++ twisted-names-12.1.0/doc/howto/names.html 2012-06-04 08:46:25.000000000 +0000 @@ -129,6 +129,6 @@

Index

- Version: 11.0.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.0.0/doc/index.html twisted-names-12.1.0/doc/index.html --- twisted-names-11.0.0/doc/index.html 2011-04-02 01:47:24.000000000 +0000 +++ twisted-names-12.1.0/doc/index.html 2012-06-04 08:46:25.000000000 +0000 @@ -20,6 +20,6 @@

Index

- Version: 11.0.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.0.0/LICENSE twisted-names-12.1.0/LICENSE --- twisted-names-11.0.0/LICENSE 2011-03-22 23:04:17.000000000 +0000 +++ twisted-names-12.1.0/LICENSE 2012-02-11 13:47:35.000000000 +0000 @@ -1,4 +1,4 @@ -Copyright (c) 2001-2011 +Copyright (c) 2001-2012 Allen Short Andy Gayton Andrew Bennetts @@ -12,7 +12,7 @@ Donovan Preston Eric Mangold Eyal Lotem -Itamar Shtull-Trauring +Itamar Turner-Trauring James Knight Jason A. Mobarak Jean-Paul Calderone diff -Nru twisted-names-11.0.0/NEWS twisted-names-12.1.0/NEWS --- twisted-names-11.0.0/NEWS 2011-04-02 01:10:15.000000000 +0000 +++ twisted-names-12.1.0/NEWS 2012-06-02 07:04:01.000000000 +0000 @@ -1,6 +1,68 @@ Ticket numbers in this file can be looked up by visiting http://twistedmatrix.com/trac/ticket/ +Twisted Names 12.1.0 (2012-06-02) +================================= + +Features +-------- + - "twistd dns" secondary server functionality and + twisted.names.secondary now support retrieving zone information + from a master running on a non-standard DNS port. (#5468) + +Bugfixes +-------- + - twisted.names.dns.DNSProtocol instances no longer throw an + exception when disconnecting. (#5471) + - twisted.names.tap.makeService (thus also "twistd dns") now makes a + DNS server which gives precedence to the hosts file from its + configuration over the remote DNS servers from its configuration. + (#5524) + - twisted.name.cache.CacheResolver now makes sure TTLs on returned + results are never negative. (#5579) + - twisted.names.cache.CacheResolver entries added via the initializer + are now timed out correctly. (#5638) + +Improved Documentation +---------------------- + - The examples now contain instructions on how to run them and + descriptions in the examples index. (#5588) + +Deprecations and Removals +------------------------- + - The deprecated twisted.names.dns.Record_mx.exchange attribute was + removed. (#4549) + + +Twisted Names 12.0.0 (2012-02-10) +================================= + +Bugfixes +-------- + - twisted.names.dns.Message now sets the `auth` flag on RRHeader + instances it creates to reflect the authority of the message + itself. (#5421) + + +Twisted Names 11.1.0 (2011-11-15) +================================= + +Features +-------- + - twisted.names.dns.Message now parses records of unknown type into + instances of a new `UnknownType` class. (#4603) + +Bugfixes +-------- + - twisted.names.dns.Name now detects loops in names it is decoding + and raises an exception. Previously it would follow the loop + forever, allowing a remote denial of service attack against any + twisted.names client or server. (#5064) + - twisted.names.hosts.Resolver now supports IPv6 addresses; its + lookupAddress method now filters them out and its lookupIPV6Address + method is now implemented. (#5098) + + Twisted Names 11.0.0 (2011-04-01) ================================= diff -Nru twisted-names-11.0.0/README twisted-names-12.1.0/README --- twisted-names-11.0.0/README 2011-04-02 01:10:15.000000000 +0000 +++ twisted-names-12.1.0/README 2012-06-02 07:04:01.000000000 +0000 @@ -1,3 +1,3 @@ -Twisted Names 11.0.0 +Twisted Names 12.1.0 Twisted Names depends on Twisted Core. diff -Nru twisted-names-11.0.0/twisted/names/cache.py twisted-names-12.1.0/twisted/names/cache.py --- twisted-names-11.0.0/twisted/names/cache.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/cache.py 2012-05-09 15:41:09.000000000 +0000 @@ -2,38 +2,44 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. - -import time - from zope.interface import implements -from twisted.names import dns +from twisted.names import dns, common from twisted.python import failure, log from twisted.internet import interfaces, defer -import common + class CacheResolver(common.ResolverBase): - """A resolver that serves records from a local, memory cache.""" + """ + A resolver that serves records from a local, memory cache. + + @ivar _reactor: A provider of L{interfaces.IReactorTime}. + """ implements(interfaces.IResolver) cache = None - def __init__(self, cache = None, verbose = 0): + def __init__(self, cache=None, verbose=0, reactor=None): common.ResolverBase.__init__(self) - if cache is None: - cache = {} - self.cache = cache + self.cache = {} self.verbose = verbose self.cancel = {} + if reactor is None: + from twisted.internet import reactor + self._reactor = reactor + + if cache: + for query, (seconds, payload) in cache.items(): + self.cacheResult(query, payload, seconds) def __setstate__(self, state): self.__dict__ = state - now = time.time() + now = self._reactor.seconds() for (k, (when, (ans, add, ns))) in self.cache.items(): diff = now - when for rec in ans + add + ns: @@ -50,7 +56,7 @@ def _lookup(self, name, cls, type, timeout): - now = time.time() + now = self._reactor.seconds() q = dns.Query(name, type, cls) try: when, (ans, auth, add) = self.cache[q] @@ -63,9 +69,9 @@ log.msg('Cache hit for ' + repr(name)) diff = now - when return defer.succeed(( - [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in ans], - [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in auth], - [dns.RRHeader(str(r.name), r.type, r.cls, r.ttl - diff, r.payload) for r in add] + [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in ans], + [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in auth], + [dns.RRHeader(str(r.name), r.type, r.cls, max(0, r.ttl - diff), r.payload) for r in add] )) @@ -73,22 +79,36 @@ return defer.fail(failure.Failure(dns.DomainError(name))) - def cacheResult(self, query, payload): + def cacheResult(self, query, payload, cacheTime=None): + """ + Cache a DNS entry. + + @param query: a L{dns.Query} instance. + + @param payload: a 3-tuple of lists of L{dns.RRHeader} records, the + matching result of the query (answers, authority and additional). + + @param cacheTime: The time (seconds since epoch) at which the entry is + considered to have been added to the cache. If C{None} is given, + the current time is used. + """ if self.verbose > 1: log.msg('Adding %r to cache' % query) - self.cache[query] = (time.time(), payload) + self.cache[query] = (cacheTime or self._reactor.seconds(), payload) if self.cancel.has_key(query): self.cancel[query].cancel() s = list(payload[0]) + list(payload[1]) + list(payload[2]) - m = s[0].ttl - for r in s: - m = min(m, r.ttl) + if s: + m = s[0].ttl + for r in s: + m = min(m, r.ttl) + else: + m = 0 - from twisted.internet import reactor - self.cancel[query] = reactor.callLater(m, self.clearEntry, query) + self.cancel[query] = self._reactor.callLater(m, self.clearEntry, query) def clearEntry(self, query): diff -Nru twisted-names-11.0.0/twisted/names/client.py twisted-names-12.1.0/twisted/names/client.py --- twisted-names-11.0.0/twisted/names/client.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/client.py 2012-02-08 00:35:34.000000000 +0000 @@ -236,12 +236,23 @@ def connectionMade(self, protocol): + """ + Called by associated L{dns.DNSProtocol} instances when they connect. + """ self.connections.append(protocol) for (d, q, t) in self.pending: self.queryTCP(q, t).chainDeferred(d) del self.pending[:] + def connectionLost(self, protocol): + """ + Called by associated L{dns.DNSProtocol} instances when they disconnect. + """ + if protocol in self.connections: + self.connections.remove(protocol) + + def messageReceived(self, message, protocol, address = None): log.msg("Unexpected message (%d) received from %r" % (message.id, address)) diff -Nru twisted-names-11.0.0/twisted/names/dns.py twisted-names-12.1.0/twisted/names/dns.py --- twisted-names-11.0.0/twisted/names/dns.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/dns.py 2012-04-01 20:16:36.000000000 +0000 @@ -29,7 +29,7 @@ 'Record_DNAME', 'Record_HINFO', 'Record_MB', 'Record_MD', 'Record_MF', 'Record_MG', 'Record_MINFO', 'Record_MR', 'Record_MX', 'Record_NAPTR', 'Record_NS', 'Record_NULL', 'Record_PTR', 'Record_RP', 'Record_SOA', - 'Record_SPF', 'Record_SRV', 'Record_TXT', 'Record_WKS', + 'Record_SPF', 'Record_SRV', 'Record_TXT', 'Record_WKS', 'UnknownRecord', 'QUERY_CLASSES', 'QUERY_TYPES', 'REV_CLASSES', 'REV_TYPES', 'EXT_QUERIES', @@ -48,10 +48,7 @@ import struct, random, types, socket -try: - import cStringIO as StringIO -except ImportError: - import StringIO +import cStringIO as StringIO AF_INET6 = socket.AF_INET6 @@ -332,7 +329,11 @@ @raise EOFError: Raised when there are not enough bytes available from C{strio}. + + @raise ValueError: Raised when the name cannot be decoded (for example, + because it contains a loop). """ + visited = set() self.name = '' off = 0 while 1: @@ -344,6 +345,9 @@ if (l >> 6) == 3: new_off = ((l&63) << 8 | ord(readPrecisely(strio, 1))) + if new_off in visited: + raise ValueError("Compression loop in encoded name") + visited.add(new_off) if off == 0: off = strio.tell() strio.seek(new_off) @@ -441,7 +445,9 @@ @ivar cls: The query class of the original request. @ivar ttl: The time-to-live for this record. @ivar payload: An object that implements the IEncodable interface - @ivar auth: Whether this header is authoritative or not. + + @ivar auth: A C{bool} indicating whether this C{RRHeader} was parsed from an + authoritative message. """ implements(IEncodable) @@ -476,7 +482,7 @@ @type payload: An object implementing C{IEncodable} @param payload: A Query Type specific data object. """ - assert (payload is None) or (payload.TYPE == type) + assert (payload is None) or isinstance(payload, UnknownRecord) or (payload.TYPE == type) self.name = Name(name) self.type = type @@ -1404,12 +1410,6 @@ self.name = Name() self.name.decode(strio) - def exchange(self): - warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklevel=2) - return self.name - - exchange = property(exchange) - def __hash__(self): return hash((self.preference, self.name)) @@ -1422,7 +1422,7 @@ @type data: C{list} of C{str} @ivar data: Freeform text which makes up this record. - + @type ttl: C{int} @ivar ttl: The maximum number of seconds which this record should be cached. """ @@ -1464,14 +1464,62 @@ +# This is a fallback record +class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin, object): + """ + Encapsulate the wire data for unkown record types so that they can + pass through the system unchanged. + + @type data: C{str} + @ivar data: Wire data which makes up this record. + + @type ttl: C{int} + @ivar ttl: The maximum number of seconds which this record should be cached. + + @since: 11.1 + """ + implements(IEncodable, IRecord) + + fancybasename = 'UNKNOWN' + compareAttributes = ('data', 'ttl') + showAttributes = ('data', 'ttl') + + def __init__(self, data='', ttl=None): + self.data = data + self.ttl = str2time(ttl) + + + def encode(self, strio, compDict=None): + """ + Write the raw bytes corresponding to this record's payload to the + stream. + """ + strio.write(self.data) + + + def decode(self, strio, length=None): + """ + Load the bytes which are part of this record from the stream and store + them unparsed and unmodified. + """ + if length is None: + raise Exception('must know length for unknown record types') + self.data = readPrecisely(strio, length) + + + def __hash__(self): + return hash((self.data, self.ttl)) + + + class Record_SPF(Record_TXT): """ Structurally, freeform text. Semantically, a policy definition, formatted as defined in U{rfc 4408}. - + @type data: C{list} of C{str} @ivar data: Freeform text which makes up this record. - + @type ttl: C{int} @ivar ttl: The maximum number of seconds which this record should be cached. """ @@ -1583,7 +1631,7 @@ def parseRecords(self, list, num, strio): for i in range(num): - header = RRHeader() + header = RRHeader(auth=self.auth) try: header.decode(strio) except EOFError: @@ -1624,7 +1672,7 @@ can be found for the given type. @rtype: L{types.ClassType} """ - return self._recordTypes.get(type, None) + return self._recordTypes.get(type, UnknownRecord) def toStr(self): diff -Nru twisted-names-11.0.0/twisted/names/hosts.py twisted-names-12.1.0/twisted/names/hosts.py --- twisted-names-11.0.0/twisted/names/hosts.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/hosts.py 2011-06-30 13:43:44.000000000 +0000 @@ -1,3 +1,4 @@ +# -*- test-case-name: twisted.names.test.test_hosts -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. @@ -8,17 +9,19 @@ from twisted.names import dns from twisted.persisted import styles from twisted.python import failure +from twisted.python.filepath import FilePath from twisted.internet import defer +from twisted.internet.abstract import isIPAddress from twisted.names import common - -def searchFileFor(file, name): +def searchFileForAll(hostsFile, name): """ - Grep given file, which is in hosts(5) standard format, for an address + Search the given file, which is in hosts(5) standard format, for an address entry with a given name. - @param file: The name of the hosts(5)-format file to search. + @param hostsFile: The name of the hosts(5)-format file to search. + @type hostsFile: L{FilePath} @param name: The name to search for. @type name: C{str} @@ -26,14 +29,13 @@ @return: C{None} if the name is not found in the file, otherwise a C{str} giving the address in the file associated with the name. """ + results = [] try: - fp = open(file) + lines = hostsFile.getContent().splitlines() except: - return None - try: - lines = fp.readlines() - finally: - fp.close() + return results + + name = name.lower() for line in lines: idx = line.find('#') if idx != -1: @@ -41,15 +43,37 @@ if not line: continue parts = line.split() + if name.lower() in [s.lower() for s in parts[1:]]: - return parts[0] + results.append(parts[0]) + return results + + + +def searchFileFor(file, name): + """ + Grep given file, which is in hosts(5) standard format, for an address + entry with a given name. + + @param file: The name of the hosts(5)-format file to search. + + @param name: The name to search for. + @type name: C{str} + + @return: C{None} if the name is not found in the file, otherwise a + C{str} giving the address in the file associated with the name. + """ + addresses = searchFileForAll(FilePath(file), name) + if addresses: + return addresses[0] return None class Resolver(common.ResolverBase, styles.Versioned): - """A resolver that services hosts(5) format files.""" - #TODO: IPv6 support + """ + A resolver that services hosts(5) format files. + """ persistenceVersion = 1 @@ -66,14 +90,68 @@ self.ttl = ttl - def lookupAddress(self, name, timeout = None): - res = searchFileFor(self.file, name) - if res: - return defer.succeed([ - (dns.RRHeader(name, dns.A, dns.IN, self.ttl, dns.Record_A(res, self.ttl)),), (), () - ]) + def _aRecords(self, name): + """ + Return a tuple of L{dns.RRHeader} instances for all of the IPv4 + addresses in the hosts file. + """ + return tuple([ + dns.RRHeader(name, dns.A, dns.IN, self.ttl, + dns.Record_A(addr, self.ttl)) + for addr + in searchFileForAll(FilePath(self.file), name) + if isIPAddress(addr)]) + + + def _aaaaRecords(self, name): + """ + Return a tuple of L{dns.RRHeader} instances for all of the IPv6 + addresses in the hosts file. + """ + return tuple([ + dns.RRHeader(name, dns.AAAA, dns.IN, self.ttl, + dns.Record_AAAA(addr, self.ttl)) + for addr + in searchFileForAll(FilePath(self.file), name) + if not isIPAddress(addr)]) + + + def _respond(self, name, records): + """ + Generate a response for the given name containing the given result + records, or a failure if there are no result records. + + @param name: The DNS name the response is for. + @type name: C{str} + + @param records: A tuple of L{dns.RRHeader} instances giving the results + that will go into the response. + + @return: A L{Deferred} which will fire with a three-tuple of result + records, authority records, and additional records, or which will + fail with L{dns.DomainError} if there are no result records. + """ + if records: + return defer.succeed((records, (), ())) return defer.fail(failure.Failure(dns.DomainError(name))) - # When we put IPv6 support in, this'll need a real impl + def lookupAddress(self, name, timeout=None): + """ + Read any IPv4 addresses from C{self.file} and return them as L{Record_A} + instances. + """ + return self._respond(name, self._aRecords(name)) + + + def lookupIPV6Address(self, name, timeout=None): + """ + Read any IPv4 addresses from C{self.file} and return them as L{Record_A} + instances. + """ + return self._respond(name, self._aaaaRecords(name)) + + # Someday this should include IPv6 addresses too, but that will cause + # problems if users of the API (mainly via getHostByName) aren't updated to + # know about IPv6 first. lookupAllRecords = lookupAddress diff -Nru twisted-names-11.0.0/twisted/names/secondary.py twisted-names-12.1.0/twisted/names/secondary.py --- twisted-names-11.0.0/twisted/names/secondary.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/secondary.py 2012-03-06 14:14:56.000000000 +0000 @@ -1,17 +1,24 @@ +# -*- test-case-name: twisted.names.test.test_names -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +__all__ = ['SecondaryAuthority', 'SecondaryAuthorityService'] + from twisted.internet import task, defer from twisted.names import dns from twisted.names import common from twisted.names import client from twisted.names import resolve +from twisted.names.authority import FileAuthority + from twisted.python import log, failure from twisted.application import service class SecondaryAuthorityService(service.Service): calls = None + _port = 53 + def __init__(self, primary, domains): """ @param primary: The IP address of the server from which to perform @@ -23,6 +30,32 @@ self.primary = primary self.domains = [SecondaryAuthority(primary, d) for d in domains] + + @classmethod + def fromServerAddressAndDomains(cls, serverAddress, domains): + """ + Construct a new L{SecondaryAuthorityService} from a tuple giving a + server address and a C{str} giving the name of a domain for which this + is an authority. + + @param serverAddress: A two-tuple, the first element of which is a + C{str} giving an IP address and the second element of which is a + C{int} giving a port number. Together, these define where zone + transfers will be attempted from. + + @param domain: A C{str} giving the domain to transfer. + + @return: A new instance of L{SecondaryAuthorityService}. + """ + service = cls(None, []) + service.primary = serverAddress[0] + service._port = serverAddress[1] + service.domains = [ + SecondaryAuthority.fromServerAddressAndDomain(serverAddress, d) + for d in domains] + return service + + def getAuthority(self): return resolve.ResolverChain(self.domains) @@ -42,25 +75,69 @@ c.stop() -from twisted.names.authority import FileAuthority class SecondaryAuthority(common.ResolverBase): - """An Authority that keeps itself updated by performing zone transfers""" + """ + An Authority that keeps itself updated by performing zone transfers. - transferring = False + @ivar primary: The IP address of the server from which zone transfers will + be attempted. + @type primary: C{str} + + @ivar _port: The port number of the server from which zone transfers will be + attempted. + @type: C{int} + + @ivar _reactor: The reactor to use to perform the zone transfers, or C{None} + to use the global reactor. + """ + transferring = False soa = records = None + _port = 53 + _reactor = None + def __init__(self, primaryIP, domain): common.ResolverBase.__init__(self) self.primary = primaryIP self.domain = domain + + @classmethod + def fromServerAddressAndDomain(cls, serverAddress, domain): + """ + Construct a new L{SecondaryAuthority} from a tuple giving a server + address and a C{str} giving the name of a domain for which this is an + authority. + + @param serverAddress: A two-tuple, the first element of which is a + C{str} giving an IP address and the second element of which is a + C{int} giving a port number. Together, these define where zone + transfers will be attempted from. + + @param domain: A C{str} giving the domain to transfer. + + @return: A new instance of L{SecondaryAuthority}. + """ + secondary = cls(None, None) + secondary.primary = serverAddress[0] + secondary._port = serverAddress[1] + secondary.domain = domain + return secondary + + def transfer(self): if self.transferring: return self.transfering = True - return client.Resolver(servers=[(self.primary, dns.PORT)] - ).lookupZone(self.domain + + reactor = self._reactor + if reactor is None: + from twisted.internet import reactor + + resolver = client.Resolver( + servers=[(self.primary, self._port)], reactor=reactor) + return resolver.lookupZone(self.domain ).addCallback(self._cbZone ).addErrback(self._ebZone ) diff -Nru twisted-names-11.0.0/twisted/names/tap.py twisted-names-12.1.0/twisted/names/tap.py --- twisted-names-11.0.0/twisted/names/tap.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/tap.py 2012-03-18 14:44:22.000000000 +0000 @@ -1,7 +1,7 @@ +# -*- test-case-name: twisted.names.test.test_tap -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. - """ Domain Name Server """ @@ -31,6 +31,10 @@ ["verbose", "v", "Log verbosely"], ] + compData = usage.Completions( + optActions={"interface" : usage.CompleteNetInterfaces()} + ) + zones = None zonefiles = None @@ -61,8 +65,19 @@ """ args = ip_domain.split('/', 1) if len(args) != 2: - raise usage.UsageError("Argument must be of the form IP/domain") - self.secondaries.append((args[0], [args[1]])) + raise usage.UsageError("Argument must be of the form IP[:port]/domain") + address = args[0].split(':') + if len(address) == 1: + address = (address[0], dns.PORT) + else: + try: + port = int(address[1]) + except ValueError: + raise usage.UsageError( + "Specify an integer port number, not %r" % (address[1],)) + address = (address[0], port) + self.secondaries.append((address, [args[1]])) + def opt_verbose(self): """Increment verbosity level""" @@ -78,17 +93,18 @@ for f in self.zonefiles: try: self.zones.append(authority.PySourceAuthority(f)) - except Exception, e: + except Exception: traceback.print_exc() raise usage.UsageError("Invalid syntax in " + f) for f in self.bindfiles: try: self.zones.append(authority.BindAuthority(f)) - except Exception, e: + except Exception: traceback.print_exc() raise usage.UsageError("Invalid syntax in " + f) for f in self.secondaries: - self.svcs.append(secondary.SecondaryAuthorityService(*f)) + svc = secondary.SecondaryAuthorityService.fromServerAddressAndDomains(*f) + self.svcs.append(svc) self.zones.append(self.svcs[-1].getAuthority()) try: self['port'] = int(self['port']) @@ -96,16 +112,31 @@ raise usage.UsageError("Invalid port: %r" % (self['port'],)) -def makeService(config): - import client, cache, hosts +def _buildResolvers(config): + """ + Build DNS resolver instances in an order which leaves recursive + resolving as a last resort. + + @type config: L{Options} instance + @param config: Parsed command-line configuration + + @return: Two-item tuple of a list of cache resovers and a list of client + resolvers + """ + from twisted.names import client, cache, hosts ca, cl = [], [] if config['cache']: ca.append(cache.CacheResolver(verbose=config['verbose'])) - if config['recursive']: - cl.append(client.createResolver(resolvconf=config['resolv-conf'])) if config['hosts-file']: cl.append(hosts.Resolver(file=config['hosts-file'])) + if config['recursive']: + cl.append(client.createResolver(resolvconf=config['resolv-conf'])) + return ca, cl + + +def makeService(config): + ca, cl = _buildResolvers(config) f = server.DNSServerFactory(config.zones, ca, cl, config['verbose']) p = dns.DNSDatagramProtocol(f) diff -Nru twisted-names-11.0.0/twisted/names/test/test_cache.py twisted-names-12.1.0/twisted/names/test/test_cache.py --- twisted-names-11.0.0/twisted/names/test/test_cache.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_cache.py 2012-05-09 15:41:09.000000000 +0000 @@ -6,9 +6,104 @@ from twisted.trial import unittest from twisted.names import dns, cache +from twisted.internet import task class Caching(unittest.TestCase): - def testLookup(self): + """ + Tests for L{cache.CacheResolver}. + """ + + def test_lookup(self): + c = cache.CacheResolver({ + dns.Query(name='example.com', type=dns.MX, cls=dns.IN): + (time.time(), ([], [], []))}) + return c.lookupMailExchange('example.com').addCallback( + self.assertEqual, ([], [], [])) + + + def test_constructorExpires(self): + """ + Cache entries passed into L{cache.CacheResolver.__init__} get + cancelled just like entries added with cacheResult + """ + + r = ([dns.RRHeader("example.com", dns.A, dns.IN, 60, + dns.Record_A("127.0.0.1", 60))], + [dns.RRHeader("example.com", dns.A, dns.IN, 50, + dns.Record_A("127.0.0.1", 50))], + [dns.RRHeader("example.com", dns.A, dns.IN, 40, + dns.Record_A("127.0.0.1", 40))]) + + clock = task.Clock() + query = dns.Query(name="example.com", type=dns.A, cls=dns.IN) + + c = cache.CacheResolver({ query : (clock.seconds(), r)}, reactor=clock) + + # 40 seconds is enough to expire the entry because expiration is based + # on the minimum TTL. + clock.advance(40) + + self.assertNotIn(query, c.cache) + + return self.assertFailure( + c.lookupAddress("example.com"), dns.DomainError) + + + def test_normalLookup(self): + """ + When a cache lookup finds a cached entry from 1 second ago, it is + returned with a TTL of original TTL minus the elapsed 1 second. + """ + r = ([dns.RRHeader("example.com", dns.A, dns.IN, 60, + dns.Record_A("127.0.0.1", 60))], + [dns.RRHeader("example.com", dns.A, dns.IN, 50, + dns.Record_A("127.0.0.1", 50))], + [dns.RRHeader("example.com", dns.A, dns.IN, 40, + dns.Record_A("127.0.0.1", 40))]) + + clock = task.Clock() + + c = cache.CacheResolver(reactor=clock) + c.cacheResult(dns.Query(name="example.com", type=dns.A, cls=dns.IN), r) + + clock.advance(1) + + def cbLookup(result): + self.assertEquals(result[0][0].ttl, 59) + self.assertEquals(result[1][0].ttl, 49) + self.assertEquals(result[2][0].ttl, 39) + self.assertEquals(result[0][0].name.name, "example.com") + + return c.lookupAddress("example.com").addCallback(cbLookup) + + + def test_negativeTTLLookup(self): + """ + When the cache is queried exactly as the cached entry should expire + but before it has actually been cleared, the TTL will be 0, not + negative. + """ + r = ([dns.RRHeader("example.com", dns.A, dns.IN, 60, + dns.Record_A("127.0.0.1", 60))], + [dns.RRHeader("example.com", dns.A, dns.IN, 50, + dns.Record_A("127.0.0.1", 50))], + [dns.RRHeader("example.com", dns.A, dns.IN, 40, + dns.Record_A("127.0.0.1", 40))]) + + clock = task.Clock() + # Make sure timeouts never happen, so entries won't get cleared: + clock.callLater = lambda *args, **kwargs: None + c = cache.CacheResolver({ - dns.Query(name='example.com', type=dns.MX, cls=dns.IN): (time.time(), ([], [], []))}) - return c.lookupMailExchange('example.com').addCallback(self.assertEquals, ([], [], [])) + dns.Query(name="example.com", type=dns.A, cls=dns.IN) : + (clock.seconds(), r)}, reactor=clock) + + clock.advance(60.1) + + def cbLookup(result): + self.assertEquals(result[0][0].ttl, 0) + self.assertEquals(result[0][0].ttl, 0) + self.assertEquals(result[0][0].ttl, 0) + self.assertEquals(result[0][0].name.name, "example.com") + + return c.lookupAddress("example.com").addCallback(cbLookup) diff -Nru twisted-names-11.0.0/twisted/names/test/test_client.py twisted-names-12.1.0/twisted/names/test/test_client.py --- twisted-names-11.0.0/twisted/names/test/test_client.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_client.py 2012-02-08 00:35:34.000000000 +0000 @@ -415,6 +415,21 @@ return self.assertFailure(queryResult, ExpectedException) + def test_tcpDisconnectRemovesFromConnections(self): + """ + When a TCP DNS protocol associated with a Resolver disconnects, it is + removed from the Resolver's connection list. + """ + resolver = client.Resolver(servers=[('example.com', 53)]) + protocol = resolver.factory.buildProtocol(None) + protocol.makeConnection(None) + self.assertIn(protocol, resolver.connections) + + # Disconnecting should remove the protocol from the connection list: + protocol.connectionLost(None) + self.assertNotIn(protocol, resolver.connections) + + class ClientTestCase(unittest.TestCase): @@ -438,14 +453,14 @@ Verify that the result is the same query type as what is expected. """ result = results[0] - self.assertEquals(str(result.name), self.hostname) - self.assertEquals(result.type, qtype) + self.assertEqual(str(result.name), self.hostname) + self.assertEqual(result.type, qtype) def checkGetHostByName(self, result): """ Test that the getHostByName query returns the 127.0.0.1 address. """ - self.assertEquals(result, '127.0.0.1') + self.assertEqual(result, '127.0.0.1') def test_getHostByName(self): """ @@ -654,10 +669,10 @@ """ client.ThreadedResolver() warnings = self.flushWarnings(offendingFunctions=[self.test_deprecated]) - self.assertEquals( + self.assertEqual( warnings[0]['message'], "twisted.names.client.ThreadedResolver is deprecated since " "Twisted 9.0, use twisted.internet.base.ThreadedResolver " "instead.") - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals(len(warnings), 1) + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual(len(warnings), 1) diff -Nru twisted-names-11.0.0/twisted/names/test/test_dns.py twisted-names-12.1.0/twisted/names/test/test_dns.py --- twisted-names-11.0.0/twisted/names/test/test_dns.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_dns.py 2012-01-07 14:25:15.000000000 +0000 @@ -6,10 +6,7 @@ Tests for twisted.names.dns. """ -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO +from cStringIO import StringIO import struct @@ -21,6 +18,171 @@ from twisted.test import proto_helpers +RECORD_TYPES = [ + dns.Record_NS, dns.Record_MD, dns.Record_MF, dns.Record_CNAME, + dns.Record_MB, dns.Record_MG, dns.Record_MR, dns.Record_PTR, + dns.Record_DNAME, dns.Record_A, dns.Record_SOA, dns.Record_NULL, + dns.Record_WKS, dns.Record_SRV, dns.Record_AFSDB, dns.Record_RP, + dns.Record_HINFO, dns.Record_MINFO, dns.Record_MX, dns.Record_TXT, + dns.Record_AAAA, dns.Record_A6, dns.Record_NAPTR, dns.UnknownRecord, + ] + +class NameTests(unittest.TestCase): + """ + Tests for L{Name}, the representation of a single domain name with support + for encoding into and decoding from DNS message format. + """ + def test_decode(self): + """ + L{Name.decode} populates the L{Name} instance with name information read + from the file-like object passed to it. + """ + n = dns.Name() + n.decode(StringIO("\x07example\x03com\x00")) + self.assertEqual(n.name, "example.com") + + + def test_encode(self): + """ + L{Name.encode} encodes its name information and writes it to the + file-like object passed to it. + """ + name = dns.Name("foo.example.com") + stream = StringIO() + name.encode(stream) + self.assertEqual(stream.getvalue(), "\x03foo\x07example\x03com\x00") + + + def test_encodeWithCompression(self): + """ + If a compression dictionary is passed to it, L{Name.encode} uses offset + information from it to encode its name with references to existing + labels in the stream instead of including another copy of them in the + output. It also updates the compression dictionary with the location of + the name it writes to the stream. + """ + name = dns.Name("foo.example.com") + compression = {"example.com": 0x17} + + # Some bytes already encoded into the stream for this message + previous = "some prefix to change .tell()" + stream = StringIO() + stream.write(previous) + + # The position at which the encoded form of this new name will appear in + # the stream. + expected = len(previous) + dns.Message.headerSize + name.encode(stream, compression) + self.assertEqual( + "\x03foo\xc0\x17", + stream.getvalue()[len(previous):]) + self.assertEqual( + {"example.com": 0x17, "foo.example.com": expected}, + compression) + + + def test_unknown(self): + """ + A resource record of unknown type and class is parsed into an + L{UnknownRecord} instance with its data preserved, and an + L{UnknownRecord} instance is serialized to a string equal to the one it + was parsed from. + """ + wire = ( + '\x01\x00' # Message ID + '\x00' # answer bit, opCode nibble, auth bit, trunc bit, recursive + # bit + '\x00' # recursion bit, empty bit, empty bit, empty bit, response + # code nibble + '\x00\x01' # number of queries + '\x00\x01' # number of answers + '\x00\x00' # number of authorities + '\x00\x01' # number of additionals + + # query + '\x03foo\x03bar\x00' # foo.bar + '\xde\xad' # type=0xdead + '\xbe\xef' # cls=0xbeef + + # 1st answer + '\xc0\x0c' # foo.bar - compressed + '\xde\xad' # type=0xdead + '\xbe\xef' # cls=0xbeef + '\x00\x00\x01\x01' # ttl=257 + '\x00\x08somedata' # some payload data + + # 1st additional + '\x03baz\x03ban\x00' # baz.ban + '\x00\x01' # type=A + '\x00\x01' # cls=IN + '\x00\x00\x01\x01' # ttl=257 + '\x00\x04' # len=4 + '\x01\x02\x03\x04' # 1.2.3.4 + + ) + + msg = dns.Message() + msg.fromStr(wire) + + self.assertEqual(msg.queries, [ + dns.Query('foo.bar', type=0xdead, cls=0xbeef), + ]) + self.assertEqual(msg.answers, [ + dns.RRHeader('foo.bar', type=0xdead, cls=0xbeef, ttl=257, + payload=dns.UnknownRecord('somedata', ttl=257)), + ]) + self.assertEqual(msg.additional, [ + dns.RRHeader('baz.ban', type=dns.A, cls=dns.IN, ttl=257, + payload=dns.Record_A('1.2.3.4', ttl=257)), + ]) + + enc = msg.toStr() + + self.assertEqual(enc, wire) + + + def test_decodeWithCompression(self): + """ + If the leading byte of an encoded label (in bytes read from a stream + passed to L{Name.decode}) has its two high bits set, the next byte is + treated as a pointer to another label in the stream and that label is + included in the name being decoded. + """ + # Slightly modified version of the example from RFC 1035, section 4.1.4. + stream = StringIO( + "x" * 20 + + "\x01f\x03isi\x04arpa\x00" + "\x03foo\xc0\x14" + "\x03bar\xc0\x20") + stream.seek(20) + name = dns.Name() + name.decode(stream) + # Verify we found the first name in the stream and that the stream + # position is left at the first byte after the decoded name. + self.assertEqual("f.isi.arpa", name.name) + self.assertEqual(32, stream.tell()) + + # Get the second name from the stream and make the same assertions. + name.decode(stream) + self.assertEqual(name.name, "foo.f.isi.arpa") + self.assertEqual(38, stream.tell()) + + # Get the third and final name + name.decode(stream) + self.assertEqual(name.name, "bar.foo.f.isi.arpa") + self.assertEqual(44, stream.tell()) + + + def test_rejectCompressionLoop(self): + """ + L{Name.decode} raises L{ValueError} if the stream passed to it includes + a compression pointer which forms a loop, causing the name to be + undecodable. + """ + name = dns.Name() + stream = StringIO("\xc0\x00") + self.assertRaises(ValueError, name.decode, stream) + class RoundtripDNSTestCase(unittest.TestCase): @@ -38,7 +200,7 @@ f.seek(0, 0) result = dns.Name() result.decode(f) - self.assertEquals(result.name, n) + self.assertEqual(result.name, n) def testQuery(self): for n in self.names: @@ -52,9 +214,9 @@ f.seek(0, 0) result = dns.Query() result.decode(f) - self.assertEquals(result.name.name, n) - self.assertEquals(result.type, dnstype) - self.assertEquals(result.cls, dnscls) + self.assertEqual(result.name.name, n) + self.assertEqual(result.type, dnstype) + self.assertEqual(result.cls, dnscls) def testRR(self): # encode the RR @@ -65,10 +227,10 @@ f.seek(0, 0) result = dns.RRHeader() result.decode(f) - self.assertEquals(str(result.name), "test.org") - self.assertEquals(result.type, 3) - self.assertEquals(result.cls, 4) - self.assertEquals(result.ttl, 17) + self.assertEqual(str(result.name), "test.org") + self.assertEqual(result.type, 3) + self.assertEqual(result.cls, 4) + self.assertEqual(result.ttl, 17) def testResources(self): @@ -85,26 +247,17 @@ f.seek(0, 0) result = dns.SimpleRecord() result.decode(f) - self.assertEquals(str(result.name), s) + self.assertEqual(str(result.name), s) def test_hashable(self): """ Instances of all record types are hashable. """ - records = [ - dns.Record_NS, dns.Record_MD, dns.Record_MF, dns.Record_CNAME, - dns.Record_MB, dns.Record_MG, dns.Record_MR, dns.Record_PTR, - dns.Record_DNAME, dns.Record_A, dns.Record_SOA, dns.Record_NULL, - dns.Record_WKS, dns.Record_SRV, dns.Record_AFSDB, dns.Record_RP, - dns.Record_HINFO, dns.Record_MINFO, dns.Record_MX, dns.Record_TXT, - dns.Record_AAAA, dns.Record_A6, dns.Record_NAPTR - ] - - for k in records: + for k in RECORD_TYPES: k1, k2 = k(), k() hk1 = hash(k1) hk2 = hash(k2) - self.assertEquals(hk1, hk2, "%s != %s (for %s)" % (hk1,hk2,k)) + self.assertEqual(hk1, hk2, "%s != %s (for %s)" % (hk1,hk2,k)) def test_Charstr(self): @@ -120,7 +273,7 @@ f.seek(0, 0) result = dns.Charstr() result.decode(f) - self.assertEquals(result.string, n) + self.assertEqual(result.string, n) def test_NAPTR(self): @@ -140,13 +293,13 @@ e.seek(0,0) rout = dns.Record_NAPTR() rout.decode(e) - self.assertEquals(rin.order, rout.order) - self.assertEquals(rin.preference, rout.preference) - self.assertEquals(rin.flags, rout.flags) - self.assertEquals(rin.service, rout.service) - self.assertEquals(rin.regexp, rout.regexp) - self.assertEquals(rin.replacement.name, rout.replacement.name) - self.assertEquals(rin.ttl, rout.ttl) + self.assertEqual(rin.order, rout.order) + self.assertEqual(rin.preference, rout.preference) + self.assertEqual(rin.flags, rout.flags) + self.assertEqual(rin.service, rout.service) + self.assertEqual(rin.regexp, rout.regexp) + self.assertEqual(rin.replacement.name, rout.replacement.name) + self.assertEqual(rin.ttl, rout.ttl) @@ -179,15 +332,15 @@ '\x00\x00' # number of authorities '\x00\x00' # number of additionals ) - self.assertEquals(msg.id, 256) + self.assertEqual(msg.id, 256) self.failIf(msg.answer, "Message was not supposed to be an answer.") - self.assertEquals(msg.opCode, dns.OP_QUERY) + self.assertEqual(msg.opCode, dns.OP_QUERY) self.failIf(msg.auth, "Message was not supposed to be authoritative.") self.failIf(msg.trunc, "Message was not supposed to be truncated.") - self.assertEquals(msg.queries, []) - self.assertEquals(msg.answers, []) - self.assertEquals(msg.authority, []) - self.assertEquals(msg.additional, []) + self.assertEqual(msg.queries, []) + self.assertEqual(msg.answers, []) + self.assertEqual(msg.authority, []) + self.assertEqual(msg.additional, []) def testNULL(self): @@ -203,19 +356,73 @@ msg2.decode(s) self.failUnless(isinstance(msg2.answers[0].payload, dns.Record_NULL)) - self.assertEquals(msg2.answers[0].payload.payload, bytes) + self.assertEqual(msg2.answers[0].payload.payload, bytes) def test_lookupRecordTypeDefault(self): """ - L{Message.lookupRecordType} returns C{None} if it is called - with an integer which doesn't correspond to any known record + L{Message.lookupRecordType} returns C{dns.UnknownRecord} if it is + called with an integer which doesn't correspond to any known record type. """ # 65280 is the first value in the range reserved for private # use, so it shouldn't ever conflict with an officially # allocated value. - self.assertIdentical(dns.Message().lookupRecordType(65280), None) + self.assertIdentical( + dns.Message().lookupRecordType(65280), dns.UnknownRecord) + + + def test_nonAuthoritativeMessage(self): + """ + The L{RRHeader} instances created by L{Message} from a non-authoritative + message are marked as not authoritative. + """ + buf = StringIO() + answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0)) + answer.encode(buf) + message = dns.Message() + message.fromStr( + '\x01\x00' # Message ID + # answer bit, opCode nibble, auth bit, trunc bit, recursive bit + '\x00' + # recursion bit, empty bit, empty bit, empty bit, response code + # nibble + '\x00' + '\x00\x00' # number of queries + '\x00\x01' # number of answers + '\x00\x00' # number of authorities + '\x00\x00' # number of additionals + + buf.getvalue() + ) + self.assertEqual(message.answers, [answer]) + self.assertFalse(message.answers[0].auth) + + + def test_authoritativeMessage(self): + """ + The L{RRHeader} instances created by L{Message} from an authoritative + message are marked as authoritative. + """ + buf = StringIO() + answer = dns.RRHeader(payload=dns.Record_A('1.2.3.4', ttl=0)) + answer.encode(buf) + message = dns.Message() + message.fromStr( + '\x01\x00' # Message ID + # answer bit, opCode nibble, auth bit, trunc bit, recursive bit + '\x04' + # recursion bit, empty bit, empty bit, empty bit, response code + # nibble + '\x00' + '\x00\x00' # number of queries + '\x00\x01' # number of answers + '\x00\x00' # number of authorities + '\x00\x00' # number of additionals + + buf.getvalue() + ) + answer.auth = True + self.assertEqual(message.answers, [answer]) + self.assertTrue(message.answers[0].auth) @@ -266,7 +473,7 @@ """ self.proto.datagramReceived('', address.IPv4Address('UDP', '127.0.0.1', 12345)) - self.assertEquals(self.controller.messages, []) + self.assertEqual(self.controller.messages, []) def test_simpleQuery(self): @@ -274,13 +481,12 @@ Test content received after a query. """ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')]) - self.assertEquals(len(self.proto.liveMessages.keys()), 1) + self.assertEqual(len(self.proto.liveMessages.keys()), 1) m = dns.Message() m.id = self.proto.liveMessages.items()[0][0] m.answers = [dns.RRHeader(payload=dns.Record_A(address='1.2.3.4'))] - called = False def cb(result): - self.assertEquals(result.answers[0].payload.dottedQuad(), '1.2.3.4') + self.assertEqual(result.answers[0].payload.dottedQuad(), '1.2.3.4') d.addCallback(cb) self.proto.datagramReceived(m.toStr(), ('127.0.0.1', 21345)) return d @@ -291,10 +497,10 @@ Test that query timeouts after some seconds. """ d = self.proto.query(('127.0.0.1', 21345), [dns.Query('foo')]) - self.assertEquals(len(self.proto.liveMessages), 1) + self.assertEqual(len(self.proto.liveMessages), 1) self.clock.advance(10) self.assertFailure(d, dns.DNSQueryTimeoutError) - self.assertEquals(len(self.proto.liveMessages), 0) + self.assertEqual(len(self.proto.liveMessages), 0) return d @@ -384,10 +590,10 @@ Test that query timeouts after some seconds. """ d = self.proto.query([dns.Query('foo')]) - self.assertEquals(len(self.proto.liveMessages), 1) + self.assertEqual(len(self.proto.liveMessages), 1) self.clock.advance(60) self.assertFailure(d, dns.DNSQueryTimeoutError) - self.assertEquals(len(self.proto.liveMessages), 0) + self.assertEqual(len(self.proto.liveMessages), 0) return d @@ -396,13 +602,12 @@ Test content received after a query. """ d = self.proto.query([dns.Query('foo')]) - self.assertEquals(len(self.proto.liveMessages.keys()), 1) + self.assertEqual(len(self.proto.liveMessages.keys()), 1) m = dns.Message() m.id = self.proto.liveMessages.items()[0][0] m.answers = [dns.RRHeader(payload=dns.Record_A(address='1.2.3.4'))] - called = False def cb(result): - self.assertEquals(result.answers[0].payload.dottedQuad(), '1.2.3.4') + self.assertEqual(result.answers[0].payload.dottedQuad(), '1.2.3.4') d.addCallback(cb) s = m.toStr() s = struct.pack('!H', len(s)) + s @@ -668,17 +873,27 @@ repr(dns.Record_TXT("foo", "bar", ttl=15)), "") + def test_spf(self): """ The repr of a L{dns.Record_SPF} instance includes the data and ttl - fields of the record, since it is structurally - similar to L{dns.Record_TXT}. + fields of the record. """ self.assertEqual( repr(dns.Record_SPF("foo", "bar", ttl=15)), "") + def test_unknown(self): + """ + The repr of a L{dns.UnknownRecord} instance includes the data and ttl + fields of the record. + """ + self.assertEqual( + repr(dns.UnknownRecord("foo\x1fbar", 12)), + "") + + class _Equal(object): """ @@ -1228,8 +1443,8 @@ def test_spf(self): """ - L{dns.Record_SPF} records are structurally similar to L{dns.Record_TXT} - records, so they are equal if and only if they have the same data and ttl. + L{dns.Record_SPF} instances compare equal if and only if they have the + same data and ttl. """ # Vary the length of the data self._equalityTest( @@ -1246,3 +1461,25 @@ dns.Record_SPF('foo', 'bar', ttl=10), dns.Record_SPF('foo', 'bar', ttl=10), dns.Record_SPF('foo', 'bar', ttl=100)) + + + def test_unknown(self): + """ + L{dns.UnknownRecord} instances compare equal if and only if they have + the same data and ttl. + """ + # Vary the length of the data + self._equalityTest( + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foobar', ttl=10)) + # Vary the value of the data + self._equalityTest( + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('bar', ttl=10)) + # Vary the ttl + self._equalityTest( + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=100)) diff -Nru twisted-names-11.0.0/twisted/names/test/test_hosts.py twisted-names-12.1.0/twisted/names/test/test_hosts.py --- twisted-names-11.0.0/twisted/names/test/test_hosts.py 1970-01-01 00:00:00.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_hosts.py 2011-06-30 13:43:44.000000000 +0000 @@ -0,0 +1,232 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Tests for the I{hosts(5)}-based resolver, L{twisted.names.hosts}. +""" + +from twisted.trial.unittest import TestCase +from twisted.python.filepath import FilePath +from twisted.internet.defer import gatherResults + +from twisted.names.dns import ( + A, AAAA, IN, DomainError, RRHeader, Query, Record_A, Record_AAAA) +from twisted.names.hosts import Resolver, searchFileFor, searchFileForAll + + +class SearchHostsFileTests(TestCase): + """ + Tests for L{searchFileFor}, a helper which finds the first address for a + particular hostname in a I{hosts(5)}-style file. + """ + def test_findAddress(self): + """ + If there is an IPv4 address for the hostname passed to + L{searchFileFor}, it is returned. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent( + "10.2.3.4 foo.example.com\n") + self.assertEqual( + "10.2.3.4", searchFileFor(hosts.path, "foo.example.com")) + + + def test_notFoundAddress(self): + """ + If there is no address information for the hostname passed to + L{searchFileFor}, C{None} is returned. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent( + "10.2.3.4 foo.example.com\n") + self.assertIdentical( + None, searchFileFor(hosts.path, "bar.example.com")) + + + def test_firstAddress(self): + """ + The first address associated with the given hostname is returned. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent( + "::1 foo.example.com\n" + "10.1.2.3 foo.example.com\n" + "fe80::21b:fcff:feee:5a1d foo.example.com\n") + self.assertEqual( + "::1", searchFileFor(hosts.path, "foo.example.com")) + + + def test_searchFileForAliases(self): + """ + For a host with a canonical name and one or more aliases, + L{searchFileFor} can find an address given any of the names. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent( + "127.0.1.1 helmut.example.org helmut\n" + "# a comment\n" + "::1 localhost ip6-localhost ip6-loopback\n") + self.assertEqual(searchFileFor(hosts.path, 'helmut'), '127.0.1.1') + self.assertEqual( + searchFileFor(hosts.path, 'helmut.example.org'), '127.0.1.1') + self.assertEqual(searchFileFor(hosts.path, 'ip6-localhost'), '::1') + self.assertEqual(searchFileFor(hosts.path, 'ip6-loopback'), '::1') + self.assertEqual(searchFileFor(hosts.path, 'localhost'), '::1') + + + +class SearchHostsFileForAllTests(TestCase): + """ + Tests for L{searchFileForAll}, a helper which finds all addresses for a + particular hostname in a I{hosts(5)}-style file. + """ + def test_allAddresses(self): + """ + L{searchFileForAll} returns a list of all addresses associated with the + name passed to it. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent( + "127.0.0.1 foobar.example.com\n" + "127.0.0.2 foobar.example.com\n" + "::1 foobar.example.com\n") + self.assertEqual( + ["127.0.0.1", "127.0.0.2", "::1"], + searchFileForAll(hosts, "foobar.example.com")) + + + def test_caseInsensitively(self): + """ + L{searchFileForAll} searches for names case-insensitively. + """ + hosts = FilePath(self.mktemp()) + hosts.setContent("127.0.0.1 foobar.EXAMPLE.com\n") + self.assertEqual( + ["127.0.0.1"], searchFileForAll(hosts, "FOOBAR.example.com")) + + + def test_readError(self): + """ + If there is an error reading the contents of the hosts file, + L{searchFileForAll} returns an empty list. + """ + self.assertEqual( + [], searchFileForAll(FilePath(self.mktemp()), "example.com")) + + + +class HostsTestCase(TestCase): + """ + Tests for the I{hosts(5)}-based L{twisted.names.hosts.Resolver}. + """ + def setUp(self): + f = open('EtcHosts', 'w') + f.write(''' +1.1.1.1 EXAMPLE EXAMPLE.EXAMPLETHING +::2 mixed +1.1.1.2 MIXED +::1 ip6thingy +1.1.1.3 multiple +1.1.1.4 multiple +::3 ip6-multiple +::4 ip6-multiple +''') + f.close() + self.ttl = 4200 + self.resolver = Resolver('EtcHosts', self.ttl) + + def testGetHostByName(self): + data = [('EXAMPLE', '1.1.1.1'), + ('EXAMPLE.EXAMPLETHING', '1.1.1.1'), + ('MIXED', '1.1.1.2'), + ] + ds = [self.resolver.getHostByName(n).addCallback(self.assertEqual, ip) + for n, ip in data] + return gatherResults(ds) + + + 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('multiple') + 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) + d.addCallback(resolved) + return d + + + def test_lookupIPV6Address(self): + """ + L{hosts.Resolver.lookupIPV6Address} returns a L{Deferred} which fires + with AAAA records from the hosts file. + """ + d = self.resolver.lookupIPV6Address('ip6-multiple') + def resolved((results, authority, additional)): + self.assertEqual( + (RRHeader("ip6-multiple", AAAA, IN, self.ttl, + Record_AAAA("::3", self.ttl)), + RRHeader("ip6-multiple", AAAA, IN, self.ttl, + Record_AAAA("::4", self.ttl))), + results) + d.addCallback(resolved) + return d + + + def test_lookupAllRecords(self): + """ + L{hosts.Resolver.lookupAllRecords} returns a L{Deferred} which fires + with A records from the hosts file. + """ + d = self.resolver.lookupAllRecords('mixed') + def resolved((results, authority, additional)): + self.assertEqual( + (RRHeader("mixed", A, IN, self.ttl, + Record_A("1.1.1.2", self.ttl)),), + results) + d.addCallback(resolved) + return d + + + def testNotImplemented(self): + return self.assertFailure(self.resolver.lookupMailExchange('EXAMPLE'), + NotImplementedError) + + def testQuery(self): + d = self.resolver.query(Query('EXAMPLE')) + d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(), + '1.1.1.1')) + return d + + def test_lookupAddressNotFound(self): + """ + L{hosts.Resolver.lookupAddress} returns a L{Deferred} which fires with + L{dns.DomainError} if the name passed in has no addresses in the hosts + file. + """ + return self.assertFailure(self.resolver.lookupAddress('foueoa'), + DomainError) + + def test_lookupIPV6AddressNotFound(self): + """ + Like L{test_lookupAddressNotFound}, but for + L{hosts.Resolver.lookupIPV6Address}. + """ + return self.assertFailure(self.resolver.lookupIPV6Address('foueoa'), + DomainError) + + def test_lookupAllRecordsNotFound(self): + """ + Like L{test_lookupAddressNotFound}, but for + L{hosts.Resolver.lookupAllRecords}. + """ + return self.assertFailure(self.resolver.lookupAllRecords('foueoa'), + DomainError) + + diff -Nru twisted-names-11.0.0/twisted/names/test/test_names.py twisted-names-12.1.0/twisted/names/test/test_names.py --- twisted-names-11.0.0/twisted/names/test/test_names.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_names.py 2012-03-06 14:14:56.000000000 +0000 @@ -7,12 +7,14 @@ """ import socket, operator, copy +from StringIO import StringIO from twisted.trial import unittest from twisted.internet import reactor, defer, error +from twisted.internet.task import Clock from twisted.internet.defer import succeed -from twisted.names import client, server, common, authority, hosts, dns +from twisted.names import client, server, common, authority, dns from twisted.python import failure from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError @@ -20,9 +22,12 @@ from twisted.names.dns import EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED from twisted.names.dns import Message from twisted.names.client import Resolver - +from twisted.names.secondary import ( + SecondaryAuthorityService, SecondaryAuthority) from twisted.names.test.test_client import StubPort + from twisted.python.compat import reduce +from twisted.test.proto_helpers import StringTransport, MemoryReactor def justPayload(results): return [r.payload for r in results[0]] @@ -551,7 +556,7 @@ m.queries = [dns.Query('fooby.com', dns.AXFR, dns.IN)] m.answers = self.records self.controller.messageReceived(m, None) - self.assertEquals(self.results, self.records) + self.assertEqual(self.results, self.records) def _gotResults(self, result): self.results = result @@ -564,72 +569,7 @@ m.queries = [] # DJB *doesn't* specify any queries.. hmm.. m.answers = [records.pop(0)] self.controller.messageReceived(m, None) - self.assertEquals(self.results, self.records) - -class HostsTestCase(unittest.TestCase): - def setUp(self): - f = open('EtcHosts', 'w') - f.write(''' -1.1.1.1 EXAMPLE EXAMPLE.EXAMPLETHING -1.1.1.2 HOOJY -::1 ip6thingy -''') - f.close() - self.resolver = hosts.Resolver('EtcHosts') - - def testGetHostByName(self): - data = [('EXAMPLE', '1.1.1.1'), - ('EXAMPLE.EXAMPLETHING', '1.1.1.1'), - ('HOOJY', '1.1.1.2'), - ] - ds = [self.resolver.getHostByName(n).addCallback(self.assertEqual, ip) - for n, ip in data] - return defer.gatherResults(ds) - - def testLookupAddress(self): - d = self.resolver.lookupAddress('HOOJY') - d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(), - '1.1.1.2')) - return d - - def testIPv6(self): - d = self.resolver.lookupIPV6Address('ip6thingy') - d.addCallback(self.assertEqual, '::1') - return d - - testIPv6.skip = 'IPv6 support is not in our hosts resolver yet' - - def testNotImplemented(self): - return self.assertFailure(self.resolver.lookupMailExchange('EXAMPLE'), - NotImplementedError) - - def testQuery(self): - d = self.resolver.query(dns.Query('EXAMPLE')) - d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(), - '1.1.1.1')) - return d - - def testNotFound(self): - return self.assertFailure(self.resolver.lookupAddress('foueoa'), - dns.DomainError) - - - def test_searchFileFor(self): - """ - L{searchFileFor} parses hosts(5) files and returns the address for - the given name, or C{None} if the name is not found. - """ - tmp = self.mktemp() - f = open(tmp, 'w') - f.write('127.0.1.1 helmut.example.org helmut\n') - f.write('# a comment\n') - f.write('::1 localhost ip6-localhost ip6-loopback\n') - f.close() - self.assertEquals(hosts.searchFileFor(tmp, 'helmut'), '127.0.1.1') - self.assertEquals(hosts.searchFileFor(tmp, 'ip6-localhost'), '::1') - self.assertIdentical(hosts.searchFileFor(tmp, 'blah'), None) - - + self.assertEqual(self.results, self.records) class FakeDNSDatagramProtocol(object): def __init__(self): @@ -678,8 +618,8 @@ expected.sort() for ((addr, query, timeout, id), expectedAddr) in zip(tries, expected): - self.assertEquals(addr, (expectedAddr, 53)) - self.assertEquals(timeout, t) + self.assertEqual(addr, (expectedAddr, 53)) + self.assertEqual(timeout, t) self.failIf(fakeProto.queries) @@ -687,7 +627,7 @@ def testMissing(self): resolvConf = self.mktemp() r = client.Resolver(resolv=resolvConf) - self.assertEquals(r.dynServers, [('127.0.0.1', 53)]) + self.assertEqual(r.dynServers, [('127.0.0.1', 53)]) r._parseCall.cancel() def testEmpty(self): @@ -695,7 +635,7 @@ fObj = file(resolvConf, 'w') fObj.close() r = client.Resolver(resolv=resolvConf) - self.assertEquals(r.dynServers, [('127.0.0.1', 53)]) + self.assertEqual(r.dynServers, [('127.0.0.1', 53)]) r._parseCall.cancel() @@ -807,14 +747,14 @@ result = [] d.addCallback(result.append) answer, authority, additional = result[0] - self.assertEquals(answer, []) - self.assertEquals( + self.assertEqual(answer, []) + self.assertEqual( authority, [ dns.RRHeader( str(soa_record.mname), soa_record.TYPE, ttl=soa_record.expire, payload=soa_record, auth=True)]) - self.assertEquals(additional, []) + self.assertEqual(additional, []) def _referralTest(self, method): @@ -835,12 +775,12 @@ result = [] d.addCallback(result.append) answer, authority, additional = result[0] - self.assertEquals(answer, []) - self.assertEquals( + self.assertEqual(answer, []) + self.assertEqual( authority, [dns.RRHeader( subdomain, dns.NS, ttl=soa_record.expire, payload=nameserver, auth=False)]) - self.assertEquals(additional, []) + self.assertEqual(additional, []) def test_referral(self): @@ -895,3 +835,122 @@ messages.append(m) return self.assertFailure( resolver.getHostByName("fooby.com"), socket.gaierror) + + + +class SecondaryAuthorityServiceTests(unittest.TestCase): + """ + Tests for L{SecondaryAuthorityService}, a service which keeps one or more + authorities up to date by doing zone transfers from a master. + """ + + def test_constructAuthorityFromHost(self): + """ + L{SecondaryAuthorityService} can be constructed with a C{str} giving a + master server address and several domains, causing the creation of a + secondary authority for each domain and that master server address and + the default DNS port. + """ + primary = '192.168.1.2' + service = SecondaryAuthorityService( + primary, ['example.com', 'example.org']) + self.assertEqual(service.primary, primary) + self.assertEqual(service._port, 53) + + self.assertEqual(service.domains[0].primary, primary) + self.assertEqual(service.domains[0]._port, 53) + self.assertEqual(service.domains[0].domain, 'example.com') + + self.assertEqual(service.domains[1].primary, primary) + self.assertEqual(service.domains[1]._port, 53) + self.assertEqual(service.domains[1].domain, 'example.org') + + + def test_constructAuthorityFromHostAndPort(self): + """ + L{SecondaryAuthorityService.fromServerAddressAndDomains} constructs a + new L{SecondaryAuthorityService} from a C{str} giving a master server + address and DNS port and several domains, causing the creation of a secondary + authority for each domain and that master server address and the given + DNS port. + """ + primary = '192.168.1.3' + port = 5335 + service = SecondaryAuthorityService.fromServerAddressAndDomains( + (primary, port), ['example.net', 'example.edu']) + self.assertEqual(service.primary, primary) + self.assertEqual(service._port, 5335) + + self.assertEqual(service.domains[0].primary, primary) + self.assertEqual(service.domains[0]._port, port) + self.assertEqual(service.domains[0].domain, 'example.net') + + self.assertEqual(service.domains[1].primary, primary) + self.assertEqual(service.domains[1]._port, port) + self.assertEqual(service.domains[1].domain, 'example.edu') + + + +class SecondaryAuthorityTests(unittest.TestCase): + """ + L{twisted.names.secondary.SecondaryAuthority} correctly constructs objects + with a specified IP address and optionally specified DNS port. + """ + + def test_defaultPort(self): + """ + When constructed using L{SecondaryAuthority.__init__}, the default port + of 53 is used. + """ + secondary = SecondaryAuthority('192.168.1.1', 'inside.com') + self.assertEqual(secondary.primary, '192.168.1.1') + self.assertEqual(secondary._port, 53) + self.assertEqual(secondary.domain, 'inside.com') + + + def test_explicitPort(self): + """ + When constructed using L{SecondaryAuthority.fromServerAddressAndDomain}, + the specified port is used. + """ + secondary = SecondaryAuthority.fromServerAddressAndDomain( + ('192.168.1.1', 5353), 'inside.com') + self.assertEqual(secondary.primary, '192.168.1.1') + self.assertEqual(secondary._port, 5353) + self.assertEqual(secondary.domain, 'inside.com') + + + def test_transfer(self): + """ + An attempt is made to transfer the zone for the domain the + L{SecondaryAuthority} was constructed with from the server address it + was constructed with when L{SecondaryAuthority.transfer} is called. + """ + class ClockMemoryReactor(Clock, MemoryReactor): + def __init__(self): + Clock.__init__(self) + MemoryReactor.__init__(self) + + secondary = SecondaryAuthority.fromServerAddressAndDomain( + ('192.168.1.2', 1234), 'example.com') + secondary._reactor = reactor = ClockMemoryReactor() + + secondary.transfer() + + # Verify a connection attempt to the server address above + host, port, factory, timeout, bindAddress = reactor.tcpClients.pop(0) + self.assertEqual(host, '192.168.1.2') + self.assertEqual(port, 1234) + + # See if a zone transfer query is issued. + proto = factory.buildProtocol((host, port)) + transport = StringTransport() + proto.makeConnection(transport) + + msg = Message() + # DNSProtocol.writeMessage length encodes the message by prepending a + # 2 byte message length to the buffered value. + msg.decode(StringIO(transport.value()[2:])) + + self.assertEqual( + [dns.Query('example.com', dns.AXFR, dns.IN)], msg.queries) diff -Nru twisted-names-11.0.0/twisted/names/test/test_rootresolve.py twisted-names-12.1.0/twisted/names/test/test_rootresolve.py --- twisted-names-11.0.0/twisted/names/test/test_rootresolve.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_rootresolve.py 2011-07-14 19:05:14.000000000 +0000 @@ -159,14 +159,14 @@ msg.fromStr(packet) # It should be a query with the parameters used above. - self.assertEquals(msg.queries, [Query('foo.example.com', A, IN)]) - self.assertEquals(msg.answers, []) - self.assertEquals(msg.authority, []) - self.assertEquals(msg.additional, []) + self.assertEqual(msg.queries, [Query('foo.example.com', A, IN)]) + self.assertEqual(msg.answers, []) + self.assertEqual(msg.authority, []) + self.assertEqual(msg.additional, []) response = [] d.addCallback(response.append) - self.assertEquals(response, []) + self.assertEqual(response, []) # Once a reply is received, the Deferred should fire. del msg.queries[:] @@ -184,11 +184,11 @@ result is a three-tuple of lists of records. """ answer, authority, additional = self._queryTest(True) - self.assertEquals( + self.assertEqual( answer, [RRHeader('foo.example.com', payload=Record_A('5.8.13.21', ttl=0))]) - self.assertEquals(authority, []) - self.assertEquals(additional, []) + self.assertEqual(authority, []) + self.assertEqual(additional, []) def test_unfilteredQuery(self): @@ -199,12 +199,12 @@ """ message = self._queryTest(False) self.assertIsInstance(message, Message) - self.assertEquals(message.queries, []) - self.assertEquals( + self.assertEqual(message.queries, []) + self.assertEqual( message.answers, [RRHeader('foo.example.com', payload=Record_A('5.8.13.21', ttl=0))]) - self.assertEquals(message.authority, []) - self.assertEquals(message.additional, []) + self.assertEqual(message.authority, []) + self.assertEqual(message.additional, []) def _respond(self, answers=[], authority=[], additional=[], rCode=OK): @@ -282,7 +282,7 @@ resolver = self._getResolver(servers) d = resolver.lookupAddress('foo.example.com') d.addCallback(lambda (ans, auth, add): ans[0].payload.dottedQuad()) - d.addCallback(self.assertEquals, '10.0.0.1') + d.addCallback(self.assertEqual, '10.0.0.1') return d @@ -311,7 +311,7 @@ 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')) + d.addCallback(self.assertEqual, Record_A('10.0.0.3')) return d @@ -339,7 +339,7 @@ resolver = self._getResolver(servers) d = resolver.lookupAddress('foo.example.com') d.addCallback(lambda (ans, auth, add): ans[0].payload.dottedQuad()) - d.addCallback(self.assertEquals, '10.0.0.2') + d.addCallback(self.assertEqual, '10.0.0.2') return d @@ -436,7 +436,7 @@ resolver = self._getResolver(servers) d = resolver.lookupNameservers('example.com') d.addCallback(lambda (ans, auth, add): str(ans[0].payload.name)) - d.addCallback(self.assertEquals, 'ns1.example.com') + d.addCallback(self.assertEqual, 'ns1.example.com') return d @@ -457,7 +457,7 @@ d = resolver.lookupAddress('example.com') d.addCallback(lambda (ans, auth, add): ans) d.addCallback( - self.assertEquals, + self.assertEqual, [RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')), RRHeader('example.net', A, payload=Record_A('10.0.0.7'))]) return d @@ -483,7 +483,7 @@ d = resolver.lookupAddress('example.com') d.addCallback(lambda (ans, auth, add): ans) d.addCallback( - self.assertEquals, + self.assertEqual, [RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')), RRHeader('example.net', A, payload=Record_A('10.0.0.5'))]) return d @@ -553,7 +553,7 @@ succeeder = self._getResolver(servers, 4) succeedD = succeeder.lookupAddress('example.com') succeedD.addCallback(lambda (ans, auth, add): ans[0].payload) - succeedD.addCallback(self.assertEquals, Record_A('10.0.0.4')) + succeedD.addCallback(self.assertEqual, Record_A('10.0.0.4')) return gatherResults([failD, succeedD]) @@ -567,12 +567,12 @@ warnings = self.flushWarnings([ self.test_discoveredAuthorityDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.Resolver.discoveredAuthority is deprecated since ' 'Twisted 10.0. Use twisted.names.client.Resolver directly, instead.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) # This will time out quickly, but we need to wait for it because there # are resources associated with. @@ -614,13 +614,13 @@ warnings = self.flushWarnings([ self.test_lookupNameserversDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.lookupNameservers is deprecated since Twisted ' '10.0. Use twisted.names.root.Resolver.lookupNameservers ' 'instead.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) test_lookupNameserversDeprecated.suppress = [_retrySuppression] @@ -634,13 +634,13 @@ warnings = self.flushWarnings([ self.test_lookupAddressDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.lookupAddress is deprecated since Twisted ' '10.0. Use twisted.names.root.Resolver.lookupAddress ' 'instead.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) test_lookupAddressDeprecated.suppress = [_retrySuppression] @@ -652,12 +652,12 @@ warnings = self.flushWarnings([ self.test_extractAuthorityDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.extractAuthority is deprecated since Twisted ' '10.0. Please inspect the Message object directly.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) def test_discoverAuthorityDeprecated(self): @@ -669,13 +669,13 @@ warnings = self.flushWarnings([ self.test_discoverAuthorityDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.discoverAuthority is deprecated since Twisted ' '10.0. Use twisted.names.root.Resolver.lookupNameservers ' 'instead.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) # discoverAuthority is implemented in terms of deprecated functions, # too. Ignore those. @@ -697,9 +697,9 @@ warnings = self.flushWarnings([ self.test_retryDeprecated]) - self.assertEquals(warnings[0]['category'], DeprecationWarning) - self.assertEquals( + self.assertEqual(warnings[0]['category'], DeprecationWarning) + self.assertEqual( warnings[0]['message'], 'twisted.names.root.retry is deprecated since Twisted ' '10.0. Use a Resolver object for retry logic.') - self.assertEquals(len(warnings), 1) + self.assertEqual(len(warnings), 1) diff -Nru twisted-names-11.0.0/twisted/names/test/test_srvconnect.py twisted-names-12.1.0/twisted/names/test/test_srvconnect.py --- twisted-names-11.0.0/twisted/names/test/test_srvconnect.py 2011-02-14 04:45:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_srvconnect.py 2011-07-14 19:05:14.000000000 +0000 @@ -74,7 +74,7 @@ self.connector.connect() self.assertIdentical(None, self.factory.reason) - self.assertEquals( + self.assertEqual( self.reactor.tcpClients.pop()[:2], ('host.example.org', 6269)) @@ -86,7 +86,7 @@ self.connector.connect() self.assertIdentical(None, self.factory.reason) - self.assertEquals( + self.assertEqual( self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server')) @@ -98,7 +98,7 @@ self.connector.connect() self.assertIdentical(None, self.factory.reason) - self.assertEquals( + self.assertEqual( self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server')) @@ -113,7 +113,7 @@ self.connector.connect() self.assertIdentical(None, self.factory.reason) - self.assertEquals( + self.assertEqual( self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server')) @@ -130,4 +130,4 @@ self.assertNotIdentical(None, self.factory.reason) self.factory.reason.trap(DNSLookupError) - self.assertEquals(self.reactor.tcpClients, []) + self.assertEqual(self.reactor.tcpClients, []) diff -Nru twisted-names-11.0.0/twisted/names/test/test_tap.py twisted-names-12.1.0/twisted/names/test/test_tap.py --- twisted-names-11.0.0/twisted/names/test/test_tap.py 1970-01-01 00:00:00.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_tap.py 2012-03-18 14:44:22.000000000 +0000 @@ -0,0 +1,99 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Tests for L{twisted.names.tap}. +""" + +from twisted.trial.unittest import TestCase +from twisted.python.usage import UsageError +from twisted.names.tap import Options, _buildResolvers +from twisted.names.dns import PORT +from twisted.names.secondary import SecondaryAuthorityService +from twisted.names.resolve import ResolverChain +from twisted.names.client import Resolver + +class OptionsTests(TestCase): + """ + Tests for L{Options}, defining how command line arguments for the DNS server + are parsed. + """ + def test_malformedSecondary(self): + """ + If the value supplied for an I{--secondary} option does not provide a + server IP address, optional port number, and domain name, + L{Options.parseOptions} raises L{UsageError}. + """ + options = Options() + self.assertRaises( + UsageError, options.parseOptions, ['--secondary', '']) + self.assertRaises( + UsageError, options.parseOptions, ['--secondary', '1.2.3.4']) + self.assertRaises( + UsageError, options.parseOptions, ['--secondary', '1.2.3.4:hello']) + self.assertRaises( + UsageError, options.parseOptions, + ['--secondary', '1.2.3.4:hello/example.com']) + + + def test_secondary(self): + """ + An argument of the form C{"ip/domain"} is parsed by L{Options} for the + I{--secondary} option and added to its list of secondaries, using the + default DNS port number. + """ + options = Options() + options.parseOptions(['--secondary', '1.2.3.4/example.com']) + self.assertEqual( + [(('1.2.3.4', PORT), ['example.com'])], options.secondaries) + + + def test_secondaryExplicitPort(self): + """ + An argument of the form C{"ip:port/domain"} can be used to specify an + alternate port number for for which to act as a secondary. + """ + options = Options() + options.parseOptions(['--secondary', '1.2.3.4:5353/example.com']) + self.assertEqual( + [(('1.2.3.4', 5353), ['example.com'])], options.secondaries) + + + def test_secondaryAuthorityServices(self): + """ + After parsing I{--secondary} options, L{Options} constructs a + L{SecondaryAuthorityService} instance for each configured secondary. + """ + options = Options() + options.parseOptions(['--secondary', '1.2.3.4:5353/example.com', + '--secondary', '1.2.3.5:5354/example.com']) + self.assertEqual(len(options.svcs), 2) + secondary = options.svcs[0] + self.assertIsInstance(options.svcs[0], SecondaryAuthorityService) + self.assertEqual(secondary.primary, '1.2.3.4') + self.assertEqual(secondary._port, 5353) + secondary = options.svcs[1] + self.assertIsInstance(options.svcs[1], SecondaryAuthorityService) + self.assertEqual(secondary.primary, '1.2.3.5') + self.assertEqual(secondary._port, 5354) + + + def test_recursiveConfiguration(self): + """ + Recursive DNS lookups, if enabled, should be a last-resort option. + Any other lookup method (cache, local lookup, etc.) should take + precedence over recursive lookups + """ + options = Options() + options.parseOptions(['--hosts-file', 'hosts.txt', '--recursive']) + ca, cl = _buildResolvers(options) + + # Extra cleanup, necessary on POSIX because client.Resolver doesn't know + # when to stop parsing resolv.conf. See #NNN for improving this. + for x in cl: + if isinstance(x, ResolverChain): + recurser = x.resolvers[-1] + if isinstance(recurser, Resolver): + recurser._parseCall.cancel() + + self.assertIsInstance(cl[-1], ResolverChain) diff -Nru twisted-names-11.0.0/twisted/names/_version.py twisted-names-12.1.0/twisted/names/_version.py --- twisted-names-11.0.0/twisted/names/_version.py 2011-04-02 01:10:15.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/_version.py 2012-06-02 07:04:01.000000000 +0000 @@ -1,3 +1,3 @@ # This is an auto-generated file. Do not edit it. from twisted.python import versions -version = versions.Version('twisted.names', 11, 0, 0) +version = versions.Version('twisted.names', 12, 1, 0)