Index: doc/names/examples/index.xhtml
===================================================================
--- doc/names/examples/index.xhtml	(revision 34065)
+++ doc/names/examples/index.xhtml	(working copy)
@@ -15,6 +15,7 @@
         <li><a href="testdns.py">testdns.py</a> - Prints the results of an Address record lookup, Mail-Exchanger record lookup, and Nameserver record lookup for the given hostname for a given hostname.</li>
         <li><a href="dns-service.py">dns-service.py</a> - Searches for SRV records in DNS.</li>
         <li><a href="gethostbyname.py">gethostbyname.py</a> - Returns the IP address for a given hostname.</li>
+        <li><a href="dnssecTest.py">dnssecTest.py</a></li>
     </ul>
 </body>
 </html>
Index: doc/names/examples/testdnssec.py
===================================================================
--- doc/names/examples/testdnssec.py	(revision 0)
+++ doc/names/examples/testdnssec.py	(working copy)
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Sample app to lookup DS records and show a DNSSEC result
+"""
+
+import sys
+from twisted.names.common import DnssecConfig
+from twisted.names.client import Resolver
+from twisted.names import client
+from twisted.internet import reactor
+
+def deferredAnswer(result):
+    #expect - a 4-tuple with a couple of DS RR's and an RRSIG RR in the answer.
+    print "    Answer: %s\n    Authority: %s\n    Additional: %s\n    Message: %s" % result
+    
+    #expect - the AD bit to be set
+    message = result[3]
+    authData = not not message.authData
+    print "expect authData to be True: %s" % authData
+    
+    reactor.stop()
+
+def errAnswer(error):
+    print "Error: %s" % error
+    reactor.stop()
+
+def main():
+
+    reactor.callWhenRunning(doLookup)
+    reactor.run()
+
+def doLookup():
+
+    #DnssecConfig - set ednsEnabled True if dnssecOk is True or you won't get
+    # a DNSSEC result - since DO is in the EDNS OPT record.
+    #For efficiency, set maxUdpPktSz large enough to get a DNSSEC message 
+	# without falling back to TCP but not so large that a network with small
+    # MTU fragments packets.  1492 is generally absolutely safe.  
+	# 4096 usually works. It depends on the network.	
+    dsc = DnssecConfig(ednsEnabled=True, maxUdpPktSz=4096, dnssecOk=True, chkDis=False)
+
+    #setup a DNSSEC resolver using a public validating resolver 
+    # and set it as the client's theResolver
+    resolver = Resolver(servers=[('149.20.64.20', 53)], dnssecConfig=dsc)
+    client.theResolver = resolver
+
+    #query for DS records for comcast.net 
+    client.lookupDS('comcast.net')\
+        .addCallback(deferredAnswer)\
+        .addErrback(errAnswer)
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
Index: doc/names/howto/names.xhtml
===================================================================
--- doc/names/howto/names.xhtml	(revision 34065)
+++ doc/names/howto/names.xhtml	(working copy)
@@ -49,4 +49,12 @@
 directives are not yet supported.
 </p>
 
+<h2>Using DNS Security Extensions (DNSSEC)</h2>
+
+<p>DNSSEC adds data origin authentication and data integrity to the Domain Name System (see <a href="http://www.ietf.org/rfc/rfc4033.txt">RFC4033</a>.) To use DNSSEC requires EDNS0, because to enable DNSSEC requires setting the DNSSEC OK (DO) bit which is in the EDNS0 OPT Record. <a href="http://www.ietf.org/rfc/rfc4035.txt"> RFC4035</a> discusses how a non-validating security aware resolver (which is what twisted.names is with the DNSSEC change) should handle the DO, CD and AD bits.
+</p>
+
+<p><a href="../../../doc/names/examples/dnssecTest.py" class="py-listings">dnssecTest.py</a> is an example of using DNSSEC to check the AD bit of a domain that should validate.
+</p>
+
 </body></html>
Index: twisted/names/client.py
===================================================================
--- twisted/names/client.py	(revision 34065)
+++ twisted/names/client.py	(working copy)
@@ -71,7 +71,12 @@
     protocol = property(_getProtocol)
 
 
-    def __init__(self, resolv=None, servers=None, timeout=(1, 3, 11, 45), reactor=None):
+    def __init__(self,
+                 resolv=None,
+                 servers=None,
+                 timeout=(1, 3, 11, 45),
+                 reactor=None,
+                 dnssecConfig=None):
         """
         Construct a resolver which will query domain name servers listed in
         the C{resolv.conf(5)}-format file given by C{resolv} as well as
@@ -99,9 +104,11 @@
             for DNS datagrams, and enforce timeouts.  If not provided, the
             global reactor will be used.
 
+        @param dnssecConfig: An L{DnssecConfig} - see common.DnssecConfig()
+
         @raise ValueError: Raised if no nameserver addresses can be found.
         """
-        common.ResolverBase.__init__(self)
+        common.ResolverBase.__init__(self, dnssecConfig)
 
         if reactor is None:
             from twisted.internet import reactor
@@ -381,9 +388,19 @@
             return self.queryTCP(message.queries).addCallback(self.filterAnswers)
         if message.rCode != dns.OK:
             return failure.Failure(self.exceptionForCode(message.rCode)(message))
-        return (message.answers, message.authority, message.additional)
 
+        # if dnssecOk is enabled, return a reference to the message as the
+        # 4th member of the tuple, so the caller can access the message header
+        # flags. There are more flags than just AD (authentic Data) that are
+        # set in a response that may be of interest (e.g., Truncate-TC).
+        if self.dnssecConfig.dnssecOk:
+            return (message.answers, message.authority,
+                    message.additional, message)
+        else:
+            #dnssecOk was not requested - return a legacy 3-tuple
+            return (message.answers, message.authority, message.additional)
 
+
     def _lookup(self, name, cls, type, timeout):
         """
         Build a L{dns.Query} for the given parameters and dispatch it via UDP.
@@ -953,3 +970,111 @@
     @rtype: C{Deferred}
     """
     return getResolver().lookupNamingAuthorityPointer(name, timeout)
+
+
+def lookupDNSKey(name, timeout=None):
+    """
+    DNSKEY 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupDNSKey(name, timeout)
+
+
+def lookupDS(name, timeout=None):
+    """
+    DS 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupDS(name, timeout)
+
+
+def lookupNSEC(name, timeout=None):
+    """
+    NSEC 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupNSEC(name, timeout)
+
+
+def lookupNSEC3(name, timeout=None):
+    """
+    NSEC3 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupNSEC3(name, timeout)
+
+
+def lookupNSEC3Param(name, timeout=None):
+    """
+    NSEC3 Param 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupNSEC3Param(name, timeout)
+
+
+def lookupRRSIG(name, timeout=None):
+    """
+    RRSIG 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}
+    
+    @since: 12.1
+    """
+    return getResolver().lookupRRSIG(name, timeout)
Index: twisted/names/common.py
===================================================================
--- twisted/names/common.py	(revision 34065)
+++ twisted/names/common.py	(working copy)
@@ -18,6 +18,74 @@
 
 EMPTY_RESULT = (), (), ()
 
