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