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