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

Source Code for Module dns.message

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