+
+
+class DnssecConfig():
+    """
+    Sets recDes and DNSSEC parameters. See the following RFC's for details:
+    
+        U{RFC 4033: DNS Security Introduction and Requirements
+            <http://www.ietf.org/rfc/rfc4033.txt>}
+    
+        U{RFC 4034: Resource Records for the DNS Security Extensions
+            <http://www.ietf.org/rfc/rfc4034.txt>}
+    
+        U{RFC 4035: Protocol Modifications for the DNS Security Extensions
+            <http://www.ietf.org/rfc/rfc4035.txt>}
+
+    @type recDes: C{bool}
+    @ivar recDes: Recursion Desired (RD flag). Not a DNSSEC parameter and does
+        not require L{ednsEnabled}, but still nice to be able to control 
+        whether or not you're asking for recursion.
+
+    @type ednsEnabled: C{bool}
+    @ivar ednsEnabled: If True, adds an OPT record to the query. 
+        The OPT record contains a version field (see L{version}) that 
+        indicates the version of EDNS that you can handle.
+
+    @type maxUdpPktSz: C{int}
+    @ivar maxUdpPktSz: The max size UDP packet (bytes) that your end-to-end
+        network can handle. On a modern network a reliable size is 1492,
+        although up to 65535 is possible. Requires L{ednsEnabled} to be True.
+
+    @type dnssecOK: C{bool}
+    @ivar dnssecOK: Dnssec Ok (DO flag). If True, sets a flag that indicates 
+        you want DNSSEC RR's and validation if the resolver validates. If DO 
+        is set, AD will be set in the response if the answer validates.
+        Requires L{ednsEnabled}.
+
+    @type chkDis: C{bool}
+    @ivar chkDis: Checking Disabled (CD flag). If dnssecOk and chkDis are both
+        True, a validating resolver won't do validation but will return the
+        DNSSEC RR's so that YOU can. Requires L{ednsEnabled}
+
+    @type version: C{int}
+    @ivar version: Sets the EDNS version level. Currently, only version 0 is
+        defined and supported by U{RFC 2671: Extension Mechanisms for DNS 
+        (EDNS0)<http://www.ietf.org/rfc/rfc2671.txt>}      
+                    
+    @since: 12.1
+    """
+    def __init__(self,
+                 recDes=True,
+                 ednsEnabled=False,
+                 maxUdpPktSz=512,
+                 dnssecOk=False,
+                 chkDis=False,
+                 version=0):
+
+        self.recDes = recDes
+        self.ednsEnabled = ednsEnabled
+        self.maxUdpPktSz = maxUdpPktSz
+        self.dnssecOk = dnssecOk
+        self.chkDis = chkDis
+        self.version = version
+
+        assert not self.ednsEnabled or 512 <= self.maxUdpPktSz <= 65535
+        assert version == 0
+
+
+
 class ResolverBase:
     """
     L{ResolverBase} is a base class for L{IResolver} implementations which
@@ -36,12 +104,14 @@
 
     typeToMethod = None
 
-    def __init__(self):
+    def __init__(self, dnssecConfig=None):
         self.typeToMethod = {}
         for (k, v) in typeToMethod.items():
             self.typeToMethod[k] = getattr(self, v)
+        self.dnssecConfig = dnssecConfig
+        if self.dnssecConfig == None:
+            self.dnssecConfig = DnssecConfig()
 
-
     def exceptionForCode(self, responseCode):
         """
         Convert a response code (one of the possible values of
@@ -201,6 +271,61 @@
         """
         return self._lookup(name, dns.IN, dns.ALL_RECORDS, timeout)
 
+
+    def lookupDNSKey(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupDNSKey
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.DNSKEY, timeout)
+
+
+    def lookupDS(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupDS
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.DS, timeout)
+
+
+    def lookupNSEC(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.NSEC, timeout)
+
+
+    def lookupNSEC3(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC3
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.NSEC3, timeout)
+
+
+    def lookupNSEC3Param(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC3Param
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.NSEC3PARAM, timeout)
+
+
+    def lookupRRSIG(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupRRSIG
+        
+        @since: 12.1
+        """
+        return self._lookup(name, dns.IN, dns.RRSIG, timeout)
+
+
     def getHostByName(self, name, timeout = None, effort = 10):
         """
         @see: twisted.names.client.getHostByName
@@ -268,6 +393,12 @@
     dns.MX:    'lookupMailExchange',
     dns.TXT:   'lookupText',
     dns.SPF:   'lookupSenderPolicy',
+    dns.DNSKEY:'lookupDNSKey',
+    dns.DS:    'lookupDS',
+    dns.NSEC:  'lookupNSEC',
+    dns.NSEC3: 'lookupNSEC3',
+    dns.NSEC3PARAM: 'lookupNSEC3Param',
+    dns.RRSIG: 'lookupRRSIG',
 
     dns.RP:    'lookupResponsibility',
     dns.AFSDB: 'lookupAFSDatabase',
Index: twisted/names/dns.py
===================================================================
--- twisted/names/dns.py	(revision 34065)
+++ twisted/names/dns.py	(working copy)
@@ -15,9 +15,10 @@
 __all__ = [
     'IEncodable', 'IRecord',
 
-    'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'HINFO',
+    'A', 'A6', 'AAAA', 'AFSDB', 'CNAME', 'DNAME', 'DNSKEY', 'DS', 'HINFO',
     'MAILA', 'MAILB', 'MB', 'MD', 'MF', 'MG', 'MINFO', 'MR', 'MX',
-    'NAPTR', 'NS', 'NULL', 'PTR', 'RP', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS',
+    'NAPTR', 'NS', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'NULL', 'OPT', 'PTR',
+    'RP', 'RRSIG', 'SOA', 'SPF', 'SRV', 'TXT', 'WKS',
 
     'ANY', 'CH', 'CS', 'HS', 'IN',
 
@@ -26,15 +27,17 @@
     '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_DNAME', 'Record_DNSKEY', 'Record_DS', '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',
+    'Record_NS', 'Record_NSEC', 'Record_NSEC3', 'Record_NULL', 'Record_OPT',
+    'Record_PTR', 'Record_RP', 'Record_RRSIG', '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',
+    'Charstr', 'Message', 'Name', 'OPTHeader', 'Query', 'RRHeader',
+    'SimpleRecord','DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol',
 
     'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE',
     'PORT',
@@ -46,6 +49,7 @@
 # System imports
 import warnings
 
+import re
 import struct, random, types, socket
 
 import cStringIO as StringIO
@@ -54,6 +58,7 @@
 
 from zope.interface import implements, Interface, Attribute
 
+from base64 import b64decode, b64encode
 
 # Twisted imports
 from twisted.internet import protocol, defer
@@ -61,6 +66,7 @@
 from twisted.python import log, failure
 from twisted.python import util as tputil
 from twisted.python import randbytes
+from twisted.names.ser_num_arith import SNA, DateSNA
 
 
 def randomSource():
@@ -79,6 +85,13 @@
 NAPTR = 35
 A6 = 38
 DNAME = 39
+OPT = 41
+DS = 43
+RRSIG = 46
+NSEC = 47
+DNSKEY = 48
+NSEC3 = 50
+NSEC3PARAM = 51
 SPF = 99
 
 QUERY_TYPES = {
@@ -108,7 +121,14 @@
     NAPTR: 'NAPTR',
     A6: 'A6',
     DNAME: 'DNAME',
-    SPF: 'SPF'
+    OPT: 'OPT',
+    DS: 'DS',
+    RRSIG: 'RRSIG',
+    NSEC: 'NSEC',
+    DNSKEY: 'DNSKEY',
+    NSEC3: 'NSEC3',
+    NSEC3PARAM: 'NSEC3PARAM',
+    SPF: 'SPF',
 }
 
 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
@@ -371,6 +391,196 @@
     def __str__(self):
         return self.name
 
+class Sigstr(object):
+    """
+    for signatures and keys. display as b64 encoded
+    
+    @since: 12.1
+    """
+    implements(IEncodable)
+
+    def __init__(self, string=''):
+        if not isinstance(string, str):
+            raise ValueError("%r is not a string" % (string, ))
+        self.string = string # b64encoded string
+
+    def encode(self, strio, compDict=None):
+        """
+        Write the byte representation (the un-b64-encoded string)
+        to the file.
+
+        @type strio: file
+        @param srio: The byte representation of this signature or key
+        will be written to this file
+
+        @type compDict: dict
+        @param compDict: not used.
+        """
+        strio.write(b64decode(self.string))
+
+    def decode(self, strio, length=None):
+        """
+        Decode a signature or a key.
+
+        @type strio: file
+        @param strio: Exactly length bytes will be read from this file
+        to decode the full signature or key
+
+        @type length: int
+        @param lenth: length must always be given. A signature or key
+        is always the last thing in an RR and so you can always determine
+        its length.
+        """
+        self.string = ''
+        if length == None:
+            return
+
+        assert isinstance(length, int)
+        buff = readPrecisely(strio, length)
+        self.string = b64encode(buff)
+
+    def __eq__(self, other):
+        if isinstance(other, Sigstr):
+            return self.string == other.string
+        return False
+
+    def __hash__(self):
+        return hash(self.string)
+
+    def __str__(self):
+        return self.string
+
+
+class TypeBitmaps(object):
+    """
+    bitmap encoding scheme used by NSEC and NSEC3 RR's
+    to indicate the RRset types that exist at the
+    NSEC/NSEC3 RR's original owner name or hashed name.
+
+    See the following RFC's for details: 
+    
+        U{RFC 4034: Resource Records for the DNS Security Extensions
+            <http://www.ietf.org/rfc/rfc4034.txt>}
+
+        U{RFC 5155: DNS Security (DNSSEC) Hashed Authenticated Denial of
+            Existence <http://tools.ietf.org/rfc/rfc5155.txt>}
+    
+    @since: 12.1
+    """
+    fmt = 'BB'
+    typeRegex = re.compile('TYPE(\d+)')
+
+    def __init__(self, string=''):
+        self.string = string
+
+    def encode(self, strio, compDict=None):
+        """
+        Encode the string field, which consists of a set
+        of type names, into an NSEC/NSEC3 type bitmap.
+
+        @type strio: file
+        @param strio: the byte representation of the type bitmap
+        will be written to this file.
+
+        @type compDict: dict
+        @param compDict: not used.
+        """
+        if not self.string:
+            return;
+
+        # get a sorted list of RR Type Values
+        mnus = self.string.split(' ')
+        mnuVals = []
+        for mnu in mnus:
+            mnuVal = REV_TYPES.get(mnu, None)
+            if not mnuVal:
+                m = self.typeRegex.match(mnu)
+                if m.groups():
+                    mnuVal = int(m.group(1))
+                    assert mnuVal < 65536
+                else:
+                    log.err("can't parse %s in %s" % (mnu, self.string, ))
+                    continue;
+            mnuVals.append(mnuVal)
+        mnuVals.sort()
+
+        # convert that to a dict of windows and lists
+        windDict = {}
+        for v in mnuVals:
+            window = (v >> 8) & 0xFF
+            if window not in windDict:
+                windDict[window] = []
+            windDict[window].append(v & 0xFF)
+
+        # have to sort the keys - they're not in order!
+        windows = windDict.keys()
+        windows.sort()
+
+        # create the bitmaps
+        bmap = bytearray()
+        for w in windows:
+            bmapseg = bytearray(32)
+            maxoff = 0
+            for v in windDict[w]:
+                vm1 = v - 1
+                off = vm1 >> 3
+                bit = vm1 & 0x7
+                msk = 1 << bit
+                bmapseg[off] |= msk
+                maxoff = max(off, maxoff)
+            bmapseg = bmapseg[0:maxoff+1]
+            bmap += chr(w) + chr(maxoff+1) + bmapseg
+
+        strio.write(str(bmap))
+
+    def decode(self, strio, length=None):
+        """
+        Decode an NSEC/NSEC3 type bitmap into a string
+        representation of type names.
+        """
+        self.type_bitmaps = ""
+        if length == None:
+            return
+
+        type_bitmaps = bytearray()
+        l = struct.calcsize(self.fmt)
+        parsed_length = 0
+        while parsed_length < length:
+            buff = readPrecisely(strio, l)
+            wb_num, bm_len = struct.unpack(self.fmt, buff)
+            assert parsed_length + 2 + bm_len <= length
+            bm = readPrecisely(strio, bm_len)
+            byteNum = -1
+            for b in bm:
+                byteNum += 1
+                ob = ord(b)
+                if ob == 0:
+                    continue
+
+                for v in range(8):
+                    msk = 1<<v
+                    if ob & msk:
+                        val = wb_num*256 + byteNum*8 + v + 1
+                        mnu = QUERY_TYPES.get(val, None)
+                        if not mnu:
+                            mnu = 'TYPE' + str(val)
+                        type_bitmaps += (mnu + ' ')
+
+            parsed_length += 2 + bm_len
+
+        self.type_bitmaps = str(type_bitmaps[0:-1])
+
+    def __eq__(self, other):
+        if isinstance(other, TypeBitmaps):
+            return self.string == other.string
+        return False
+
+    def __hash__(self):
+        return hash(self.string)
+
+    def __str__(self):
+        return self.string
+
 class Query:
     """
     Represent a single DNS query.
@@ -434,8 +644,129 @@
         return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
 
 
-class RRHeader(tputil.FancyEqMixin):
+
+class OPTHeader(tputil.FancyEqMixin):
     """
+    A OPT record header.
+
+    @cvar fmt: C{str} specifying the byte format of an OPT Header.
+
+    @ivar name: Root (0, 8-bits)
+    @ivar type: 41 (OPT Record)
+    @ivar payload: An object that implements the IEncodable interface
+    @ivar auth: Whether this header is authoritative or not.
+    
+    @since: 12.1
+    """
+
+    implements(IEncodable)
+
+    compareAttributes = ('name', 'type', 'payload', 'auth')
+
+    fmt = "!H"
+
+    name = None
+    type = None
+    payload = None
+
+    # OPTHeader _really_ has no ttl or rdlength, but the
+    # existence of the attributes is required.
+    ttl = None
+    rdlength = None
+
+    cachedResponse = None
+
+
+    def __init__(self, payload=None, auth=False):
+        """
+        @type name: C{str}
+        @param name: Root (0)
+
+        @type type: C{int}
+        @param type: Query type 41.
+
+        @type payload: An object implementing C{IEncodable}
+        @param payload: The OPT payload
+        """
+        assert (payload is None) or (payload.TYPE == OPT)
+
+        self.name = 0
+        self.type = OPT
+        self.payload = payload
+        self.auth = auth
+
+
+    def encode(self, strio, compDict=None):
+        """
+        Encode this OPT record header into proper format
+        
+        @type strio: file
+        @param strio: the byte representation of this OPTHeader will be written
+        to this file.
+        
+        @type compDict: dict
+        @param compDict: not used.
+        """
+        strio.write(struct.pack('!B', 0))
+        strio.write(struct.pack(self.fmt, self.type))
+        if self.payload:
+            prefix = strio.tell()
+            self.payload.encode(strio, compDict)
+            aft = strio.tell()
+            strio.seek(prefix - 2, 0)
+            strio.write(struct.pack('!H', aft - prefix))
+            strio.seek(aft, 0)
+
+
+    def decode(self, strio, length = None):
+        """
+        Decode a byte string into this OPTHeader.
+
+        @type strio: file
+        @param strio: Bytes will be read from this file until the full 
+        OPTHeader is decoded.
+        """
+        self.name.decode(strio)
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.type = r[0]
+
+
+    def isAuthoritative(self):
+        return self.auth
+
+
+    def __str__(self):
+        return '<OPT auth=%s>' % (self.auth and 'True' or 'False')
+
+
+    @classmethod
+    def _headerFactory(cls, strio, auth=False):
+        """
+        reads enough of the stream to figure out if what is there is
+        an OPTHeader or an RRHeader
+        """
+        beginPos = strio.tell()
+        name = Name()
+        name.decode(strio)
+        type = struct.unpack(cls.fmt, readPrecisely(strio, 2))[0]
+
+        if len(name.name) == 0 and type == OPT:
+            return cls()
+        else:
+            # back up to the beginning and try again
+            strio.seek(beginPos, 0)
+            rrh = RRHeader(auth=auth)
+            rrh.decode(strio)
+            return rrh
+
+    __repr__ = __str__
+
+
+
+class RRHeader(OPTHeader):
+    """
     A resource record header.
 
     @cvar fmt: C{str} specifying the byte format of an RR.
@@ -445,9 +776,7 @@
     @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: A C{bool} indicating whether this C{RRHeader} was parsed from an
-        authoritative message.
+    @ivar auth: Whether this header is authoritative or not.
     """
 
     implements(IEncodable)
@@ -1050,7 +1379,8 @@
 
     fancybasename = 'SRV'
     compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
-    showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
+    showAttributes = ('priority', 'weight',
+                      ('target', 'target', '%s'), 'port', 'ttl')
 
     def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
         self.priority = int(priority)
@@ -1464,6 +1794,548 @@
 
 
 
+class Record_OPT(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    EDNS0 Option record.
+
+    @type payload_size: C{int}
+    @ivar payload_size: Specifies the max UDP Packet size (bytes) that your
+        network can handle.
+
+    @type dnssecOk: C{bool}
+    @ivar dnssecOk: Requests the server to send DNSSEC RRs and to do DNSSEC
+        validation (and set the AD bit if the response validates).
+
+    @type version: C{int}
+    @ivar version: The version of DNSSEC used. Currently only version 0
+        is defined.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = OPT
+    fmt = '!HBBHH'
+
+    fancybasename = 'OPT'
+    showAttributes = ('payload_size', ('flags', 'flags', '0x%x'), 'version')
+    compareAttributes = ('payload_size', 'flags', 'version')
+
+
+    def __init__(self, payload_size=512, dnssecOk=0, version=0, ttl=None):
+        self.payload_size = payload_size
+        self.version = version
+        self.flags = (dnssecOk & 1) << 15
+
+
+    def encode(self, strio, compDict = None):
+        OPTHeader().encode(strio)
+        strio.write(struct.pack('!H', self.payload_size))
+        strio.write(struct.pack('!B', 0)) # high order 0
+        strio.write(struct.pack('!B', self.version))
+        strio.write(struct.pack('!H', self.flags)) # DO(bit) + Z's
+        strio.write(struct.pack('!H', 0)) # Data length: 0
+
+
+    def decode(self, strio, length=None):
+        """
+        are OPT Records always 0 rdlength?
+        """
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.payload_size, z, self.version, self.flags, length = r
+        assert length == 0
+
+
+    def __hash__(self):
+        return hash((self.payload_size, self.version, self.flags))
+
+
+
+class Record_RRSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    DNSSEC RRSIG record.  See RFC 4034 for details.
+
+    @type type_covered: C{int}
+    @ivar type_covered: Identifies the type of the RRset that this RRSIG covers.
+
+    @type algo: C{int}
+    @ivar algo: Identifies the crypto algorithm type used to create the
+        signature.
+        (5 - RSH/SHA-1 is mandatory. See RFC 4034 App A.1 for the full list.)
+
+    @type labels: C{int}
+    @ivar labels: Specifies the number of labels in the original RRSIG RR
+        owner name. A validator can use this to determine whether the answer
+        was synthesized from a wildcard.
+
+    @type original_ttl: C{int}
+    @ivar original_ttl: Specifies the TTL of the covered RRset as it appears
+        in the authoritative zone.
+
+    @type sig_expiration: C{int}
+    @ivar sig_expiration: This RRSIG must NOT be used after this time. Seconds
+        since 1/1/1970, Modulo 2**32, compare using DNS Serial Number Arithmetic
+
+    @type sig_inception: C{int}
+    @ivar sig_inception: This RRSIG must NOT be used prior to this time. Seconds
+        since 1/1/1970, Modulo 2**32, compare using DNS Serial Number Arithmetic
+
+    @type key_tag: C{int}
+    @ivar key_tag: Contains the key tag value of the DNSKEY RR that validates
+        this signature, in network byte order. See RFC 4034 App B.
+
+    @type signers_name: L{Name}
+    @ivar signers_name: Identifies the owner name of the DNSKEY RR that a
+        validator should use to validate this signature. Must not use DNS
+        name compression.
+
+    @type signature: L{Sigstr}
+    @ivar signature: Contains the cryptographic signature that covers the RRSIG
+        RDATA (excluding the Signature field and the RRset specified by the
+        RRSIG owner name, RRSIG class and RRSIG Type Covered fields.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = RRSIG
+    fmt = '!HBBIIIH'
+
+    fancybasename = 'RRSIG'
+    showAttributes = ('type_covered', 'algo', 'labels', 'original_ttl',
+                         ('_sig_expiration', 'sig_expiration', '%s'),
+                         ('_sig_inception', 'sig_inception', '%s'),
+                         'key_tag',
+                         ('signers_name', 'signers_name', '%s'),
+                         ('_signature', 'signature', '%s'), 'ttl')
+    compareAttributes = ('type_covered', 'algo', 'labels', 'original_ttl',
+                         'sig_expiration', 'sig_inception', 'key_tag',
+                         'signers_name', 'signature', 'ttl')
+
+    _sig_expiration = property(lambda self: str(self.sig_expiration))
+    _sig_inception = property(lambda self: str(self.sig_inception))
+    _signature = property(lambda self: self.signature.string)
+
+
+    def __init__(self, type_covered=A, algo=0, labels=0, original_ttl=0,
+                 sig_expiration='', sig_inception='', key_tag=0,
+                 signers_name='', signature='', ttl=None):
+        self.type_covered = type_covered
+        self.algo = algo
+        self.labels = labels
+        self.original_ttl = original_ttl
+        self.sig_expiration = DateSNA(sig_expiration)
+        self.sig_inception = DateSNA(sig_inception)
+        self.key_tag = key_tag
+        self.signers_name = Name(signers_name)
+        self.signature = Sigstr(signature)
+        self.ttl = str2time(ttl)
+
+
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt,
+                                self.type_covered,
+                                self.algo,
+                                self.labels,
+                                self.original_ttl,
+                                self.sig_expiration.asInt(),
+                                self.sig_inception.asInt(),
+                                self.key_tag))
+        self.signers_name.encode(strio, None)
+        self.signature.encode(strio, compDict)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.type_covered, self.algo, self.labels, self.original_ttl, \
+            sig_expiration, sig_inception, self.key_tag = r
+        self.sig_expiration = DateSNA.fromInt(sig_expiration)
+        self.sig_inception = DateSNA.fromInt(sig_inception)
+        self.signers_name.decode(strio)
+        here = strio.tell()
+        self.signature.decode(strio, length + start - here if length else None)
+
+
+    def __hash__(self):
+        return hash((self.type_covered, self.algo, self.labels, self.original_ttl,
+                     self.sig_expiration, self.sig_inception, self.key_tag,
+                     self.signers_name, self.signature))
+
+
+
+class Record_DS(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC DS record.
+
+    @type key_tag: C{int}
+    @ivar key_tag: Lists the key tag of the DNSKEY RR referred to by this DS
+        record.
+
+    @type algo: C{int}
+    @ivar algo: Lists the algorithm number of the DNSKEY RR referenced by this
+        DS record.
+
+    @type digest_type: C{int}
+    @ivar digest_type: Identifies the algorithm used to construct the digest
+        field.
+
+    @type digest: L{Sigstr}
+    @ivar digest: Contains a digest of the refeerenced DNSKEY RR calculated by
+        the algorithm identified by the digest_type field.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = DS
+    fmt = '!HBB'
+
+    fancybasename = 'DS'
+    showAttributes = ('key_tag', 'algo', 'digest_type',
+                      ('_digest', 'digest', '%s'), 'ttl')
+    compareAttributes = ('key_tag', 'algo', 'digest_type', 'digest', 'ttl')
+
+    _digest = property(lambda self: self.digest.string)
+
+
+    def __init__(self, key_tag=0, algo=0, digest_type=0, digest='', ttl=None):
+        self.key_tag = key_tag
+        self.algo = algo
+        self.digest_type = digest_type
+        self.digest = Sigstr(digest)
+        self.ttl = str2time(ttl)
+
+
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt,
+                                self.key_tag,
+                                self.algo,
+                                self.digest_type))
+        self.digest.encode(strio, None)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.key_tag, self.algo, self.digest_type = r
+        here = strio.tell()
+        self.digest.decode(strio, length + start - here if length else None)
+
+
+    def __hash__(self):
+        return hash((self.key_tag, self.algo, self.digest_type, self.digest))
+
+
+
+class Record_DNSKEY(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC DNSKEY record. Holds the public key for a signed RRset.
+
+    @type flags: C{int}
+    @ivar flags: Bit 7 is the Zone Key flag. If bit 7 has value 1, then the
+        DNSKEY record holds a DNS zone key and the DNSKEY RR's owner name is
+        the name of a zone.  If bit 7 has value 0, then the DNSKEY record
+        holds some other type of DNS public key and MUST NOT be used to
+        verify RRSIGs that cover RRsets.
+        Bit 15 is the Secure Entry Point flag. See RFC 3757.)
+        All other bits are reserved and must be zero.
+
+    @type protocol: C{int}
+    @ivar protocol: Must have value 3. The DNSKEY RR must be treated as invalid
+        if this field does not contain 3.
+
+    @type algo: C{int}
+    @ivar algo: Identifies the public key's cryptographic algorithm and
+        determines the format of the pub_key field.  See RFC 4034 App A.
+
+    @type pub_key: L{Sigstr}
+    @ivar pub_key: Holds the public key material.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = DNSKEY
+    fmt = '!HBB'
+
+    fancybasename = 'DNSKEY'
+    showAttributes = ('flags', 'protocol', 'algo',
+                      ('_pub_key', 'pub_key', '%s'), 'ttl')
+    compareAttributes = ('flags', 'protocol', 'algo', 'pub_key', 'ttl')
+
+    _pub_key = property(lambda self: self.pub_key.string)
+
+
+    def __init__(self, flags=0, protocol=0, algo=0, pub_key='', ttl=None):
+        self.flags = flags
+        self.protocol = protocol
+        self.algo = algo
+        self.pub_key = Sigstr(pub_key)
+        self.ttl = str2time(ttl)
+
+
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt,
+                                self.flags,
+                                self.protocol,
+                                self.algo))
+        self.pub_key.encode(strio, None)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.flags, self.protocol, self.algo = r
+        here = strio.tell()
+        self.pub_key.decode(strio, length + start - here if length else None)
+
+
+    def __hash__(self):
+        return hash((self.flags, self.protocol, self.algo, self.pub_key))
+
+
+
+class Record_NSEC(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC NSEC record provides authenticated denial of existance for DNS
+    data.
+
+    A DNSSEC NSEC record lists:
+
+        1) the next owner name in canonical ordering of the zone that contains
+           authoritative data or a delegation point NS RRset.
+
+        2) the set of RR types present at the NSEC RR's owner name.
+
+    @type nxt_name: L{Name}
+    @ivar nxt_name: The next owner name that has authoritative data or contains
+        a delegation point NS RRset.
+
+    @type type_bitmaps: L{TypeBitmaps}
+    @ivar type_bitmaps: Identifies the RRset types that exist at the NSEC RR's
+        owner name.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC
+
+    fancybasename = 'NSEC'
+    showAttributes = (('nxt_name', 'nxt_name', '%s'),
+                      ('_type_bitmaps', 'type_bitmaps', '%s'), 'ttl')
+    compareAttributes = ('nxt_name', 'type_bitmaps', 'ttl')
+
+    _type_bitmaps = property(lambda self: self.type_bitmaps.string)
+
+
+    def __init__(self, nxt_name='', type_bitmaps=None, ttl=None):
+        self.nxt_name = Name(nxt_name)
+        self.type_bitmaps = TypeBitmaps(type_bitmaps)
+        self.ttl = str2time(ttl)
+
+
+    def encode(self, strio, compDict = None):
+        self.nxt_name.encode(strio, None)
+        self.type_bitmaps.encode(strio, None)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        self.nxt_name.decode(strio, None)
+        here = strio.tell()
+        self.type_bitmaps.decode(strio, length + start - here if length
+                                 else None)
+
+
+    def __hash__(self):
+        return hash((self.nxt_name, self.type_bitmaps))
+
+
+
+class Record_NSEC3PARAM(tputil.FancyEqMixin, tputil.FancyStrMixin):
+    """
+    A DNSSEC NSEC3PARAM record contains the NSEC3 parameters (hash algorithm,
+    flags, iterations and salt) needed by authoritative servers to calculate
+    hashed owner names.  The presence of an NSEC3PARAM RR at a zone apex
+    indicates that the specified parameters may be used by authoritative
+    servers to choose an appropriate set of NSEC3 RRs for negative responses.
+
+    @type hash_algo: C{int}
+    @ivar hash_algo: Identifies the cryptographic hash algorithm used to
+        construct the hash value.
+
+    @type flags: C{int}
+    @ivar flags: Identifies 8 1-bit flags. The only flag presently defined is
+        the opt-out flag. If the opt-out flag is set, the NSEC3 record covers
+        zero or more unsigned delegations. If the opt-out flag is clear, the
+        NSEC3 record covers zero unsigned delegations.
+
+    @type iterations: C{int}
+    @ivar iterations: Defines the nubmer of additional times the hash algorithm
+        has been performed.
+
+    @type salt: L{Charset}
+    @ivar salt: Identifies the salt value provided to the hash.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC3
+    fmt = '!BBH'
+
+    fancybasename = 'NSEC3'
+    showAttributes = ('hash_algo', 'flags', 'iterations',
+                      ('_salt', 'salt', '%s'), 'ttl')
+    compareAttributes = ('hash_algo', 'flags', 'iterations', 'salt', 'ttl')
+
+    _salt = property(lambda self: self.salt.string)
+
+
+    def __init__(self, hash_algo=0, flags=0, iterations=0, salt='', ttl=None):
+        self.hash_algo = hash_algo
+        self.flags = flags
+        self.iterations = iterations
+        self.salt = Charstr(salt)
+        self.ttl = str2time(ttl)
+
+
+    def encode(self, strio, compDict = None):
+        strio.write(struct.pack(self.fmt,
+                                self.hash_algo,
+                                self.flags,
+                                self.iterations))
+        self.salt.encode(strio, None)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        l = struct.calcsize(self.fmt)
+        buff = readPrecisely(strio, l)
+        r = struct.unpack(self.fmt, buff)
+        self.hash_algo, self.flags, self.iterations = r
+        self.salt.decode(strio)
+        here = strio.tell()
+
+
+    def __hash__(self):
+        return hash((self.hash_algo, self.flags, self.iterations, self.salt))
+
+
+
+class Record_NSEC3(Record_NSEC3PARAM):
+    """
+    A DNSSEC NSEC3 record provides non-zone-enumerable authenticated denial of
+    existence for DNS data and permits a gradual expansion of delegation-centric
+    zones.
+
+    A DNSSEC NSEC3 record lists:
+
+        1) the set of RR types present at the original owner name of the NSEC
+           RR.
+
+        2) the next hashed owner name in the hash order of the zone.
+
+    @type hash_algo: C{int}
+    @ivar hash_algo: Identifies the cryptographic hash algorithm used to
+        construct the hash value.
+
+    @type flags: C{int}
+    @ivar flags: Identifies 8 1-bit flags. The only flag presently defined
+        is the opt-out flag. If the opt-out flag is set, the NSEC3 record
+        covers zero or more unsigned delegations. If the opt-out flag is
+        clear, the NSEC3 record covers zero unsigned delegations.
+
+    @type iterations: C{int}
+    @ivar iterations: Defines the nubmer of additional times the hash algorithm
+        has been performed.
+
+    @type salt: L{Charset}
+    @ivar salt: Identifies the salt value provided to the hash.
+
+    @type nxt_hash_owner_name: L{Charset}
+    @ivar nxt_hash_owner_name: Contains the next hashed owner name in the zone
+        in hash order.
+
+    @type type_bitmaps: L{TypeBitmaps}
+    @ivar type_bitmaps: Identifies the RRset types that exist at the NSEC3 RR's
+        original owner name.
+
+    @type ttl: C{int}
+    @ivar ttl: The maximum number of seconds which this record should be
+        cached.
+        
+    @since: 12.1
+    """
+    implements(IEncodable, IRecord)
+    TYPE = NSEC3
+    fmt = '!BBH'
+
+    fancybasename = 'NSEC3'
+    showAttributes = ('hash_algo', 'flags', 'iterations',
+                      ('_salt', 'salt', '%s'),
+                      ('nxt_hash_owner_name', 'nxt_hash_owner_name', '%s'),
+                      ('_type_bitmaps', 'type_bitmaps', '%s'), 'ttl')
+    compareAttributes = ('hash_algo', 'flags', 'iterations',
+                         'salt', 'nxt_hash_owner_name', 'type_bitmaps', 'ttl')
+
+    _salt = property(lambda self: self.salt.string)
+    _type_bitmaps = property(lambda self: self.type_bitmaps.string)
+
+
+    def __init__(self, hash_algo=0, flags=0, iterations=0, salt='',
+                 nxt_hash_owner='', type_bitmaps=None, ttl=None):
+        Record_NSEC3PARAM.__init__(self, hash_algo, flags,
+                                   iterations, salt, ttl)
+        self.nxt_hash_owner_name = Charstr(nxt_hash_owner)
+        self.type_bitmaps = TypeBitmaps(type_bitmaps)
+
+
+    def encode(self, strio, compDict = None):
+        Record_NSEC3PARAM.encode(self, strio, compDict)
+        self.nxt_hash_owner_name.encode(strio, None)
+        self.type_bitmaps.encode(strio, None)
+
+
+    def decode(self, strio, length=None):
+        start = strio.tell()
+        Record_NSEC3PARAM.decode(self, strio, compDict)
+        self.nxt_hash_owner_name.decode(strio)
+        here = strio.tell()
+        self.type_bitmaps.decode(strio, length + start - here if length
+                                 else None)
+
+
+    def __hash__(self):
+        return hash((self.hash_algo, self.flags, self.iterations, self.salt,
+                     self.nxt_hash_owner_name, self.type_bitmaps))
+
+
+
 # This is a fallback record
 class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin, object):
     """
