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