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

Source Code for Module dns.message

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