@@ -1484,6 +2356,7 @@
     compareAttributes = ('data', 'ttl')
     showAttributes = ('data', 'ttl')
 
+
     def __init__(self, data='', ttl=None):
         self.data = data
         self.ttl = str2time(ttl)
@@ -1540,15 +2413,17 @@
     queries = answers = add = ns = None
 
     def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
-                       auth=0, rCode=OK, trunc=0, maxSize=512):
+                 auth=0, rCode=OK, trunc=0, maxSize=512, authData=0, chkDis=0):
         self.maxSize = maxSize
         self.id = id
         self.answer = answer
         self.opCode = opCode
-        self.auth = auth
-        self.trunc = trunc
-        self.recDes = recDes
-        self.recAv = recAv
+        self.auth = auth            # AA - Authoritative Answer
+        self.trunc = trunc          # TC - TrunCated
+        self.recDes = recDes        # RD - Recursion Desired
+        self.recAv = recAv          # RA - Recursion Available
+        self.authData = authData    # AD - Authentic Data
+        self.chkDis = chkDis        # CD - Checking Disabled
         self.rCode = rCode
         self.queries = []
         self.answers = []
@@ -1588,13 +2463,15 @@
         if self.maxSize and size > self.maxSize:
             self.trunc = 1
             body = body[:self.maxSize - self.headerSize]
