Ticket #5668: opt-record-5668-examples.patch

File opt-record-5668-examples.patch, 30.7 KB (added by rwall, 15 months ago)

Some examples of _OPTHeader usage.

  • doc/names/examples/edns_server.tac.py

     
     1# Copyright (c) Twisted Matrix Laboratories. 
     2# See LICENSE for details. 
     3 
     4""" 
     5A DNS server which replies NXDOMAIN to all queries. 
     6 
     7Usage: twistd -noy doc/names/examples/edns_auth_server.tac.py 
     8 
     9This server uses the protocol hacks from edns.py 
     10 
     11The important thing is that because messages are decoded using 
     12EDNSMessage rather than dns.Message, OPT records are extracted from 
     13the additional section of EDNS query messages during decoding. 
     14 
     15This is one way of fixing #6645. 
     16 
     17Additionally we force ednsVersion=None so that the server doesn't 
     18respond with any OPT records. 
     19Although RFC6891-7 suggests that the correct response should be FORMERR. 
     20 * https://tools.ietf.org/html/rfc6891#section-7 
     21 
     22Ultimately, DNSServerFactory will need modifying or replacing so that 
     23it can dynamically respond using the correct EDNS settings and RCODE 
     24based on the client request. 
     25 
     26EDNSMessage will also need to be made aware of RRSets so that it can 
     27correctly limit the size of (or truncate) responses based on the 
     28chosen maxSize. 
     29 * https://twistedmatrix.com/trac/wiki/EDNS0#Selectivetruncate 
     30""" 
     31 
     32from functools import partial 
     33 
     34from twisted.application.internet import TCPServer, UDPServer 
     35from twisted.application.service import Application, MultiService 
     36 
     37from twisted.names import edns, server 
     38 
     39 
     40 
     41PORT = 10053 
     42EDNS_VERSION = None 
     43 
     44 
     45def makeService(): 
     46    masterService = MultiService() 
     47 
     48    factory = server.DNSServerFactory( 
     49        authorities=[], 
     50        caches=[], 
     51        clients=[]) 
     52 
     53    factory.protocol = partial(edns.EDNSStreamProtocol, ednsVersion=EDNS_VERSION) 
     54    proto = edns.EDNSDatagramProtocol(ednsVersion=EDNS_VERSION, controller=factory) 
     55 
     56    UDPServer(PORT, proto).setServiceParent(masterService) 
     57    TCPServer(PORT, factory).setServiceParent(masterService) 
     58 
     59    return masterService 
     60 
     61 
     62 
     63application = Application("An EDNS aware noop DNS server") 
     64 
     65 
     66 
     67makeService().setServiceParent(application) 
  • doc/names/examples/test_edns_compliance.py

     
     1# Copyright (c) Twisted Matrix Laboratories. 
     2# See LICENSE for details. 
     3 
     4""" 
     5An example trial test module which demonstrates how the low level 
     6L{dns._OPTHeader} class can be used for testing DNS servers for 
     7compliance with DNS RFCs. 
     8 
     9This example should be run using trial eg 
     10 
     11 trial doc/names/examples/test_edns_compliance.py 
     12 
     13 OR 
     14 
     15 TARGET=127.0.0.1 trial doc/names/examples/test_edns_compliance.py 
     16""" 
     17 
     18import os 
     19 
     20from twisted.internet import reactor 
     21from twisted.names import dns 
     22from twisted.trial import unittest 
     23 
     24 
     25 
     26class DNSMessageManglingProtocol(dns.DNSDatagramProtocol): 
     27    """ 
     28    A L{dns.DNSDatagramProtocol} subclass with hooks for mangling a 
     29    L{dns.Message} before it is sent. 
     30    """ 
     31 
     32    def __init__(self, *args, **kwargs): 
     33        """ 
     34        @param mangler: A callable which will be passed a message 
     35            argument and must return a message which will then be 
     36            encoded and sent. 
     37        @type mangler: L{callable} 
     38 
     39        @see: L{dns.DNSDatagramProtocol.__init__} for inherited 
     40            arguments. 
     41        """ 
     42        self.mangler = kwargs.pop('mangler') 
     43        dns.DNSDatagramProtocol.__init__(self, *args, **kwargs) 
     44 
     45 
     46    def writeMessage(self, message, address): 
     47        """ 
     48        Send a message holding DNS queries. 
     49 
     50        @type message: L{dns.Message} 
     51        """ 
     52        message = self.mangler(message) 
     53        return dns.DNSDatagramProtocol.writeMessage(self, message, address) 
     54 
     55 
     56 
     57def serversUnderTest(default): 
     58    """ 
     59    Return a list of server information tuples found in the 
     60    environment or C{default} if none are found. 
     61 
     62    @param default: A default list of servers to be tested if none 
     63        were found among the environment variables. 
     64    @type default: L{list} of 3-L{tuple}. 
     65 
     66    @return: L{list} of L{tuple} containing target server info 
     67        (host, port, description) 
     68    """ 
     69    targetServer = os.environ.get('TARGET') 
     70    if targetServer is not None: 
     71        parts = targetServer.split(',', 2) 
     72        if len(parts) == 2: 
     73            parts.append(parts[0]) 
     74        if len(parts) == 1: 
     75            parts.extend([53, parts[0]]) 
     76        parts[1] = int(parts[1]) 
     77        return [tuple(parts)] 
     78    else: 
     79        return default 
     80 
     81 
     82 
     83# Default servers to be tested 
     84SERVERS = [ 
     85    # GoogleDNS public recursive resolver 
     86    ('8.8.8.8', 53, 'GoogleRecursiveDns'), 
     87 
     88    # OpenDNS public recursive resolver 
     89    ('208.67.222.222', 53, 'OpenDNS'), 
     90 
     91    # Twisted 13.1 Authoritative DNS (ns1.twistedmatrix.com) 
     92    ('66.35.39.66', 53, 'TwistedAuthoritativeDns'), 
     93 
     94    # Bind 9.9.3-S1-P1 (as reported by version.bind CH TXT) (ams.sns-pb.isc.org) 
     95    ('199.6.1.30', 53, 'Bind9.9'), 
     96 
     97    # Power DNS (as reported by version.bind CH TXT) (dns-us1.powerdns.net) 
     98    ('46.165.192.30', 53, 'PowerDNS'), 
     99 
     100    # NSD 4.0.0b5 (as reported by version.bind CH TXT) (open.nlnetlabs.nl) 
     101    ('213.154.224.1', 53, 'NSD4'), 
     102 
     103    # DJBDNS (uz5dz39x8xk8wyq3dzn7vpt670qmvzx0zd9zg4ldwldkv6kx9ft090.ns.yp.to.) 
     104    ('131.155.71.143', 53, 'DJBDNS') 
     105] 
     106 
     107 
     108 
     109class DNSComplianceTestBuilder(object): 
     110    """ 
     111    Build a dictionary of L{unittest.TestCase} classes each of which 
     112    runs a group of tests against a particular server. 
     113    """ 
     114    @classmethod 
     115    def makeTestCaseClasses(cls): 
     116        """ 
     117        Create a L{unittest.TestCase} subclass which mixes in C{cls} 
     118        for each server and return a dict mapping their names to them. 
     119        """ 
     120        classes = {} 
     121        for host, port, description in serversUnderTest(SERVERS): 
     122            name = (cls.__name__ + "." + description).replace(".", "_") 
     123            class testcase(cls, unittest.TestCase): 
     124                __module__ = cls.__module__ 
     125                server = (host, port) 
     126            testcase.__name__ = name 
     127            classes[testcase.__name__] = testcase 
     128        return classes 
     129 
     130 
     131 
     132def hasAdditionalOptRecord(message): 
     133    """ 
     134    Test a message for an L{dns._OPTHeader} instance among its 
     135    additional records. 
     136    """ 
     137    for r in message.additional: 
     138        if r.type == dns.OPT: 
     139            return True 
     140    return False 
     141 
     142 
     143 
     144class RFC6891Tests(DNSComplianceTestBuilder): 
     145    """ 
     146    Tests for compliance with RFC6891. 
     147 
     148    https://tools.ietf.org/html/rfc6891#section-6.1.1 
     149    """ 
     150    def connectProtocol(self, proto): 
     151        """ 
     152        Connect C{proto} to a listening UDP port and add a cleanup to 
     153        stop the port when the current test finishes. 
     154 
     155        @param proto: A L{twisted.internet.protocols.DatagramProtocol} 
     156            instance. 
     157        """ 
     158        port = reactor.listenUDP(0, proto) 
     159        self.addCleanup(port.stopListening) 
     160 
     161 
     162    def test_611_ednsResponseToEdnsRequest(self): 
     163        """ 
     164        If an OPT record is present in a received request, compliant 
     165        responders MUST include an OPT record in their respective 
     166        responses. 
     167 
     168        https://tools.ietf.org/html/rfc6891#section-6.1.1 
     169        """ 
     170 
     171        def addOptRecord(message): 
     172            message.additional.append(dns._OPTHeader(version=1)) 
     173            return message 
     174 
     175        proto = DNSMessageManglingProtocol( 
     176            controller=None, mangler=addOptRecord) 
     177        self.connectProtocol(proto) 
     178 
     179        d = proto.query(self.server, [dns.Query('.', dns.NS, dns.IN)]) 
     180 
     181        def checkForOpt(message): 
     182            self.assertTrue( 
     183                hasAdditionalOptRecord(message), 
     184                'Message did not contain an OPT record ' 
     185                + 'in its additional section. ' 
     186                + 'rCode: %s, ' % (message.rCode,) 
     187                + 'answers: %s, ' % (message.answers,) 
     188                + 'authority: %s, ' % (message.authority,) 
     189                + 'additional: %s ' % (message.additional,)) 
     190        d.addCallback(checkForOpt) 
     191 
     192        return d 
     193 
     194 
     195    def test_611_formErrOnMultipleOptRecords(self): 
     196        """ 
     197        When an OPT RR is included within any DNS message, it MUST be 
     198        the only OPT RR in that message.  If a query message with more 
     199        than one OPT RR is received, a FORMERR (RCODE=1) MUST be 
     200        returned. 
     201 
     202        https://tools.ietf.org/html/rfc6891#section-6.1.1 
     203        """ 
     204        def addMultipleOptRecord(message): 
     205            message.additional.extend([dns._OPTHeader(), dns._OPTHeader()]) 
     206            return message 
     207 
     208        proto = DNSMessageManglingProtocol( 
     209            controller=None, mangler=addMultipleOptRecord) 
     210        self.connectProtocol(proto) 
     211 
     212        d = proto.query(self.server, [dns.Query('.', dns.NS, dns.IN)]) 
     213 
     214        d.addCallback( 
     215            lambda message: self.assertEqual(message.rCode, dns.EFORMAT)) 
     216 
     217        return d 
     218 
     219 
     220    def test_7_nonEdnsResponseToNonEdnsRequest(self): 
     221        """ 
     222        Lack of presence of an OPT record in a request MUST be taken as an 
     223        indication that the requestor does not implement any part of this 
     224        specification and that the responder MUST NOT include an OPT record 
     225        in its response. 
     226 
     227        https://tools.ietf.org/html/rfc6891#section-7 
     228        """ 
     229 
     230        proto = dns.DNSDatagramProtocol(controller=None) 
     231        self.connectProtocol(proto) 
     232 
     233        d = proto.query(self.server, [dns.Query('.', dns.NS, dns.IN)]) 
     234 
     235        def checkForOpt(message): 
     236            self.assertFalse( 
     237                hasAdditionalOptRecord(message), 
     238                'Message contained an OPT record ' 
     239                + 'in its additional section. ' 
     240                + 'rCode: %s, ' % (message.rCode,) 
     241                + 'answers: %s, ' % (message.answers,) 
     242                + 'authority: %s, ' % (message.authority,) 
     243                + 'additional: %s ' % (message.additional,)) 
     244        d.addCallback(checkForOpt) 
     245 
     246        return d 
     247 
     248 
     249 
     250globals().update( 
     251    RFC6891Tests.makeTestCaseClasses()) 
  • doc/names/examples/txdig.py

     
     1# Copyright (c) Twisted Matrix Laboratories. 
     2# See LICENSE for details. 
     3 
     4""" 
     5A flexible tool for interrogating DNS name servers. 
     6 
     7Example usage: 
     8 txdig -s 8.8.8.8 example.com NS 
     9 
     10This is a usecase for an API with the convenience of client.Resolver 
     11while allowing fine control of the DNS query message. 
     12 
     13I use client.Resolver.queryUDP and queryTCP instead of IResolver 
     14methods because I want to choose the transport protocol and because 
     15these functions return a message instance instead of just the record 
     16sections. 
     17 
     18This example relies on _EDNSMessage, which I've temporarily copied 
     19from ticket:5675 into twisted.names.edns. 
     20 
     21I've also hacked together some supporting classes in that module which 
     22demonstrate how _EDNSMessage can be integrated with the existing 
     23protocol and factory classes with some subclasses. More comments in 
     24edns.py. 
     25""" 
     26 
     27from functools import partial 
     28import re 
     29import sys 
     30 
     31from twisted.internet import task 
     32from twisted.names import dns 
     33from twisted.names.edns import EDNSResolver 
     34from twisted.python import usage 
     35 
     36 
     37 
     38ALL_QUERY_TYPES = dict(dns.QUERY_TYPES.items() + dns.EXT_QUERIES.items()) 
     39 
     40 
     41 
     42class Options(usage.Options): 
     43    """ 
     44    Options based on dig. 
     45    """ 
     46 
     47    synopsis = 'Usage: txdig [OPTIONS] DOMAIN_NAME QUERY_TYPE' 
     48 
     49    optFlags = [ 
     50        ["tcp", None, "Use TCP when querying name servers."], 
     51        ["noedns", None, "Disable EDNS."], 
     52        ["dnssec", None, ("Requests DNSSEC records be sent " 
     53                          "by setting the DNSSEC OK bit (DO) " 
     54                          "in the OPT record in the additional section " 
     55                          "of the query.")], 
     56    ] 
     57 
     58    optParameters = [ 
     59            ["server", "s", '127.0.0.1', 
     60             "The name or IP address of the name server to query.", str], 
     61 
     62            ["port", "p", 53, 
     63             "The port number of the name server to query.", int], 
     64 
     65            ["timeout", "t", 5, 
     66             "The timeout for a query in seconds.", float], 
     67 
     68            ["tries", "T", 3, 
     69             "The number of times to try UDP queries to server.", int], 
     70 
     71            ["edns", None, 0, 
     72             "Specify the EDNS version to query with.", int], 
     73 
     74            ["bufsize", None, 4096, 
     75             "Set the UDP message buffer size advertised using EDNS0.", int], 
     76        ] 
     77 
     78 
     79    def parseArgs(self, queryName='', queryType='ALL_RECORDS'): 
     80        self['queryName'] = queryName 
     81        try: 
     82            self['queryType'] = dns.REV_TYPES[queryType] 
     83        except KeyError: 
     84            raise usage.UsageError( 
     85                'Unrecognised QUERY_TYPE %r. ' % (queryType,) 
     86                + 'Must be one of %r' % (sorted(dns.REV_TYPES.keys()),)) 
     87 
     88 
     89    def postOptions(self): 
     90        if self['noedns']: 
     91            self['edns'] = None 
     92 
     93 
     94 
     95def parseOptions(): 
     96    """ 
     97    Parse command line options and print the full usage message to 
     98    stderr if there are errors. 
     99    """ 
     100    options = Options() 
     101    try: 
     102        options.parseOptions() 
     103    except usage.UsageError as errortext: 
     104        sys.stderr.write(str(options) + '\n') 
     105        sys.stderr.write('ERROR: %s\n' % (errortext,)) 
     106        raise SystemExit(1) 
     107    return options 
     108 
     109 
     110 
     111def formatRecord(record): 
     112    """ 
     113    Format a record and its payload to match the dig long form. 
     114    """ 
     115    line = [] 
     116 
     117    if isinstance(record, dns.Query): 
     118        line.append(';') 
     119 
     120    line.append(record.name.name.ljust(25)) 
     121 
     122    if isinstance(record, dns.RRHeader): 
     123        line.append(str(record.ttl).ljust(6)) 
     124 
     125    line.append( 
     126        dns.QUERY_CLASSES.get( 
     127            record.cls, '(%s)' % (record.cls,)).ljust(5)) 
     128 
     129    line.append( 
     130        ALL_QUERY_TYPES.get( 
     131            record.type, '(%s)' % (record.type,)).ljust(5)) 
     132 
     133    if isinstance(record, dns.RRHeader): 
     134        payload = str(record.payload) 
     135        # Remove the <RECORD_NAME and > from the payload str 
     136        line.append(payload[payload.find(' '):-1]) 
     137 
     138    # Remove the ttl from the payload, its already printed from the RRHeader. 
     139    line = re.sub('\s+ttl=\d+', '', ' '.join(line)) 
     140 
     141    return line 
     142 
     143 
     144 
     145def printMessage(message): 
     146    """ 
     147    Print the sections of a message in dig long form. 
     148    """ 
     149    sections = ("queries", "answers", "authority", "additional") 
     150    print ";; flags:", 
     151    for a in message.showAttributes: 
     152        if a in sections: 
     153            continue 
     154        print '%s: %s,' % (a, getattr(message, a)), 
     155    print 
     156 
     157    for section in sections: 
     158        records = getattr(message, section) 
     159        print ";;", section.upper(), "SECTION:", len(records) 
     160        for r in records: 
     161            print formatRecord(r) 
     162        print 
     163 
     164    print ";; MSG SIZE recvd:", len(message.toStr()) 
     165 
     166    return message 
     167 
     168 
     169 
     170def dig(reactor, queryName='', queryType=dns.ALL_RECORDS, queryClass=dns.IN, 
     171        edns=0, bufsize=4096, dnssec=False, 
     172        tcp=False, timeout=5, tries=3, 
     173        server='127.0.0.1', port=53, **kwargs): 
     174    """ 
     175    Query a DNS server. 
     176    """ 
     177    r = EDNSResolver(servers=[(server, port)], 
     178                     reactor=reactor, 
     179                     ednsVersion=edns, 
     180                     maxSize=bufsize, 
     181                     dnssecOK=dnssec) 
     182 
     183    if tcp: 
     184        queryMethod = partial(r.queryTCP, timeout=timeout) 
     185    else: 
     186        queryMethod = partial(r.queryUDP, timeout=(timeout,) * tries) 
     187 
     188    d = queryMethod(queries=[dns.Query(queryName, queryType, queryClass)]) 
     189 
     190    d.addCallback(printMessage) 
     191 
     192    return d 
     193 
     194 
     195 
     196def main(reactor): 
     197    return dig(reactor, **parseOptions()) 
     198 
     199 
     200 
     201if __name__ == "__main__": 
     202    task.react(main) 
  • twisted/names/edns.py

     
     1# Copyright (c) Twisted Matrix Laboratories. 
     2# See LICENSE for details. 
     3 
     4""" 
     5_EDNSMessage copied from #5675. 
     6 
     7Plus subclasses of dns.DNSDatagramProtocol, dns.DNSProtocol and 
     8client.Resolver which integrate EDNSMessage. 
     9""" 
     10 
     11from twisted.internet import error 
     12from twisted.names import client, dns 
     13from twisted.names.dns import EFORMAT, Message, OPT, _OPTHeader, OP_QUERY 
     14from twisted.python import util as tputil 
     15 
     16 
     17 
     18class EDNSDatagramProtocol(dns.DNSDatagramProtocol): 
     19    """ 
     20    This hack is necessary because dns.DNSDatagramProtocol is 
     21    hardcoded to use dns.Message for building outbound query datagrams 
     22    and for decoding incoming datagrams. 
     23 
     24    It would be easier to integrate new EDNS components if DNS 
     25    protocols had a convenient way of specifying an alternative 
     26    message factory. 
     27    """ 
     28    def __init__(self, *args, **kwargs): 
     29        """ 
     30        This seems ugly too. If I could provide a messageFactory 
     31        function, these EDNSMessage arguments needn't be passed 
     32        explicitly to the DNS protocols. Instead just pass 
     33        partial(EDNSMessage, ednsVersion=x, maxSize=y). 
     34        """ 
     35        self.ednsVersion = kwargs.pop('ednsVersion', 0) 
     36        self.maxSize = kwargs.pop('maxSize', 4096) 
     37        self.dnssecOK = kwargs.pop('dnssecOK', False) 
     38 
     39        dns.DNSDatagramProtocol.__init__(self, *args, **kwargs) 
     40 
     41 
     42    def writeMessage(self, message, address): 
     43        """ 
     44        Again, this is a hack, but it demonstrates the usefulness of 
     45        _EDNSMessage.fromMessage for wrapping dns.Message. 
     46 
     47        It might be convenient if I could provide EDNS specific 
     48        keyword arguments to fromMessage - ednsVersion, maxSize, etc. 
     49        """ 
     50        message = _EDNSMessage.fromMessage(message) 
     51 
     52        message.ednsVersion = self.ednsVersion 
     53        message.maxSize = self.maxSize 
     54        message.dnssecOK = self.dnssecOK 
     55 
     56        return dns.DNSDatagramProtocol.writeMessage(self, message, address) 
     57 
     58 
     59    def _query(self, *args, **kwargs): 
     60        d = dns.DNSDatagramProtocol._query(self, *args, **kwargs) 
     61 
     62        return d.addCallback(_EDNSMessage.fromMessage) 
     63 
     64 
     65 
     66class EDNSStreamProtocol(dns.DNSProtocol): 
     67    """ 
     68    See comments for EDNSDatagramProtocol. 
     69 
     70    It's a shame we have to duplicate the same hacks for the TCP DNS 
     71    protocol. 
     72 
     73    If DNSDatagramProtocol used connected UDP instead, there would be 
     74    less difference between the UDP and TCP protocols eg writeMessage 
     75    would have a consistent signature and maybe this duplication 
     76    wouldn't be necessary. 
     77    """ 
     78    def __init__(self, *args, **kwargs): 
     79        self.ednsVersion = kwargs.pop('ednsVersion', 0) 
     80        self.maxSize = kwargs.pop('maxSize', 4096) 
     81        self.dnssecOK = kwargs.pop('dnssecOK', False) 
     82 
     83        dns.DNSProtocol.__init__(self, *args, **kwargs) 
     84 
     85 
     86    def writeMessage(self, message): 
     87        message = _EDNSMessage.fromMessage(message) 
     88        message.ednsVersion = self.controller.ednsVersion 
     89        message.maxSize = self.controller.maxSize 
     90        message.dnssecOK = self.controller.dnssecOK 
     91 
     92        return dns.DNSProtocol.writeMessage(self, message) 
     93 
     94 
     95    def _query(self, *args, **kwargs): 
     96        d = dns.DNSProtocol._query(self, *args, **kwargs) 
     97        d.addCallback(_EDNSMessage.fromMessage) 
     98        return d 
     99 
     100 
     101 
     102class EDNSClientFactory(client.DNSClientFactory): 
     103    def buildProtocol(self, addr): 
     104        p = EDNSStreamProtocol(controller=self.controller) 
     105        p.factory = self 
     106        return p 
     107 
     108 
     109 
     110class EDNSResolver(client.Resolver): 
     111    """ 
     112    client.Resolver is hardcoded to use dns.DNSDatagramProtcol and 
     113    dns.DNSProtocol (via client.DNSClientFactory). 
     114 
     115    It would be nice if I could specify dnsDatagramProtocolFactory and 
     116    dnsStreamProtocolFactory as arguments to client.Resolver. 
     117 
     118    Also need to consider whether client.Resolver is a suitable place 
     119    to do EDNS buffer size detection. 
     120 
     121    The IResolver methods of client.Resolver currently respond to 
     122    truncated UDP messages by issuing a follow up TCP query. 
     123 
     124    In addition they could respond to timeouts by re-issue a UDP query 
     125    with a smaller advertised EDNS buffersize. 
     126 
     127    See 
     128     * https://tools.ietf.org/html/rfc6891#section-6.2.2 
     129     * https://www.dns-oarc.net/oarc/services/replysizetest 
     130    """ 
     131    def __init__(self, *args, **kwargs): 
     132        self.ednsVersion = kwargs.pop('ednsVersion', 0) 
     133        self.maxSize = kwargs.pop('maxSize', 4096) 
     134        self.dnssecOK = kwargs.pop('dnssecOK', False) 
     135 
     136        client.Resolver.__init__(self, *args, **kwargs) 
     137 
     138        self.factory = EDNSClientFactory(self, self.timeout) 
     139 
     140 
     141    def _connectedProtocol(self): 
     142        proto = EDNSDatagramProtocol( 
     143            ednsVersion=self.ednsVersion, 
     144            maxSize=self.maxSize, 
     145            dnssecOK=self.dnssecOK, 
     146            controller=self, 
     147            reactor=self._reactor) 
     148 
     149        while True: 
     150            try: 
     151                self._reactor.listenUDP(dns.randomSource(), proto) 
     152            except error.CannotListenError: 
     153                pass 
     154            else: 
     155                return proto 
     156 
     157 
     158 
     159class _EDNSMessage(tputil.FancyStrMixin, tputil.FancyEqMixin, object): 
     160    """ 
     161    An C{EDNS} message. 
     162 
     163    Designed for compatibility with L{Message} but with a narrower 
     164    public interface. 
     165 
     166    Most importantly, L{_EDNSMessage.fromStr} will interpret and 
     167    remove OPT records that are present in the additional records 
     168    section. 
     169 
     170    The OPT records are used to populate certain EDNS specific 
     171    attributes. 
     172 
     173    L{_EDNSMessage.toStr} will add suitable OPT records to the 
     174    additional section to represent the extended EDNS information. 
     175 
     176    @see: U{https://tools.ietf.org/html/rfc6891} 
     177 
     178    @ivar id: A 16 bit identifier assigned by the program that 
     179        generates any kind of query.  This identifier is copied the 
     180        corresponding reply and can be used by the requester to match 
     181        up replies to outstanding queries. 
     182 
     183    @ivar answer: A one bit field that specifies whether this message 
     184        is a query (0), or a response (1). 
     185 
     186    @ivar opCode: A four bit field that specifies kind of query in 
     187        this message.  This value is set by the originator of a query 
     188        and copied into the response.  The values are: 
     189                0               a standard query (QUERY) 
     190                1               an inverse query (IQUERY) 
     191                2               a server status request (STATUS) 
     192                3-15            reserved for future use 
     193 
     194    @ivar auth: Authoritative Answer - this bit is valid in responses, 
     195        and specifies that the responding name server is an authority 
     196        for the domain name in question section. 
     197 
     198    @ivar trunc: TrunCation - specifies that this message was 
     199        truncated due to length greater than that permitted on the 
     200        transmission channel. 
     201 
     202    @ivar recDes: Recursion Desired - this bit may be set in a query 
     203        and is copied into the response.  If RD is set, it directs the 
     204        name server to pursue the query recursively.  Recursive query 
     205        support is optional. 
     206 
     207    @ivar recAv: Recursion Available - this be is set or cleared in a 
     208        response, and denotes whether recursive query support is 
     209        available in the name server. 
     210 
     211    @ivar rCode: Response code - this 4 bit field is set as part of 
     212        responses.  The values have the following interpretation: 
     213                0               No error condition 
     214 
     215                1               Format error - The name server was 
     216                                unable to interpret the query. 
     217                2               Server failure - The name server was 
     218                                unable to process this query due to a 
     219                                problem with the name server. 
     220 
     221                3               Name Error - Meaningful only for 
     222                                responses from an authoritative name 
     223                                server, this code signifies that the 
     224                                domain name referenced in the query does 
     225                                not exist. 
     226 
     227                4               Not Implemented - The name server does 
     228                                not support the requested kind of query. 
     229 
     230                5               Refused - The name server refuses to 
     231                                perform the specified operation for 
     232                                policy reasons.  For example, a name 
     233                                server may not wish to provide the 
     234                                information to the particular requester, 
     235                                or a name server may not wish to perform 
     236                                a particular operation (e.g., zone 
     237                                transfer) for particular data. 
     238 
     239    @ivar ednsVersion: Indicates the EDNS implementation level. Set to 
     240        C{None} to prevent any EDNS attributes and options being added 
     241        to the encoded byte string. 
     242 
     243    @ivar queries: A L{list} of L{Query} instances. 
     244 
     245    @ivar answers: A L{list} of L{RRHeader} instances. 
     246 
     247    @ivar authority: A L{list} of L{RRHeader} instances. 
     248 
     249    @ivar additional: A L{list} of L{RRHeader} instances. 
     250    """ 
     251 
     252    showAttributes = ( 
     253        'id', 'answer', 'opCode', 'auth', 'trunc', 
     254        'recDes', 'recAv', 'rCode', 'ednsVersion', 'dnssecOK', 
     255        'maxSize', 
     256        'queries', 'answers', 'authority', 'additional') 
     257 
     258    compareAttributes = showAttributes 
     259 
     260    def __init__(self, id=0, answer=0, 
     261                 opCode=OP_QUERY, auth=0, 
     262                 trunc=0, recDes=0, 
     263                 recAv=0, rCode=0, ednsVersion=0, dnssecOK=False, maxSize=512, 
     264                 queries=None, answers=None, authority=None, additional=None): 
     265        """ 
     266        All arguments are stored as attributes with the same names. 
     267 
     268        @see: L{_EDNSMessage} for an explanation of the meaning of 
     269            each attribute. 
     270 
     271        @type id: C{int} 
     272        @type answer: C{int} 
     273        @type opCode: C{int} 
     274        @type auth: C{int} 
     275        @type trunc: C{int} 
     276        @type recDes: C{int} 
     277        @type recAv: C{int} 
     278        @type rCode: C{int} 
     279        @type ednsVersion: C{int} or C{None} 
     280        @type queries: C{list} of L{Query} 
     281        @type answers: C{list} of L{RRHeader} 
     282        @type authority: C{list} of L{RRHeader} 
     283        @type additional: C{list} of L{RRHeader} 
     284        """ 
     285        self.id = id 
     286        self.answer = answer 
     287        self.opCode = opCode 
     288 
     289        # XXX: AA bit can be determined by checking for an 
     290        # authoritative answer record whose name matches the query 
     291        # name - perhaps in a higher level EDNSResponse class? 
     292        self.auth = auth 
     293 
     294        # XXX: TC bit can be determined during encoding based on EDNS max 
     295        # packet size. 
     296        self.trunc = trunc 
     297 
     298        self.recDes = recDes 
     299        self.recAv = recAv 
     300        self.rCode = rCode 
     301        self.ednsVersion = ednsVersion 
     302        self.dnssecOK = dnssecOK 
     303        self.maxSize = maxSize 
     304 
     305        self.queries = queries or [] 
     306        self.answers = answers or [] 
     307        self.authority = authority or [] 
     308        self.additional = additional or [] 
     309 
     310        self._decodingErrors = [] 
     311 
     312 
     313    def toStr(self): 
     314        """ 
     315        Encode to wire format. 
     316 
     317        If C{ednsVersion} is not None, an L{_OPTHeader} instance 
     318        containing all the I{EDNS} specific attributes and options 
     319        will be appended to the list of C{additional} records and this 
     320        will be encoded into the byte string as an C{OPT} record byte 
     321        string. 
     322 
     323        @return: A L{bytes} string. 
     324        """ 
     325        m = Message( 
     326            id=self.id, 
     327            answer=self.answer, 
     328            opCode=self.opCode, 
     329            auth=self.auth, 
     330            trunc=self.trunc, 
     331            recDes=self.recDes, 
     332            recAv=self.recAv, 
     333            rCode=self.rCode, 
     334            maxSize=self.maxSize) 
     335 
     336        m.queries = list(self.queries) 
     337        m.answers = list(self.answers) 
     338        m.authority = list(self.authority) 
     339        m.additional = list(self.additional) 
     340 
     341        if self.ednsVersion is not None: 
     342            o = _OPTHeader(version=self.ednsVersion, 
     343                           udpPayloadSize=self.maxSize, 
     344                           dnssecOK=self.dnssecOK) 
     345            m.additional.append(o) 
     346 
     347        return m.toStr() 
     348 
     349 
     350    @classmethod 
     351    def fromMessage(cls, message): 
     352        """ 
     353        Construct and return a new L(_EDNSMessage} whose attributes 
     354        and records are derived from the attributes and records of 
     355        C{message} (a L{Message} instance) 
     356 
     357        If present, an I{OPT} record will be extracted from the 
     358        C{additional} section and its attributes and options will be 
     359        used to set the EDNS specific attributes C{extendedRCODE}, 
     360        c{ednsVersion}, c{dnssecOK}, c{ednsOptions}. 
     361 
     362        The C{extendedRCODE} will be combined with C{message.rCode} 
     363        and assigned to C{self.rCode}. 
     364 
     365        If multiple I{OPT} records are found, this is considered an 
     366        error and no EDNS specific attributes will be 
     367        set. Additionally, an L{EFORMAT} error will be appended to 
     368        C{_decodingErrors}. 
     369        """ 
     370        additional = [] 
     371        optRecords = [] 
     372        for r in message.additional: 
     373            if r.type == OPT: 
     374                optRecords.append(_OPTHeader.fromRRHeader(r)) 
     375            else: 
     376                additional.append(r) 
     377 
     378        newMessage = cls( 
     379            id=message.id, 
     380            answer=message.answer, 
     381            opCode=message.opCode, 
     382            auth=message.auth, 
     383            trunc=message.trunc, 
     384            recDes=message.recDes, 
     385            recAv=message.recAv, 
     386            rCode=message.rCode, 
     387            # Default to None, it will be updated later when the OPT 
     388            # records are parsed. 
     389            ednsVersion=None, 
     390            queries=list(message.queries), 
     391            answers=list(message.answers), 
     392            authority=list(message.authority), 
     393            additional=additional, 
     394            ) 
     395 
     396        if optRecords: 
     397            if len(optRecords) > 1: 
     398                newMessage._decodingErrors.append(EFORMAT) 
     399            else: 
     400                opt = optRecords[0] 
     401                newMessage.ednsVersion = opt.version 
     402                newMessage.maxSize = opt.udpPayloadSize 
     403                newMessage.dnssecOK = opt.dnssecOK 
     404 
     405        return newMessage 
     406 
     407 
     408    def fromStr(self, bytes): 
     409        """ 
     410        Decode from wire format, saving flags, values and records to 
     411        this L{_EDNSMessage} instance in place. 
     412 
     413        @type bytes: L{bytes} 
     414        @param bytes: The full byte string to be decoded. 
     415        """ 
     416        m = Message() 
     417        m.fromStr(bytes) 
     418 
     419        ednsMessage = self.fromMessage(m) 
     420        self.__dict__ = ednsMessage.__dict__