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