1 # Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
3 # Permission to use, copy, modify, and distribute this software and its
4 # documentation for any purpose with or without fee is hereby granted,
5 # provided that the above copyright notice and this permission notice
6 # appear in all copies.
8 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
38 class ShortHeader(dns
.exception
.FormError
):
39 """Raised if the DNS packet passed to from_wire() is too short."""
42 class TrailingJunk(dns
.exception
.FormError
):
43 """Raised if the DNS packet passed to from_wire() has extra junk
47 class UnknownHeaderField(dns
.exception
.DNSException
):
48 """Raised if a header field name is not recognized when converting from
49 text into a message."""
52 class BadEDNS(dns
.exception
.FormError
):
53 """Raised if an OPT record occurs somewhere other than the start of
54 the additional data section."""
57 class BadTSIG(dns
.exception
.FormError
):
58 """Raised if a TSIG record occurs somewhere other than the end of
59 the additional data section."""
62 class UnknownTSIGKey(dns
.exception
.DNSException
):
63 """Raised if we got a TSIG but don't know the key."""
66 class Message(object):
69 @ivar id: The query id; the default is a randomly chosen id.
71 @ivar flags: The DNS flags of the message. @see: RFC 1035 for an
72 explanation of these flags.
74 @ivar question: The question section.
75 @type question: list of dns.rrset.RRset objects
76 @ivar answer: The answer section.
77 @type answer: list of dns.rrset.RRset objects
78 @ivar authority: The authority section.
79 @type authority: list of dns.rrset.RRset objects
80 @ivar additional: The additional data section.
81 @type additional: list of dns.rrset.RRset objects
82 @ivar edns: The EDNS level to use. The default is -1, no Edns.
84 @ivar ednsflags: The EDNS flags
86 @ivar payload: The EDNS payload size. The default is 0.
88 @ivar options: The EDNS options
89 @type options: list of dns.edns.Option objects
90 @ivar request_payload: The associated request's EDNS payload size.
91 @type request_payload: int
92 @ivar keyring: The TSIG keyring to use. The default is None.
94 @ivar keyname: The TSIG keyname to use. The default is None.
95 @type keyname: dns.name.Name object
96 @ivar keyalgorithm: The TSIG algorithm to use; defaults to
97 dns.tsig.default_algorithm. Constants for TSIG algorithms are defined
98 in dns.tsig, and the currently implemented algorithms are
99 HMAC_MD5, HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, and
101 @type keyalgorithm: string
102 @ivar request_mac: The TSIG MAC of the request message associated with
103 this message; used when validating TSIG signatures. @see: RFC 2845 for
104 more information on TSIG fields.
105 @type request_mac: string
106 @ivar fudge: TSIG time fudge; default is 300 seconds.
108 @ivar original_id: TSIG original id; defaults to the message's id
109 @type original_id: int
110 @ivar tsig_error: TSIG error code; default is 0.
111 @type tsig_error: int
112 @ivar other_data: TSIG other data.
113 @type other_data: string
114 @ivar mac: The TSIG MAC for this message.
116 @ivar xfr: Is the message being used to contain the results of a DNS
117 zone transfer? The default is False.
119 @ivar origin: The origin of the zone in messages which are used for
120 zone transfers or for DNS dynamic updates. The default is None.
121 @type origin: dns.name.Name object
122 @ivar tsig_ctx: The TSIG signature context associated with this
123 message. The default is None.
124 @type tsig_ctx: hmac.HMAC object
125 @ivar had_tsig: Did the message decoded from wire format have a TSIG
128 @ivar multi: Is this message part of a multi-message sequence? The
129 default is false. This variable is used when validating TSIG signatures
130 on messages which are part of a zone transfer.
132 @ivar first: Is this message standalone, or the first of a multi
133 message sequence? This variable is used when validating TSIG signatures
134 on messages which are part of a zone transfer.
136 @ivar index: An index of rrsets in the message. The index key is
137 (section, name, rdclass, rdtype, covers, deleting). Indexing can be
138 disabled by setting the index to None.
142 def __init__(self
, id=None):
144 self
.id = dns
.entropy
.random_16()
156 self
.request_payload
= 0
159 self
.keyalgorithm
= dns
.tsig
.default_algorithm
160 self
.request_mac
= ''
164 self
.original_id
= self
.id
169 self
.had_tsig
= False
175 return '<DNS message, ID ' + `self
.id`
+ '>'
178 return self
.to_text()
180 def to_text(self
, origin
=None, relativize
=True, **kw
):
181 """Convert the message to text.
183 The I{origin}, I{relativize}, and any other keyword
184 arguments are passed to the rrset to_wire() method.
189 s
= cStringIO
.StringIO()
190 print >> s
, 'id %d' % self
.id
191 print >> s
, 'opcode %s' % \
192 dns
.opcode
.to_text(dns
.opcode
.from_flags(self
.flags
))
193 rc
= dns
.rcode
.from_flags(self
.flags
, self
.ednsflags
)
194 print >> s
, 'rcode %s' % dns
.rcode
.to_text(rc
)
195 print >> s
, 'flags %s' % dns
.flags
.to_text(self
.flags
)
197 print >> s
, 'edns %s' % self
.edns
198 if self
.ednsflags
!= 0:
199 print >> s
, 'eflags %s' % \
200 dns
.flags
.edns_to_text(self
.ednsflags
)
201 print >> s
, 'payload', self
.payload
202 is_update
= dns
.opcode
.is_update(self
.flags
)
206 print >> s
, ';QUESTION'
207 for rrset
in self
.question
:
208 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
210 print >> s
, ';PREREQ'
212 print >> s
, ';ANSWER'
213 for rrset
in self
.answer
:
214 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
216 print >> s
, ';UPDATE'
218 print >> s
, ';AUTHORITY'
219 for rrset
in self
.authority
:
220 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
221 print >> s
, ';ADDITIONAL'
222 for rrset
in self
.additional
:
223 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
225 # We strip off the final \n so the caller can print the result without
226 # doing weird things to get around eccentricities in Python print
229 return s
.getvalue()[:-1]
231 def __eq__(self
, other
):
232 """Two messages are equal if they have the same content in the
233 header, question, answer, and authority sections.
235 if not isinstance(other
, Message
):
237 if self
.id != other
.id:
239 if self
.flags
!= other
.flags
:
241 for n
in self
.question
:
242 if n
not in other
.question
:
244 for n
in other
.question
:
245 if n
not in self
.question
:
247 for n
in self
.answer
:
248 if n
not in other
.answer
:
250 for n
in other
.answer
:
251 if n
not in self
.answer
:
253 for n
in self
.authority
:
254 if n
not in other
.authority
:
256 for n
in other
.authority
:
257 if n
not in self
.authority
:
261 def __ne__(self
, other
):
262 """Are two messages not equal?
264 return not self
.__eq
__(other
)
266 def is_response(self
, other
):
267 """Is other a response to self?
269 if other
.flags
& dns
.flags
.QR
== 0 or \
270 self
.id != other
.id or \
271 dns
.opcode
.from_flags(self
.flags
) != \
272 dns
.opcode
.from_flags(other
.flags
):
274 if dns
.rcode
.from_flags(other
.flags
, other
.ednsflags
) != \
277 if dns
.opcode
.is_update(self
.flags
):
279 for n
in self
.question
:
280 if n
not in other
.question
:
282 for n
in other
.question
:
283 if n
not in self
.question
:
287 def section_number(self
, section
):
288 if section
is self
.question
:
290 elif section
is self
.answer
:
292 elif section
is self
.authority
:
294 elif section
is self
.additional
:
297 raise ValueError('unknown section')
299 def find_rrset(self
, section
, name
, rdclass
, rdtype
,
300 covers
=dns
.rdatatype
.NONE
, deleting
=None, create
=False,
302 """Find the RRset with the given attributes in the specified section.
304 @param section: the section of the message to look in, e.g.
306 @type section: list of dns.rrset.RRset objects
307 @param name: the name of the RRset
308 @type name: dns.name.Name object
309 @param rdclass: the class of the RRset
311 @param rdtype: the type of the RRset
313 @param covers: the covers value of the RRset
315 @param deleting: the deleting value of the RRset
317 @param create: If True, create the RRset if it is not found.
318 The created RRset is appended to I{section}.
320 @param force_unique: If True and create is also True, create a
321 new RRset regardless of whether a matching RRset exists already.
322 @type force_unique: bool
323 @raises KeyError: the RRset was not found and create was False
324 @rtype: dns.rrset.RRset object"""
326 key
= (self
.section_number(section
),
327 name
, rdclass
, rdtype
, covers
, deleting
)
329 if not self
.index
is None:
330 rrset
= self
.index
.get(key
)
331 if not rrset
is None:
334 for rrset
in section
:
335 if rrset
.match(name
, rdclass
, rdtype
, covers
, deleting
):
339 rrset
= dns
.rrset
.RRset(name
, rdclass
, rdtype
, covers
, deleting
)
340 section
.append(rrset
)
341 if not self
.index
is None:
342 self
.index
[key
] = rrset
345 def get_rrset(self
, section
, name
, rdclass
, rdtype
,
346 covers
=dns
.rdatatype
.NONE
, deleting
=None, create
=False,
348 """Get the RRset with the given attributes in the specified section.
350 If the RRset is not found, None is returned.
352 @param section: the section of the message to look in, e.g.
354 @type section: list of dns.rrset.RRset objects
355 @param name: the name of the RRset
356 @type name: dns.name.Name object
357 @param rdclass: the class of the RRset
359 @param rdtype: the type of the RRset
361 @param covers: the covers value of the RRset
363 @param deleting: the deleting value of the RRset
365 @param create: If True, create the RRset if it is not found.
366 The created RRset is appended to I{section}.
368 @param force_unique: If True and create is also True, create a
369 new RRset regardless of whether a matching RRset exists already.
370 @type force_unique: bool
371 @rtype: dns.rrset.RRset object or None"""
374 rrset
= self
.find_rrset(section
, name
, rdclass
, rdtype
, covers
,
375 deleting
, create
, force_unique
)
380 def to_wire(self
, origin
=None, max_size
=0, **kw
):
381 """Return a string containing the message in DNS compressed wire
384 Additional keyword arguments are passed to the rrset to_wire()
387 @param origin: The origin to be appended to any relative names.
388 @type origin: dns.name.Name object
389 @param max_size: The maximum size of the wire format output; default
390 is 0, which means 'the message's request payload, if nonzero, or
393 @raises dns.exception.TooBig: max_size was exceeded
398 if self
.request_payload
!= 0:
399 max_size
= self
.request_payload
404 elif max_size
> 65535:
406 r
= dns
.renderer
.Renderer(self
.id, self
.flags
, max_size
, origin
)
407 for rrset
in self
.question
:
408 r
.add_question(rrset
.name
, rrset
.rdtype
, rrset
.rdclass
)
409 for rrset
in self
.answer
:
410 r
.add_rrset(dns
.renderer
.ANSWER
, rrset
, **kw
)
411 for rrset
in self
.authority
:
412 r
.add_rrset(dns
.renderer
.AUTHORITY
, rrset
, **kw
)
414 r
.add_edns(self
.edns
, self
.ednsflags
, self
.payload
, self
.options
)
415 for rrset
in self
.additional
:
416 r
.add_rrset(dns
.renderer
.ADDITIONAL
, rrset
, **kw
)
418 if not self
.keyname
is None:
419 r
.add_tsig(self
.keyname
, self
.keyring
[self
.keyname
],
420 self
.fudge
, self
.original_id
, self
.tsig_error
,
421 self
.other_data
, self
.request_mac
,
426 def use_tsig(self
, keyring
, keyname
=None, fudge
=300,
427 original_id
=None, tsig_error
=0, other_data
='',
428 algorithm
=dns
.tsig
.default_algorithm
):
429 """When sending, a TSIG signature using the specified keyring
430 and keyname should be added.
432 @param keyring: The TSIG keyring to use; defaults to None.
434 @param keyname: The name of the TSIG key to use; defaults to None.
435 The key must be defined in the keyring. If a keyring is specified
436 but a keyname is not, then the key used will be the first key in the
437 keyring. Note that the order of keys in a dictionary is not defined,
438 so applications should supply a keyname when a keyring is used, unless
439 they know the keyring contains only one key.
440 @type keyname: dns.name.Name or string
441 @param fudge: TSIG time fudge; default is 300 seconds.
443 @param original_id: TSIG original id; defaults to the message's id
444 @type original_id: int
445 @param tsig_error: TSIG error code; default is 0.
446 @type tsig_error: int
447 @param other_data: TSIG other data.
448 @type other_data: string
449 @param algorithm: The TSIG algorithm to use; defaults to
450 dns.tsig.default_algorithm
453 self
.keyring
= keyring
455 self
.keyname
= self
.keyring
.keys()[0]
457 if isinstance(keyname
, (str, unicode)):
458 keyname
= dns
.name
.from_text(keyname
)
459 self
.keyname
= keyname
460 self
.keyalgorithm
= algorithm
462 if original_id
is None:
463 self
.original_id
= self
.id
465 self
.original_id
= original_id
466 self
.tsig_error
= tsig_error
467 self
.other_data
= other_data
469 def use_edns(self
, edns
=0, ednsflags
=0, payload
=1280, request_payload
=None, options
=None):
470 """Configure EDNS behavior.
471 @param edns: The EDNS level to use. Specifying None, False, or -1
472 means 'do not use EDNS', and in this case the other parameters are
473 ignored. Specifying True is equivalent to specifying 0, i.e. 'use
475 @type edns: int or bool or None
476 @param ednsflags: EDNS flag values.
478 @param payload: The EDNS sender's payload field, which is the maximum
479 size of UDP datagram the sender can handle.
481 @param request_payload: The EDNS payload size to use when sending
482 this message. If not specified, defaults to the value of payload.
483 @type request_payload: int or None
484 @param options: The EDNS options
485 @type options: None or list of dns.edns.Option objects
488 if edns
is None or edns
is False:
492 if request_payload
is None:
493 request_payload
= payload
500 # make sure the EDNS version in ednsflags agrees with edns
501 ednsflags
&= 0xFF00FFFFL
502 ednsflags |
= (edns
<< 16)
506 self
.ednsflags
= ednsflags
507 self
.payload
= payload
508 self
.options
= options
509 self
.request_payload
= request_payload
511 def want_dnssec(self
, wanted
=True):
512 """Enable or disable 'DNSSEC desired' flag in requests.
513 @param wanted: Is DNSSEC desired? If True, EDNS is enabled if
514 required, and then the DO bit is set. If False, the DO bit is
515 cleared if EDNS is enabled.
521 self
.ednsflags |
= dns
.flags
.DO
523 self
.ednsflags
&= ~dns
.flags
.DO
529 return dns
.rcode
.from_flags(self
.flags
, self
.ednsflags
)
531 def set_rcode(self
, rcode
):
533 @param rcode: the rcode
536 (value
, evalue
) = dns
.rcode
.to_flags(rcode
)
539 self
.ednsflags
&= 0x00FFFFFFL
540 self
.ednsflags |
= evalue
541 if self
.ednsflags
!= 0 and self
.edns
< 0:
545 """Return the opcode.
548 return dns
.opcode
.from_flags(self
.flags
)
550 def set_opcode(self
, opcode
):
552 @param opcode: the opcode
556 self
.flags |
= dns
.opcode
.to_flags(opcode
)
558 class _WireReader(object):
559 """Wire format reader.
561 @ivar wire: the wire-format message.
563 @ivar message: The message object being built
564 @type message: dns.message.Message object
565 @ivar current: When building a message object from wire format, this
566 variable contains the offset from the beginning of wire of the next octet
569 @ivar updating: Is the message a dynamic update?
571 @ivar one_rr_per_rrset: Put each RR into its own RRset?
572 @type one_rr_per_rrset: bool
573 @ivar zone_rdclass: The class of the zone in messages which are
575 @type zone_rdclass: int
578 def __init__(self
, wire
, message
, question_only
=False,
579 one_rr_per_rrset
=False):
581 self
.message
= message
583 self
.updating
= False
584 self
.zone_rdclass
= dns
.rdataclass
.IN
585 self
.question_only
= question_only
586 self
.one_rr_per_rrset
= one_rr_per_rrset
588 def _get_question(self
, qcount
):
589 """Read the next I{qcount} records from the wire data and add them to
590 the question section.
591 @param qcount: the number of questions in the message
594 if self
.updating
and qcount
> 1:
595 raise dns
.exception
.FormError
597 for i
in xrange(0, qcount
):
598 (qname
, used
) = dns
.name
.from_wire(self
.wire
, self
.current
)
599 if not self
.message
.origin
is None:
600 qname
= qname
.relativize(self
.message
.origin
)
601 self
.current
= self
.current
+ used
602 (rdtype
, rdclass
) = \
604 self
.wire
[self
.current
:self
.current
+ 4])
605 self
.current
= self
.current
+ 4
606 self
.message
.find_rrset(self
.message
.question
, qname
,
607 rdclass
, rdtype
, create
=True,
610 self
.zone_rdclass
= rdclass
612 def _get_section(self
, section
, count
):
613 """Read the next I{count} records from the wire data and add them to
614 the specified section.
615 @param section: the section of the message to which to add records
616 @type section: list of dns.rrset.RRset objects
617 @param count: the number of records to read
620 if self
.updating
or self
.one_rr_per_rrset
:
625 for i
in xrange(0, count
):
626 rr_start
= self
.current
627 (name
, used
) = dns
.name
.from_wire(self
.wire
, self
.current
)
629 if not self
.message
.origin
is None:
630 name
= name
.relativize(self
.message
.origin
)
631 self
.current
= self
.current
+ used
632 (rdtype
, rdclass
, ttl
, rdlen
) = \
633 struct
.unpack('!HHIH',
634 self
.wire
[self
.current
:self
.current
+ 10])
635 self
.current
= self
.current
+ 10
636 if rdtype
== dns
.rdatatype
.OPT
:
637 if not section
is self
.message
.additional
or seen_opt
:
639 self
.message
.payload
= rdclass
640 self
.message
.ednsflags
= ttl
641 self
.message
.edns
= (ttl
& 0xff0000) >> 16
642 self
.message
.options
= []
643 current
= self
.current
648 self
.wire
[current
:current
+ 4])
649 current
= current
+ 4
650 opt
= dns
.edns
.option_from_wire(otype
, self
.wire
, current
, olen
)
651 self
.message
.options
.append(opt
)
652 current
= current
+ olen
653 optslen
= optslen
- 4 - olen
655 elif rdtype
== dns
.rdatatype
.TSIG
:
656 if not (section
is self
.message
.additional
and
659 if self
.message
.keyring
is None:
660 raise UnknownTSIGKey('got signed message without keyring')
661 secret
= self
.message
.keyring
.get(absolute_name
)
663 raise UnknownTSIGKey("key '%s' unknown" % name
)
664 self
.message
.tsig_ctx
= \
665 dns
.tsig
.validate(self
.wire
,
669 self
.message
.request_mac
,
673 self
.message
.tsig_ctx
,
676 self
.message
.had_tsig
= True
680 if self
.updating
and \
681 (rdclass
== dns
.rdataclass
.ANY
or
682 rdclass
== dns
.rdataclass
.NONE
):
684 rdclass
= self
.zone_rdclass
687 if deleting
== dns
.rdataclass
.ANY
or \
688 (deleting
== dns
.rdataclass
.NONE
and \
689 section
is self
.message
.answer
):
690 covers
= dns
.rdatatype
.NONE
693 rd
= dns
.rdata
.from_wire(rdclass
, rdtype
, self
.wire
,
697 if self
.message
.xfr
and rdtype
== dns
.rdatatype
.SOA
:
699 rrset
= self
.message
.find_rrset(section
, name
,
700 rdclass
, rdtype
, covers
,
701 deleting
, True, force_unique
)
704 self
.current
= self
.current
+ rdlen
707 """Read a wire format DNS message and build a dns.message.Message
713 (self
.message
.id, self
.message
.flags
, qcount
, ancount
,
714 aucount
, adcount
) = struct
.unpack('!HHHHHH', self
.wire
[:12])
716 if dns
.opcode
.is_update(self
.message
.flags
):
718 self
._get
_question
(qcount
)
719 if self
.question_only
:
721 self
._get
_section
(self
.message
.answer
, ancount
)
722 self
._get
_section
(self
.message
.authority
, aucount
)
723 self
._get
_section
(self
.message
.additional
, adcount
)
724 if self
.current
!= l
:
726 if self
.message
.multi
and self
.message
.tsig_ctx
and \
727 not self
.message
.had_tsig
:
728 self
.message
.tsig_ctx
.update(self
.wire
)
731 def from_wire(wire
, keyring
=None, request_mac
='', xfr
=False, origin
=None,
732 tsig_ctx
= None, multi
= False, first
= True,
733 question_only
= False, one_rr_per_rrset
= False):
734 """Convert a DNS wire format message into a message
737 @param keyring: The keyring to use if the message is signed.
739 @param request_mac: If the message is a response to a TSIG-signed request,
740 I{request_mac} should be set to the MAC of that request.
741 @type request_mac: string
742 @param xfr: Is this message part of a zone transfer?
744 @param origin: If the message is part of a zone transfer, I{origin}
745 should be the origin name of the zone.
746 @type origin: dns.name.Name object
747 @param tsig_ctx: The ongoing TSIG context, used when validating zone
749 @type tsig_ctx: hmac.HMAC object
750 @param multi: Is this message part of a multiple message sequence?
752 @param first: Is this message standalone, or the first of a multi
755 @param question_only: Read only up to the end of the question section?
756 @type question_only: bool
757 @param one_rr_per_rrset: Put each RR into its own RRset
758 @type one_rr_per_rrset: bool
759 @raises ShortHeader: The message is less than 12 octets long.
760 @raises TrailingJunk: There were octets in the message past the end
761 of the proper DNS message.
762 @raises BadEDNS: An OPT record was in the wrong section, or occurred more
764 @raises BadTSIG: A TSIG record was not the last record of the additional
766 @rtype: dns.message.Message object"""
770 m
.request_mac
= request_mac
773 m
.tsig_ctx
= tsig_ctx
777 reader
= _WireReader(wire
, m
, question_only
, one_rr_per_rrset
)
783 class _TextReader(object):
784 """Text format reader.
786 @ivar tok: the tokenizer
787 @type tok: dns.tokenizer.Tokenizer object
788 @ivar message: The message object being built
789 @type message: dns.message.Message object
790 @ivar updating: Is the message a dynamic update?
792 @ivar zone_rdclass: The class of the zone in messages which are
794 @type zone_rdclass: int
795 @ivar last_name: The most recently read name when building a message object
797 @type last_name: dns.name.Name object
800 def __init__(self
, text
, message
):
801 self
.message
= message
802 self
.tok
= dns
.tokenizer
.Tokenizer(text
)
803 self
.last_name
= None
804 self
.zone_rdclass
= dns
.rdataclass
.IN
805 self
.updating
= False
807 def _header_line(self
, section
):
808 """Process one line from the text format header section."""
810 token
= self
.tok
.get()
813 self
.message
.id = self
.tok
.get_int()
814 elif what
== 'flags':
816 token
= self
.tok
.get()
817 if not token
.is_identifier():
818 self
.tok
.unget(token
)
820 self
.message
.flags
= self
.message
.flags | \
821 dns
.flags
.from_text(token
.value
)
822 if dns
.opcode
.is_update(self
.message
.flags
):
825 self
.message
.edns
= self
.tok
.get_int()
826 self
.message
.ednsflags
= self
.message
.ednsflags | \
827 (self
.message
.edns
<< 16)
828 elif what
== 'eflags':
829 if self
.message
.edns
< 0:
830 self
.message
.edns
= 0
832 token
= self
.tok
.get()
833 if not token
.is_identifier():
834 self
.tok
.unget(token
)
836 self
.message
.ednsflags
= self
.message
.ednsflags | \
837 dns
.flags
.edns_from_text(token
.value
)
838 elif what
== 'payload':
839 self
.message
.payload
= self
.tok
.get_int()
840 if self
.message
.edns
< 0:
841 self
.message
.edns
= 0
842 elif what
== 'opcode':
843 text
= self
.tok
.get_string()
844 self
.message
.flags
= self
.message
.flags | \
845 dns
.opcode
.to_flags(dns
.opcode
.from_text(text
))
846 elif what
== 'rcode':
847 text
= self
.tok
.get_string()
848 self
.message
.set_rcode(dns
.rcode
.from_text(text
))
850 raise UnknownHeaderField
853 def _question_line(self
, section
):
854 """Process one line from the text format question section."""
856 token
= self
.tok
.get(want_leading
= True)
857 if not token
.is_whitespace():
858 self
.last_name
= dns
.name
.from_text(token
.value
, None)
859 name
= self
.last_name
860 token
= self
.tok
.get()
861 if not token
.is_identifier():
862 raise dns
.exception
.SyntaxError
865 rdclass
= dns
.rdataclass
.from_text(token
.value
)
866 token
= self
.tok
.get()
867 if not token
.is_identifier():
868 raise dns
.exception
.SyntaxError
869 except dns
.exception
.SyntaxError:
870 raise dns
.exception
.SyntaxError
872 rdclass
= dns
.rdataclass
.IN
874 rdtype
= dns
.rdatatype
.from_text(token
.value
)
875 self
.message
.find_rrset(self
.message
.question
, name
,
876 rdclass
, rdtype
, create
=True,
879 self
.zone_rdclass
= rdclass
882 def _rr_line(self
, section
):
883 """Process one line from the text format answer, authority, or
884 additional data sections.
889 token
= self
.tok
.get(want_leading
= True)
890 if not token
.is_whitespace():
891 self
.last_name
= dns
.name
.from_text(token
.value
, None)
892 name
= self
.last_name
893 token
= self
.tok
.get()
894 if not token
.is_identifier():
895 raise dns
.exception
.SyntaxError
898 ttl
= int(token
.value
, 0)
899 token
= self
.tok
.get()
900 if not token
.is_identifier():
901 raise dns
.exception
.SyntaxError
902 except dns
.exception
.SyntaxError:
903 raise dns
.exception
.SyntaxError
908 rdclass
= dns
.rdataclass
.from_text(token
.value
)
909 token
= self
.tok
.get()
910 if not token
.is_identifier():
911 raise dns
.exception
.SyntaxError
912 if rdclass
== dns
.rdataclass
.ANY
or rdclass
== dns
.rdataclass
.NONE
:
914 rdclass
= self
.zone_rdclass
915 except dns
.exception
.SyntaxError:
916 raise dns
.exception
.SyntaxError
918 rdclass
= dns
.rdataclass
.IN
920 rdtype
= dns
.rdatatype
.from_text(token
.value
)
921 token
= self
.tok
.get()
922 if not token
.is_eol_or_eof():
923 self
.tok
.unget(token
)
924 rd
= dns
.rdata
.from_text(rdclass
, rdtype
, self
.tok
, None)
928 covers
= dns
.rdatatype
.NONE
929 rrset
= self
.message
.find_rrset(section
, name
,
930 rdclass
, rdtype
, covers
,
931 deleting
, True, self
.updating
)
936 """Read a text format DNS message and build a dns.message.Message
939 line_method
= self
._header
_line
942 token
= self
.tok
.get(True, True)
943 if token
.is_eol_or_eof():
945 if token
.is_comment():
946 u
= token
.value
.upper()
948 line_method
= self
._header
_line
949 elif u
== 'QUESTION' or u
== 'ZONE':
950 line_method
= self
._question
_line
951 section
= self
.message
.question
952 elif u
== 'ANSWER' or u
== 'PREREQ':
953 line_method
= self
._rr
_line
954 section
= self
.message
.answer
955 elif u
== 'AUTHORITY' or u
== 'UPDATE':
956 line_method
= self
._rr
_line
957 section
= self
.message
.authority
958 elif u
== 'ADDITIONAL':
959 line_method
= self
._rr
_line
960 section
= self
.message
.additional
963 self
.tok
.unget(token
)
968 """Convert the text format message into a message object.
970 @param text: The text format message.
972 @raises UnknownHeaderField:
973 @raises dns.exception.SyntaxError:
974 @rtype: dns.message.Message object"""
976 # 'text' can also be a file, but we don't publish that fact
977 # since it's an implementation detail. The official file
978 # interface is from_file().
982 reader
= _TextReader(text
, m
)
988 """Read the next text format message from the specified file.
990 @param f: file or string. If I{f} is a string, it is treated
991 as the name of a file to open.
992 @raises UnknownHeaderField:
993 @raises dns.exception.SyntaxError:
994 @rtype: dns.message.Message object"""
996 if sys
.hexversion
>= 0x02030000:
997 # allow Unicode filenames; turn on universal newline support
998 str_type
= basestring
1003 if isinstance(f
, str_type
):
1016 def make_query(qname
, rdtype
, rdclass
= dns
.rdataclass
.IN
, use_edns
=None,
1018 """Make a query message.
1020 The query name, type, and class may all be specified either
1021 as objects of the appropriate type, or as strings.
1023 The query will have a randomly choosen query id, and its DNS flags
1024 will be set to dns.flags.RD.
1026 @param qname: The query name.
1027 @type qname: dns.name.Name object or string
1028 @param rdtype: The desired rdata type.
1030 @param rdclass: The desired rdata class; the default is class IN.
1032 @param use_edns: The EDNS level to use; the default is None (no EDNS).
1033 See the description of dns.message.Message.use_edns() for the possible
1034 values for use_edns and their meanings.
1035 @type use_edns: int or bool or None
1036 @param want_dnssec: Should the query indicate that DNSSEC is desired?
1037 @type want_dnssec: bool
1038 @rtype: dns.message.Message object"""
1040 if isinstance(qname
, (str, unicode)):
1041 qname
= dns
.name
.from_text(qname
)
1042 if isinstance(rdtype
, (str, unicode)):
1043 rdtype
= dns
.rdatatype
.from_text(rdtype
)
1044 if isinstance(rdclass
, (str, unicode)):
1045 rdclass
= dns
.rdataclass
.from_text(rdclass
)
1047 m
.flags |
= dns
.flags
.RD
1048 m
.find_rrset(m
.question
, qname
, rdclass
, rdtype
, create
=True,
1050 m
.use_edns(use_edns
)
1051 m
.want_dnssec(want_dnssec
)
1054 def make_response(query
, recursion_available
=False, our_payload
=8192):
1055 """Make a message which is a response for the specified query.
1056 The message returned is really a response skeleton; it has all
1057 of the infrastructure required of a response, but none of the
1060 The response's question section is a shallow copy of the query's
1061 question section, so the query's question RRsets should not be
1064 @param query: the query to respond to
1065 @type query: dns.message.Message object
1066 @param recursion_available: should RA be set in the response?
1067 @type recursion_available: bool
1068 @param our_payload: payload size to advertise in EDNS responses; default
1070 @type our_payload: int
1071 @rtype: dns.message.Message object"""
1073 if query
.flags
& dns
.flags
.QR
:
1074 raise dns
.exception
.FormError('specified query message is not a query')
1075 response
= dns
.message
.Message(query
.id)
1076 response
.flags
= dns
.flags
.QR |
(query
.flags
& dns
.flags
.RD
)
1077 if recursion_available
:
1078 response
.flags |
= dns
.flags
.RA
1079 response
.set_opcode(query
.opcode())
1080 response
.question
= list(query
.question
)
1082 response
.use_edns(0, 0, our_payload
, query
.payload
)
1083 if not query
.keyname
is None:
1084 response
.keyname
= query
.keyname
1085 response
.keyring
= query
.keyring
1086 response
.request_mac
= query
.mac