1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40 """The DNS packet passed to from_wire() is too short."""
41
43 """The DNS packet passed to from_wire() has extra junk at the end of it."""
44
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
58 """A TSIG with an unknown key was received."""
59
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
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
169 return '<DNS message, ID ' + `self.id` + '>'
170
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
220
221
222
223 return s.getvalue()[:-1]
224
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
256 """Are two messages not equal?
257 @rtype: bool"""
258 return not self.__eq__(other)
259
280
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
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
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
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
524
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
543
551
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
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
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
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
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
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
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
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
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
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
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
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
982
983
984
985 m = Message()
986
987 reader = _TextReader(text, m)
988 reader.read()
989
990 return m
991
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
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