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.
37 class ShortHeader(dns
.exception
.FormError
):
38 """Raised if the DNS packet passed to from_wire() is too short."""
41 class TrailingJunk(dns
.exception
.FormError
):
42 """Raised if the DNS packet passed to from_wire() has extra junk
46 class UnknownHeaderField(dns
.exception
.DNSException
):
47 """Raised if a header field name is not recognized when converting from
48 text into a message."""
51 class BadEDNS(dns
.exception
.FormError
):
52 """Raised if an OPT record occurs somewhere other than the start of
53 the additional data section."""
56 class BadTSIG(dns
.exception
.FormError
):
57 """Raised if a TSIG record occurs somewhere other than the end of
58 the additional data section."""
61 class UnknownTSIGKey(dns
.exception
.DNSException
):
62 """Raised if we got a TSIG but don't know the key."""
65 class Message(object):
68 @ivar id: The query id; the default is a randomly chosen id.
70 @ivar flags: The DNS flags of the message. @see: RFC 1035 for an
71 explanation of these flags.
73 @ivar question: The question section.
74 @type question: list of dns.rrset.RRset objects
75 @ivar answer: The answer section.
76 @type answer: list of dns.rrset.RRset objects
77 @ivar authority: The authority section.
78 @type authority: list of dns.rrset.RRset objects
79 @ivar additional: The additional data section.
80 @type additional: list of dns.rrset.RRset objects
81 @ivar edns: The EDNS level to use. The default is -1, no Edns.
83 @ivar ednsflags: The EDNS flags
85 @ivar payload: The EDNS payload size. The default is 0.
87 @ivar options: The EDNS options
88 @type options: list of dns.edns.Option objects
89 @ivar request_payload: The associated request's EDNS payload size.
90 @type request_payload: int
91 @ivar keyring: The TSIG keyring to use. The default is None.
93 @ivar keyname: The TSIG keyname to use. The default is None.
94 @type keyname: dns.name.Name object
95 @ivar keyalgorithm: The TSIG key algorithm to use. The default is
96 dns.tsig.default_algorithm.
97 @type keyalgorithm: string
98 @ivar request_mac: The TSIG MAC of the request message associated with
99 this message; used when validating TSIG signatures. @see: RFC 2845 for
100 more information on TSIG fields.
101 @type request_mac: string
102 @ivar fudge: TSIG time fudge; default is 300 seconds.
104 @ivar original_id: TSIG original id; defaults to the message's id
105 @type original_id: int
106 @ivar tsig_error: TSIG error code; default is 0.
107 @type tsig_error: int
108 @ivar other_data: TSIG other data.
109 @type other_data: string
110 @ivar mac: The TSIG MAC for this message.
112 @ivar xfr: Is the message being used to contain the results of a DNS
113 zone transfer? The default is False.
115 @ivar origin: The origin of the zone in messages which are used for
116 zone transfers or for DNS dynamic updates. The default is None.
117 @type origin: dns.name.Name object
118 @ivar tsig_ctx: The TSIG signature context associated with this
119 message. The default is None.
120 @type tsig_ctx: hmac.HMAC object
121 @ivar had_tsig: Did the message decoded from wire format have a TSIG
124 @ivar multi: Is this message part of a multi-message sequence? The
125 default is false. This variable is used when validating TSIG signatures
126 on messages which are part of a zone transfer.
128 @ivar first: Is this message standalone, or the first of a multi
129 message sequence? This variable is used when validating TSIG signatures
130 on messages which are part of a zone transfer.
132 @ivar index: An index of rrsets in the message. The index key is
133 (section, name, rdclass, rdtype, covers, deleting). Indexing can be
134 disabled by setting the index to None.
138 def __init__(self
, id=None):
140 self
.id = dns
.entropy
.random_16()
152 self
.request_payload
= 0
155 self
.keyalgorithm
= dns
.tsig
.default_algorithm
156 self
.request_mac
= ''
160 self
.original_id
= self
.id
165 self
.had_tsig
= False
171 return '<DNS message, ID ' + `self
.id`
+ '>'
174 return self
.to_text()
176 def to_text(self
, origin
=None, relativize
=True, **kw
):
177 """Convert the message to text.
179 The I{origin}, I{relativize}, and any other keyword
180 arguments are passed to the rrset to_wire() method.
185 s
= cStringIO
.StringIO()
186 print >> s
, 'id %d' % self
.id
187 print >> s
, 'opcode %s' % \
188 dns
.opcode
.to_text(dns
.opcode
.from_flags(self
.flags
))
189 rc
= dns
.rcode
.from_flags(self
.flags
, self
.ednsflags
)
190 print >> s
, 'rcode %s' % dns
.rcode
.to_text(rc
)
191 print >> s
, 'flags %s' % dns
.flags
.to_text(self
.flags
)
193 print >> s
, 'edns %s' % self
.edns
194 if self
.ednsflags
!= 0:
195 print >> s
, 'eflags %s' % \
196 dns
.flags
.edns_to_text(self
.ednsflags
)
197 print >> s
, 'payload', self
.payload
198 is_update
= dns
.opcode
.is_update(self
.flags
)
202 print >> s
, ';QUESTION'
203 for rrset
in self
.question
:
204 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
206 print >> s
, ';PREREQ'
208 print >> s
, ';ANSWER'
209 for rrset
in self
.answer
:
210 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
212 print >> s
, ';UPDATE'
214 print >> s
, ';AUTHORITY'
215 for rrset
in self
.authority
:
216 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
217 print >> s
, ';ADDITIONAL'
218 for rrset
in self
.additional
:
219 print >> s
, rrset
.to_text(origin
, relativize
, **kw
)
221 # We strip off the final \n so the caller can print the result without
222 # doing weird things to get around eccentricities in Python print
225 return s
.getvalue()[:-1]
227 def __eq__(self
, other
):
228 """Two messages are equal if they have the same content in the
229 header, question, answer, and authority sections.
231 if not isinstance(other
, Message
):
233 if self
.id != other
.id:
235 if self
.flags
!= other
.flags
:
237 for n
in self
.question
:
238 if n
not in other
.question
:
240 for n
in other
.question
:
241 if n
not in self
.question
:
243 for n
in self
.answer
:
244 if n
not in other
.answer
:
246 for n
in other
.answer
:
247 if n
not in self
.answer
:
249 for n
in self
.authority
:
250 if n
not in other
.authority
:
252 for n
in other
.authority
:
253 if n
not in self
.authority
:
257 def __ne__(self
, other
):
258 """Are two messages not equal?
260 return not self
.__eq
__(other
)
262 def is_response(self
, other
):
263 """Is other a response to self?
265 if other
.flags
& dns
.flags
.QR
== 0 or \
266 self
.id != other
.id or \
267 dns
.opcode
.from_flags(self
.flags
) != \
268 dns
.opcode
.from_flags(other
.flags
):
270 if dns
.rcode
.from_flags(other
.flags
, other
.ednsflags
) != \
273 if dns
.opcode
.is_update(self
.flags
):
275 for n
in self
.question
:
276 if n
not in other
.question
:
278 for n
in other
.question
:
279 if n
not in self
.question
:
283 def section_number(self
, section
):
284 if section
is self
.question
:
286 elif section
is self
.answer
:
288 elif section
is self
.authority
:
290 elif section
is self
.additional
:
293 raise ValueError('unknown section')
295 def find_rrset(self
, section
, name
, rdclass
, rdtype
,
296 covers
=dns
.rdatatype
.NONE
, deleting
=None, create
=False,
298 """Find the RRset with the given attributes in the specified section.
300 @param section: the section of the message to look in, e.g.
302 @type section: list of dns.rrset.RRset objects
303 @param name: the name of the RRset
304 @type name: dns.name.Name object
305 @param rdclass: the class of the RRset
307 @param rdtype: the type of the RRset
309 @param covers: the covers value of the RRset
311 @param deleting: the deleting value of the RRset
313 @param create: If True, create the RRset if it is not found.
314 The created RRset is appended to I{section}.
316 @param force_unique: If True and create is also True, create a
317 new RRset regardless of whether a matching RRset exists already.
318 @type force_unique: bool
319 @raises KeyError: the RRset was not found and create was False
320 @rtype: dns.rrset.RRset object"""
322 key
= (self
.section_number(section
),
323 name
, rdclass
, rdtype
, covers
, deleting
)
325 if not self
.index
is None:
326 rrset
= self
.index
.get(key
)
327 if not rrset
is None:
330 for rrset
in section
:
331 if rrset
.match(name
, rdclass
, rdtype
, covers
, deleting
):
335 rrset
= dns
.rrset
.RRset(name
, rdclass
, rdtype
, covers
, deleting
)
336 section
.append(rrset
)
337 if not self
.index
is None:
338 self
.index
[key
] = rrset
341 def get_rrset(self
, section
, name
, rdclass
, rdtype
,
342 covers
=dns
.rdatatype
.NONE
, deleting
=None, create
=False,
344 """Get the RRset with the given attributes in the specified section.
346 If the RRset is not found, None is returned.
348 @param section: the section of the message to look in, e.g.
350 @type section: list of dns.rrset.RRset objects
351 @param name: the name of the RRset
352 @type name: dns.name.Name object
353 @param rdclass: the class of the RRset
355 @param rdtype: the type of the RRset
357 @param covers: the covers value of the RRset
359 @param deleting: the deleting value of the RRset
361 @param create: If True, create the RRset if it is not found.
362 The created RRset is appended to I{section}.
364 @param force_unique: If True and create is also True, create a
365 new RRset regardless of whether a matching RRset exists already.
366 @type force_unique: bool
367 @rtype: dns.rrset.RRset object or None"""
370 rrset
= self
.find_rrset(section
, name
, rdclass
, rdtype
, covers
,
371 deleting
, create
, force_unique
)
376 def to_wire(self
, origin
=None, max_size
=0, **kw
):
377 """Return a string containing the message in DNS compressed wire
380 Additional keyword arguments are passed to the rrset to_wire()
383 @param origin: The origin to be appended to any relative names.
384 @type origin: dns.name.Name object
385 @param max_size: The maximum size of the wire format output; default
386 is 0, which means 'the message's request payload, if nonzero, or
389 @raises dns.exception.TooBig: max_size was exceeded
394 if self
.request_payload
!= 0:
395 max_size
= self
.request_payload
400 elif max_size
> 65535:
402 r
= dns
.renderer
.Renderer(self
.id, self
.flags
, max_size
, origin
)
403 for rrset
in self
.question
:
404 r
.add_question(rrset
.name
, rrset
.rdtype
, rrset
.rdclass
)
405 for rrset
in self
.answer
:
406 r
.add_rrset(dns
.renderer
.ANSWER
, rrset
, **kw
)
407 for rrset
in self
.authority
:
408 r
.add_rrset(dns
.renderer
.AUTHORITY
, rrset
, **kw
)
410 r
.add_edns(self
.edns
, self
.ednsflags
, self
.payload
, self
.options
)
411 for rrset
in self
.additional
:
412 r
.add_rrset(dns
.renderer
.ADDITIONAL
, rrset
, **kw
)
414 if not self
.keyname
is None:
415 r
.add_tsig(self
.keyname
, self
.keyring
[self
.keyname
],
416 self
.fudge
, self
.original_id
, self
.tsig_error
,
417 self
.other_data
, self
.request_mac
,
422 def use_tsig(self
, keyring
, keyname
=None, fudge
=300,
423 original_id
=None, tsig_error
=0, other_data
='',
424 algorithm
=dns
.tsig
.default_algorithm
):
425 """When sending, a TSIG signature using the specified keyring
426 and keyname should be added.
428 @param keyring: The TSIG keyring to use; defaults to None.
430 @param keyname: The name of the TSIG key to use; defaults to None.
431 The key must be defined in the keyring. If a keyring is specified
432 but a keyname is not, then the key used will be the first key in the
433 keyring. Note that the order of keys in a dictionary is not defined,
434 so applications should supply a keyname when a keyring is used, unless
435 they know the keyring contains only one key.
436 @type keyname: dns.name.Name or string
437 @param fudge: TSIG time fudge; default is 300 seconds.
439 @param original_id: TSIG original id; defaults to the message's id
440 @type original_id: int
441 @param tsig_error: TSIG error code; default is 0.
442 @type tsig_error: int
443 @param other_data: TSIG other data.
444 @type other_data: string
445 @param algorithm: The TSIG algorithm to use; defaults to
446 dns.tsig.default_algorithm
449 self
.keyring
= keyring
451 self
.keyname
= self
.keyring
.keys()[0]
453 if isinstance(keyname
, (str, unicode)):
454 keyname
= dns
.name
.from_text(keyname
)
455 self
.keyname
= keyname
456 self
.keyalgorithm
= algorithm
458 if original_id
is None:
459 self
.original_id
= self
.id
461 self
.original_id
= original_id
462 self
.tsig_error
= tsig_error
463 self
.other_data
= other_data
465 def use_edns(self
, edns
=0, ednsflags
=0, payload
=1280, request_payload
=None, options
=None):
466 """Configure EDNS behavior.
467 @param edns: The EDNS level to use. Specifying None, False, or -1
468 means 'do not use EDNS', and in this case the other parameters are
469 ignored. Specifying True is equivalent to specifying 0, i.e. 'use
471 @type edns: int or bool or None
472 @param ednsflags: EDNS flag values.
474 @param payload: The EDNS sender's payload field, which is the maximum
475 size of UDP datagram the sender can handle.
477 @param request_payload: The EDNS payload size to use when sending
478 this message. If not specified, defaults to the value of payload.
479 @type request_payload: int or None
480 @param options: The EDNS options
481 @type options: None or list of dns.edns.Option objects
484 if edns
is None or edns
is False:
488 if request_payload
is None:
489 request_payload
= payload
496 # make sure the EDNS version in ednsflags agrees with edns
497 ednsflags
&= 0xFF00FFFFL
498 ednsflags |
= (edns
<< 16)
502 self
.ednsflags
= ednsflags
503 self
.payload
= payload
504 self
.options
= options
505 self
.request_payload
= request_payload
507 def want_dnssec(self
, wanted
=True):
508 """Enable or disable 'DNSSEC desired' flag in requests.
509 @param wanted: Is DNSSEC desired? If True, EDNS is enabled if
510 required, and then the DO bit is set. If False, the DO bit is
511 cleared if EDNS is enabled.
517 self
.ednsflags |
= dns
.flags
.DO
519 self
.ednsflags
&= ~dns
.flags
.DO
525 return dns
.rcode
.from_flags(self
.flags
, self
.ednsflags
)
527 def set_rcode(self
, rcode
):
529 @param rcode: the rcode
532 (value
, evalue
) = dns
.rcode
.to_flags(rcode
)
535 self
.ednsflags
&= 0x00FFFFFFL
536 self
.ednsflags |
= evalue
537 if self
.ednsflags
!= 0 and self
.edns
< 0:
541 """Return the opcode.
544 return dns
.opcode
.from_flags(self
.flags
)
546 def set_opcode(self
, opcode
):
548 @param opcode: the opcode
552 self
.flags |
= dns
.opcode
.to_flags(opcode
)
554 class _WireReader(object):
555 """Wire format reader.
557 @ivar wire: the wire-format message.
559 @ivar message: The message object being built
560 @type message: dns.message.Message object
561 @ivar current: When building a message object from wire format, this
562 variable contains the offset from the beginning of wire of the next octet
565 @ivar updating: Is the message a dynamic update?
567 @ivar one_rr_per_rrset: Put each RR into its own RRset?
568 @type one_rr_per_rrset: bool
569 @ivar zone_rdclass: The class of the zone in messages which are
571 @type zone_rdclass: int
574 def __init__(self
, wire
, message
, question_only
=False,
575 one_rr_per_rrset
=False):
577 self
.message
= message
579 self
.updating
= False
580 self
.zone_rdclass
= dns
.rdataclass
.IN
581 self
.question_only
= question_only
582 self
.one_rr_per_rrset
= one_rr_per_rrset
584 def _get_question(self
, qcount
):
585 """Read the next I{qcount} records from the wire data and add them to
586 the question section.
587 @param qcount: the number of questions in the message
590 if self
.updating
and qcount
> 1:
591 raise dns
.exception
.FormError
593 for i
in xrange(0, qcount
):
594 (qname
, used
) = dns
.name
.from_wire(self
.wire
, self
.current
)
595 if not self
.message
.origin
is None:
596 qname
= qname
.relativize(self
.message
.origin
)
597 self
.current
= self
.current
+ used
598 (rdtype
, rdclass
) = \
600 self
.wire
[self
.current
:self
.current
+ 4])
601 self
.current
= self
.current
+ 4
602 self
.message
.find_rrset(self
.message
.question
, qname
,
603 rdclass
, rdtype
, create
=True,
606 self
.zone_rdclass
= rdclass
608 def _get_section(self
, section
, count
):
609 """Read the next I{count} records from the wire data and add them to
610 the specified section.
611 @param section: the section of the message to which to add records
612 @type section: list of dns.rrset.RRset objects
613 @param count: the number of records to read
616 if self
.updating
or self
.one_rr_per_rrset
:
621 for i
in xrange(0, count
):
622 rr_start
= self
.current
623 (name
, used
) = dns
.name
.from_wire(self
.wire
, self
.current
)
625 if not self
.message
.origin
is None:
626 name
= name
.relativize(self
.message
.origin
)
627 self
.current
= self
.current
+ used
628 (rdtype
, rdclass
, ttl
, rdlen
) = \
629 struct
.unpack('!HHIH',
630 self
.wire
[self
.current
:self
.current
+ 10])
631 self
.current
= self
.current
+ 10
632 if rdtype
== dns
.rdatatype
.OPT
:
633 if not section
is self
.message
.additional
or seen_opt
:
635 self
.message
.payload
= rdclass
636 self
.message
.ednsflags
= ttl
637 self
.message
.edns
= (ttl
& 0xff0000) >> 16
638 self
.message
.options
= []
639 current
= self
.current
644 self
.wire
[current
:current
+ 4])
645 current
= current
+ 4
646 opt
= dns
.edns
.option_from_wire(otype
, self
.wire
, current
, olen
)
647 self
.message
.options
.append(opt
)
648 current
= current
+ olen
649 optslen
= optslen
- 4 - olen
651 elif rdtype
== dns
.rdatatype
.TSIG
:
652 if not (section
is self
.message
.additional
and
655 if self
.message
.keyring
is None:
656 raise UnknownTSIGKey('got signed message without keyring')
657 secret
= self
.message
.keyring
.get(absolute_name
)
659 raise UnknownTSIGKey("key '%s' unknown" % name
)
660 self
.message
.tsig_ctx
= \
661 dns
.tsig
.validate(self
.wire
,
665 self
.message
.request_mac
,
669 self
.message
.tsig_ctx
,
672 self
.message
.had_tsig
= True
676 if self
.updating
and \
677 (rdclass
== dns
.rdataclass
.ANY
or
678 rdclass
== dns
.rdataclass
.NONE
):
680 rdclass
= self
.zone_rdclass
683 if deleting
== dns
.rdataclass
.ANY
or \
684 (deleting
== dns
.rdataclass
.NONE
and \
685 section
== self
.message
.answer
):
686 covers
= dns
.rdatatype
.NONE
689 rd
= dns
.rdata
.from_wire(rdclass
, rdtype
, self
.wire
,
693 if self
.message
.xfr
and rdtype
== dns
.rdatatype
.SOA
:
695 rrset
= self
.message
.find_rrset(section
, name
,
696 rdclass
, rdtype
, covers
,
697 deleting
, True, force_unique
)
700 self
.current
= self
.current
+ rdlen
703 """Read a wire format DNS message and build a dns.message.Message
709 (self
.message
.id, self
.message
.flags
, qcount
, ancount
,
710 aucount
, adcount
) = struct
.unpack('!HHHHHH', self
.wire
[:12])
712 if dns
.opcode
.is_update(self
.message
.flags
):
714 self
._get
_question
(qcount
)
715 if self
.question_only
:
717 self
._get
_section
(self
.message
.answer
, ancount
)
718 self
._get
_section
(self
.message
.authority
, aucount
)
719 self
._get
_section
(self
.message
.additional
, adcount
)
720 if self
.current
!= l
:
722 if self
.message
.multi
and self
.message
.tsig_ctx
and \
723 not self
.message
.had_tsig
:
724 self
.message
.tsig_ctx
.update(self
.wire
)
727 def from_wire(wire
, keyring
=None, request_mac
='', xfr
=False, origin
=None,
728 tsig_ctx
= None, multi
= False, first
= True,
729 question_only
= False, one_rr_per_rrset
= False):
730 """Convert a DNS wire format message into a message
733 @param keyring: The keyring to use if the message is signed.
735 @param request_mac: If the message is a response to a TSIG-signed request,
736 I{request_mac} should be set to the MAC of that request.
737 @type request_mac: string
738 @param xfr: Is this message part of a zone transfer?
740 @param origin: If the message is part of a zone transfer, I{origin}
741 should be the origin name of the zone.
742 @type origin: dns.name.Name object
743 @param tsig_ctx: The ongoing TSIG context, used when validating zone
745 @type tsig_ctx: hmac.HMAC object
746 @param multi: Is this message part of a multiple message sequence?
748 @param first: Is this message standalone, or the first of a multi
751 @param question_only: Read only up to the end of the question section?
752 @type question_only: bool
753 @param one_rr_per_rrset: Put each RR into its own RRset
754 @type one_rr_per_rrset: bool
755 @raises ShortHeader: The message is less than 12 octets long.
756 @raises TrailingJunk: There were octets in the message past the end
757 of the proper DNS message.
758 @raises BadEDNS: An OPT record was in the wrong section, or occurred more
760 @raises BadTSIG: A TSIG record was not the last record of the additional
762 @rtype: dns.message.Message object"""
766 m
.request_mac
= request_mac
769 m
.tsig_ctx
= tsig_ctx
773 reader
= _WireReader(wire
, m
, question_only
, one_rr_per_rrset
)
779 class _TextReader(object):
780 """Text format reader.
782 @ivar tok: the tokenizer
783 @type tok: dns.tokenizer.Tokenizer object
784 @ivar message: The message object being built
785 @type message: dns.message.Message object
786 @ivar updating: Is the message a dynamic update?
788 @ivar zone_rdclass: The class of the zone in messages which are
790 @type zone_rdclass: int
791 @ivar last_name: The most recently read name when building a message object
793 @type last_name: dns.name.Name object
796 def __init__(self
, text
, message
):
797 self
.message
= message
798 self
.tok
= dns
.tokenizer
.Tokenizer(text
)
799 self
.last_name
= None
800 self
.zone_rdclass
= dns
.rdataclass
.IN
801 self
.updating
= False
803 def _header_line(self
, section
):
804 """Process one line from the text format header section."""
806 token
= self
.tok
.get()
809 self
.message
.id = self
.tok
.get_int()
810 elif what
== 'flags':
812 token
= self
.tok
.get()
813 if not token
.is_identifier():
814 self
.tok
.unget(token
)
816 self
.message
.flags
= self
.message
.flags | \
817 dns
.flags
.from_text(token
.value
)
818 if dns
.opcode
.is_update(self
.message
.flags
):
821 self
.message
.edns
= self
.tok
.get_int()
822 self
.message
.ednsflags
= self
.message
.ednsflags | \
823 (self
.message
.edns
<< 16)
824 elif what
== 'eflags':
825 if self
.message
.edns
< 0:
826 self
.message
.edns
= 0
828 token
= self
.tok
.get()
829 if not token
.is_identifier():
830 self
.tok
.unget(token
)
832 self
.message
.ednsflags
= self
.message
.ednsflags | \
833 dns
.flags
.edns_from_text(token
.value
)
834 elif what
== 'payload':
835 self
.message
.payload
= self
.tok
.get_int()
836 if self
.message
.edns
< 0:
837 self
.message
.edns
= 0
838 elif what
== 'opcode':
839 text
= self
.tok
.get_string()
840 self
.message
.flags
= self
.message
.flags | \
841 dns
.opcode
.to_flags(dns
.opcode
.from_text(text
))
842 elif what
== 'rcode':
843 text
= self
.tok
.get_string()
844 self
.message
.set_rcode(dns
.rcode
.from_text(text
))
846 raise UnknownHeaderField
849 def _question_line(self
, section
):
850 """Process one line from the text format question section."""
852 token
= self
.tok
.get(want_leading
= True)
853 if not token
.is_whitespace():
854 self
.last_name
= dns
.name
.from_text(token
.value
, None)
855 name
= self
.last_name
856 token
= self
.tok
.get()
857 if not token
.is_identifier():
858 raise dns
.exception
.SyntaxError
861 rdclass
= dns
.rdataclass
.from_text(token
.value
)
862 token
= self
.tok
.get()
863 if not token
.is_identifier():
864 raise dns
.exception
.SyntaxError
865 except dns
.exception
.SyntaxError:
866 raise dns
.exception
.SyntaxError
868 rdclass
= dns
.rdataclass
.IN
870 rdtype
= dns
.rdatatype
.from_text(token
.value
)
871 self
.message
.find_rrset(self
.message
.question
, name
,
872 rdclass
, rdtype
, create
=True,
875 self
.zone_rdclass
= rdclass
878 def _rr_line(self
, section
):
879 """Process one line from the text format answer, authority, or
880 additional data sections.
885 token
= self
.tok
.get(want_leading
= True)
886 if not token
.is_whitespace():
887 self
.last_name
= dns
.name
.from_text(token
.value
, None)
888 name
= self
.last_name
889 token
= self
.tok
.get()
890 if not token
.is_identifier():
891 raise dns
.exception
.SyntaxError
894 ttl
= int(token
.value
, 0)
895 token
= self
.tok
.get()
896 if not token
.is_identifier():
897 raise dns
.exception
.SyntaxError
898 except dns
.exception
.SyntaxError:
899 raise dns
.exception
.SyntaxError
904 rdclass
= dns
.rdataclass
.from_text(token
.value
)
905 token
= self
.tok
.get()
906 if not token
.is_identifier():
907 raise dns
.exception
.SyntaxError
908 if rdclass
== dns
.rdataclass
.ANY
or rdclass
== dns
.rdataclass
.NONE
:
910 rdclass
= self
.zone_rdclass
911 except dns
.exception
.SyntaxError:
912 raise dns
.exception
.SyntaxError
914 rdclass
= dns
.rdataclass
.IN
916 rdtype
= dns
.rdatatype
.from_text(token
.value
)
917 token
= self
.tok
.get()
918 if not token
.is_eol_or_eof():
919 self
.tok
.unget(token
)
920 rd
= dns
.rdata
.from_text(rdclass
, rdtype
, self
.tok
, None)
924 covers
= dns
.rdatatype
.NONE
925 rrset
= self
.message
.find_rrset(section
, name
,
926 rdclass
, rdtype
, covers
,
927 deleting
, True, self
.updating
)
932 """Read a text format DNS message and build a dns.message.Message
935 line_method
= self
._header
_line
938 token
= self
.tok
.get(True, True)
939 if token
.is_eol_or_eof():
941 if token
.is_comment():
942 u
= token
.value
.upper()
944 line_method
= self
._header
_line
945 elif u
== 'QUESTION' or u
== 'ZONE':
946 line_method
= self
._question
_line
947 section
= self
.message
.question
948 elif u
== 'ANSWER' or u
== 'PREREQ':
949 line_method
= self
._rr
_line
950 section
= self
.message
.answer
951 elif u
== 'AUTHORITY' or u
== 'UPDATE':
952 line_method
= self
._rr
_line
953 section
= self
.message
.authority
954 elif u
== 'ADDITIONAL':
955 line_method
= self
._rr
_line
956 section
= self
.message
.additional
959 self
.tok
.unget(token
)
964 """Convert the text format message into a message object.
966 @param text: The text format message.
968 @raises UnknownHeaderField:
969 @raises dns.exception.SyntaxError:
970 @rtype: dns.message.Message object"""
972 # 'text' can also be a file, but we don't publish that fact
973 # since it's an implementation detail. The official file
974 # interface is from_file().
978 reader
= _TextReader(text
, m
)
984 """Read the next text format message from the specified file.
986 @param f: file or string. If I{f} is a string, it is treated
987 as the name of a file to open.
988 @raises UnknownHeaderField:
989 @raises dns.exception.SyntaxError:
990 @rtype: dns.message.Message object"""
992 if sys
.hexversion
>= 0x02030000:
993 # allow Unicode filenames; turn on universal newline support
994 str_type
= basestring
999 if isinstance(f
, str_type
):
1012 def make_query(qname
, rdtype
, rdclass
= dns
.rdataclass
.IN
, use_edns
=None,
1014 """Make a query message.
1016 The query name, type, and class may all be specified either
1017 as objects of the appropriate type, or as strings.
1019 The query will have a randomly choosen query id, and its DNS flags
1020 will be set to dns.flags.RD.
1022 @param qname: The query name.
1023 @type qname: dns.name.Name object or string
1024 @param rdtype: The desired rdata type.
1026 @param rdclass: The desired rdata class; the default is class IN.
1028 @param use_edns: The EDNS level to use; the default is None (no EDNS).
1029 See the description of dns.message.Message.use_edns() for the possible
1030 values for use_edns and their meanings.
1031 @type use_edns: int or bool or None
1032 @param want_dnssec: Should the query indicate that DNSSEC is desired?
1033 @type want_dnssec: bool
1034 @rtype: dns.message.Message object"""
1036 if isinstance(qname
, (str, unicode)):
1037 qname
= dns
.name
.from_text(qname
)
1038 if isinstance(rdtype
, str):
1039 rdtype
= dns
.rdatatype
.from_text(rdtype
)
1040 if isinstance(rdclass
, str):
1041 rdclass
= dns
.rdataclass
.from_text(rdclass
)
1043 m
.flags |
= dns
.flags
.RD
1044 m
.find_rrset(m
.question
, qname
, rdclass
, rdtype
, create
=True,
1046 m
.use_edns(use_edns
)
1047 m
.want_dnssec(want_dnssec
)
1050 def make_response(query
, recursion_available
=False, our_payload
=8192):
1051 """Make a message which is a response for the specified query.
1052 The message returned is really a response skeleton; it has all
1053 of the infrastructure required of a response, but none of the
1056 The response's question section is a shallow copy of the query's
1057 question section, so the query's question RRsets should not be
1060 @param query: the query to respond to
1061 @type query: dns.message.Message object
1062 @param recursion_available: should RA be set in the response?
1063 @type recursion_available: bool
1064 @param our_payload: payload size to advertise in EDNS responses; default
1066 @type our_payload: int
1067 @rtype: dns.message.Message object"""
1069 if query
.flags
& dns
.flags
.QR
:
1070 raise dns
.exception
.FormError('specified query message is not a query')
1071 response
= dns
.message
.Message(query
.id)
1072 response
.flags
= dns
.flags
.QR |
(query
.flags
& dns
.flags
.RD
)
1073 if recursion_available
:
1074 response
.flags |
= dns
.flags
.RA
1075 response
.set_opcode(query
.opcode())
1076 response
.question
= list(query
.question
)
1078 response
.use_edns(0, 0, our_payload
, query
.payload
)
1079 if not query
.keyname
is None:
1080 response
.keyname
= query
.keyname
1081 response
.keyring
= query
.keyring
1082 response
.request_mac
= query
.mac