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