-        byte3 = (( ( self.answer & 1 ) << 7 )
-                 | ((self.opCode & 0xf ) << 3 )
-                 | ((self.auth & 1 ) << 2 )
-                 | ((self.trunc & 1 ) << 1 )
-                 | ( self.recDes & 1 ) )
-        byte4 = ( ( (self.recAv & 1 ) << 7 )
-                  | (self.rCode & 0xf ) )
+        byte3 = (((self.answer & 1) << 7)
+                 | ((self.opCode & 0xf) << 3)
+                 | ((self.auth & 1 ) << 2)
+                 | ((self.trunc & 1 ) << 1)
+                 | (self.recDes & 1))
+        byte4 = (((self.recAv & 1) << 7)
+                 | ((self.authData & 1) << 5)
+                 | ((self.chkDis & 1) << 4)
+                 | (self.rCode & 0xf))
 
         strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
                                 len(self.queries), len(self.answers),
@@ -1607,12 +2484,14 @@
         header = readPrecisely(strio, self.headerSize)
         r = struct.unpack(self.headerFmt, header)
         self.id, byte3, byte4, nqueries, nans, nns, nadd = r
-        self.answer = ( byte3 >> 7 ) & 1
-        self.opCode = ( byte3 >> 3 ) & 0xf
-        self.auth = ( byte3 >> 2 ) & 1
-        self.trunc = ( byte3 >> 1 ) & 1
+        self.answer = (byte3 >> 7) & 1
+        self.opCode = (byte3 >> 3) & 0xf
+        self.auth = (byte3 >> 2) & 1
+        self.trunc = (byte3 >> 1) & 1
         self.recDes = byte3 & 1
-        self.recAv = ( byte4 >> 7 ) & 1
+        self.recAv = (byte4 >> 7) & 1
+        self.authData = (byte4 >> 5) & 1
+        self.chkDis = (byte4 >> 4) & 1
         self.rCode = byte4 & 0xf
 
         self.queries = []
@@ -1624,16 +2503,17 @@
                 return
             self.queries.append(q)
 
-        items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
+        items = ((self.answers, nans),
+                 (self.authority, nns),
+                 (self.additional, nadd))
         for (l, n) in items:
             self.parseRecords(l, n, strio)
 
 
     def parseRecords(self, list, num, strio):
         for i in range(num):
-            header = RRHeader(auth=self.auth)
             try:
-                header.decode(strio)
+                header = OPTHeader._headerFactory(strio, auth=self.auth)
             except EOFError:
                 return
             t = self.lookupRecordType(header.type)
@@ -1743,8 +2623,14 @@
             query, or errbacked with any errors that could happen (exceptions
             during writing of the query, timeout errors, ...).
         """
-        m = Message(id, recDes=1)
+        dnssecConfig = self.controller.dnssecConfig
+        chkDis = dnssecConfig.chkDis
+        m = Message(id, recDes=dnssecConfig.recDes, chkDis=chkDis)
         m.queries = queries
+        if dnssecConfig.ednsEnabled:
+            m.additional = [Record_OPT(payload_size = dnssecConfig.maxUdpPktSz,
+                                       version = dnssecConfig.version,
+                                       dnssecOk = dnssecConfig.dnssecOk)]
 
         try:
             writeMessage(m)
@@ -1798,7 +2684,10 @@
         self.transport.write(message.toStr(), address)
 
     def startListening(self):
-        self._reactor.listenUDP(0, self, maxPacketSize=512)
+        maxPacketSize = 512
+        if self.controller.dnssecConfig.ednsEnabled:
+            maxPacketSize = self.controller.dnssecConfig.maxUdpPktSz
+        self._reactor.listenUDP(0, self, maxPacketSize=maxPacketSize)
 
     def datagramReceived(self, data, addr):
         """
Index: twisted/names/secondary.py
===================================================================
--- twisted/names/secondary.py	(revision 34065)
+++ twisted/names/secondary.py	(working copy)
@@ -90,6 +90,9 @@
 
     @ivar _reactor: The reactor to use to perform the zone transfers, or C{None}
         to use the global reactor.
+
+    @ivar dnssecConfig: a L{DnssecConfig} giving the DNSSEC configuration to set
+        on the resolver.
     """
 
     transferring = False
@@ -97,8 +100,8 @@
     _port = 53
     _reactor = None
 
-    def __init__(self, primaryIP, domain):
-        common.ResolverBase.__init__(self)
+    def __init__(self, primaryIP, domain, dnssecConfig=None):
+        common.ResolverBase.__init__(self, dnssecConfig)
         self.primary = primaryIP
         self.domain = domain
 
@@ -150,7 +153,7 @@
 
         return FileAuthority.__dict__['_lookup'](self, name, cls, type, timeout)
 
-    #shouldn't we just subclass? :P
+    # shouldn't we just subclass? :P
 
     lookupZone = FileAuthority.__dict__['lookupZone']
 
@@ -164,7 +167,8 @@
                 r.setdefault(str(rec.name).lower(), []).append(rec.payload)
 
     def _ebZone(self, failure):
-        log.msg("Updating %s from %s failed during zone transfer" % (self.domain, self.primary))
+        log.msg("Updating %s from %s failed during zone transfer"
+                % (self.domain, self.primary))
         log.err(failure)
 
     def update(self):
@@ -175,5 +179,6 @@
 
     def _ebTransferred(self, failure):
         self.transferred = False
-        log.msg("Transferring %s from %s failed after zone transfer" % (self.domain, self.primary))
+        log.msg("Transferring %s from %s failed after zone transfer"
+                % (self.domain, self.primary))
         log.err(failure)
Index: twisted/names/ser_num_arith.py
===================================================================
--- twisted/names/ser_num_arith.py	(revision 0)
+++ twisted/names/ser_num_arith.py	(working copy)
@@ -0,0 +1,189 @@
+# -*- test-case-name: twisted.names.test.test_ser_num_arith -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Serial Number Arithmetic
+
+This module implements RFC 1982 DNS Serial Number Arithmetic
+(see http://tools.ietf.org/pdf/rfc1982.pdf).
+SNA is used in DNS and specifically in DNSSEC as defined in
+RFC 4034 in the DNSSEC Signature Expiration and Inception Fields.
+
+@author: Bob Novas
+
+@since: 12.1
+"""
+
+import calendar, time
+
+
+
+class SNA(object):
+    """
+    implements RFC 1982 - DNS Serial Number Arithmetic
+    """
+    SERIAL_BITS = 32
+    MODULOVAL = 2**SERIAL_BITS
+    HLFRNG = 2**(SERIAL_BITS-1)
+    MAXADD = (2**(SERIAL_BITS-1)-1)
+
+
+    def __init__(self, number):
+        self._number = int(number)%self.MODULOVAL
+
+
+    def __repr__(self):
+        return str(self._number)
+
+
+    def asInt(self):
+        """
+        return an integer representing the object
+        """
+        return self._number
+
+
+    def __eq__(self, sna2):
+        """
+        define the equality operator
+        """
+        return sna2._number == self._number
+
+
+    def __lt__(self, sna2):
+        """
+        define the less than operator
+        """
+        return ((self != sna2) and
+               ((self._number < sna2._number) and
+                ((sna2._number - self._number) < self.HLFRNG) or
+               (self._number > sna2._number) and
+                ((self._number - sna2._number) > self.HLFRNG)))
+
+
+    def __gt__(self, sna2):
+        """
+        define the greater than operator
+        """
+        return ((self != sna2) and
+               ((self._number < sna2._number) and
+               ((sna2._number - self._number) > self.HLFRNG) or
+               (self._number > sna2._number) and
+               ((self._number - sna2._number) < self.HLFRNG)))
+
+
+    def __le__(self, sna2):
+        """
+        define the less than or equal operator
+        """
+        return self == sna2 or self < sna2
+
+
+    def __ge__(self, sna2):
+        """
+        define the greater than or equal operator
+        """
+        return self == sna2 or self > sna2
+
+
+    def __add__(self, sna2):
+        """
+        define the addition operator
+        """
+        if sna2 <= SNA(self.MAXADD):
+            return SNA( (self._number + sna2._number)%self.MODULOVAL )
+        else:
+            raise ArithmeticError
+
+
+    def __hash__(self):
+        """
+        define a hash function
+        """
+        return hash(self._number)
+
+
+
+def max(snaList):
+    """
+    takes a list of sna's from which it will pick the one
+    with the highest value
+    """
+    if len(snaList) == 0:
+        return None
+    trialMax = snaList[0]
+    for s in snaList[1:]:
+        if not trialMax:
+            trialMax = s
+        elif s and s > trialMax:
+            trialMax = s
+    return trialMax
+
+
+
+class DateSNA(SNA):
+    """
+    implements DNS Serial Number Arithmetic
+    for dates 'YYYYMMDDHHMMSS' per RFC 4034 P3.1.5
+    """
+    fmt = '%Y%m%d%H%M%S'
+
+
+    def __init__(self, utcDateTime=''):
+        """
+        accept a UTC date/time string as YYMMDDHHMMSS
+        and convert it to seconds since the epoch
+        """
+        if not utcDateTime:
+            utcDateTime = '19700101000000'
+        dtstruct = time.strptime(utcDateTime, DateSNA.fmt)
+        secondsSinceE = calendar.timegm(dtstruct)
+        super(DateSNA, self).__init__(secondsSinceE)
+
+
+    def __add__(self, sna2):
+        """
+        define the addition operator
+        """
+        if not isinstance(sna2, SNA):
+            return NotImplemented
+
+        if (sna2 <= SNA(self.MAXADD) and
+            (self._number + sna2._number < self.MODULOVAL)):
+            sna = SNA((self._number + sna2._number)%self.MODULOVAL)
+            return DateSNA.fromSNA(sna)
+        else:
+            raise ArithmeticError
+
+
+    def asDate(self):
+        """return a representation of the object as a date string"""
+        dtstruct = time.gmtime(self._number)
+        return time.strftime(DateSNA.fmt, dtstruct)
+
+
+    @classmethod
+    def fromSNA(cls, sna):
+        """
+        create an DateSNA object from an SNA
+        """
+        d = cls()
+        d._number = sna._number
+        return d
+
+
+    @classmethod
+    def fromInt(cls, i):
+        """
+        create an DateSNA object from an int
+        """
+        return cls.fromSNA(SNA(i))
+
+
+    def __str__(self):
+        """
+        return a string representation of the object
+        """
+        return self.asDate()
+
Index: twisted/names/test/test_client.py
===================================================================
--- twisted/names/test/test_client.py	(revision 34065)
+++ twisted/names/test/test_client.py	(working copy)
@@ -8,11 +8,13 @@
 from twisted.names import client, dns
 from twisted.names.error import DNSQueryTimeoutError
 from twisted.trial import unittest
