Index: names/authority.py
===================================================================
--- names/authority.py	(revision 1)
+++ names/authority.py	(working copy)
@@ -82,7 +82,10 @@
         additional = []
         default_ttl = max(self.soa[1].minimum, self.soa[1].expire)
 
-        domain_records = self.records.get(name.lower())
+        #a record in self.records has a trailing dot if it was created with one, but is never
+        #queried with a trailing dot. So, if the get fails with no trailing dot, try it with 
+        #a trailing dot.  (You could do a better job, and trim the trailing dot, but that's hard.)
+        domain_records = self.records.get(name.lower(), self.records.get(name.lower() + '.'))
 
         if domain_records:
             for record in domain_records:
@@ -208,9 +211,8 @@
 
     def stripComments(self, lines):
         return [
-            a.find(';') == -1 and a or a[:a.find(';')] for a in [
-                b.strip() for b in lines
-            ]
+            #leading whitespace is significant - don't strip it!
+            a.find(';') == -1 and a or a[:a.find(';')] for a in lines
         ]
 
 
@@ -233,7 +235,13 @@
         lines = L
         L = []
         for line in lines:
-            L.append(line.split())
+            if not line:
+                continue
+            
+            if line[0] in [' ', '\t']:
+                L.append([''] + line.split())
+            else:
+                L.append(line.split())
         return filter(None, L)
 
 
@@ -242,6 +250,7 @@
         ORIGIN = self.origin
 
         self.records = {}
+        self.previousDomain = None
 
         for (line, index) in zip(lines, range(len(lines))):
             if line[0] == '$TTL':
@@ -275,7 +284,7 @@
             r.ttl = ttl
             self.records.setdefault(domain.lower(), []).append(r)
 
-            print 'Adding IN Record', domain, ttl, r
+            #print 'Adding IN Record', domain, ttl, r
             if type == 'SOA':
                 self.soa = (domain, r)
         else:
@@ -283,51 +292,51 @@
 
 
     #
-    # This file ends here.  Read no further.
+    # parse a bind format line.
     #
     def parseRecordLine(self, origin, ttl, line):
-        MARKERS = dns.QUERY_CLASSES.values() + dns.QUERY_TYPES.values()
         cls = 'IN'
-        owner = origin
-
+        
         if line[0] == '@':
-            line = line[1:]
             owner = origin
-#            print 'default owner'
-        elif not line[0].isdigit() and line[0] not in MARKERS:
+        elif line[0]:
             owner = line[0]
-            line = line[1:]
-#            print 'owner is ', owner
+        else:
+            owner = self.previousDomain
 
-        if line[0].isdigit() or line[0] in MARKERS:
+        #if the line starts with whitespace, it's not a name
+        if not line[0]:
             domain = owner
             owner = origin
-#            print 'woops, owner is ', owner, ' domain is ', domain
+            line = line[1:]
         else:
             domain = line[0]
             line = line[1:]
-#            print 'domain is ', domain
-
+        
+        if line[0].isdigit():
+            ttl = int(line[0])
+            line = line[1:]
+            
         if line[0] in dns.QUERY_CLASSES.values():
             cls = line[0]
             line = line[1:]
-#            print 'cls is ', cls
-            if line[0].isdigit():
-                ttl = int(line[0])
-                line = line[1:]
-#                print 'ttl is ', ttl
-        elif line[0].isdigit():
-            ttl = int(line[0])
+            
+        if line[0] in dns.QUERY_TYPES.values():
+            type = line[0]
             line = line[1:]
-#            print 'ttl is ', ttl
-            if line[0] in dns.QUERY_CLASSES.values():
-                cls = line[0]
-                line = line[1:]
-#                print 'cls is ', cls
-
-        type = line[0]
-#        print 'type is ', type
-        rdata = line[1:]
-#        print 'rdata is ', rdata
-
+        
+        if type == dns.QUERY_TYPES[dns.RRSIG] and len(line) > 9:
+            line[8] = ''.join(line[8:])
+            line = line[0:9]
+        elif type == dns.QUERY_TYPES[dns.DS] and len(line) > 4:
+            line[3] = ''.join(line[3:])
+            line = line[0:3]
+        elif type == dns.QUERY_TYPES[dns.DNSKEY] and len(line) > 4:
+            line[3] = ''.join(line[3:])
+            line = line[0:3]
+            
+        rdata = line
+        
         self.addRecord(owner, ttl, type, domain, cls, rdata)
+        
+        self.previousDomain = domain
Index: names/client.py
===================================================================
--- names/client.py	(revision 1)
+++ 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
@@ -98,10 +103,12 @@
             L{IReactorTCP} which will be used to establish connections, listen
             for DNS datagrams, and enforce timeouts.  If not provided, the
             global reactor will be used.
+            
+        @param dnssecConfig: A provider of DNSSEC parameters - 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
@@ -129,7 +136,6 @@
 
         self.maybeParseConfig()
 
