Package dns :: Module message
[hide private]
[frames] | no frames]

Source Code for Module dns.message

   1  # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. 
   2  # 
   3  # Permission to use, copy, modify, and distribute this software and its 
   4  # documentation for any purpose with or without fee is hereby granted, 
   5  # provided that the above copyright notice and this permission notice 
   6  # appear in all copies. 
   7  # 
   8  # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 
   9  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
  10  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 
  11  # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
  12  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
  13  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 
  14  # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
  15   
  16  """DNS Messages""" 
  17   
  18  from __future__ import absolute_import 
  19   
  20  from io import StringIO 
  21  import struct 
  22  import sys 
  23  import time 
  24   
  25  import dns.edns 
  26  import dns.exception 
  27  import dns.flags 
  28  import dns.name 
  29  import dns.opcode 
  30  import dns.entropy 
  31  import dns.rcode 
  32  import dns.rdata 
  33  import dns.rdataclass 
  34  import dns.rdatatype 
  35  import dns.rrset 
  36  import dns.renderer 
  37  import dns.tsig 
  38  import dns.wiredata 
  39   
  40  from ._compat import long, xrange, string_types 
  41   
  42   
43 -class ShortHeader(dns.exception.FormError):
44 45 """The DNS packet passed to from_wire() is too short."""
46 47
48 -class TrailingJunk(dns.exception.FormError):
49 50 """The DNS packet passed to from_wire() has extra junk at the end of it."""
51 52
53 -class UnknownHeaderField(dns.exception.DNSException):
54 55 """The header field name was not recognized when converting from text 56 into a message."""
57 58
59 -class BadEDNS(dns.exception.FormError):
60 61 """OPT record occurred somewhere other than the start of 62 the additional data section."""
63 64
65 -class BadTSIG(dns.exception.FormError):
66 67 """A TSIG record occurred somewhere other than the end of 68 the additional data section."""
69 70
71 -class UnknownTSIGKey(dns.exception.DNSException):
72 73 """A TSIG with an unknown key was received."""
74 75
76 -class Message(object):
77 78 """A DNS message. 79 80 @ivar id: The query id; the default is a randomly chosen id. 81 @type id: int 82 @ivar flags: The DNS flags of the message. @see: RFC 1035 for an 83 explanation of these flags. 84 @type flags: int 85 @ivar question: The question section. 86 @type question: list of dns.rrset.RRset objects 87 @ivar answer: The answer section. 88 @type answer: list of dns.rrset.RRset objects 89 @ivar authority: The authority section. 90 @type authority: list of dns.rrset.RRset objects 91 @ivar additional: The additional data section. 92 @type additional: list of dns.rrset.RRset objects 93 @ivar edns: The EDNS level to use. The default is -1, no Edns. 94 @type edns: int 95 @ivar ednsflags: The EDNS flags 96 @type ednsflags: long 97 @ivar payload: The EDNS payload size. The default is 0. 98 @type payload: int 99 @ivar options: The EDNS options 100 @type options: list of dns.edns.Option objects 101 @ivar request_payload: The associated request's EDNS payload size. 102 @type request_payload: int 103 @ivar keyring: The TSIG keyring to use. The default is None. 104 @type keyring: dict 105 @ivar keyname: The TSIG keyname to use. The default is None. 106 @type keyname: dns.name.Name object 107 @ivar keyalgorithm: The TSIG algorithm to use; defaults to 108 dns.tsig.default_algorithm. Constants for TSIG algorithms are defined 109 in dns.tsig, and the currently implemented algorithms are 110 HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and 111 HMAC_SHA512. 112 @type keyalgorithm: string 113 @ivar request_mac: The TSIG MAC of the request message associated with 114 this message; used when validating TSIG signatures. @see: RFC 2845 for 115 more information on TSIG fields. 116 @type request_mac: string 117 @ivar fudge: TSIG time fudge; default is 300 seconds. 118 @type fudge: int 119 @ivar original_id: TSIG original id; defaults to the message's id 120 @type original_id: int 121 @ivar tsig_error: TSIG error code; default is 0. 122 @type tsig_error: int 123 @ivar other_data: TSIG other data. 124 @type other_data: string 125 @ivar mac: The TSIG MAC for this message. 126 @type mac: string 127 @ivar xfr: Is the message being used to contain the results of a DNS 128 zone transfer? The default is False. 129 @type xfr: bool 130 @ivar origin: The origin of the zone in messages which are used for 131 zone transfers or for DNS dynamic updates. The default is None. 132 @type origin: dns.name.Name object 133 @ivar tsig_ctx: The TSIG signature context associated with this 134 message. The default is None. 135 @type tsig_ctx: hmac.HMAC object 136 @ivar had_tsig: Did the message decoded from wire format have a TSIG 137 signature? 138 @type had_tsig: bool 139 @ivar multi: Is this message part of a multi-message sequence? The 140 default is false. This variable is used when validating TSIG signatures 141 on messages which are part of a zone transfer. 142 @type multi: bool 143 @ivar first: Is this message standalone, or the first of a multi 144 message sequence? This variable is used when validating TSIG signatures 145 on messages which are part of a zone transfer. 146 @type first: bool 147 @ivar index: An index of rrsets in the message. The index key is 148 (section, name, rdclass, rdtype, covers, deleting). Indexing can be 149 disabled by setting the index to None. 150 @type index: dict 151 """ 152
153 - def __init__(self, id=None):
154 if id is None: 155 self.id = dns.entropy.random_16() 156 else: 157 self.id = id 158 self.flags = 0 159 self.question = [] 160 self.answer = [] 161 self.authority = [] 162 self.additional = [] 163 self.edns = -1 164 self.ednsflags = 0 165 self.payload = 0 166 self.options = [] 167 self.request_payload = 0 168 self.keyring = None 169 self.keyname = None 170 self.keyalgorithm = dns.tsig.default_algorithm 171 self.request_mac = '' 172 self.other_data = '' 173 self.tsig_error = 0 174 self.fudge = 300 175 self.original_id = self.id 176 self.mac = '' 177 self.xfr = False 178 self.origin = None 179 self.tsig_ctx = None 180 self.had_tsig = False 181 self.multi = False 182 self.first = True 183 self.index = {}
184
185 - def __repr__(self):
186 return '<DNS message, ID ' + repr(self.id) + '>'
187
188 - def __str__(self):
189 return self.to_text()
190
191 - def to_text(self, origin=None, relativize=True, **kw):
192 """Convert the message to text. 193 194 The I{origin}, I{relativize}, and any other keyword 195 arguments are passed to the rrset to_wire() method. 196 197 @rtype: string 198 """ 199 200 s = StringIO() 201 s.write(u'id %d\n' % self.id) 202 s.write(u'opcode %s\n' % 203 dns.opcode.to_text(dns.opcode.from_flags(self.flags))) 204 rc = dns.rcode.from_flags(self.flags, self.ednsflags) 205 s.write(u'rcode %s\n' % dns.rcode.to_text(rc)) 206 s.write(u'flags %s\n' % dns.flags.to_text(self.flags)) 207 if self.edns >= 0: 208 s.write(u'edns %s\n' % self.edns) 209 if self.ednsflags != 0: 210 s.write(u'eflags %s\n' % 211 dns.flags.edns_to_text(self.ednsflags)) 212 s.write(u'payload %d\n' % self.payload) 213 is_update = dns.opcode.is_update(self.flags) 214 if is_update: 215 s.write(u';ZONE\n') 216 else: 217 s.write(u';QUESTION\n') 218 for rrset in self.question: 219 s.write(rrset.to_text(origin, relativize, **kw)) 220 s.write(u'\n') 221 if is_update: 222 s.write(u';PREREQ\n') 223 else: 224 s.write(u';ANSWER\n') 225 for rrset in self.answer: 226 s.write(rrset.to_text(origin, relativize, **kw)) 227 s.write(u'\n') 228 if is_update: 229 s.write(u';UPDATE\n') 230 else: 231 s.write(u';AUTHORITY\n') 232 for rrset in self.authority: 233 s.write(rrset.to_text(origin, relativize, **kw)) 234 s.write(u'\n') 235 s.write(u';ADDITIONAL\n') 236 for rrset in self.additional: 237 s.write(rrset.to_text(origin, relativize, **kw)) 238 s.write(u'\n') 239 # 240 # We strip off the final \n so the caller can print the result without 241 # doing weird things to get around eccentricities in Python print 242 # formatting 243 # 244 return s.getvalue()[:-1]
245
246 - def __eq__(self, other):
247 """Two messages are equal if they have the same content in the 248 header, question, answer, and authority sections. 249 @rtype: bool""" 250 if not isinstance(other, Message): 251 return False 252 if self.id != other.id: 253 return False 254 if self.flags != other.flags: 255 return False 256 for n in self.question: 257 if n not in other.question: 258 return False 259 for n in other.question: 260 if n not in self.question: 261 return False 262 for n in self.answer: 263 if n not in other.answer: 264 return False 265 for n in other.answer: 266 if n not in self.answer: 267 return False 268 for n in self.authority: 269 if n not in other.authority: 270 return False 271 for n in other.authority: 272 if n not in self.authority: 273 return False 274 return True
275
276 - def __ne__(self, other):
277 """Are two messages not equal? 278 @rtype: bool""" 279 return not self.__eq__(other)
280
281 - def is_response(self, other):
282 """Is other a response to self? 283 @rtype: bool""" 284 if other.flags & dns.flags.QR == 0 or \ 285 self.id != other.id or \ 286 dns.opcode.from_flags(self.flags) != \ 287 dns.opcode.from_flags(other.flags): 288 return False 289 if dns.rcode.from_flags(other.flags, other.ednsflags) != \ 290 dns.rcode.NOERROR: 291 return True 292 if dns.opcode.is_update(self.flags): 293 return True 294 for n in self.question: 295 if n not in other.question: 296 return False 297 for n in other.question: 298 if n not in self.question: 299 return False 300 return True
301
302 - def section_number(self, section):
303 if section is self.question: 304 return 0 305 elif section is self.answer: 306 return 1 307 elif section is self.authority: 308 return 2 309 elif section is self.additional: 310 return 3 311 else: 312 raise ValueError('unknown section')
313
314 - def find_rrset(self, section, name, rdclass, rdtype, 315 covers=dns.rdatatype.NONE, deleting=None, create=False, 316 force_unique=False):
317 """Find the RRset with the given attributes in the specified section. 318 319 @param section: the section of the message to look in, e.g. 320 self.answer. 321 @type section: list of dns.rrset.RRset objects 322 @param name: the name of the RRset 323 @type name: dns.name.Name object 324 @param rdclass: the class of the RRset 325 @type rdclass: int 326 @param rdtype: the type of the RRset 327 @type rdtype: int 328 @param covers: the covers value of the RRset 329 @type covers: int 330 @param deleting: the deleting value of the RRset 331 @type deleting: int 332 @param create: If True, create the RRset if it is not found. 333 The created RRset is appended to I{section}. 334 @type create: bool 335 @param force_unique: If True and create is also True, create a 336 new RRset regardless of whether a matching RRset exists already. 337 @type force_unique: bool 338 @raises KeyError: the RRset was not found and create was False 339 @rtype: dns.rrset.RRset object""" 340 341 key = (self.section_number(section), 342 name, rdclass, rdtype, covers, deleting) 343 if not force_unique: 344 if self.index is not None: 345 rrset = self.index.get(key) 346 if rrset is not None: 347 return rrset 348 else: 349 for rrset in section: 350 if rrset.match(name, rdclass, rdtype, covers, deleting): 351 return rrset 352 if not create: 353 raise KeyError 354 rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting) 355 section.append(rrset) 356 if self.index is not None: 357 self.index[key] = rrset 358 return rrset
359
360 - def get_rrset(self, section, name, rdclass, rdtype, 361 covers=dns.rdatatype.NONE, deleting=None, create=False, 362 force_unique=False):
363 """Get the RRset with the given attributes in the specified section. 364 365 If the RRset is not found, None is returned. 366 367 @param section: the section of the message to look in, e.g. 368 self.answer. 369 @type section: list of dns.rrset.RRset objects 370 @param name: the name of the RRset 371 @type name: dns.name.Name object 372 @param rdclass: the class of the RRset 373 @type rdclass: int 374 @param rdtype: the type of the RRset 375 @type rdtype: int 376 @param covers: the covers value of the RRset 377 @type covers: int 378 @param deleting: the deleting value of the RRset 379 @type deleting: int 380 @param create: If True, create the RRset if it is not found. 381 The created RRset is appended to I{section}. 382 @type create: bool 383 @param force_unique: If True and create is also True, create a 384 new RRset regardless of whether a matching RRset exists already. 385 @type force_unique: bool 386 @rtype: dns.rrset.RRset object or None""" 387 388 try: 389 rrset = self.find_rrset(section, name, rdclass, rdtype, covers, 390 deleting, create, force_unique) 391 except KeyError: 392 rrset = None 393 return rrset
394
395 - def to_wire(self, origin=None, max_size=0, **kw):
396 """Return a string containing the message in DNS compressed wire 397 format. 398 399 Additional keyword arguments are passed to the rrset to_wire() 400 method. 401 402 @param origin: The origin to be appended to any relative names. 403 @type origin: dns.name.Name object 404 @param max_size: The maximum size of the wire format output; default 405 is 0, which means 'the message's request payload, if nonzero, or 406 65536'. 407 @type max_size: int 408 @raises dns.exception.TooBig: max_size was exceeded 409 @rtype: string 410 """ 411 412 if max_size == 0: 413 if self.request_payload != 0: 414 max_size = self.request_payload 415 else: 416 max_size = 65535 417 if max_size < 512: 418 max_size = 512 419 elif max_size > 65535: 420 max_size = 65535 421 r = dns.renderer.Renderer(self.id, self.flags, max_size, origin) 422 for rrset in self.question: 423 r.add_question(rrset.name, rrset.rdtype, rrset.rdclass) 424 for rrset in self.answer: 425 r.add_rrset(dns.renderer.ANSWER, rrset, **kw) 426 for rrset in self.authority: 427 r.add_rrset(dns.renderer.AUTHORITY, rrset, **kw) 428 if self.edns >= 0: 429 r.add_edns(self.edns, self.ednsflags, self.payload, self.options) 430 for rrset in self.additional: 431 r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw) 432 r.write_header() 433 if self.keyname is not None: 434 r.add_tsig(self.keyname, self.keyring[self.keyname], 435 self.fudge, self.original_id, self.tsig_error, 436 self.other_data, self.request_mac, 437 self.keyalgorithm) 438 self.mac = r.mac 439 return r.get_wire()
440
441 - def use_tsig(self, keyring, keyname=None, fudge=300, 442 original_id=None, tsig_error=0, other_data='', 443 algorithm=dns.tsig.default_algorithm):
444 """When sending, a TSIG signature using the specified keyring 445 and keyname should be added. 446 447 @param keyring: The TSIG keyring to use; defaults to None. 448 @type keyring: dict 449 @param keyname: The name of the TSIG key to use; defaults to None. 450 The key must be defined in the keyring. If a keyring is specified 451 but a keyname is not, then the key used will be the first key in the 452 keyring. Note that the order of keys in a dictionary is not defined, 453 so applications should supply a keyname when a keyring is used, unless 454 they know the keyring contains only one key. 455 @type keyname: dns.name.Name or string 456 @param fudge: TSIG time fudge; default is 300 seconds. 457 @type fudge: int 458 @param original_id: TSIG original id; defaults to the message's id 459 @type original_id: int 460 @param tsig_error: TSIG error code; default is 0. 461 @type tsig_error: int 462 @param other_data: TSIG other data. 463 @type other_data: string 464 @param algorithm: The TSIG algorithm to use; defaults to 465 dns.tsig.default_algorithm 466 """ 467 468 self.keyring = keyring 469 if keyname is None: 470 self.keyname = list(self.keyring.keys())[0] 471 else: 472 if isinstance(keyname, string_types): 473 keyname = dns.name.from_text(keyname) 474 self.keyname = keyname 475 self.keyalgorithm = algorithm 476 self.fudge = fudge 477 if original_id is None: 478 self.original_id = self.id 479 else: 480 self.original_id = original_id 481 self.tsig_error = tsig_error 482 self.other_data = other_data
483
484 - def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None, 485 options=None):
486 """Configure EDNS behavior. 487 @param edns: The EDNS level to use. Specifying None, False, or -1 488 means 'do not use EDNS', and in this case the other parameters are 489 ignored. Specifying True is equivalent to specifying 0, i.e. 'use 490 EDNS0'. 491 @type edns: int or bool or None 492 @param ednsflags: EDNS flag values. 493 @type ednsflags: int 494 @param payload: The EDNS sender's payload field, which is the maximum 495 size of UDP datagram the sender can handle. 496 @type payload: int 497 @param request_payload: The EDNS payload size to use when sending 498 this message. If not specified, defaults to the value of payload. 499 @type request_payload: int or None 500 @param options: The EDNS options 501 @type options: None or list of dns.edns.Option objects 502 @see: RFC 2671 503 """ 504 if edns is None or edns is False: 505 edns = -1 506 if edns is True: 507 edns = 0 508 if request_payload is None: 509 request_payload = payload 510 if edns < 0: 511 ednsflags = 0 512 payload = 0 513 request_payload = 0 514 options = [] 515 else: 516 # make sure the EDNS version in ednsflags agrees with edns 517 ednsflags &= long(0xFF00FFFF) 518 ednsflags |= (edns << 16) 519 if options is None: 520 options = [] 521 self.edns = edns 522 self.ednsflags = ednsflags 523 self.payload = payload 524 self.options = options 525 self.request_payload = request_payload
526
527 - def want_dnssec(self, wanted=True):
528 """Enable or disable 'DNSSEC desired' flag in requests. 529 @param wanted: Is DNSSEC desired? If True, EDNS is enabled if 530 required, and then the DO bit is set. If False, the DO bit is 531 cleared if EDNS is enabled. 532 @type wanted: bool 533 """ 534 if wanted: 535 if self.edns < 0: 536 self.use_edns() 537 self.ednsflags |= dns.flags.DO 538 elif self.edns >= 0: 539 self.ednsflags &= ~dns.flags.DO
540
541 - def rcode(self):
542 """Return the rcode. 543 @rtype: int 544 """ 545 return dns.rcode.from_flags(self.flags, self.ednsflags)
546
547 - def set_rcode(self, rcode):
548 """Set the rcode. 549 @param rcode: the rcode 550 @type rcode: int 551 """ 552 (value, evalue) = dns.rcode.to_flags(rcode) 553 self.flags &= 0xFFF0 554 self.flags |= value 555 self.ednsflags &= long(0x00FFFFFF) 556 self.ednsflags |= evalue 557 if self.ednsflags != 0 and self.edns < 0: 558 self.edns = 0
559
560 - def opcode(self):
561 """Return the opcode. 562 @rtype: int 563 """ 564 return dns.opcode.from_flags(self.flags)
565
566 - def set_opcode(self, opcode):
567 """Set the opcode. 568 @param opcode: the opcode 569 @type opcode: int 570 """ 571 self.flags &= 0x87FF 572 self.flags |= dns.opcode.to_flags(opcode)
573 574
575 -class _WireReader(object):
576 577 """Wire format reader. 578 579 @ivar wire: the wire-format message. 580 @type wire: string 581 @ivar message: The message object being built 582 @type message: dns.message.Message object 583 @ivar current: When building a message object from wire format, this 584 variable contains the offset from the beginning of wire of the next octet 585 to be read. 586 @type current: int 587 @ivar updating: Is the message a dynamic update? 588 @type updating: bool 589 @ivar one_rr_per_rrset: Put each RR into its own RRset? 590 @type one_rr_per_rrset: bool 591 @ivar ignore_trailing: Ignore trailing junk at end of request? 592 @type ignore_trailing: bool 593 @ivar zone_rdclass: The class of the zone in messages which are 594 DNS dynamic updates. 595 @type zone_rdclass: int 596 """ 597
598 - def __init__(self, wire, message, question_only=False, 599 one_rr_per_rrset=False, ignore_trailing=False):
600 self.wire = dns.wiredata.maybe_wrap(wire) 601 self.message = message 602 self.current = 0 603 self.updating = False 604 self.zone_rdclass = dns.rdataclass.IN 605 self.question_only = question_only 606 self.one_rr_per_rrset = one_rr_per_rrset 607 self.ignore_trailing = ignore_trailing
608
609 - def _get_question(self, qcount):
610 """Read the next I{qcount} records from the wire data and add them to 611 the question section. 612 @param qcount: the number of questions in the message 613 @type qcount: int""" 614 615 if self.updating and qcount > 1: 616 raise dns.exception.FormError 617 618 for i in xrange(0, qcount): 619 (qname, used) = dns.name.from_wire(self.wire, self.current) 620 if self.message.origin is not None: 621 qname = qname.relativize(self.message.origin) 622 self.current = self.current + used 623 (rdtype, rdclass) = \ 624 struct.unpack('!HH', 625 self.wire[self.current:self.current + 4]) 626 self.current = self.current + 4 627 self.message.find_rrset(self.message.question, qname, 628 rdclass, rdtype, create=True, 629 force_unique=True) 630 if self.updating: 631 self.zone_rdclass = rdclass
632
633 - def _get_section(self, section, count):
634 """Read the next I{count} records from the wire data and add them to 635 the specified section. 636 @param section: the section of the message to which to add records 637 @type section: list of dns.rrset.RRset objects 638 @param count: the number of records to read 639 @type count: int""" 640 641 if self.updating or self.one_rr_per_rrset: 642 force_unique = True 643 else: 644 force_unique = False 645 seen_opt = False 646 for i in xrange(0, count): 647 rr_start = self.current 648 (name, used) = dns.name.from_wire(self.wire, self.current) 649 absolute_name = name 650 if self.message.origin is not None: 651 name = name.relativize(self.message.origin) 652 self.current = self.current + used 653 (rdtype, rdclass, ttl, rdlen) = \ 654 struct.unpack('!HHIH', 655 self.wire[self.current:self.current + 10]) 656 self.current = self.current + 10 657 if rdtype == dns.rdatatype.OPT: 658 if section is not self.message.additional or seen_opt: 659 raise BadEDNS 660 self.message.payload = rdclass 661 self.message.ednsflags = ttl 662 self.message.edns = (ttl & 0xff0000) >> 16 663 self.message.options = [] 664 current = self.current 665 optslen = rdlen 666 while optslen > 0: 667 (otype, olen) = \ 668 struct.unpack('!HH', 669 self.wire[current:current + 4]) 670 current = current + 4 671 opt = dns.edns.option_from_wire( 672 otype, self.wire, current, olen) 673 self.message.options.append(opt) 674 current = current + olen 675 optslen = optslen - 4 - olen 676 seen_opt = True 677 elif rdtype == dns.rdatatype.TSIG: 678 if not (section is self.message.additional and 679 i == (count - 1)): 680 raise BadTSIG 681 if self.message.keyring is None: 682 raise UnknownTSIGKey('got signed message without keyring') 683 secret = self.message.keyring.get(absolute_name) 684 if secret is None: 685 raise UnknownTSIGKey("key '%s' unknown" % name) 686 self.message.keyname = absolute_name 687 (self.message.keyalgorithm, self.message.mac) = \ 688 dns.tsig.get_algorithm_and_mac(self.wire, self.current, 689 rdlen) 690 self.message.tsig_ctx = \ 691 dns.tsig.validate(self.wire, 692 absolute_name, 693 secret, 694 int(time.time()), 695 self.message.request_mac, 696 rr_start, 697 self.current, 698 rdlen, 699 self.message.tsig_ctx, 700 self.message.multi, 701 self.message.first) 702 self.message.had_tsig = True 703 else: 704 if ttl < 0: 705 ttl = 0 706 if self.updating and \ 707 (rdclass == dns.rdataclass.ANY or 708 rdclass == dns.rdataclass.NONE): 709 deleting = rdclass 710 rdclass = self.zone_rdclass 711 else: 712 deleting = None 713 if deleting == dns.rdataclass.ANY or \ 714 (deleting == dns.rdataclass.NONE and 715 section is self.message.answer): 716 covers = dns.rdatatype.NONE 717 rd = None 718 else: 719 rd = dns.rdata.from_wire(rdclass, rdtype, self.wire, 720 self.current, rdlen, 721 self.message.origin) 722 covers = rd.covers() 723 if self.message.xfr and rdtype == dns.rdatatype.SOA: 724 force_unique = True 725 rrset = self.message.find_rrset(section, name, 726 rdclass, rdtype, covers, 727 deleting, True, force_unique) 728 if rd is not None: 729 rrset.add(rd, ttl) 730 self.current = self.current + rdlen
731
732 - def read(self):
733 """Read a wire format DNS message and build a dns.message.Message 734 object.""" 735 736 l = len(self.wire) 737 if l < 12: 738 raise ShortHeader 739 (self.message.id, self.message.flags, qcount, ancount, 740 aucount, adcount) = struct.unpack('!HHHHHH', self.wire[:12]) 741 self.current = 12 742 if dns.opcode.is_update(self.message.flags): 743 self.updating = True 744 self._get_question(qcount) 745 if self.question_only: 746 return 747 self._get_section(self.message.answer, ancount) 748 self._get_section(self.message.authority, aucount) 749 self._get_section(self.message.additional, adcount) 750 if not self.ignore_trailing and self.current != l: 751 raise TrailingJunk 752 if self.message.multi and self.message.tsig_ctx and \ 753 not self.message.had_tsig: 754 self.message.tsig_ctx.update(self.wire)
755 756
757 -def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None, 758 tsig_ctx=None, multi=False, first=True, 759 question_only=False, one_rr_per_rrset=False, 760 ignore_trailing=False):
761 """Convert a DNS wire format message into a message 762 object. 763 764 @param keyring: The keyring to use if the message is signed. 765 @type keyring: dict 766 @param request_mac: If the message is a response to a TSIG-signed request, 767 I{request_mac} should be set to the MAC of that request. 768 @type request_mac: string 769 @param xfr: Is this message part of a zone transfer? 770 @type xfr: bool 771 @param origin: If the message is part of a zone transfer, I{origin} 772 should be the origin name of the zone. 773 @type origin: dns.name.Name object 774 @param tsig_ctx: The ongoing TSIG context, used when validating zone 775 transfers. 776 @type tsig_ctx: hmac.HMAC object 777 @param multi: Is this message part of a multiple message sequence? 778 @type multi: bool 779 @param first: Is this message standalone, or the first of a multi 780 message sequence? 781 @type first: bool 782 @param question_only: Read only up to the end of the question section? 783 @type question_only: bool 784 @param one_rr_per_rrset: Put each RR into its own RRset 785 @type one_rr_per_rrset: bool 786 @param ignore_trailing: Ignore trailing junk at end of request? 787 @type ignore_trailing: bool 788 @raises ShortHeader: The message is less than 12 octets long. 789 @raises TrailingJunk: There were octets in the message past the end 790 of the proper DNS message. 791 @raises BadEDNS: An OPT record was in the wrong section, or occurred more 792 than once. 793 @raises BadTSIG: A TSIG record was not the last record of the additional 794 data section. 795 @rtype: dns.message.Message object""" 796 797 m = Message(id=0) 798 m.keyring = keyring 799 m.request_mac = request_mac 800 m.xfr = xfr 801 m.origin = origin 802 m.tsig_ctx = tsig_ctx 803 m.multi = multi 804 m.first = first 805 806 reader = _WireReader(wire, m, question_only, one_rr_per_rrset, 807 ignore_trailing) 808 reader.read() 809 810 return m
811 812
813 -class _TextReader(object):
814 815 """Text format reader. 816 817 @ivar tok: the tokenizer 818 @type tok: dns.tokenizer.Tokenizer object 819 @ivar message: The message object being built 820 @type message: dns.message.Message object 821 @ivar updating: Is the message a dynamic update? 822 @type updating: bool 823 @ivar zone_rdclass: The class of the zone in messages which are 824 DNS dynamic updates. 825 @type zone_rdclass: int 826 @ivar last_name: The most recently read name when building a message object 827 from text format. 828 @type last_name: dns.name.Name object 829 """ 830
831 - def __init__(self, text, message):
832 self.message = message 833 self.tok = dns.tokenizer.Tokenizer(text) 834 self.last_name = None 835 self.zone_rdclass = dns.rdataclass.IN 836 self.updating = False
837
838 - def _header_line(self, section):
839 """Process one line from the text format header section.""" 840 841 token = self.tok.get() 842 what = token.value 843 if what == 'id': 844 self.message.id = self.tok.get_int() 845 elif what == 'flags': 846 while True: 847 token = self.tok.get() 848 if not token.is_identifier(): 849 self.tok.unget(token) 850 break 851 self.message.flags = self.message.flags | \ 852 dns.flags.from_text(token.value) 853 if dns.opcode.is_update(self.message.flags): 854 self.updating = True 855 elif what == 'edns': 856 self.message.edns = self.tok.get_int() 857 self.message.ednsflags = self.message.ednsflags | \ 858 (self.message.edns << 16) 859 elif what == 'eflags': 860 if self.message.edns < 0: 861 self.message.edns = 0 862 while True: 863 token = self.tok.get() 864 if not token.is_identifier(): 865 self.tok.unget(token) 866 break 867 self.message.ednsflags = self.message.ednsflags | \ 868 dns.flags.edns_from_text(token.value) 869 elif what == 'payload': 870 self.message.payload = self.tok.get_int() 871 if self.message.edns < 0: 872 self.message.edns = 0 873 elif what == 'opcode': 874 text = self.tok.get_string() 875 self.message.flags = self.message.flags | \ 876 dns.opcode.to_flags(dns.opcode.from_text(text)) 877 elif what == 'rcode': 878 text = self.tok.get_string() 879 self.message.set_rcode(dns.rcode.from_text(text)) 880 else: 881 raise UnknownHeaderField 882 self.tok.get_eol()
883
884 - def _question_line(self, section):
885 """Process one line from the text format question section.""" 886 887 token = self.tok.get(want_leading=True) 888 if not token.is_whitespace(): 889 self.last_name = dns.name.from_text(token.value, None) 890 name = self.last_name 891 token = self.tok.get() 892 if not token.is_identifier(): 893 raise dns.exception.SyntaxError 894 # Class 895 try: 896 rdclass = dns.rdataclass.from_text(token.value) 897 token = self.tok.get() 898 if not token.is_identifier(): 899 raise dns.exception.SyntaxError 900 except dns.exception.SyntaxError: 901 raise dns.exception.SyntaxError 902 except: 903 rdclass = dns.rdataclass.IN 904 # Type 905 rdtype = dns.rdatatype.from_text(token.value) 906 self.message.find_rrset(self.message.question, name, 907 rdclass, rdtype, create=True, 908 force_unique=True) 909 if self.updating: 910 self.zone_rdclass = rdclass 911 self.tok.get_eol()
912
913 - def _rr_line(self, section):
914 """Process one line from the text format answer, authority, or 915 additional data sections. 916 """ 917 918 deleting = None 919 # Name 920 token = self.tok.get(want_leading=True) 921 if not token.is_whitespace(): 922 self.last_name = dns.name.from_text(token.value, None) 923 name = self.last_name 924 token = self.tok.get() 925 if not token.is_identifier(): 926 raise dns.exception.SyntaxError 927 # TTL 928 try: 929 ttl = int(token.value, 0) 930 token = self.tok.get() 931 if not token.is_identifier(): 932 raise dns.exception.SyntaxError 933 except dns.exception.SyntaxError: 934 raise dns.exception.SyntaxError 935 except: 936 ttl = 0 937 # Class 938 try: 939 rdclass = dns.rdataclass.from_text(token.value) 940 token = self.tok.get() 941 if not token.is_identifier(): 942 raise dns.exception.SyntaxError 943 if rdclass == dns.rdataclass.ANY or rdclass == dns.rdataclass.NONE: 944 deleting = rdclass 945 rdclass = self.zone_rdclass 946 except dns.exception.SyntaxError: 947 raise dns.exception.SyntaxError 948 except: 949 rdclass = dns.rdataclass.IN 950 # Type 951 rdtype = dns.rdatatype.from_text(token.value) 952 token = self.tok.get() 953 if not token.is_eol_or_eof(): 954 self.tok.unget(token) 955 rd = dns.rdata.from_text(rdclass, rdtype, self.tok, None) 956 covers = rd.covers() 957 else: 958 rd = None 959 covers = dns.rdatatype.NONE 960 rrset = self.message.find_rrset(section, name, 961 rdclass, rdtype, covers, 962 deleting, True, self.updating) 963 if rd is not None: 964 rrset.add(rd, ttl)
965
966 - def read(self):
967 """Read a text format DNS message and build a dns.message.Message 968 object.""" 969 970 line_method = self._header_line 971 section = None 972 while 1: 973 token = self.tok.get(True, True) 974 if token.is_eol_or_eof(): 975 break 976 if token.is_comment(): 977 u = token.value.upper() 978 if u == 'HEADER': 979 line_method = self._header_line 980 elif u == 'QUESTION' or u == 'ZONE': 981 line_method = self._question_line 982 section = self.message.question 983 elif u == 'ANSWER' or u == 'PREREQ': 984 line_method = self._rr_line 985 section = self.message.answer 986 elif u == 'AUTHORITY' or u == 'UPDATE': 987 line_method = self._rr_line 988 section = self.message.authority 989 elif u == 'ADDITIONAL': 990 line_method = self._rr_line 991 section = self.message.additional 992 self.tok.get_eol() 993 continue 994 self.tok.unget(token) 995 line_method(section)
996 997
998 -def from_text(text):
999 """Convert the text format message into a message object. 1000 1001 @param text: The text format message. 1002 @type text: string 1003 @raises UnknownHeaderField: 1004 @raises dns.exception.SyntaxError: 1005 @rtype: dns.message.Message object""" 1006 1007 # 'text' can also be a file, but we don't publish that fact 1008 # since it's an implementation detail. The official file 1009 # interface is from_file(). 1010 1011 m = Message() 1012 1013 reader = _TextReader(text, m) 1014 reader.read() 1015 1016 return m
1017 1018
1019 -def from_file(f):
1020 """Read the next text format message from the specified file. 1021 1022 @param f: file or string. If I{f} is a string, it is treated 1023 as the name of a file to open. 1024 @raises UnknownHeaderField: 1025 @raises dns.exception.SyntaxError: 1026 @rtype: dns.message.Message object""" 1027 1028 str_type = string_types 1029 opts = 'rU' 1030 1031 if isinstance(f, str_type): 1032 f = open(f, opts) 1033 want_close = True 1034 else: 1035 want_close = False 1036 1037 try: 1038 m = from_text(f) 1039 finally: 1040 if want_close: 1041 f.close() 1042 return m
1043 1044
1045 -def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None, 1046 want_dnssec=False, ednsflags=None, payload=None, 1047 request_payload=None, options=None):
1048 """Make a query message. 1049 1050 The query name, type, and class may all be specified either 1051 as objects of the appropriate type, or as strings. 1052 1053 The query will have a randomly chosen query id, and its DNS flags 1054 will be set to dns.flags.RD. 1055 1056 @param qname: The query name. 1057 @type qname: dns.name.Name object or string 1058 @param rdtype: The desired rdata type. 1059 @type rdtype: int 1060 @param rdclass: The desired rdata class; the default is class IN. 1061 @type rdclass: int 1062 @param use_edns: The EDNS level to use; the default is None (no EDNS). 1063 See the description of dns.message.Message.use_edns() for the possible 1064 values for use_edns and their meanings. 1065 @type use_edns: int or bool or None 1066 @param want_dnssec: Should the query indicate that DNSSEC is desired? 1067 @type want_dnssec: bool 1068 @param ednsflags: EDNS flag values. 1069 @type ednsflags: int 1070 @param payload: The EDNS sender's payload field, which is the maximum 1071 size of UDP datagram the sender can handle. 1072 @type payload: int 1073 @param request_payload: The EDNS payload size to use when sending 1074 this message. If not specified, defaults to the value of payload. 1075 @type request_payload: int or None 1076 @param options: The EDNS options 1077 @type options: None or list of dns.edns.Option objects 1078 @see: RFC 2671 1079 @rtype: dns.message.Message object""" 1080 1081 if isinstance(qname, string_types): 1082 qname = dns.name.from_text(qname) 1083 if isinstance(rdtype, string_types): 1084 rdtype = dns.rdatatype.from_text(rdtype) 1085 if isinstance(rdclass, string_types): 1086 rdclass = dns.rdataclass.from_text(rdclass) 1087 m = Message() 1088 m.flags |= dns.flags.RD 1089 m.find_rrset(m.question, qname, rdclass, rdtype, create=True, 1090 force_unique=True) 1091 # only pass keywords on to use_edns if they have been set to a 1092 # non-None value. Setting a field will turn EDNS on if it hasn't 1093 # been configured. 1094 kwargs = {} 1095 if ednsflags is not None: 1096 kwargs['ednsflags'] = ednsflags 1097 if use_edns is None: 1098 use_edns = 0 1099 if payload is not None: 1100 kwargs['payload'] = payload 1101 if use_edns is None: 1102 use_edns = 0 1103 if request_payload is not None: 1104 kwargs['request_payload'] = request_payload 1105 if use_edns is None: 1106 use_edns = 0 1107 if options is not None: 1108 kwargs['options'] = options 1109 if use_edns is None: 1110 use_edns = 0 1111 kwargs['edns'] = use_edns 1112 m.use_edns(**kwargs) 1113 m.want_dnssec(want_dnssec) 1114 return m
1115 1116
1117 -def make_response(query, recursion_available=False, our_payload=8192, 1118 fudge=300):
1119 """Make a message which is a response for the specified query. 1120 The message returned is really a response skeleton; it has all 1121 of the infrastructure required of a response, but none of the 1122 content. 1123 1124 The response's question section is a shallow copy of the query's 1125 question section, so the query's question RRsets should not be 1126 changed. 1127 1128 @param query: the query to respond to 1129 @type query: dns.message.Message object 1130 @param recursion_available: should RA be set in the response? 1131 @type recursion_available: bool 1132 @param our_payload: payload size to advertise in EDNS responses; default 1133 is 8192. 1134 @type our_payload: int 1135 @param fudge: TSIG time fudge; default is 300 seconds. 1136 @type fudge: int 1137 @rtype: dns.message.Message object""" 1138 1139 if query.flags & dns.flags.QR: 1140 raise dns.exception.FormError('specified query message is not a query') 1141 response = dns.message.Message(query.id) 1142 response.flags = dns.flags.QR | (query.flags & dns.flags.RD) 1143 if recursion_available: 1144 response.flags |= dns.flags.RA 1145 response.set_opcode(query.opcode()) 1146 response.question = list(query.question) 1147 if query.edns >= 0: 1148 response.use_edns(0, 0, our_payload, query.payload) 1149 if query.had_tsig: 1150 response.use_tsig(query.keyring, query.keyname, fudge, None, 0, '', 1151 query.keyalgorithm) 1152 response.request_mac = query.mac 1153 return response
1154