-from twisted.names.common import ResolverBase
+from twisted.names.common import ResolverBase, DnssecConfig
+from twisted.names.dns import Query, Message
 from twisted.internet import defer, error
 from twisted.python import failure
 from twisted.python.deprecate import getWarningMethod, setWarningMethod
 from twisted.python.compat import set
+from twisted.names.test.test_rootresolve import MemoryReactor
 
 
 class FakeResolver(ResolverBase):
@@ -430,7 +432,144 @@
         self.assertNotIn(protocol, resolver.connections)
 
 
+    def _edns0ConfigurationTest(self, reactor, resolver):
+        """
+        A Resolver created with edns0 sends an OPT record as part of the
+        query indicating the maxUdpPktSz and EDNS version supported
+        (only EDNS version 0 - the default - is defined by spec).
 
+        In addition, check that the message flag bits (DO, CD, RC) agree
+        with the flags set in the resolver.
+        
+        @since: 12.1
+        """
+        # make sure EDNS is enabled
+        self.assertTrue(resolver.dnssecConfig.ednsEnabled)
+
+        d = resolver._query(('example.com', 53),
+                            [Query('foo.example.com',dns.A, dns.IN)],
+                            30)
+
+        # A UDP port should have been started
+        portNumber, transport = reactor.udpPorts.popitem()
+
+        # and a DNS packet sent
+        [(packet, address)] = transport._sentPackets
+
+        msg = Message()
+        msg.fromStr(packet)
+
+        # the query should have an additional OPT Header with
+        # version == 0 and payload_size as defined by the resolver's
+        # dnssecConfig and DO should be set
+        self.assertEqual(len(msg.additional), 1)
+        additional = msg.additional[0]
+        payload = additional.payload
+        dnssecConfig = resolver.dnssecConfig
+        self.assertEqual(additional.type, dns.OPT)
+        self.assertEqual(payload.version, dnssecConfig.version)
+        self.assertEqual(payload.payload_size, dnssecConfig.maxUdpPktSz)
+        # payload.flags bit 15 is DO, should equal resolver.dnssecOk
+        self.assertEqual(not not(payload.flags & 0x8000), dnssecConfig.dnssecOk)
+
+        # the rest of the query should be as above also
+        self.assertEqual(msg.queries, [Query('foo.example.com', dns.A, dns.IN)])
+        self.assertEqual(msg.answers, [])
+        self.assertEqual(msg.authority, [])
+
+        # the message header flags should be set as per the resolver's
+        # dnssecConfig settings
+        self.assertEqual(msg.chkDis, dnssecConfig.chkDis)
+        self.assertEqual(msg.recDes, dnssecConfig.recDes)
+
+        response = []
+        d.addCallback(response.append)
+        self.assertEqual(response, [])
+
+        # Once a reply is received, the Deferred should fire.
+        # Make the flag bits in the message agree with what you asked for
+        del msg.queries[:]
+        msg.answer = 1
+        msg.answers.append(dns.RRHeader('foo.example.com',
+                                        payload=dns.Record_A('5.8.13.21')))
+        # if you requested DO, say you got AD
+        msg.authData = dnssecConfig.dnssecOk
+        msg.chkDis = dnssecConfig.chkDis
+        msg.recDes = dnssecConfig.recDes
+        transport._protocol.datagramReceived(msg.toStr(), ('1.1.2.4', 1053))
+        return response[0]
+
+
+    def test_dnssecEnabled(self):
+        """
+        A query sent with DNSSEC Enabled has an additional OPT record with
+        DO set. Such a query returns a 4-tuple as a result, with the 4th
+        member of the tuple being the message with the header bits set as
+        set in the original query.
+        
+        @since: 12.1
+        """
+        # Create a resolver with EDNS0, max packet size = 4096,
+        # and dnssecOk, chkDis and recDes = True
+        maxPacketSize = 4096
+        dsc = DnssecConfig(ednsEnabled=True,
+                           maxUdpPktSz=maxPacketSize,
+                           dnssecOk=True,  # DO
+                           chkDis=True,    # CD
+                           recDes=True)    # RC
+
+        reactor = MemoryReactor()
+        resolver = client.Resolver(servers=[('example.com', 53)],
+                                   reactor=reactor,
+                                   dnssecConfig=dsc)
+
+        message = self._edns0ConfigurationTest(reactor, resolver)
+
+        # check that a resolver with dnssecOk returns a 4-tuple with the
+        # header set as in the query
+        answer, authority, additional, message = resolver.filterAnswers(message)
+        self.assertEqual(answer, [dns.RRHeader('foo.example.com',
+                                  payload=dns.Record_A('5.8.13.21', ttl=0))])
+        self.assertEqual(authority, [])
+        self.assertEqual(additional, [])
+        self.assertIsInstance(message, dns.Message)
+        self.assertTrue(message.authData)
+        self.assertTrue(message.chkDis)
+        self.assertTrue(message.recDes)
+
+
+    def test_dnssecDisabled(self):
+        """
+        A query sent with DNSSEC Disabled but EDNS enabled has an additional
+        OPT record with DO clear. Such a query returns a 3-tuple as a result.
+        """
+
+        # Create a resolver with EDNS0, max packet size = 4096,
+        # and dnssecOk = False, but recDes = True
+        maxPacketSize = 4096
+        dsc = DnssecConfig(ednsEnabled=True,
+                           maxUdpPktSz=maxPacketSize,
+                           dnssecOk=False, # not DO
+                           recDes=True)    # RC
+
+        reactor = MemoryReactor()
+        resolver = client.Resolver(servers=[('example.com', 53)],
+                                   reactor=reactor,
+                                   dnssecConfig=dsc)
+
+        message = self._edns0ConfigurationTest(reactor, resolver)
+
+        # check that a resolver with dnssecOk == False returns a 3-tuple
+        # containing the right stuff.
+        # Note - no access to header info in this case
+        answer, authority, additional = resolver.filterAnswers(message)
+        self.assertEqual(answer,
+                         [dns.RRHeader('foo.example.com',
+                                    payload=dns.Record_A('5.8.13.21', ttl=0))])
+        self.assertEqual(authority, [])
+        self.assertEqual(additional, [])
+
+
 class ClientTestCase(unittest.TestCase):
 
     def setUp(self):
@@ -658,6 +797,70 @@
         return d
 
 
