Index: doc/names/examples/index.xhtml
===================================================================
--- doc/names/examples/index.xhtml	(revision 33731)
+++ doc/names/examples/index.xhtml	(working copy)
@@ -15,6 +15,7 @@
         <li><a href="testdns.py">testdns.py</a></li>
         <li><a href="dns-service.py">dns-service.py</a></li>
         <li><a href="gethostbyname.py">gethostbyname.py</a></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 33731)
+++ 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 RFC4033.) To use DNSSEC requires EDNS0, because to enable DNSSEC requires setting the DNSSEC OK (DO) bit which is in the EDNS0 OPT Record. RFC4035 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 33731)
+++ 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,99 @@
     @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}
+    """
+    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}
+    """
+    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}
+    """
+    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}
+    """
+    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}
+    """
+    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}
+    """
+    return getResolver().lookupRRSIG(name, timeout)
Index: twisted/names/common.py
===================================================================
--- twisted/names/common.py	(revision 33731)
+++ twisted/names/common.py	(working copy)
@@ -18,6 +18,62 @@
 
 EMPTY_RESULT = (), (), ()
 
+
+
+class DnssecConfig():
+    """
+    Sets recDes and the DNSSEC parameters. See RFC's 4033, 4034 and 4035.
+
+    recDes (RD)   - Recursion Desired. Not a DNSSEC parameter and does not
+                    require ednsEnabled, but still nice to be able to control
+                    whether or not you're asking for recursion.
+                    (bool)
+
+    ednsEnabled   - EDNS Enabled adds an OPT record to the query. The OPT record
+                    contains a version field that indicates the version of EDNS
+                    that you can handle.
+                    (bool)
+
+    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 ednsEnabled.
+                    (int)
+
+    dnssecOK (DO) - Dnssec Ok. A bit 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 ednsEnabled.
+                    (bool)
+
+    chkDis (CD)   - Checking Disabled. If DO and CD are set, a validating
+                    resolver won't do validation but will return the DNSSEC
+                    RR's so that YOU can.
+                    (bool)
+
+    version       - sets the edns version level. Currently, only version 0 is
+                    defined and supported by RFC2671.
+                    (int)
+    """
+    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 +92,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 +259,49 @@
         """
         return self._lookup(name, dns.IN, dns.ALL_RECORDS, timeout)
 
+
+    def lookupDNSKey(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupDNSKey
+        """
+        return self._lookup(name, dns.IN, dns.DNSKEY, timeout)
+
+
+    def lookupDS(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupDS
+        """
+        return self._lookup(name, dns.IN, dns.DS, timeout)
+
+
+    def lookupNSEC(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC
+        """
+        return self._lookup(name, dns.IN, dns.NSEC, timeout)
+
+
+    def lookupNSEC3(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC3
+        """
+        return self._lookup(name, dns.IN, dns.NSEC3, timeout)
+
+
+    def lookupNSEC3Param(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupNSEC3Param
+        """
+        return self._lookup(name, dns.IN, dns.NSEC3PARAM, timeout)
+
+
+    def lookupRRSIG(self, name, timeout=None):
+        """
+        @see: twisted.names.client.lookupRRSIG
+        """
+        return self._lookup(name, dns.IN, dns.RRSIG, timeout)
+
+
     def getHostByName(self, name, timeout = None, effort = 10):
         """
         @see: twisted.names.client.getHostByName
@@ -268,6 +369,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 33731)
+++ 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,185 @@
     def __str__(self):
         return self.name
 
+class Sigstr(object):
+    '''
+    for signatures and keys. display as b64 encoded
+    '''
+    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 RFC 4034 and RFC 5155.
+    '''
+    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 +633,110 @@
         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.
+    """
+
+    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):
+        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):
+        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 +746,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 +1349,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)
@@ -1470,6 +1770,534 @@
 
 
 
+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.
+    """
+    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.
+    """
+    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.
+    """
+    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.
+    """
+    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.
+    """
+    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.
+    """
+    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.
+    """
+    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):
     """
@@ -1490,6 +2318,7 @@
     compareAttributes = ('data', 'ttl')
     showAttributes = ('data', 'ttl')
 
+
     def __init__(self, data='', ttl=None):
         self.data = data
         self.ttl = str2time(ttl)
@@ -1546,15 +2375,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 = []
@@ -1594,13 +2425,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),
@@ -1613,12 +2446,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 = []
@@ -1630,16 +2465,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)
@@ -1749,8 +2585,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)
@@ -1804,7 +2646,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 33731)
+++ 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
 
@@ -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,163 @@
+# -*- 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.
+
+bob.novas@shinkuro.com
+"""
+
+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 33731)
+++ 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,140 @@
         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.
+        """
+        #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.
+        """
+        # 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 +793,60 @@
         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}
+        """
+        d = client.lookupDS(self.hostname)
+        d.addCallback(self.checkResult, dns.DS)
+        return d
+
+
+    def test_lookupNSEC(self):
+        """
+        See L{test_lookupAddress}
+        """
+        d = client.lookupNSEC(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC)
+        return d
+
+
+    def test_lookupNSEC3(self):
+        """
+        See L{test_lookupAddress}
+        """
+        d = client.lookupNSEC3(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC3)
+        return d
+
+
+    def test_lookupNSEC3Param(self):
+        """
+        See L{test_lookupAddress}
+        """
+        d = client.lookupNSEC3Param(self.hostname)
+        d.addCallback(self.checkResult, dns.NSEC3PARAM)
+        return d
+
+
+    def test_lookupRRSIG(self):
+        """
+        See L{test_lookupAddress}
+        """
+        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 33731)
+++ 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,64 @@
             self.assertEqual(result.string, n)
 
 
+    def test_Sigstr(self):
+        """
+        Test L{dns.Sigstr} encode and decode.
+        """
+        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.
+        """
+        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 +419,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 +533,7 @@
         Initialize the controller: create a list of messages.
         """
         self.messages = []
+        self.dnssecConfig = common.DnssecConfig()
 
 
     def messageReceived(self, msg, proto, addr):
@@ -893,8 +988,86 @@
             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.
+        """
+        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.
+        """
+        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.
+        """
+        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.
+        """
+        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.
+        """
+        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.)
+        """
+        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.
+        """
+        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 +1134,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 +1159,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 +1182,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 +1676,183 @@
             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
+        """
+        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
+        """
+        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
+        """
+        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
+        """
+        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
+        """
+        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
+        """
+        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 33731)
+++ 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,18 @@
                               "!^.*$!sip:information@domain.tld!",
                               ttl=19283784)])
 
+    def test_DNSKEY(self):
+        """Test DNS 'DNSKEY' record queries."""
+        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 +566,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 +579,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 +942,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,133 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test cases for L{twisted.names.ser_num_arith}.
+"""
+
+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,49 @@
+Twisted Names 12.1.0 (2012-03-13)
+=================================
+
+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
