diff -Nru twisted-names-11.1.0/debian/changelog twisted-names-12.1.0/debian/changelog --- twisted-names-11.1.0/debian/changelog 2011-12-21 11:56:47.000000000 +0000 +++ twisted-names-12.1.0/debian/changelog 2012-06-23 14:43:09.000000000 +0000 @@ -1,3 +1,16 @@ +twisted-names (12.1.0-1~ppa1~precise1) precise; urgency=low + + * New upstream release. + * Rebuild for use in PPA. + + -- Jessica McKellar Sat, 23 Jun 2012 10:42:40 -0400 + +twisted-names (12.0.0-1~ppa1~precise1) precise; urgency=low + + * Rebuild for use in PPA. + + -- Jessica McKellar Tue, 29 May 2012 18:31:23 -0400 + twisted-names (11.1.0-1) unstable; urgency=low * New upstream release. diff -Nru twisted-names-11.1.0/debian/compat twisted-names-12.1.0/debian/compat --- twisted-names-11.1.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.1.0/debian/control twisted-names-12.1.0/debian/control --- twisted-names-11.1.0/debian/control 2011-12-21 11:57:07.000000000 +0000 +++ twisted-names-12.1.0/debian/control 2012-06-19 23:04:12.000000000 +0000 @@ -3,16 +3,15 @@ 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.1), 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.2 Package: python-twisted-names Architecture: all -Depends: ${python:Depends}, python-twisted-core (>= 11.1), ${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: 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" diff -Nru twisted-names-11.1.0/debian/rules twisted-names-12.1.0/debian/rules --- twisted-names-11.1.0/debian/rules 2011-12-21 11:57:23.000000000 +0000 +++ twisted-names-12.1.0/debian/rules 2012-06-19 23:02:40.000000000 +0000 @@ -27,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.1.0/debian/watch twisted-names-12.1.0/debian/watch --- twisted-names-11.1.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.1.0/doc/examples/dns-service.py twisted-names-12.1.0/doc/examples/dns-service.py --- twisted-names-11.1.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.1.0/doc/examples/gethostbyname.py twisted-names-12.1.0/doc/examples/gethostbyname.py --- twisted-names-11.1.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.1.0/doc/examples/index.html twisted-names-12.1.0/doc/examples/index.html --- twisted-names-11.1.0/doc/examples/index.html 2011-11-17 10:51:14.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.1.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.1.0/doc/examples/testdns.py twisted-names-12.1.0/doc/examples/testdns.py --- twisted-names-11.1.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.1.0/doc/howto/index.html twisted-names-12.1.0/doc/howto/index.html --- twisted-names-11.1.0/doc/howto/index.html 2011-11-17 10:51:14.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.1.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.1.0/doc/howto/names.html twisted-names-12.1.0/doc/howto/names.html --- twisted-names-11.1.0/doc/howto/names.html 2011-11-17 10:51:14.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.1.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.1.0/doc/index.html twisted-names-12.1.0/doc/index.html --- twisted-names-11.1.0/doc/index.html 2011-11-17 10:51:13.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.1.0 + Version: 12.1.0 \ No newline at end of file diff -Nru twisted-names-11.1.0/LICENSE twisted-names-12.1.0/LICENSE --- twisted-names-11.1.0/LICENSE 2011-05-05 02:48:02.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 diff -Nru twisted-names-11.1.0/NEWS twisted-names-12.1.0/NEWS --- twisted-names-11.1.0/NEWS 2011-11-15 16:39:18.000000000 +0000 +++ twisted-names-12.1.0/NEWS 2012-06-02 07:04:01.000000000 +0000 @@ -1,6 +1,49 @@ 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) ================================= diff -Nru twisted-names-11.1.0/README twisted-names-12.1.0/README --- twisted-names-11.1.0/README 2011-11-15 16:39:18.000000000 +0000 +++ twisted-names-12.1.0/README 2012-06-02 07:04:01.000000000 +0000 @@ -1,3 +1,3 @@ -Twisted Names 11.1.0 +Twisted Names 12.1.0 Twisted Names depends on Twisted Core. diff -Nru twisted-names-11.1.0/twisted/names/cache.py twisted-names-12.1.0/twisted/names/cache.py --- twisted-names-11.1.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.1.0/twisted/names/client.py twisted-names-12.1.0/twisted/names/client.py --- twisted-names-11.1.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.1.0/twisted/names/dns.py twisted-names-12.1.0/twisted/names/dns.py --- twisted-names-11.1.0/twisted/names/dns.py 2011-09-20 18:21:29.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/dns.py 2012-04-01 20:16:36.000000000 +0000 @@ -445,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) @@ -1408,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)) @@ -1635,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: diff -Nru twisted-names-11.1.0/twisted/names/secondary.py twisted-names-12.1.0/twisted/names/secondary.py --- twisted-names-11.1.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.1.0/twisted/names/tap.py twisted-names-12.1.0/twisted/names/tap.py --- twisted-names-11.1.0/twisted/names/tap.py 2011-10-18 10:31:11.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 """ @@ -65,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""" @@ -82,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']) @@ -100,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.1.0/twisted/names/test/test_cache.py twisted-names-12.1.0/twisted/names/test/test_cache.py --- twisted-names-11.1.0/twisted/names/test/test_cache.py 2011-07-14 19:05:14.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.assertEqual, ([], [], [])) + 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.1.0/twisted/names/test/test_client.py twisted-names-12.1.0/twisted/names/test/test_client.py --- twisted-names-11.1.0/twisted/names/test/test_client.py 2011-07-14 19:05:14.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): diff -Nru twisted-names-11.1.0/twisted/names/test/test_dns.py twisted-names-12.1.0/twisted/names/test/test_dns.py --- twisted-names-11.1.0/twisted/names/test/test_dns.py 2011-09-20 18:21:29.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_dns.py 2012-01-07 14:25:15.000000000 +0000 @@ -372,6 +372,59 @@ 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) + + class TestController(object): """ diff -Nru twisted-names-11.1.0/twisted/names/test/test_names.py twisted-names-12.1.0/twisted/names/test/test_names.py --- twisted-names-11.1.0/twisted/names/test/test_names.py 2011-07-14 19:05:14.000000000 +0000 +++ twisted-names-12.1.0/twisted/names/test/test_names.py 2012-03-06 14:14:56.000000000 +0000 @@ -7,10 +7,12 @@ """ 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, dns from twisted.python import failure @@ -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]] @@ -830,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.1.0/twisted/names/test/test_tap.py twisted-names-12.1.0/twisted/names/test/test_tap.py --- twisted-names-11.1.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.1.0/twisted/names/_version.py twisted-names-12.1.0/twisted/names/_version.py --- twisted-names-11.1.0/twisted/names/_version.py 2011-11-15 16:39:18.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, 1, 0) +version = versions.Version('twisted.names', 12, 1, 0)