+    def test_lookupDNSKey(self):
+        """
+        See L{test_lookupAddress}
+        """
+        d = client.lookupDNSKey(self.hostname)
+        d.addCallback(self.checkResult, dns.DNSKEY)
+        return d
+
+
+    def test_lookupDS(self):
+        """
+        See L{test_lookupAddress}
+        
+        @since: 12.1
+        """
+        d = client.lookupDS(self.hostname)
+        d.addCallback(self.checkResult, dns.DS)
+        return d
+
+
+    def test_lookupNSEC(self):
+        """
+        See L{test_lookupAddress}
+        
+        @since: 12.1
+        """
+        d = client.lookupNSEC(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC)
+        return d
+
+
+    def test_lookupNSEC3(self):
+        """
+        See L{test_lookupAddress}
+        
+        @since: 12.1
+        """
+        d = client.lookupNSEC3(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC3)
+        return d
+
+
+    def test_lookupNSEC3Param(self):
+        """
+        See L{test_lookupAddress}
+        
+        @since: 12.1
+        """
+        d = client.lookupNSEC3Param(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC3PARAM)
+        return d
+
+
+    def test_lookupRRSIG(self):
+        """
+        See L{test_lookupAddress}
+        
+        @since: 12.1
+        """
+        d = client.lookupRRSIG(self.hostname)
+        d.addCallback(self.checkResult, dns.RRSIG)
+        return d
+
+
 class ThreadedResolverTests(unittest.TestCase):
     """
     Tests for L{client.ThreadedResolver}.
Index: twisted/names/test/test_dns.py
===================================================================
--- twisted/names/test/test_dns.py	(revision 34065)
+++ twisted/names/test/test_dns.py	(working copy)
@@ -8,13 +8,14 @@
 
 from cStringIO import StringIO
 
+import re
 import struct
 
 from twisted.python.failure import Failure
 from twisted.internet import address, task
 from twisted.internet.error import CannotListenError, ConnectionDone
 from twisted.trial import unittest
-from twisted.names import dns
+from twisted.names import dns, common
 
 from twisted.test import proto_helpers
 
@@ -25,6 +26,8 @@
     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,
+    dns.Record_OPT, dns.Record_RRSIG, dns.Record_DS, dns.Record_DNSKEY,
+    dns.Record_NSEC, dns.Record_NSEC3PARAM, dns.Record_NSEC3,
     ]
 
 class NameTests(unittest.TestCase):
@@ -189,6 +192,39 @@
     """Encoding and then decoding various objects."""
 
     names = ["example.org", "go-away.fish.tv", "23strikesback.net"]
+    sigs = ["Qm12VZVaZgKS0/DZx35SGECDwPiTTf3ngChb7OkgSv5iupVmJGhPWudm "
+            "/18qBSXKyv9hxMlEXFFgpBieNqLfSBkP1bwKnlqPfr1Hx7ctDwDUpkT3 "
+            "cS8u/ms9yo3Fu1ybpO4Hfsb1HbA2N3zzQnjWKnyk26AAQSz8KgjNTFzD "
+            "tJM=",
+            "ZH2kahMD1g2WOieIotAcBwB0e/o30Zq6YR//M/xwP1ktkYuclmcR56iv "
+            "XiR3QFWqmN5Xz3YpgmM4tZkjIeSMp2doYa7XYORZ7OpzG7oyfo8IoXxc "
+            "j1VGDeAn1CeNCpBtoSGapRABG1gjY7oeRj/smPQPp2Gkf79+WZfuzRom "
+            "/t4=",
+            "AZpaboyNQAmbnBO1K66QmZ0c+VCdY/wu9QpEdRnMpnIOLPD28pNVu6hk "
+            "GQMz6eg5WYkPYDdJK+1D/oyAQkDmRgn10+O9EdeFDyLqYqq/htEAvDm4 "
+            "CziMSOpD/mkg1bSWCZ2mdln/GBk8WooCeeM7LEHmRjmHMMj0xb6N4SKa "
+            "MEc=",
+            "AwEAAbi5VQa3x+R3WQouBDNts+ZX2zIKZNoj9pzl7ew446kI/2omv3j6 "
+            "c/4RQ6VneYE3mK7r0fFIKhVagmiRroFO1rRUJ8sVidssZ35CldE0sju+ "
+            "E7wymVg3tV+ZUUO/+5v6Sfj+tw3rlp6eKqm7EGKKM88t+KuXiGYMu0Vw "
+            "Rm9OUO1n",
+            "AwEAAbmTL+kuV45kAxGN//iBKz93Y6lutgxoptp+I1+PZZMsBkhm/dZj "
+            "q57040Pz/Hr3f2zQX7z6fFu7/Ml3MHPH1eQDiVXDvOkeNq2x4IbCO7x+ "
+            "0p6bGYj4fw/tEfh/8dUzyzvMwfuAMsOvXza8Kh+UP4jvFc95cUuGgYus "
+            "uEjUOp40PsL7EtYvAks3UssA6/OZP4w/1Z5m/VFx4PzgY0dkEuc=",
+            "VGPxa8A81eV1dtUxVhz9b9Jsp6FF4M5H6J0QhzbNCUTHTHjLNR2VHYfE "
+            "fM+Akwo3/qKq3D6vzTfzqtyPAXP8CmGfdD8hfv0s7Hae9c7Is8usdlrk "
+            "ZpoXEFMW+YVG8G9OieYViq6tBIpUvKgMVZ+oXKo63KJ/tC/yBW0H0VQP "
+            "YwdzZ3ZvYRDmZDvrXoX7T0YNU+0HYHnb7g7nUECIJ/4HHg==",
+            "M+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9H4T/K+oeC7Y81HIOFE "
+            "h9k6ZS/Ba5X0/Fr1yyq5Z/+0/Q845Kya8Lmkp/ikJVe/9id2TC2hoffp "
+            "Z9pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55 "
+            "Y1k="]
+    type_bitmaps = ["A MX RRSIG NSEC TYPE1234",
+                    "AAAA NS CNAME MX TXT RRSIG NSEC3 DNAME",
+                    "NSEC3 A AAAA RRSIG DS NS "
+                    "TYPE5000 TYPE1000 TYPE2000 TYPE3000 TYPE4000",
+                    None]
 
     def testName(self):
         for n in self.names:
@@ -276,6 +312,68 @@
             self.assertEqual(result.string, n)
 
 
+    def test_Sigstr(self):
+        """
+        Test L{dns.Sigstr} encode and decode.
+        
+        @since: 12.1
+        """
+        for s in self.sigs:
+            # encode the signature/key
+            f = StringIO()
+            dns.Sigstr(s).encode(f)
+            l = f.tell()
+
+            # decode the signature/key
+            f.seek(0, 0)
+            result = dns.Sigstr()
+            result.decode(f,l)
+            # spaces are free, and dig sticks them in
+            self.assertEqual(result.string, s.replace(' ', ''))
+
+
+    def test_TypeBitmaps(self):
+        """
+        Test L{dns.TypeBitmaps} encode and decode.
+        
+        @since: 12.1
+        """
+        typeRegex = re.compile('TYPE(\d+)')
+
+        for b in self.type_bitmaps:
+            # encode the type_bitmaps
+            f = StringIO()
+            dns.TypeBitmaps(b).encode(f)
+            l = f.tell()
+
+            # decode the type_bitmaps
+            f.seek(0, 0)
+            result = dns.TypeBitmaps()
+            result.decode(f,l)
+
+            def mnuVal(mnu):
+                mnuVal = dns.REV_TYPES.get(mnu, None)
+                if not mnuVal:
+                    m = typeRegex.match(mnu)
+                    if m.groups():
+                        mnuVal = int(m.group(1))
+                        assert mnuVal < 65536
+                    else:
+                        log.err("can't parse %s in %s" % (mnu, self.string, ))
+                        mnuVal = 0
+                return mnuVal
+
+            def sorttok(string):
+                if not string:
+                    return ''
+
+                toks = string.split(' ')
+                toks.sort(key = mnuVal)
+                return ' '.join(toks)
+
+            self.assertEqual(result.type_bitmaps, sorttok(b))
+
+
     def test_NAPTR(self):
         """
         Test L{dns.Record_NAPTR} encode and decode.
@@ -325,8 +423,8 @@
         msg = dns.Message()
         msg.fromStr(
             '\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' # answer bit, opCode nibble, auth, trunc, recursive bits
+            '\x00' # recursion bit, 3 empty bits, response code nibble
             '\x00\x00' # number of queries
             '\x00\x00' # number of answers
             '\x00\x00' # number of authorities
@@ -439,6 +537,7 @@
         Initialize the controller: create a list of messages.
         """
         self.messages = []
+        self.dnssecConfig = common.DnssecConfig()
 
 
     def messageReceived(self, msg, proto, addr):
@@ -893,8 +992,100 @@
             repr(dns.UnknownRecord("foo\x1fbar", 12)),
             "<UNKNOWN data='foo\\x1fbar' ttl=12>")
 
+    def test_dnskey(self):
+        """
+        The repr of a L{dns.DNSKEY} instance includes the flags, protocol,
+        algo, pub_key and ttl fields of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_DNSKEY(10, 20, 30, "foo\x1fbar", ttl=20)),
+            "<DNSKEY flags=10 protocol=20 algo=30 pub_key=foo\x1fbar ttl=20>")
 
+    def test_ds(self):
+        """
+        The repr of a L{dns.DS} instance includes the key_tag, algo, digest_type,
+        digest and ttl fields of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_DS(11, 22, 33, "foo\x1fbar1", ttl=21)),
+            "<DS key_tag=11 algo=22 digest_type=33 digest=foo\x1fbar1 ttl=21>")
 
+    def test_nsec(self):
+        """
+        The repr of a L{dns.NSEC} instance includes the nxt_name, type_bitmaps
+        and ttl fields of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC('bob', "\x1fabcd", ttl=31)),
+            "<NSEC nxt_name=bob type_bitmaps=\x1fabcd ttl=31>")
+
+    def test_nsec3param(self):
+        """
+        The repr of a L{dns.NSEC3PARAM} instance includes the hash_algo, flags,
+        iterations, salt and ttl fields of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC3PARAM(1, 2, 3, '\x12\x34', ttl=31)),
+            "<NSEC3 hash_algo=1 flags=2 iterations=3 salt=\x12\x34 ttl=31>")
+
+    def test_nsec3(self):
+        """
+        The repr of a L{dns.NSEC3} instance includes the hash_algo, flags,
+        iterations, salt, nxt_hash_owner_name, type_bitmaps and ttl fields
+        of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_NSEC3(1, 2, 3, '\x12\x34', 'bob',
+                                  "\x1fabcd", ttl=31)),
+            "<NSEC3 hash_algo=1 flags=2 iterations=3 "
+            "salt=\x12\x34 nxt_hash_owner_name=bob "
+            "type_bitmaps=\x1fabcd ttl=31>")
+
+    def test_opt(self):
+        """
+        The repr of a L{dns.OPT} instance includes the payload_size, dnssecOk
+        flag, and version fields of the record.
+        (The OPT record has no ttl field.)
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_OPT(payload_size=1492, dnssecOk=1, version=0)),
+                 "<OPT payload_size=1492 flags=0x8000 version=0>")
+
+    def test_rrsig(self):
+        """
+        The repr of a L{dns.RRSIG} instance includes the algo, labels,
+        original_ttl sig_expiration, sig_inception, key_tag, signers_name,
+        signature and ttl fields of the record.
+        
+        @since: 12.1
+        """
+        self.assertEqual(
+            repr(dns.Record_RRSIG(type_covered=dns.A,
+                                  algo=2,
+                                  labels=3,
+                                  original_ttl=30,
+                                  sig_expiration='20110101123456',
+                                  sig_inception= '20110202112233',
+                                  key_tag=60,
+                                  signers_name='bob',
+                                  signature='\x12\x34sig',
+                                  ttl=70)),
+        "<RRSIG type_covered=1 algo=2 labels=3 original_ttl=30"
+        " sig_expiration=20110101123456 sig_inception=20110202112233 key_tag=60"
+        " signers_name=bob signature=\x12\x34sig ttl=70>")
+
 class _Equal(object):
     """
     A class the instances of which are equal to anything and everything.
@@ -961,6 +1152,24 @@
             cls('example.com', 123),
             cls('example.org', 123))
 
+    def test_optheader(self):
+        """
+        Two OptHeader instances comapare equal iff the have the same
+        (Record_OPT) payload and auth bit.
+        """
+        self._equalityTest(
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1024,
+                                                 dnssecOk=True,
+                                                 version=0,
+                                                 ttl=30)),
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1024,
+                                                 dnssecOk=True,
+                                                 version=0,
+                                                 ttl=30)),
+            dns.OPTHeader(payload=dns.Record_OPT(payload_size=1492,
+                                                 dnssecOk=False,
+                                                 version=0,
+                                                 ttl=40), auth=True))
 
     def test_rrheader(self):
         """
@@ -968,16 +1177,18 @@
         the same name, type, class, time to live, payload, and authoritative
         bit.
         """
+        aRec = dns.Record_A('1.2.3.4')
+
         # Vary the name
         self._equalityTest(
-            dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.org', payload=dns.Record_A('1.2.3.4')))
+            dns.RRHeader('example.com', payload=aRec),
+            dns.RRHeader('example.com', payload=aRec),
+            dns.RRHeader('example.org', payload=aRec))
 
         # Vary the payload
         self._equalityTest(
-            dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.4')),
+            dns.RRHeader('example.com', payload=aRec),
+            dns.RRHeader('example.com', payload=aRec),
             dns.RRHeader('example.com', payload=dns.Record_A('1.2.3.5')))
 
         # Vary the type.  Leave the payload as None so that we don't have to
@@ -989,21 +1200,21 @@
 
         # Probably not likely to come up.  Most people use the internet.
         self._equalityTest(
-            dns.RRHeader('example.com', cls=dns.IN, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', cls=dns.IN, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', cls=dns.CS, payload=dns.Record_A('1.2.3.4')))
+            dns.RRHeader('example.com', cls=dns.IN, payload=aRec),
+            dns.RRHeader('example.com', cls=dns.IN, payload=aRec),
+            dns.RRHeader('example.com', cls=dns.CS, payload=aRec))
 
         # Vary the ttl
         self._equalityTest(
-            dns.RRHeader('example.com', ttl=60, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', ttl=60, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', ttl=120, payload=dns.Record_A('1.2.3.4')))
+            dns.RRHeader('example.com', ttl=60, payload=aRec),
+            dns.RRHeader('example.com', ttl=60, payload=aRec),
+            dns.RRHeader('example.com', ttl=120, payload=aRec))
 
         # Vary the auth bit
         self._equalityTest(
-            dns.RRHeader('example.com', auth=1, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', auth=1, payload=dns.Record_A('1.2.3.4')),
-            dns.RRHeader('example.com', auth=0, payload=dns.Record_A('1.2.3.4')))
+            dns.RRHeader('example.com', auth=1, payload=aRec),
+            dns.RRHeader('example.com', auth=1, payload=aRec),
+            dns.RRHeader('example.com', auth=0, payload=aRec))
 
 
     def test_ns(self):
@@ -1483,3 +1694,195 @@
             dns.UnknownRecord('foo', ttl=10),
             dns.UnknownRecord('foo', ttl=10),
             dns.UnknownRecord('foo', ttl=100))
