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