-
     def __getstate__(self):
         d = self.__dict__.copy()
         d['connections'] = []
@@ -240,8 +246,16 @@
         for (d, q, t) in self.pending:
             self.queryTCP(q, t).chainDeferred(d)
         del self.pending[:]
+        
+    def connectionLost(self, protocol):
+        """
+        Called on UDP protocol when a DNS UDP query times out and is retried
+        on the TCP protocol. 30 seconds after the TCP query completes, this
+        method is called on the UDP protocol.
+        """
+        self.connections.remove(protocol)
+        del protocol
 
-
     def messageReceived(self, message, protocol, address = None):
         log.msg("Unexpected message (%d) received from %r" % (message.id, address))
 
@@ -370,9 +384,17 @@
             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 edns0 is enabled, return a reference to the message as the 4th member of 
+        #the tuple, so the caller can access AD. You could just return AD, but if you
+        #return message, the caller can also access a reference to the query and you
+        #never know when that might come in handy.
+        if self.dnssecConfig.ednsEnabled:
+            return (message.answers, message.authority, message.additional, message)
+        else:
+            #not edns0Enabled - 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.
@@ -942,3 +964,18 @@
     @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)
\ No newline at end of file
Index: names/common.py
===================================================================
--- names/common.py	(revision 1)
+++ names/common.py	(working copy)
@@ -18,6 +18,65 @@
 
 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.
+                    Presently, only EDNS Version 0 is defined.
+                    (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 0 is defined and supported.
+                    (int)
+                    
+    authData (AD) - Authentic Data. If set in a response from a validating resolver that you can
+                    trust, indicates that the validating resolver validated the answer. Requires 
+                    ednsEnabled and DO set. (See note below.)
+                    (bool)
+                    
+    Note - RFC-4035 does not define AD for a query. However, most validating resolvers seem 
+    to honor it set in a query and return AD set in a response to a query that validates. But 
+    you should not to rely on this behavior - set DO instead.
+    """
+    def __init__(self, 
+                 recDes=True,
+                 ednsEnabled=False, 
+                 maxUdpPktSz=512, 
+                 dnssecOk=False,
+                 chkDis=False,
+                 version=0,
+                 authData=False):
+
+        self.recDes = recDes
+        self.ednsEnabled = ednsEnabled
+        self.maxUdpPktSz = maxUdpPktSz
+        self.dnssecOk = dnssecOk
+        self.chkDis = chkDis
+        self.version = version
+        self.authData = authData
+
+        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 +95,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
@@ -200,6 +261,12 @@
         @see: twisted.names.client.lookupAllRecords
         """
         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 getHostByName(self, name, timeout = None, effort = 10):
         """
@@ -268,6 +335,7 @@
     dns.MX:    'lookupMailExchange',
     dns.TXT:   'lookupText',
     dns.SPF:   'lookupSenderPolicy',
+    dns.DNSKEY:'lookupDNSKey',
 
     dns.RP:    'lookupResponsibility',
     dns.AFSDB: 'lookupAFSDatabase',
Index: names/dns.py
===================================================================
--- names/dns.py	(revision 1)
+++ 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,14 +27,15 @@
     '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',
+    'Charstr', 'Message', 'Name', 'OPTHeader', 'Query', 'RRHeader', 'SimpleRecord',
     'DNSDatagramProtocol', 'DNSMixin', 'DNSProtocol',
 
     'OK', 'OP_INVERSE', 'OP_NOTIFY', 'OP_QUERY', 'OP_STATUS', 'OP_UPDATE',
@@ -46,6 +48,8 @@
 # System imports
 import warnings
 
+import re
+
 import struct, random, types, socket
 
 import cStringIO as StringIO
@@ -54,13 +58,16 @@
 
 from zope.interface import implements, Interface, Attribute
 
+from base64 import b64decode, b64encode
 
+
 # Twisted imports
 from twisted.internet import protocol, defer
 from twisted.internet.error import CannotListenError
 from twisted.python import log, failure
 from twisted.python import util as tputil
 from twisted.python import randbytes
+from twisted.names.serialNumberArithmetic import SNA, DateSNA
 
 
 def randomSource():
@@ -79,6 +86,14 @@
 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 +123,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)
@@ -277,8 +299,6 @@
     def __str__(self):
         return self.string
 
-
-
 class Name:
     implements(IEncodable)
 
@@ -370,7 +390,187 @@
 
     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 +634,103 @@
         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')
+
+    @staticmethod
+    def factory(strio):
+        '''
+        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(OPTHeader.fmt, readPrecisely(strio, 2))[0]
+        
+        if len(name.name) == 0 and type == OPT:
+            return OPTHeader()
+        else:
+            #back up to the beginning and try again
+            strio.seek(beginPos, 0)
+            rrh = RRHeader()
+            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.
@@ -1467,7 +1762,463 @@
         return hash(tuple(self.data))
 
 
+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 authoritataive
+           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 existance 
+    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):
     """
@@ -1544,15 +2295,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 = []
@@ -1597,7 +2350,9 @@
                  | ((self.auth & 1 ) << 2 )
                  | ((self.trunc & 1 ) << 1 )
                  | ( self.recDes & 1 ) )
