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.
16 """Help for building DNS wire format messages"""
31 class Renderer(object):
32 """Helper class for building DNS wire-format messages.
34 Most applications can use the higher-level L{dns.message.Message}
35 class and its to_wire() method to generate wire-format messages.
36 This class is for those applications which need finer control
37 over the generation of messages.
41 r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512)
42 r.add_question(qname, qtype, qclass)
43 r.add_rrset(dns.renderer.ANSWER, rrset_1)
44 r.add_rrset(dns.renderer.ANSWER, rrset_2)
45 r.add_rrset(dns.renderer.AUTHORITY, ns_rrset)
46 r.add_edns(0, 0, 4096)
47 r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_1)
48 r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_2)
50 r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac)
53 @ivar output: where rendering is written
54 @type output: cStringIO.StringIO object
55 @ivar id: the message id
57 @ivar flags: the message flags
59 @ivar max_size: the maximum size of the message
61 @ivar origin: the origin to use when rendering relative names
62 @type origin: dns.name.Name object
63 @ivar compress: the compression table
65 @ivar section: the section currently being rendered
66 @type section: int (dns.renderer.QUESTION, dns.renderer.ANSWER,
67 dns.renderer.AUTHORITY, or dns.renderer.ADDITIONAL)
68 @ivar counts: list of the number of RRs in each section
69 @type counts: int list of length 4
70 @ivar mac: the MAC of the rendered message (if TSIG was used)
74 def __init__(self
, id=None, flags
=0, max_size
=65535, origin
=None):
75 """Initialize a new renderer.
77 @param id: the message id
79 @param flags: the DNS message flags
81 @param max_size: the maximum message size; the default is 65535.
82 If rendering results in a message greater than I{max_size},
83 then L{dns.exception.TooBig} will be raised.
85 @param origin: the origin to use when rendering relative names
86 @type origin: dns.name.Namem or None.
89 self
.output
= cStringIO
.StringIO()
91 self
.id = random
.randint(0, 65535)
95 self
.max_size
= max_size
98 self
.section
= QUESTION
99 self
.counts
= [0, 0, 0, 0]
100 self
.output
.write('\x00' * 12)
103 def _rollback(self
, where
):
104 """Truncate the output buffer at offset I{where}, and remove any
105 compression table entries that pointed beyond the truncation
108 @param where: the offset
112 self
.output
.seek(where
)
113 self
.output
.truncate()
115 for k
, v
in self
.compress
.iteritems():
117 keys_to_delete
.append(k
)
118 for k
in keys_to_delete
:
121 def _set_section(self
, section
):
122 """Set the renderer's current section.
124 Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
125 ADDITIONAL. Sections may be empty.
127 @param section: the section
129 @raises dns.exception.FormError: an attempt was made to set
130 a section value less than the current section.
133 if self
.section
!= section
:
134 if self
.section
> section
:
135 raise dns
.exception
.FormError
136 self
.section
= section
138 def add_question(self
, qname
, rdtype
, rdclass
=dns
.rdataclass
.IN
):
139 """Add a question to the message.
141 @param qname: the question name
142 @type qname: dns.name.Name
143 @param rdtype: the question rdata type
145 @param rdclass: the question rdata class
149 self
._set
_section
(QUESTION
)
150 before
= self
.output
.tell()
151 qname
.to_wire(self
.output
, self
.compress
, self
.origin
)
152 self
.output
.write(struct
.pack("!HH", rdtype
, rdclass
))
153 after
= self
.output
.tell()
154 if after
>= self
.max_size
:
155 self
._rollback
(before
)
156 raise dns
.exception
.TooBig
157 self
.counts
[QUESTION
] += 1
159 def add_rrset(self
, section
, rrset
, **kw
):
160 """Add the rrset to the specified section.
162 Any keyword arguments are passed on to the rdataset's to_wire()
165 @param section: the section
167 @param rrset: the rrset
168 @type rrset: dns.rrset.RRset object
171 self
._set
_section
(section
)
172 before
= self
.output
.tell()
173 n
= rrset
.to_wire(self
.output
, self
.compress
, self
.origin
, **kw
)
174 after
= self
.output
.tell()
175 if after
>= self
.max_size
:
176 self
._rollback
(before
)
177 raise dns
.exception
.TooBig
178 self
.counts
[section
] += n
180 def add_rdataset(self
, section
, name
, rdataset
, **kw
):
181 """Add the rdataset to the specified section, using the specified
182 name as the owner name.
184 Any keyword arguments are passed on to the rdataset's to_wire()
187 @param section: the section
189 @param name: the owner name
190 @type name: dns.name.Name object
191 @param rdataset: the rdataset
192 @type rdataset: dns.rdataset.Rdataset object
195 self
._set
_section
(section
)
196 before
= self
.output
.tell()
197 n
= rdataset
.to_wire(name
, self
.output
, self
.compress
, self
.origin
,
199 after
= self
.output
.tell()
200 if after
>= self
.max_size
:
201 self
._rollback
(before
)
202 raise dns
.exception
.TooBig
203 self
.counts
[section
] += n
205 def add_edns(self
, edns
, ednsflags
, payload
, options
=None):
206 """Add an EDNS OPT record to the message.
208 @param edns: The EDNS level to use.
210 @param ednsflags: EDNS flag values.
212 @param payload: The EDNS sender's payload field, which is the maximum
213 size of UDP datagram the sender can handle.
215 @param options: The EDNS options list
216 @type options: list of dns.edns.Option instances
220 # make sure the EDNS version in ednsflags agrees with edns
221 ednsflags
&= 0xFF00FFFFL
222 ednsflags |
= (edns
<< 16)
223 self
._set
_section
(ADDITIONAL
)
224 before
= self
.output
.tell()
225 self
.output
.write(struct
.pack('!BHHIH', 0, dns
.rdatatype
.OPT
, payload
,
227 if not options
is None:
228 lstart
= self
.output
.tell()
230 stuff
= struct
.pack("!HH", opt
.otype
, 0)
231 self
.output
.write(stuff
)
232 start
= self
.output
.tell()
233 opt
.to_wire(self
.output
)
234 end
= self
.output
.tell()
235 assert end
- start
< 65536
236 self
.output
.seek(start
- 2)
237 stuff
= struct
.pack("!H", end
- start
)
238 self
.output
.write(stuff
)
239 self
.output
.seek(0, 2)
240 lend
= self
.output
.tell()
241 assert lend
- lstart
< 65536
242 self
.output
.seek(lstart
- 2)
243 stuff
= struct
.pack("!H", lend
- lstart
)
244 self
.output
.write(stuff
)
245 self
.output
.seek(0, 2)
246 after
= self
.output
.tell()
247 if after
>= self
.max_size
:
248 self
._rollback
(before
)
249 raise dns
.exception
.TooBig
250 self
.counts
[ADDITIONAL
] += 1
252 def add_tsig(self
, keyname
, secret
, fudge
, id, tsig_error
, other_data
,
253 request_mac
, algorithm
=dns
.tsig
.default_algorithm
):
254 """Add a TSIG signature to the message.
256 @param keyname: the TSIG key name
257 @type keyname: dns.name.Name object
258 @param secret: the secret to use
260 @param fudge: TSIG time fudge
262 @param id: the message id to encode in the tsig signature
264 @param tsig_error: TSIG error code; default is 0.
265 @type tsig_error: int
266 @param other_data: TSIG other data.
267 @type other_data: string
268 @param request_mac: This message is a response to the request which
269 had the specified MAC.
270 @param algorithm: the TSIG algorithm to use
271 @type request_mac: string
274 self
._set
_section
(ADDITIONAL
)
275 before
= self
.output
.tell()
276 s
= self
.output
.getvalue()
277 (tsig_rdata
, self
.mac
, ctx
) = dns
.tsig
.sign(s
,
287 keyname
.to_wire(self
.output
, self
.compress
, self
.origin
)
288 self
.output
.write(struct
.pack('!HHIH', dns
.rdatatype
.TSIG
,
289 dns
.rdataclass
.ANY
, 0, 0))
290 rdata_start
= self
.output
.tell()
291 self
.output
.write(tsig_rdata
)
292 after
= self
.output
.tell()
293 assert after
- rdata_start
< 65536
294 if after
>= self
.max_size
:
295 self
._rollback
(before
)
296 raise dns
.exception
.TooBig
297 self
.output
.seek(rdata_start
- 2)
298 self
.output
.write(struct
.pack('!H', after
- rdata_start
))
299 self
.counts
[ADDITIONAL
] += 1
301 self
.output
.write(struct
.pack('!H', self
.counts
[ADDITIONAL
]))
302 self
.output
.seek(0, 2)
304 def write_header(self
):
305 """Write the DNS message header.
307 Writing the DNS message header is done asfter all sections
308 have been rendered, but before the optional TSIG signature
313 self
.output
.write(struct
.pack('!HHHHHH', self
.id, self
.flags
,
314 self
.counts
[0], self
.counts
[1],
315 self
.counts
[2], self
.counts
[3]))
316 self
.output
.seek(0, 2)
319 """Return the wire format message.
324 return self
.output
.getvalue()