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