+
+    def test_rrsig(self):
+        """
+        L(dns.RRSIG) instances compare equal iff they have the same
+        type_covered, algo, labels, original_ttl, sig_expiration, sig_inception,
+        key_tag, signers_name, signature, and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_RRSIG(type_covered=dns.A),
+            dns.Record_RRSIG(type_covered=dns.A),
+            dns.Record_RRSIG(type_covered=dns.AAAA))
+        self._equalityTest(
+            dns.Record_RRSIG(algo=1),
+            dns.Record_RRSIG(algo=1),
+            dns.Record_RRSIG(algo=2))
+        self._equalityTest(
+            dns.Record_RRSIG(labels=3),
+            dns.Record_RRSIG(labels=3),
+            dns.Record_RRSIG(labels=4))
+        self._equalityTest(
+            dns.Record_RRSIG(original_ttl=5),
+            dns.Record_RRSIG(original_ttl=5),
+            dns.Record_RRSIG(original_ttl=6))
+        self._equalityTest(
+            dns.Record_RRSIG(sig_expiration='20110101000000'),
+            dns.Record_RRSIG(sig_expiration='20110101000000'),
+            dns.Record_RRSIG(sig_expiration='20110101000001'))
+        self._equalityTest(
+            dns.Record_RRSIG(sig_inception='20120101000000'),
+            dns.Record_RRSIG(sig_inception='20120101000000'),
+            dns.Record_RRSIG(sig_inception='20120101000001'))
+        self._equalityTest(
+            dns.Record_RRSIG(key_tag=11),
+            dns.Record_RRSIG(key_tag=11),
+            dns.Record_RRSIG(key_tag=12))
+        self._equalityTest(
+            dns.Record_RRSIG(signers_name='bob'),
+            dns.Record_RRSIG(signers_name='bob'),
+            dns.Record_RRSIG(signers_name='joe'))
+        self._equalityTest(
+            dns.Record_RRSIG(signature='abcdef'),
+            dns.Record_RRSIG(signature='abcdef'),
+            dns.Record_RRSIG(signature='abcdefg'))
+        self._equalityTest(
+            dns.Record_RRSIG(ttl=10),
+            dns.Record_RRSIG(ttl=10),
+            dns.Record_RRSIG(ttl=20))
+
+    def test_ds(self):
+        """
+        L(dns.DS) instances compare equal iff they have the same
+        key_tag, algo, digest_type, digest and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_DS(key_tag=1),
+            dns.Record_DS(key_tag=1),
+            dns.Record_DS(key_tag=2))
+        self._equalityTest(
+            dns.Record_DS(algo=3),
+            dns.Record_DS(algo=3),
+            dns.Record_DS(algo=4))
+        self._equalityTest(
+            dns.Record_DS(digest_type=5),
+            dns.Record_DS(digest_type=5),
+            dns.Record_DS(digest_type=6))
+        self._equalityTest(
+            dns.Record_DS(digest='abcdef-digest'),
+            dns.Record_DS(digest='abcdef-digest'),
+            dns.Record_DS(digest='abcdef-digest-f'))
+        self._equalityTest(
+            dns.Record_DS(ttl=10),
+            dns.Record_DS(ttl=10),
+            dns.Record_DS(ttl=20))
+
+    def test_dnskey(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        flags, protocol, algo, pub_key and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_DNSKEY(flags=1),
+            dns.Record_DNSKEY(flags=1),
+            dns.Record_DNSKEY(flags=2))
+        self._equalityTest(
+            dns.Record_DNSKEY(protocol=3),
+            dns.Record_DNSKEY(protocol=3),
+            dns.Record_DNSKEY(protocol=4))
+        self._equalityTest(
+            dns.Record_DNSKEY(algo=5),
+            dns.Record_DNSKEY(algo=5),
+            dns.Record_DNSKEY(algo=6))
+        self._equalityTest(
+            dns.Record_DNSKEY(pub_key='abcdef-digest'),
+            dns.Record_DNSKEY(pub_key='abcdef-digest'),
+            dns.Record_DNSKEY(pub_key='abcdef-digest-f'))
+        self._equalityTest(
+            dns.Record_DNSKEY(ttl=10),
+            dns.Record_DNSKEY(ttl=10),
+            dns.Record_DNSKEY(ttl=20))
+
+    def test_nsec(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        nxt_name, type_bitmaps and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_NSEC(nxt_name="example.com"),
+            dns.Record_NSEC(nxt_name="example.com"),
+            dns.Record_NSEC(nxt_name="a.example.com"))
+        self._equalityTest(
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC(type_bitmaps="A AAAA NS NSEC3 RRSIG"))
+        self._equalityTest(
+            dns.Record_NSEC(ttl=5),
+            dns.Record_NSEC(ttl=5),
+            dns.Record_NSEC(ttl=6))
+
+    def test_nsec3param(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        hash_algo, flags, iterations, salt, nxt_hash_owner_name, type_bitmaps and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(hash_algo=1),
+            dns.Record_NSEC3PARAM(hash_algo=1),
+            dns.Record_NSEC3PARAM(hash_algo=2))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(flags=1),
+            dns.Record_NSEC3PARAM(flags=1),
+            dns.Record_NSEC3PARAM(flags=2))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(iterations=5),
+            dns.Record_NSEC3PARAM(iterations=5),
+            dns.Record_NSEC3PARAM(iterations=6))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(salt="abcdef"),
+            dns.Record_NSEC3PARAM(salt="abcdef"),
+            dns.Record_NSEC3PARAM(salt="abcdefg"))
+        self._equalityTest(
+            dns.Record_NSEC3PARAM(ttl=5),
+            dns.Record_NSEC3PARAM(ttl=5),
+            dns.Record_NSEC3PARAM(ttl=6))
+
+    def test_nsec3(self):
+        """
+        L(dns.DNSKEY) instances compare equal iff they have the same
+        hash_algo, flags, iterations, salt, nxt_hash_owner_name, type_bitmaps
+        and ttl
+        
+        @since: 12.1
+        """
+        self._equalityTest(
+            dns.Record_NSEC3(hash_algo=1),
+            dns.Record_NSEC3(hash_algo=1),
+            dns.Record_NSEC3(hash_algo=2))
+        self._equalityTest(
+            dns.Record_NSEC3(flags=1),
+            dns.Record_NSEC3(flags=1),
+            dns.Record_NSEC3(flags=2))
+        self._equalityTest(
+            dns.Record_NSEC3(iterations=5),
+            dns.Record_NSEC3(iterations=5),
+            dns.Record_NSEC3(iterations=6))
+        self._equalityTest(
+            dns.Record_NSEC3(salt="abcdef"),
+            dns.Record_NSEC3(salt="abcdef"),
+            dns.Record_NSEC3(salt="abcdefg"))
+        self._equalityTest(
+            dns.Record_NSEC3(nxt_hash_owner="example.com"),
+            dns.Record_NSEC3(nxt_hash_owner="example.com"),
+            dns.Record_NSEC3(nxt_hash_owner="a.example.com"))
+        self._equalityTest(
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3"),
+            dns.Record_NSEC3(type_bitmaps="A AAAA NS NSEC3 RRSIG"))
+        self._equalityTest(
+            dns.Record_NSEC3(ttl=5),
+            dns.Record_NSEC3(ttl=5),
+            dns.Record_NSEC3(ttl=6))
+
+
Index: twisted/names/test/test_names.py
===================================================================
--- twisted/names/test/test_names.py	(revision 34065)
+++ twisted/names/test/test_names.py	(working copy)
@@ -98,13 +98,27 @@
                            '\x12\x01\x16\xfe\xc1\x00\x01'),
             dns.Record_NAPTR(100, 10, "u", "sip+E2U",
                              "!^.*$!sip:information@domain.tld!"),
-            dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF')],
+            dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF'),
+            dns.Record_DNSKEY(0x10, 3, 5,
+                              "M+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9H4T"
+                              "/K+oeC7Y81HIOFEh9k6ZS/Ba5X0/Fr1yyq5Z/+0/Q"
+                              "845Kya8Lmkp/ikJVe/9id2TC2hoffpZ9pbZRjIeBT"
+                              "AvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8"
+                              "LJ55Y1k="
+                             )],
         'http.tcp.test-domain.com': [
             dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool')
         ],
         'host.test-domain.com': [
             dns.Record_A('123.242.1.5'),
             dns.Record_A('0.255.0.255'),
+            dns.Record_RRSIG(dns.A, 5, 3, 86400,
+                             '20120101000000', '20120201000000',
+                             2642, 'test-domain.com',
+                            "M+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9H4T/K"
+                            "+oeC7Y81HIOFEh9k6ZS/Ba5X0/Fr1yyq5Z/+0/Q845K"
+                            "ya8Lmkp/ikJVe/9id2TC2hoffpZ9pbZRjIeBTAvdTbo"
+                            "GmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55Y1k=")
         ],
         'host-two.test-domain.com': [
 #
@@ -224,7 +238,8 @@
         """Test DNS 'A' record queries with multiple answers"""
         return self.namesTest(
             self.resolver.lookupAddress('host.test-domain.com'),
-            [dns.Record_A('123.242.1.5', ttl=19283784), dns.Record_A('0.255.0.255', ttl=19283784)]
+            [dns.Record_A('123.242.1.5', ttl=19283784),
+             dns.Record_A('0.255.0.255', ttl=19283784)]
         )
 
 
@@ -232,7 +247,8 @@
         """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)]
+            [dns.Record_A('255.255.255.254', ttl=19283784),
+             dns.Record_A('0.0.0.0', ttl=19283784)]
         )
 
 
@@ -264,7 +280,8 @@
         """Test DNS 'HINFO' record queries"""
         return self.namesTest(
             self.resolver.lookupHostInfo('test-domain.com'),
-            [dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know', ttl=19283784)]
+            [dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know',
+                              ttl=19283784)]
         )
 
     def testPTR(self):
@@ -286,7 +303,8 @@
         """Test additional processing for CNAME records"""
         return self.namesTest(
         self.resolver.lookupAddress('cname.test-domain.com'),
-        [dns.Record_CNAME('test-domain.com', ttl=19283784), dns.Record_A('127.0.0.1', ttl=19283784)]
+        [dns.Record_CNAME('test-domain.com', ttl=19283784),
+         dns.Record_A('127.0.0.1', ttl=19283784)]
     )
 
     def testMB(self):
@@ -317,7 +335,8 @@
         """Test DNS 'MINFO' record queries"""
         return self.namesTest(
             self.resolver.lookupMailboxInfo('test-domain.com'),
-            [dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box', ttl=19283784)]
+            [dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box',
+                              ttl=19283784)]
         )
 
 
@@ -325,14 +344,16 @@
         """Test DNS 'SRV' record queries"""
         return self.namesTest(
             self.resolver.lookupService('http.tcp.test-domain.com'),
-            [dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl=19283784)]
+            [dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool',
+                            ttl=19283784)]
         )
 
     def testAFSDB(self):
         """Test DNS 'AFSDB' record queries"""
         return self.namesTest(
             self.resolver.lookupAFSDatabase('test-domain.com'),
-            [dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com', ttl=19283784)]
+            [dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com',
+                              ttl=19283784)]
         )
 
 
@@ -340,7 +361,8 @@
         """Test DNS 'RP' record queries"""
         return self.namesTest(
             self.resolver.lookupResponsibility('test-domain.com'),
-            [dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text', ttl=19283784)]
+            [dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text',
+                           ttl=19283784)]
         )
 
 
@@ -348,8 +370,10 @@
         """Test DNS 'TXT' record queries"""
         return self.namesTest(
             self.resolver.lookupText('test-domain.com'),
-            [dns.Record_TXT('A First piece of Text', 'a SecoNd piece', ttl=19283784),
-             dns.Record_TXT('Some more text, haha!  Yes.  \0  Still here?', ttl=19283784)]
+            [dns.Record_TXT('A First piece of Text', 'a SecoNd piece',
+                            ttl=19283784),
+             dns.Record_TXT('Some more text, haha!  Yes.  \0  Still here?',
+                            ttl=19283784)]
         )
 
 
@@ -359,8 +383,11 @@
         """
         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)]
+            [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)]
         )
 
 
@@ -368,7 +395,8 @@
         """Test DNS 'WKS' record queries"""
         return self.namesTest(
             self.resolver.lookupWellKnownServices('test-domain.com'),
-            [dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP, '\x12\x01\x16\xfe\xc1\x00\x01', ttl=19283784)]
+            [dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP,
+                            '\x12\x01\x16\xfe\xc1\x00\x01', ttl=19283784)]
         )
 
 
@@ -381,7 +409,8 @@
              dns.Record_A('1.2.3.4', ttl='1S'),
              dns.Record_NS('ns1.domain', ttl='2M'),
              dns.Record_NS('ns2.domain', ttl='3H'),
-             dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl='4D')]
+             dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool',
+                            ttl='4D')]
             )
 
 