-        byte4 = ( ( (self.recAv & 1 ) << 7 )
+        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,
@@ -1617,6 +2372,8 @@
         self.trunc = ( byte3 >> 1 ) & 1
         self.recDes = byte3 & 1
         self.recAv = ( byte4 >> 7 ) & 1
+        self.authData = ( byte4 >> 5 ) & 1
+        self.chkDis = ( byte4 >> 4 ) & 1
         self.rCode = byte4 & 0xf
 
         self.queries = []
@@ -1635,9 +2392,8 @@
 
     def parseRecords(self, list, num, strio):
         for i in range(num):
-            header = RRHeader()
             try:
-                header.decode(strio)
+                header = OPTHeader.factory(strio)
             except EOFError:
                 return
             t = self.lookupRecordType(header.type)
@@ -1747,8 +2503,14 @@
             query, or errbacked with any errors that could happen (exceptions
             during writing of the query, timeout errors, ...).
         """
-        m = Message(id, recDes=1)
+        m = Message(id, recDes=self.controller.dnssecConfig.recDes)
         m.queries = queries
+        if self.controller.dnssecConfig.ednsEnabled:
+            authData=self.controller.dnssecConfig.authData
+            chkDis=self.controller.dnssecConfig.chkDis
+            m.additional = [Record_OPT(payload_size = self.controller.dnssecConfig.maxUdpPktSz,
+                                       version = self.controller.dnssecConfig.version,
+                                       dnssecOk = self.controller.dnssecConfig.dnssecOk)]
 
         try:
             writeMessage(m)
@@ -1802,7 +2564,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: names/secondary.py
===================================================================
--- names/secondary.py	(revision 1)
+++ names/secondary.py	(working copy)
@@ -59,8 +59,16 @@
         if self.transferring:
             return
         self.transfering = True
-        return client.Resolver(servers=[(self.primary, dns.PORT)]
-            ).lookupZone(self.domain
+        
+        primary = self.primary
+        port = dns.PORT
+        parts = self.primary.split(':')
+        if len(parts) == 2:
+            primary = parts[0]
+            port = int(parts[1])
+            
+        return client.Resolver(servers=[(primary, port)]
+            ).lookupZone(self.domain, timeout=600
             ).addCallback(self._cbZone
             ).addErrback(self._ebZone
             )
Index: names/test/test_client.py
===================================================================
--- names/test/test_client.py	(revision 1)
+++ names/test/test_client.py	(working copy)
@@ -642,7 +642,15 @@
         d.addCallback(self.checkResult, dns.NAPTR)
         return d
 
+    def test_lookupDNSKey(self):
+        """
+        See L{test_lookupAddress}
+        """
+        d = client.lookupDNSKey(self.hostname)
+        d.addCallback(self.checkResult, dns.DNSKEY)
+        return d
 
+
 class ThreadedResolverTests(unittest.TestCase):
     """
     Tests for L{client.ThreadedResolver}.
Index: names/test/test_dns.py
===================================================================
--- names/test/test_dns.py	(revision 1)
+++ 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,7 +192,18 @@
     """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:
             # encode the name
@@ -276,6 +290,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.
@@ -386,6 +458,7 @@
         Initialize the controller: create a list of messages.
         """
         self.messages = []
+        self.dnssecConfig = common.DnssecConfig()
 
 
     def messageReceived(self, msg, proto, addr):
@@ -840,7 +913,80 @@
             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):
     """
@@ -908,6 +1054,15 @@
             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):
         """
@@ -1430,3 +1585,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))
+
+
+                
\ No newline at end of file
Index: names/test/test_names.py
===================================================================
--- names/test/test_names.py	(revision 1)
+++ names/test/test_names.py	(working copy)
@@ -93,13 +93,18 @@
                            '\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/Q845Kya8Lmkp/ikJVe/9id2TC2hoffpZ9pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55Y1k="
+                             )],
         '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/Q845Kya8Lmkp/ikJVe/9id2TC2hoffpZ9pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55Y1k=")
         ],
         'host-two.test-domain.com': [
 #
@@ -429,9 +434,15 @@
             [dns.Record_NAPTR(100, 10, "u", "sip+E2U",
                               "!^.*$!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+u8Uxm2y2Q142qED0kVNIiSOHBkfiU3xBhMq9H4T/K+oeC7Y81HIOFEh9k6ZS/Ba5X0/Fr1yyq5Z/+0/Q845Kya8Lmkp/ikJVe/9id2TC2hoffpZ9pbZRjIeBTAvdTboGmGuqG/ljnDLJrJpoF6g8g6fHR9eekIWis8LJ55Y1k=",
+                               ttl=19283784)])
 
-
-
 class DNSServerFactoryTests(unittest.TestCase):
     """
     Tests for L{server.DNSServerFactory}.
