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