@@ -389,7 +418,8 @@
         """Test DNS 'AAAA' record queries (IPv6)"""
         return self.namesTest(
             self.resolver.lookupIPV6Address('test-domain.com'),
-            [dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF', ttl=19283784)]
+            [dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF',
+                             ttl=19283784)]
         )
 
     def testA6(self):
@@ -398,7 +428,8 @@
             self.resolver.lookupAddress6('test-domain.com'),
             [dns.Record_A6(0, 'ABCD::4321', '', ttl=19283784),
              dns.Record_A6(12, '0:0069::0', 'some.network.tld', ttl=19283784),
-             dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF', 'tra.la.la.net', ttl=19283784)]
+             dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF',
+                           'tra.la.la.net', ttl=19283784)]
          )
 
 
@@ -407,7 +438,9 @@
         Test DNS 'AXFR' queries (Zone transfer)
         """
         default_ttl = soa_record.expire
-        results = [copy.copy(r) for r in reduce(operator.add, test_domain_com.records.values())]
+        results = [copy.copy(r)
+                   for r in reduce(operator.add,
+                                   test_domain_com.records.values())]
         for r in results:
             if r.ttl is None:
                 r.ttl = default_ttl
@@ -435,8 +468,22 @@
                               "!^.*$!sip:information@domain.tld!",
                               ttl=19283784)])
 
+    def test_DNSKEY(self):
+        """
+        Test DNS 'DNSKEY' record queries.
+        
+        @since: 12.1
+        """
+        return self.namesTest(
+            self.resolver.lookupDNSKey('test-domain.com'),
+            [dns.Record_DNSKEY(0x10, 3, 5,
+                               "M+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9"
+                               "H4T/K+oeC7Y81HIOFEh9k6ZS/Ba5X0/Fr1yyq5"
+                               "Z/+0/Q845Kya8Lmkp/ikJVe/9id2TC2hoffpZ9"
+                               "pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6"
+                               "fHR9eekIWis8LJ55Y1k=",
+                               ttl=19283784)])
 
-
 class DNSServerFactoryTests(unittest.TestCase):
     """
     Tests for L{server.DNSServerFactory}.
@@ -523,7 +570,8 @@
         self.d.addCallback(self._gotResults)
         self.controller = client.AXFRController('fooby.com', self.d)
 
-        self.soa = dns.RRHeader(name='fooby.com', type=dns.SOA, cls=dns.IN, ttl=86400, auth=False,
+        self.soa = dns.RRHeader(name='fooby.com', type=dns.SOA, cls=dns.IN,
+                                ttl=86400, auth=False,
                                 payload=dns.Record_SOA(mname='fooby.com',
                                                        rname='hooj.fooby.com',
                                                        serial=100,
@@ -535,20 +583,28 @@
 
         self.records = [
             self.soa,
-            dns.RRHeader(name='fooby.com', type=dns.NS, cls=dns.IN, ttl=700, auth=False,
-                         payload=dns.Record_NS(name='ns.twistedmatrix.com', ttl=700)),
+            dns.RRHeader(name='fooby.com', type=dns.NS, cls=dns.IN, ttl=700,
+                         auth=False,
+                         payload=dns.Record_NS(name='ns.twistedmatrix.com',
+                                               ttl=700)),
 
-            dns.RRHeader(name='fooby.com', type=dns.MX, cls=dns.IN, ttl=700, auth=False,
-                         payload=dns.Record_MX(preference=10, exchange='mail.mv3d.com', ttl=700)),
+            dns.RRHeader(name='fooby.com', type=dns.MX, cls=dns.IN, ttl=700,
+                         auth=False,
+                         payload=dns.Record_MX(preference=10,
+                                               exchange='mail.mv3d.com',
+                                               ttl=700)),
 
-            dns.RRHeader(name='fooby.com', type=dns.A, cls=dns.IN, ttl=700, auth=False,
-                         payload=dns.Record_A(address='64.123.27.105', ttl=700)),
+            dns.RRHeader(name='fooby.com', type=dns.A, cls=dns.IN,
+                         ttl=700, auth=False,
+                         payload=dns.Record_A(address='64.123.27.105',
+                                              ttl=700)),
             self.soa
             ]
 
     def _makeMessage(self):
         # hooray they all have the same message format
-        return dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1, auth=1, rCode=0, trunc=0, maxSize=0)
+        return dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1,
+                           auth=1, rCode=0, trunc=0, maxSize=0)
 
     def testBindAndTNamesStyle(self):
         # Bind style = One big single message
@@ -890,7 +946,6 @@
         self.assertEqual(service.domains[1].domain, 'example.edu')
 
 
-
 class SecondaryAuthorityTests(unittest.TestCase):
     """
     L{twisted.names.secondary.SecondaryAuthority} correctly constructs objects
Index: twisted/names/test/test_ser_num_arith.py
===================================================================
--- twisted/names/test/test_ser_num_arith.py	(revision 0)
+++ twisted/names/test/test_ser_num_arith.py	(working copy)
@@ -0,0 +1,135 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.names.ser_num_arith}.
+
+@since: 12.1
+"""
+
+from twisted.names.ser_num_arith import SNA, DateSNA
+from twisted.names.ser_num_arith import max as sna_max
+from twisted.trial import unittest
+
+class SNATest(unittest.TestCase):
+
+    def setUp(self):
+        self.s1 = SNA(1)
+        self.s1a = SNA(1)
+        self.s2 = SNA(2)
+        self.sMaxVal = SNA(SNA.HLFRNG+SNA.HLFRNG-1)
+
+    def test_equality(self):
+        """
+        Test SNA equality
+        """
+        self.assertEqual(self.s1, self.s1a)
+        self.assertNotIdentical(self.s1, self.s1a)
+        self.assertEqual(hash(self.s1), hash(self.s1a))
+        self.assertNotEqual(hash(self.s1), hash(self.s2))
+
+    def test_le(self):
+        """
+        Test SNA less than or equal
+        """
+        self.assertTrue(self.s1 <= self.s1)
+        self.assertTrue(self.s1 <= self.s1a)
+        self.assertTrue(self.s1 <= self.s2)
+        self.assertFalse(self.s2 <= self.s1)
+
+    def test_ge(self):
+        """
+        Test SNA greater than or equal
+        """
+        self.assertTrue(self.s1 >= self.s1)
+        self.assertTrue(self.s1 >= self.s1a)
+        self.assertFalse(self.s1 >= self.s2)
+        self.assertTrue(self.s2 >= self.s1)
+
+
+    def test_lt(self):
+        """
+        Test SNA less than
+        """
+        self.assertFalse(self.s1 < self.s1)
+        self.assertFalse(self.s1 < self.s1a)
+        self.assertTrue(self.s1 < self.s2)
+        self.assertFalse(self.s2 < self.s1)
+
+    def test_gt(self):
+        """
+        Test SNA greater than
+        """
+        self.assertFalse(self.s1 > self.s1)
+        self.assertFalse(self.s1 > self.s1a)
+        self.assertFalse(self.s1 > self.s2)
+        self.assertTrue(self.s2 > self.s1)
+
+    def test_add(self):
+        """
+        Test SNA addition
+        """
+        self.assertEqual(self.s1 + self.s1, self.s2)
+        self.assertEqual(self.s1 + SNA(SNA.MAXADD), SNA(SNA.MAXADD + 1))
+        self.assertEqual(SNA(SNA.MAXADD) + SNA(SNA.MAXADD) + SNA(2), SNA(0))
+
+    def test_maxval(self):
+        """
+        Test SNA maxval
+        """
+        smaxplus1 = self.sMaxVal + self.s1
+        self.assertTrue(smaxplus1 > self.sMaxVal)
+        self.assertEqual(smaxplus1, SNA(0))
+
+    def test_max(self):
+        """
+        Test the SNA max function
+        """
+        self.assertEqual(sna_max([None, self.s1]), self.s1)
+        self.assertEqual(sna_max([self.s1, None]), self.s1)
+        self.assertEqual(sna_max([self.s1, self.s1a]), self.s1)
+        self.assertEqual(sna_max([self.s2, self.s1a, self.s1, None]), self.s2)
+        self.assertEqual(sna_max([SNA(SNA.MAXADD), self.s2, self.s1a, self.s1, None]),
+                          SNA(SNA.MAXADD))
+        self.assertEqual(sna_max([self.s2, self.s1a, self.s1, None, self.sMaxVal]),
+                          self.s2)
+
+    def test_dateSNA(self):
+        """
+        Test DateSNA construction and comparison
+        """
+        date1 = DateSNA('20120101000000')
+        date2 = DateSNA('20130101000000')
+        self.assertTrue(date1 < date2)
+
+    def test_dateAdd(self):
+        """
+        Test DateSNA addition
+        """
+        date3 = DateSNA('20370101000000')
+        sna1  = SNA(365*24*60*60)
+        date4 = date3 + sna1
+        self.assertEqual(date4.asInt(),  date3.asInt() + sna1.asInt())
+
+    def test_asDate(self):
+        """
+        Test DateSNA conversion
+        """
+        date1 = '20120101000000'
+        date1Sna = DateSNA(date1)
+        self.assertEqual(date1Sna.asDate(), date1)
+
+    def test_roundTrip(self):
+        """
+        Test DateSNA conversion
+        """
+        date1 = '20370101000000'
+        date1Sna = DateSNA(date1)
+        intval = date1Sna.asInt()
+        sna1a = SNA(intval)
+
+        dateSna1a = DateSNA.fromSNA(sna1a)
+        self.assertEqual(date1Sna, dateSna1a)
+
+        dateSna2 = DateSNA.fromInt(intval)
+        self.assertEqual(date1Sna, dateSna2)
Index: twisted/names/topfiles/5454.feature
===================================================================
--- twisted/names/topfiles/5454.feature	(revision 0)
+++ twisted/names/topfiles/5454.feature	(working copy)
@@ -0,0 +1,46 @@
+Features
+--------
+ - The main features introduced by this change are EDNS0 and support
+   for DNSSEC. Enabling EDNS0 adds to a query an OPT record that carries
+   two pieces of information. The first is the maximum size UDP packet 
+   that the network can handle (see RFC2671.) The second is the DNSSEC 
+   OK (DO) bit which tells an upstream resolver that the querying resolver 
+   is able to accept DNSSEC security data (See RFC3225.) Although this
+   change fully implements EDNS0, it only enables twisted.names to 
+   ask for DNSSEC data and pass that data to the user. The user may
+   check the Authentic Data (AD) bit in the message header to determine
+   if the upstream resolver validates the data. (See RFC 4035, Section 
+   4.9.3 regarding only using the AD bit from a trusted resolver 
+   on a secure channel.)
+ - Processing DNSSEC data and validating DNS answers requires doing 
+   cryptographic processing using a library such as openssl. This may 
+   be part of a future feature set but is not in the current feature.
+ - The new class twisted.names.common.DnssecConfig allows configuring
+   DNSSEC and EDNS0 parameters, as well as the recursion desired DNS
+   parameter, for a resolver. DnssecConfig is an optional input argument 
+   to ResolverBase and is implemented for client.Resolver and 
+   secondaryAuthority.Resolver
+ - ResolverBase now has new lookup methods for lookupDNSKey, lookupDS, 
+   lookupNSEC, lookupNSEC3, lookupNSEC3Param and lookupRRSIG records.
+ - twisted.names.client.Resolver now returns a 4-tuple to a query when 
+   configured with dnssecOk. The 4-tuple includes a new reference 
+   to the message so that the caller can obtain the AD flag (and 
+   any of the other flags) in the message.
+ - twisted.names.client now has new lookup methods for the six new 
+   DNSSEC record types.
+ - twisted.names.dns now supports six new DNS record types - DNSKey,
+   DS, NSEC, NSEC3, NSEC3Param and RRSIG.
+ - The new twisted.names.dns.Sigstr class encodes/decodes signatures and keys.
+ - The new twisted.names.dns.TypeBitmaps class encodes/decoes the type bitmap
+   field in an NSEC3 resource record.
+ - The new OptHeader and Record_OPT classes support the EDNS and DNSSEC 
+   options.
+ - twisted.names.dns now has new classes added for the six DNSSEC record types.
+ - The dns.Message class now has new AD and CD flags for AuthenticData and Checking Disabled.
+ - twisted.names.dns now adds the OPT record to EDNS queries.
+ - twisted.names.dns.Message.startListening now listens for larger UDP 
+   packets, as set by maxUdpPktSz.
+ - The new twisted.names.ser_num_arith class implements DNS Serial Number
+   Arithmetic (SNA) and Date SNA for handling numbers and dates that
+   roll through a 32-bit integer.
+ - New tests are added for all new features.
\ No newline at end of file
