Ticket #5668: opt-record-5668-examples.patch
File opt-record-5668-examples.patch, 30.7 KB (added by , 6 years ago) |
---|
-
doc/names/examples/edns_server.tac.py
1 # Copyright (c) Twisted Matrix Laboratories. 2 # See LICENSE for details. 3 4 """ 5 A DNS server which replies NXDOMAIN to all queries. 6 7 Usage: twistd -noy doc/names/examples/edns_auth_server.tac.py 8 9 This server uses the protocol hacks from edns.py 10 11 The important thing is that because messages are decoded using 12 EDNSMessage rather than dns.Message, OPT records are extracted from 13 the additional section of EDNS query messages during decoding. 14 15 This is one way of fixing #6645. 16 17 Additionally we force ednsVersion=None so that the server doesn't 18 respond with any OPT records. 19 Although RFC6891-7 suggests that the correct response should be FORMERR. 20 * https://tools.ietf.org/html/rfc6891#section-7 21 22 Ultimately, DNSServerFactory will need modifying or replacing so that 23 it can dynamically respond using the correct EDNS settings and RCODE 24 based on the client request. 25 26 EDNSMessage will also need to be made aware of RRSets so that it can 27 correctly limit the size of (or truncate) responses based on the 28 chosen maxSize. 29 * https://twistedmatrix.com/trac/wiki/EDNS0#Selectivetruncate 30 """ 31 32 from functools import partial 33 34 from twisted.application.internet import TCPServer, UDPServer 35 from twisted.application.service import Application, MultiService 36 37 from twisted.names import edns, server 38 39 40 41 PORT = 10053 42 EDNS_VERSION = None 43 44 45 def 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 63 application = Application("An EDNS aware noop DNS server") 64 65 66 67 makeService().setServiceParent(application) -
doc/names/examples/test_edns_compliance.py
1 # Copyright (c) Twisted Matrix Laboratories. 2 # See LICENSE for details. 3 4 """ 5 An example trial test module which demonstrates how the low level 6 L{dns._OPTHeader} class can be used for testing DNS servers for 7 compliance with DNS RFCs. 8 9 This 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 18 import os 19 20 from twisted.internet import reactor 21 from twisted.names import dns 22 from twisted.trial import unittest 23 24 25 26 class 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 57 def 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 84 SERVERS = [ 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 109 class 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 132 def 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 144 class 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 250 globals().update( 251 RFC6891Tests.makeTestCaseClasses()) -
doc/names/examples/txdig.py
1 # Copyright (c) Twisted Matrix Laboratories. 2 # See LICENSE for details. 3 4 """ 5 A flexible tool for interrogating DNS name servers. 6 7 Example usage: 8 txdig -s 8.8.8.8 example.com NS 9 10 This is a usecase for an API with the convenience of client.Resolver 11 while allowing fine control of the DNS query message. 12 13 I use client.Resolver.queryUDP and queryTCP instead of IResolver 14 methods because I want to choose the transport protocol and because 15 these functions return a message instance instead of just the record 16 sections. 17 18 This example relies on _EDNSMessage, which I've temporarily copied 19 from ticket:5675 into twisted.names.edns. 20 21 I've also hacked together some supporting classes in that module which 22 demonstrate how _EDNSMessage can be integrated with the existing 23 protocol and factory classes with some subclasses. More comments in 24 edns.py. 25 """ 26 27 from functools import partial 28 import re 29 import sys 30 31 from twisted.internet import task 32 from twisted.names import dns 33 from twisted.names.edns import EDNSResolver 34 from twisted.python import usage 35 36 37 38 ALL_QUERY_TYPES = dict(dns.QUERY_TYPES.items() + dns.EXT_QUERIES.items()) 39 40 41 42 class 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 95 def 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 111 def 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 145 def 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 170 def 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 196 def main(reactor): 197 return dig(reactor, **parseOptions()) 198 199 200 201 if __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 7 Plus subclasses of dns.DNSDatagramProtocol, dns.DNSProtocol and 8 client.Resolver which integrate EDNSMessage. 9 """ 10 11 from twisted.internet import error 12 from twisted.names import client, dns 13 from twisted.names.dns import EFORMAT, Message, OPT, _OPTHeader, OP_QUERY 14 from twisted.python import util as tputil 15 16 17 18 class 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 66 class 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 102 class EDNSClientFactory(client.DNSClientFactory): 103 def buildProtocol(self, addr): 104 p = EDNSStreamProtocol(controller=self.controller) 105 p.factory = self 106 return p 107 108 109 110 class 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 159 class _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__