diff -Nru twisted-names-10.0.0/debian/changelog twisted-names-12.0.0/debian/changelog --- twisted-names-10.0.0/debian/changelog 2012-02-19 01:48:35.000000000 +0000 +++ twisted-names-12.0.0/debian/changelog 2012-02-19 01:30:49.000000000 +0000 @@ -1,3 +1,10 @@ +twisted-names (12.0.0-1~ppa1~lucid1) lucid; urgency=low + + * New upstream version. + * Rebuild package for use in PPA. + + -- Jessica McKellar Sat, 18 Feb 2012 20:29:45 -0500 + twisted-names (10.0.0-1) unstable; urgency=low * New upstream release diff -Nru twisted-names-10.0.0/debian/compat twisted-names-12.0.0/debian/compat --- twisted-names-10.0.0/debian/compat 2012-02-19 01:48:35.000000000 +0000 +++ twisted-names-12.0.0/debian/compat 2012-02-19 01:28:09.000000000 +0000 @@ -1 +1 @@ -5 +7 diff -Nru twisted-names-10.0.0/debian/control twisted-names-12.0.0/debian/control --- twisted-names-10.0.0/debian/control 2012-02-19 01:48:35.000000000 +0000 +++ twisted-names-12.0.0/debian/control 2012-02-19 01:28:57.000000000 +0000 @@ -3,17 +3,16 @@ Priority: optional Maintainer: Matthias Klose Uploaders: Free Ekanayaka -Build-Depends: debhelper (>= 5.0.37.1), python-central (>= 0.6.11), python-all, python-twisted-core (>= 10.0), patch +Build-Depends: debhelper (>=7.0.50~), python-central (>= 0.6.11), python-all, python-twisted-core (>= 12.0), patch XS-Python-Version: all -Standards-Version: 3.8.4 +Standards-Version: 3.9.2 Package: python-twisted-names Architecture: all -Depends: ${python:Depends}, python-twisted-core (>= 10.0), ${misc:Depends} +Depends: ${python:Depends}, python-twisted-core (>= 12.0), ${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-10.0.0/debian/rules twisted-names-12.0.0/debian/rules --- twisted-names-10.0.0/debian/rules 2012-02-19 01:48:35.000000000 +0000 +++ twisted-names-12.0.0/debian/rules 2012-02-19 01:29:16.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-10.0.0/debian/source/format twisted-names-12.0.0/debian/source/format --- twisted-names-10.0.0/debian/source/format 1970-01-01 00:00:00.000000000 +0000 +++ twisted-names-12.0.0/debian/source/format 2012-02-19 01:48:35.000000000 +0000 @@ -0,0 +1 @@ +3.0 (quilt) diff -Nru twisted-names-10.0.0/doc/examples/dns-service.py twisted-names-12.0.0/doc/examples/dns-service.py --- twisted-names-10.0.0/doc/examples/dns-service.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/examples/dns-service.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ diff -Nru twisted-names-10.0.0/doc/examples/gethostbyname.py twisted-names-12.0.0/doc/examples/gethostbyname.py --- twisted-names-10.0.0/doc/examples/gethostbyname.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/examples/gethostbyname.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import sys diff -Nru twisted-names-10.0.0/doc/examples/index.html twisted-names-12.0.0/doc/examples/index.html --- twisted-names-10.0.0/doc/examples/index.html 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/examples/index.html 2012-02-10 16:32:47.000000000 +0000 @@ -19,6 +19,6 @@

Index

- Version: 10.0.0 + Version: 12.0.0 \ No newline at end of file diff -Nru twisted-names-10.0.0/doc/examples/testdns.py twisted-names-12.0.0/doc/examples/testdns.py --- twisted-names-10.0.0/doc/examples/testdns.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/examples/testdns.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import sys diff -Nru twisted-names-10.0.0/doc/howto/index.html twisted-names-12.0.0/doc/howto/index.html --- twisted-names-10.0.0/doc/howto/index.html 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/howto/index.html 2012-02-10 16:32:47.000000000 +0000 @@ -17,6 +17,6 @@

Index

- Version: 10.0.0 + Version: 12.0.0 \ No newline at end of file diff -Nru twisted-names-10.0.0/doc/howto/names.html twisted-names-12.0.0/doc/howto/names.html --- twisted-names-10.0.0/doc/howto/names.html 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/howto/names.html 2012-02-10 16:32:47.000000000 +0000 @@ -37,9 +37,9 @@ scope of this howto and will not be covered.

-

To configure Names to act as the authority for -example-domain.com, we first create a -zone file for this domain.

+

To configure Names to act as the authority +for example-domain.com, we first create a zone file for +this domain.

1 2 @@ -129,6 +129,6 @@

Index

- Version: 10.0.0 + Version: 12.0.0 \ No newline at end of file diff -Nru twisted-names-10.0.0/doc/index.html twisted-names-12.0.0/doc/index.html --- twisted-names-10.0.0/doc/index.html 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/doc/index.html 2012-02-10 16:32:47.000000000 +0000 @@ -20,6 +20,6 @@

Index

- Version: 10.0.0 + Version: 12.0.0 \ No newline at end of file diff -Nru twisted-names-10.0.0/LICENSE twisted-names-12.0.0/LICENSE --- twisted-names-10.0.0/LICENSE 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/LICENSE 2012-01-23 13:19:44.000000000 +0000 @@ -1,4 +1,4 @@ -Copyright (c) 2001-2010 +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-10.0.0/NEWS twisted-names-12.0.0/NEWS --- twisted-names-10.0.0/NEWS 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/NEWS 2012-02-10 14:56:39.000000000 +0000 @@ -1,6 +1,72 @@ Ticket numbers in this file can be looked up by visiting http://twistedmatrix.com/trac/ticket/ +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) +================================= + +No significant changes have been made for this release. + + +Twisted Names 10.2.0 (2010-11-29) +================================= + +Features +-------- + - twisted.names.server can now serve SPF resource records using + twisted.names.dns.Record_SPF. twisted.names.client can query for + them using lookupSenderPolicy. (#3928) + +Bugfixes +-------- + - twisted.names.common.extractRecords doesn't try to close the + transport anymore in case of recursion, as it's done by the + Resolver itself now. (#3998) + +Improved Documentation +---------------------- + - Tidied up the Twisted Names documentation for easier conversion. + (#4573) + + +Twisted Names 10.1.0 (2010-06-27) +================================= + +Features +-------- + - twisted.names.dns.Message now uses a specially constructed + dictionary for looking up record types. This yields a significant + performance improvement on PyPy. (#4283) + + Twisted Names 10.0.0 (2010-03-01) ================================= diff -Nru twisted-names-10.0.0/README twisted-names-12.0.0/README --- twisted-names-10.0.0/README 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/README 2012-02-10 14:56:39.000000000 +0000 @@ -1,3 +1,3 @@ -Twisted Names 10.0.0 +Twisted Names 12.0.0 Twisted Names depends on Twisted Core. diff -Nru twisted-names-10.0.0/setup.py twisted-names-12.0.0/setup.py --- twisted-names-10.0.0/setup.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/setup.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2008 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import sys diff -Nru twisted-names-10.0.0/twisted/names/authority.py twisted-names-12.0.0/twisted/names/authority.py --- twisted-names-10.0.0/twisted/names/authority.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/authority.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,9 +1,10 @@ # -*- test-case-name: twisted.names.test.test_names -*- -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. - -from __future__ import nested_scopes +""" +Authoritative resolvers. +""" import os import time @@ -90,12 +91,15 @@ else: ttl = default_ttl - if record.TYPE == type or type == dns.ALL_RECORDS: - results.append( - dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True) - ) - elif record.TYPE == dns.NS and type != dns.ALL_RECORDS: + if record.TYPE == dns.NS and name.lower() != self.soa[0].lower(): + # NS record belong to a child zone: this is a referral. As + # NS records are authoritative in the child zone, ours here + # are not. RFC 2181, section 6.1. authority.append( + dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=False) + ) + elif record.TYPE == type or type == dns.ALL_RECORDS: + results.append( dns.RRHeader(name, record.TYPE, dns.IN, ttl, record, auth=True) ) if record.TYPE == dns.CNAME: @@ -115,6 +119,13 @@ dns.RRHeader(n, dns.A, dns.IN, rec.ttl or default_ttl, rec, auth=True) ) + if not results and not authority: + # Empty response. Include SOA record to allow clients to cache + # this response. RFC 1034, sections 3.7 and 4.3.4, and RFC 2181 + # section 7.1. + authority.append( + dns.RRHeader(self.soa[0], dns.SOA, dns.IN, ttl, self.soa[1], auth=True) + ) return defer.succeed((results, authority, additional)) else: if name.lower().endswith(self.soa[0].lower()): diff -Nru twisted-names-10.0.0/twisted/names/cache.py twisted-names-12.0.0/twisted/names/cache.py --- twisted-names-10.0.0/twisted/names/cache.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/cache.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test -*- -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. diff -Nru twisted-names-10.0.0/twisted/names/client.py twisted-names-12.0.0/twisted/names/client.py --- twisted-names-10.0.0/twisted/names/client.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/client.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test.test_names -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -520,9 +520,10 @@ Create and return a Resolver. @type servers: C{list} of C{(str, int)} or C{None} - @param servers: If not C{None}, interpreted as a list of addresses of - domain name servers to attempt to use. Addresses should be in dotted-quad - form. + + @param servers: If not C{None}, interpreted as a list of domain name servers + to attempt to use. Each server is a tuple of address in C{str} dotted-quad + form and C{int} port number. @type resolvconf: C{str} or C{None} @param resolvconf: If not C{None}, on posix systems will be interpreted as @@ -850,6 +851,21 @@ """ return getResolver().lookupText(name, timeout) +def lookupSenderPolicy(name, timeout=None): + """ + Perform a SPF record lookup. + + @type name: C{str} + @param name: DNS name to resolve. + + @type timeout: Sequence of C{int} + @param timeout: Number of seconds after which to reissue the query. + When the last timeout expires, the query is considered failed. + + @rtype: C{Deferred} + """ + return getResolver().lookupSenderPolicy(name, timeout) + def lookupResponsibility(name, timeout=None): """ Perform an RP record lookup. diff -Nru twisted-names-10.0.0/twisted/names/common.py twisted-names-12.0.0/twisted/names/common.py --- twisted-names-10.0.0/twisted/names/common.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/common.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -163,6 +163,12 @@ """ return self._lookup(name, dns.IN, dns.TXT, timeout) + def lookupSenderPolicy(self, name, timeout = None): + """ + @see: twisted.names.client.lookupSenderPolicy + """ + return self._lookup(name, dns.IN, dns.SPF, timeout) + def lookupResponsibility(self, name, timeout = None): """ @see: twisted.names.client.lookupResponsibility @@ -210,7 +216,8 @@ raise error.DNSLookupError(name) return result -def extractRecord(resolver, name, answers, level = 10): + +def extractRecord(resolver, name, answers, level=10): if not level: return None if hasattr(socket, 'inet_ntop'): @@ -225,18 +232,23 @@ return socket.inet_ntop(socket.AF_INET, r.payload.address) for r in answers: if r.name == name and r.type == dns.CNAME: - result = extractRecord(resolver, r.payload.name, answers, level - 1) + result = extractRecord( + resolver, r.payload.name, answers, level - 1) if not result: - return resolver.getHostByName(str(r.payload.name), effort=level-1) + return resolver.getHostByName( + str(r.payload.name), effort=level - 1) return result - # No answers, but maybe there's a hint at who we should be asking about this + # No answers, but maybe there's a hint at who we should be asking about + # this for r in answers: if r.type == dns.NS: from twisted.names import client r = client.Resolver(servers=[(str(r.payload.name), dns.PORT)]) return r.lookupAddress(str(name) - ).addCallback(lambda (ans, auth, add): extractRecord(r, name, ans + auth + add, level - 1) - ).addBoth(lambda passthrough: (r.protocol.transport.stopListening(), passthrough)[1]) + ).addCallback( + lambda (ans, auth, add): + extractRecord(r, name, ans + auth + add, level - 1)) + typeToMethod = { dns.A: 'lookupAddress', @@ -255,6 +267,7 @@ dns.MINFO: 'lookupMailboxInfo', dns.MX: 'lookupMailExchange', dns.TXT: 'lookupText', + dns.SPF: 'lookupSenderPolicy', dns.RP: 'lookupResponsibility', dns.AFSDB: 'lookupAFSDatabase', diff -Nru twisted-names-10.0.0/twisted/names/dns.py twisted-names-12.0.0/twisted/names/dns.py --- twisted-names-10.0.0/twisted/names/dns.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/dns.py 2012-01-07 14:25:15.000000000 +0000 @@ -1,28 +1,54 @@ # -*- test-case-name: twisted.names.test.test_dns -*- -# Copyright (c) 2001-2008 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. - """ DNS protocol implementation. Future Plans: - Get rid of some toplevels, maybe. - - Put in a better lookupRecordType implementation. @author: Moshe Zadka -@author: Jp Calderone +@author: Jean-Paul Calderone """ +__all__ = [ + 'IEncodable', 'IRecord', + + 'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'HINFO', + 'MAILA', 'MAILB', 'MB', 'MD', 'MF', 'MG', 'MINFO', 'MR', 'MX', + 'NAPTR', 'NS', 'NULL', 'PTR', 'RP', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS', + + 'ANY', 'CH', 'CS', 'HS', 'IN', + + 'ALL_RECORDS', 'AXFR', 'IXFR', + + 'EFORMAT', 'ENAME', 'ENOTIMP', 'EREFUSED', 'ESERVER', + + 'Record_A', 'Record_A6', 'Record_AAAA', 'Record_AFSDB', 'Record_CNAME', + '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', 'UnknownRecord', + + 'QUERY_CLASSES', 'QUERY_TYPES', 'REV_CLASSES', 'REV_TYPES', 'EXT_QUERIES', + + 'Charstr', 'Message', 'Name', 'Query', 'RRHeader', 'SimpleRecord', + 'DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol', + + 'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE', + 'PORT', + + 'AuthoritativeDomainError', 'DNSQueryTimeoutError', 'DomainError', + ] + + # System imports import warnings import struct, random, types, socket -try: - import cStringIO as StringIO -except ImportError: - import StringIO +import cStringIO as StringIO AF_INET6 = socket.AF_INET6 @@ -53,6 +79,7 @@ NAPTR = 35 A6 = 38 DNAME = 39 +SPF = 99 QUERY_TYPES = { A: 'A', @@ -80,7 +107,8 @@ SRV: 'SRV', NAPTR: 'NAPTR', A6: 'A6', - DNAME: 'DNAME' + DNAME: 'DNAME', + SPF: 'SPF' } IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256) @@ -301,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: @@ -313,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) @@ -410,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) @@ -445,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 @@ -1391,6 +1428,9 @@ @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. """ implements(IEncodable, IRecord) @@ -1419,8 +1459,8 @@ soFar += L + 1 if soFar != length: log.msg( - "Decoded %d bytes in TXT record, but rdlength is %d" % ( - soFar, length + "Decoded %d bytes in %s record, but rdlength is %d" % ( + soFar, self.fancybasename, length ) ) @@ -1430,7 +1470,75 @@ +# 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. + """ + TYPE = SPF + fancybasename = 'SPF' + + + class Message: + """ + L{Message} contains all the information represented by a single + DNS request or response. + """ headerFmt = "!H2B4H" headerSize = struct.calcsize(headerFmt) @@ -1529,7 +1637,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: @@ -1545,8 +1653,32 @@ list.append(header) + # Create a mapping from record types to their corresponding Record_* + # classes. This relies on the global state which has been created so + # far in initializing this module (so don't define Record classes after + # this). + _recordTypes = {} + for name in globals(): + if name.startswith('Record_'): + _recordTypes[globals()[name].TYPE] = globals()[name] + + # Clear the iteration variable out of the class namespace so it + # doesn't become an attribute. + del name + + def lookupRecordType(self, type): - return globals().get('Record_' + QUERY_TYPES.get(type, ''), None) + """ + Retrieve the L{IRecord} implementation for the given record type. + + @param type: A record type, such as L{A} or L{NS}. + @type type: C{int} + + @return: An object which implements L{IRecord} or C{None} if none + can be found for the given type. + @rtype: L{types.ClassType} + """ + return self._recordTypes.get(type, UnknownRecord) def toStr(self): @@ -1559,6 +1691,8 @@ strio = StringIO.StringIO(str) self.decode(strio) + + class DNSMixin(object): """ DNS protocol mixin shared by UDP and TCP implementations. @@ -1819,4 +1953,3 @@ """ id = self.pickID() return self._query(queries, timeout, id, self.writeMessage) - diff -Nru twisted-names-10.0.0/twisted/names/error.py twisted-names-12.0.0/twisted/names/error.py --- twisted-names-10.0.0/twisted/names/error.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/error.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test -*- -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ diff -Nru twisted-names-10.0.0/twisted/names/hosts.py twisted-names-12.0.0/twisted/names/hosts.py --- twisted-names-10.0.0/twisted/names/hosts.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/hosts.py 2011-06-30 13:43:44.000000000 +0000 @@ -1,21 +1,41 @@ -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# -*- test-case-name: twisted.names.test.test_hosts -*- +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +""" +hosts(5) support. +""" 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): + """ + Search the given file, which is in hosts(5) standard format, for an address + entry with a given name. + + @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} + + @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 + return results - lines = fp.readlines() + name = name.lower() for line in lines: idx = line.find('#') if idx != -1: @@ -23,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 @@ -48,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-10.0.0/twisted/names/__init__.py twisted-names-12.0.0/twisted/names/__init__.py --- twisted-names-10.0.0/twisted/names/__init__.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/__init__.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """Resolving Internet Names""" diff -Nru twisted-names-10.0.0/twisted/names/resolve.py twisted-names-12.0.0/twisted/names/resolve.py --- twisted-names-10.0.0/twisted/names/resolve.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/resolve.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. diff -Nru twisted-names-10.0.0/twisted/names/root.py twisted-names-12.0.0/twisted/names/root.py --- twisted-names-10.0.0/twisted/names/root.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/root.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test.test_rootresolve -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ diff -Nru twisted-names-10.0.0/twisted/names/secondary.py twisted-names-12.0.0/twisted/names/secondary.py --- twisted-names-10.0.0/twisted/names/secondary.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/secondary.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2006 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. from twisted.internet import task, defer diff -Nru twisted-names-10.0.0/twisted/names/server.py twisted-names-12.0.0/twisted/names/server.py --- twisted-names-10.0.0/twisted/names/server.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/server.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test.test_names -*- -# Copyright (c) 2001-2008 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ diff -Nru twisted-names-10.0.0/twisted/names/srvconnect.py twisted-names-12.0.0/twisted/names/srvconnect.py --- twisted-names-10.0.0/twisted/names/srvconnect.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/srvconnect.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test.test_srvconnect -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import random diff -Nru twisted-names-10.0.0/twisted/names/tap.py twisted-names-12.0.0/twisted/names/tap.py --- twisted-names-10.0.0/twisted/names/tap.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/tap.py 2011-10-18 10:31:11.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. @@ -31,6 +31,10 @@ ["verbose", "v", "Log verbosely"], ] + compData = usage.Completions( + optActions={"interface" : usage.CompleteNetInterfaces()} + ) + zones = None zonefiles = None diff -Nru twisted-names-10.0.0/twisted/names/test/test_cache.py twisted-names-12.0.0/twisted/names/test/test_cache.py --- twisted-names-10.0.0/twisted/names/test/test_cache.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_cache.py 2011-07-14 19:05:14.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2006 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. import time @@ -11,4 +11,4 @@ def testLookup(self): c = cache.CacheResolver({ dns.Query(name='example.com', type=dns.MX, cls=dns.IN): (time.time(), ([], [], []))}) - return c.lookupMailExchange('example.com').addCallback(self.assertEquals, ([], [], [])) + return c.lookupMailExchange('example.com').addCallback(self.assertEqual, ([], [], [])) diff -Nru twisted-names-10.0.0/twisted/names/test/test_client.py twisted-names-12.0.0/twisted/names/test/test_client.py --- twisted-names-10.0.0/twisted/names/test/test_client.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_client.py 2011-07-14 19:05:14.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2008 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -438,14 +438,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): """ @@ -585,6 +585,14 @@ d.addCallback(self.checkResult, dns.TXT) return d + def test_lookupSenderPolicy(self): + """ + See L{test_lookupAddress} + """ + d = client.lookupSenderPolicy(self.hostname) + d.addCallback(self.checkResult, dns.SPF) + return d + def test_lookupResponsibility(self): """ See L{test_lookupAddress} @@ -646,10 +654,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-10.0.0/twisted/names/test/test_common.py twisted-names-12.0.0/twisted/names/test/test_common.py --- twisted-names-10.0.0/twisted/names/test/test_common.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_common.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ diff -Nru twisted-names-10.0.0/twisted/names/test/test_dns.py twisted-names-12.0.0/twisted/names/test/test_dns.py --- twisted-names-10.0.0/twisted/names/test/test_dns.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_dns.py 2012-01-07 14:25:15.000000000 +0000 @@ -1,15 +1,12 @@ # test-case-name: twisted.names.test.test_dns -# Copyright (c) 2001-2007 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ 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,17 +293,21 @@ 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) class MessageTestCase(unittest.TestCase): + """ + Tests for L{twisted.names.dns.Message}. + """ + def testEmptyMessage(self): """ Test that a message which has been truncated causes an EOFError to @@ -175,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): @@ -199,7 +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{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), 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) @@ -250,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): @@ -258,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 @@ -275,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 @@ -368,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 @@ -380,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 @@ -653,6 +874,26 @@ "") + def test_spf(self): + """ + The repr of a L{dns.Record_SPF} instance includes the data and ttl + 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): """ @@ -1185,16 +1426,60 @@ """ # Vary the length of the data self._equalityTest( - dns.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['foo', 'bar', 'baz'], 10)) + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('foo', 'bar', 'baz', ttl=10)) + # Vary the value of the data + self._equalityTest( + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('bar', 'foo', ttl=10)) + # Vary the ttl + self._equalityTest( + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('foo', 'bar', ttl=10), + dns.Record_TXT('foo', 'bar', ttl=100)) + + + def test_spf(self): + """ + 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( + dns.Record_SPF('foo', 'bar', ttl=10), + dns.Record_SPF('foo', 'bar', ttl=10), + dns.Record_SPF('foo', 'bar', 'baz', ttl=10)) + # Vary the value of the data + self._equalityTest( + dns.Record_SPF('foo', 'bar', ttl=10), + dns.Record_SPF('foo', 'bar', ttl=10), + dns.Record_SPF('bar', 'foo', ttl=10)) + # Vary the ttl + self._equalityTest( + 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.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['bar', 'foo'], 10)) + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('bar', ttl=10)) # Vary the ttl self._equalityTest( - dns.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['foo', 'bar'], 10), - dns.Record_TXT(['foo', 'bar'], 100)) + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=10), + dns.UnknownRecord('foo', ttl=100)) diff -Nru twisted-names-10.0.0/twisted/names/test/test_hosts.py twisted-names-12.0.0/twisted/names/test/test_hosts.py --- twisted-names-10.0.0/twisted/names/test/test_hosts.py 1970-01-01 00:00:00.000000000 +0000 +++ twisted-names-12.0.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-10.0.0/twisted/names/test/test_names.py twisted-names-12.0.0/twisted/names/test/test_names.py --- twisted-names-10.0.0/twisted/names/test/test_names.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_names.py 2011-07-14 19:05:14.000000000 +0000 @@ -1,5 +1,5 @@ # -*- test-case-name: twisted.names.test.test_names -*- -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -12,7 +12,7 @@ from twisted.internet import reactor, defer, error 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 @@ -73,6 +73,8 @@ soa_record, dns.Record_A('127.0.0.1'), dns.Record_NS('39.28.189.39'), + dns.Record_SPF('v=spf1 mx/30 mx:example.org/30 -all'), + dns.Record_SPF('v=spf1 +mx a:\0colo', '.example.com/28 -all not valid'), dns.Record_MX(10, 'host.test-domain.com'), dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know'), dns.Record_CNAME('canonical.name.com'), @@ -221,13 +223,14 @@ ) - def testAdressRecord3(self): + def testAddressRecord3(self): """Test DNS 'A' record queries with edge cases""" return self.namesTest( self.resolver.lookupAddress('host-two.test-domain.com'), [dns.Record_A('255.255.255.254', ttl=19283784), dns.Record_A('0.0.0.0', ttl=19283784)] ) + def testAuthority(self): """Test DNS 'SOA' record queries""" return self.namesTest( @@ -345,6 +348,17 @@ ) + def test_spf(self): + """ + L{DNSServerFactory} can serve I{SPF} resource records. + """ + return self.namesTest( + self.resolver.lookupSenderPolicy('test-domain.com'), + [dns.Record_SPF('v=spf1 mx/30 mx:example.org/30 -all', ttl=19283784), + dns.Record_SPF('v=spf1 +mx a:\0colo', '.example.com/28 -all not valid', ttl=19283784)] + ) + + def testWKS(self): """Test DNS 'WKS' record queries""" return self.namesTest( @@ -537,7 +551,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 @@ -550,55 +564,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) - + self.assertEqual(self.results, self.records) class FakeDNSDatagramProtocol(object): def __init__(self): @@ -647,8 +613,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) @@ -656,7 +622,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): @@ -664,7 +630,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() @@ -750,3 +716,117 @@ L{DNSUnknownError}. """ return self._rcodeTest(EREFUSED + 1, DNSUnknownError) + + + +class AuthorityTests(unittest.TestCase): + """ + Tests for the basic response record selection code in L{FileAuthority} + (independent of its fileness). + """ + def test_recordMissing(self): + """ + If a L{FileAuthority} has a zone which includes an I{NS} record for a + particular name and that authority is asked for another record for the + same name which does not exist, the I{NS} record is not included in the + authority section of the response. + """ + authority = NoFileAuthority( + soa=(str(soa_record.mname), soa_record), + records={ + str(soa_record.mname): [ + soa_record, + dns.Record_NS('1.2.3.4'), + ]}) + d = authority.lookupAddress(str(soa_record.mname)) + result = [] + d.addCallback(result.append) + answer, authority, additional = result[0] + 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.assertEqual(additional, []) + + + def _referralTest(self, method): + """ + Create an authority and make a request against it. Then verify that the + result is a referral, including no records in the answers or additional + sections, but with an I{NS} record in the authority section. + """ + subdomain = 'example.' + str(soa_record.mname) + nameserver = dns.Record_NS('1.2.3.4') + authority = NoFileAuthority( + soa=(str(soa_record.mname), soa_record), + records={ + subdomain: [ + nameserver, + ]}) + d = getattr(authority, method)(subdomain) + result = [] + d.addCallback(result.append) + answer, authority, additional = result[0] + self.assertEqual(answer, []) + self.assertEqual( + authority, [dns.RRHeader( + subdomain, dns.NS, ttl=soa_record.expire, + payload=nameserver, auth=False)]) + self.assertEqual(additional, []) + + + def test_referral(self): + """ + When an I{NS} record is found for a child zone, it is included in the + authority section of the response. It is marked as non-authoritative if + the authority is not also authoritative for the child zone (RFC 2181, + section 6.1). + """ + self._referralTest('lookupAddress') + + + def test_allRecordsReferral(self): + """ + A referral is also generated for a request of type C{ALL_RECORDS}. + """ + self._referralTest('lookupAllRecords') + + + +class NoInitialResponseTestCase(unittest.TestCase): + + def test_no_answer(self): + """ + If a request returns a L{dns.NS} response, but we can't connect to the + given server, the request fails with the error returned at connection. + """ + + def query(self, *args): + # Pop from the message list, so that it blows up if more queries + # are run than expected. + return succeed(messages.pop(0)) + + def queryProtocol(self, *args, **kwargs): + return defer.fail(socket.gaierror("Couldn't connect")) + + resolver = Resolver(servers=[('0.0.0.0', 0)]) + resolver._query = query + messages = [] + # Let's patch dns.DNSDatagramProtocol.query, as there is no easy way to + # customize it. + self.patch(dns.DNSDatagramProtocol, "query", queryProtocol) + + records = [ + dns.RRHeader(name='fooba.com', type=dns.NS, cls=dns.IN, ttl=700, + auth=False, + payload=dns.Record_NS(name='ns.twistedmatrix.com', + ttl=700))] + m = dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1, auth=1, + rCode=0, trunc=0, maxSize=0) + m.answers = records + messages.append(m) + return self.assertFailure( + resolver.getHostByName("fooby.com"), socket.gaierror) diff -Nru twisted-names-10.0.0/twisted/names/test/test_rootresolve.py twisted-names-12.0.0/twisted/names/test/test_rootresolve.py --- twisted-names-10.0.0/twisted/names/test/test_rootresolve.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_rootresolve.py 2011-07-14 19:05:14.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -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-10.0.0/twisted/names/test/test_srvconnect.py twisted-names-12.0.0/twisted/names/test/test_srvconnect.py --- twisted-names-10.0.0/twisted/names/test/test_srvconnect.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/test/test_srvconnect.py 2011-07-14 19:05:14.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2007-2009 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ @@ -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-10.0.0/twisted/names/_version.py twisted-names-12.0.0/twisted/names/_version.py --- twisted-names-10.0.0/twisted/names/_version.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/names/_version.py 2012-02-10 14:56:39.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', 10, 0, 0) +version = versions.Version('twisted.names', 12, 0, 0) diff -Nru twisted-names-10.0.0/twisted/plugins/twisted_names.py twisted-names-12.0.0/twisted/plugins/twisted_names.py --- twisted-names-10.0.0/twisted/plugins/twisted_names.py 2010-03-09 13:31:15.000000000 +0000 +++ twisted-names-12.0.0/twisted/plugins/twisted_names.py 2011-02-14 04:45:15.000000000 +0000 @@ -1,4 +1,4 @@ -# Copyright (c) 2001-2008 Twisted Matrix Laboratories. +# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. from twisted.application.service import ServiceMaker