1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Help for building DNS wire format messages"""
17
18 from io import BytesIO
19 import struct
20 import random
21 import time
22 import sys
23
24 import dns.exception
25 import dns.tsig
26 from ._compat import long
27
28
29 QUESTION = 0
30 ANSWER = 1
31 AUTHORITY = 2
32 ADDITIONAL = 3
33
34
36
37 """Helper class for building DNS wire-format messages.
38
39 Most applications can use the higher-level L{dns.message.Message}
40 class and its to_wire() method to generate wire-format messages.
41 This class is for those applications which need finer control
42 over the generation of messages.
43
44 Typical use::
45
46 r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512)
47 r.add_question(qname, qtype, qclass)
48 r.add_rrset(dns.renderer.ANSWER, rrset_1)
49 r.add_rrset(dns.renderer.ANSWER, rrset_2)
50 r.add_rrset(dns.renderer.AUTHORITY, ns_rrset)
51 r.add_edns(0, 0, 4096)
52 r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_1)
53 r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_2)
54 r.write_header()
55 r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac)
56 wire = r.get_wire()
57
58 @ivar output: where rendering is written
59 @type output: BytesIO object
60 @ivar id: the message id
61 @type id: int
62 @ivar flags: the message flags
63 @type flags: int
64 @ivar max_size: the maximum size of the message
65 @type max_size: int
66 @ivar origin: the origin to use when rendering relative names
67 @type origin: dns.name.Name object
68 @ivar compress: the compression table
69 @type compress: dict
70 @ivar section: the section currently being rendered
71 @type section: int (dns.renderer.QUESTION, dns.renderer.ANSWER,
72 dns.renderer.AUTHORITY, or dns.renderer.ADDITIONAL)
73 @ivar counts: list of the number of RRs in each section
74 @type counts: int list of length 4
75 @ivar mac: the MAC of the rendered message (if TSIG was used)
76 @type mac: string
77 """
78
79 - def __init__(self, id=None, flags=0, max_size=65535, origin=None):
80 """Initialize a new renderer.
81
82 @param id: the message id
83 @type id: int
84 @param flags: the DNS message flags
85 @type flags: int
86 @param max_size: the maximum message size; the default is 65535.
87 If rendering results in a message greater than I{max_size},
88 then L{dns.exception.TooBig} will be raised.
89 @type max_size: int
90 @param origin: the origin to use when rendering relative names
91 @type origin: dns.name.Name or None.
92 """
93
94 self.output = BytesIO()
95 if id is None:
96 self.id = random.randint(0, 65535)
97 else:
98 self.id = id
99 self.flags = flags
100 self.max_size = max_size
101 self.origin = origin
102 self.compress = {}
103 self.section = QUESTION
104 self.counts = [0, 0, 0, 0]
105 self.output.write(b'\x00' * 12)
106 self.mac = ''
107
109 """Truncate the output buffer at offset I{where}, and remove any
110 compression table entries that pointed beyond the truncation
111 point.
112
113 @param where: the offset
114 @type where: int
115 """
116
117 self.output.seek(where)
118 self.output.truncate()
119 keys_to_delete = []
120 for k, v in self.compress.items():
121 if v >= where:
122 keys_to_delete.append(k)
123 for k in keys_to_delete:
124 del self.compress[k]
125
127 """Set the renderer's current section.
128
129 Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
130 ADDITIONAL. Sections may be empty.
131
132 @param section: the section
133 @type section: int
134 @raises dns.exception.FormError: an attempt was made to set
135 a section value less than the current section.
136 """
137
138 if self.section != section:
139 if self.section > section:
140 raise dns.exception.FormError
141 self.section = section
142
144 """Add a question to the message.
145
146 @param qname: the question name
147 @type qname: dns.name.Name
148 @param rdtype: the question rdata type
149 @type rdtype: int
150 @param rdclass: the question rdata class
151 @type rdclass: int
152 """
153
154 self._set_section(QUESTION)
155 before = self.output.tell()
156 qname.to_wire(self.output, self.compress, self.origin)
157 self.output.write(struct.pack("!HH", rdtype, rdclass))
158 after = self.output.tell()
159 if after >= self.max_size:
160 self._rollback(before)
161 raise dns.exception.TooBig
162 self.counts[QUESTION] += 1
163
165 """Add the rrset to the specified section.
166
167 Any keyword arguments are passed on to the rdataset's to_wire()
168 routine.
169
170 @param section: the section
171 @type section: int
172 @param rrset: the rrset
173 @type rrset: dns.rrset.RRset object
174 """
175
176 self._set_section(section)
177 before = self.output.tell()
178 n = rrset.to_wire(self.output, self.compress, self.origin, **kw)
179 after = self.output.tell()
180 if after >= self.max_size:
181 self._rollback(before)
182 raise dns.exception.TooBig
183 self.counts[section] += n
184
186 """Add the rdataset to the specified section, using the specified
187 name as the owner name.
188
189 Any keyword arguments are passed on to the rdataset's to_wire()
190 routine.
191
192 @param section: the section
193 @type section: int
194 @param name: the owner name
195 @type name: dns.name.Name object
196 @param rdataset: the rdataset
197 @type rdataset: dns.rdataset.Rdataset object
198 """
199
200 self._set_section(section)
201 before = self.output.tell()
202 n = rdataset.to_wire(name, self.output, self.compress, self.origin,
203 **kw)
204 after = self.output.tell()
205 if after >= self.max_size:
206 self._rollback(before)
207 raise dns.exception.TooBig
208 self.counts[section] += n
209
210 - def add_edns(self, edns, ednsflags, payload, options=None):
211 """Add an EDNS OPT record to the message.
212
213 @param edns: The EDNS level to use.
214 @type edns: int
215 @param ednsflags: EDNS flag values.
216 @type ednsflags: int
217 @param payload: The EDNS sender's payload field, which is the maximum
218 size of UDP datagram the sender can handle.
219 @type payload: int
220 @param options: The EDNS options list
221 @type options: list of dns.edns.Option instances
222 @see: RFC 2671
223 """
224
225
226 ednsflags &= long(0xFF00FFFF)
227 ednsflags |= (edns << 16)
228 self._set_section(ADDITIONAL)
229 before = self.output.tell()
230 self.output.write(struct.pack('!BHHIH', 0, dns.rdatatype.OPT, payload,
231 ednsflags, 0))
232 if options is not None:
233 lstart = self.output.tell()
234 for opt in options:
235 stuff = struct.pack("!HH", opt.otype, 0)
236 self.output.write(stuff)
237 start = self.output.tell()
238 opt.to_wire(self.output)
239 end = self.output.tell()
240 assert end - start < 65536
241 self.output.seek(start - 2)
242 stuff = struct.pack("!H", end - start)
243 self.output.write(stuff)
244 self.output.seek(0, 2)
245 lend = self.output.tell()
246 assert lend - lstart < 65536
247 self.output.seek(lstart - 2)
248 stuff = struct.pack("!H", lend - lstart)
249 self.output.write(stuff)
250 self.output.seek(0, 2)
251 after = self.output.tell()
252 if after >= self.max_size:
253 self._rollback(before)
254 raise dns.exception.TooBig
255 self.counts[ADDITIONAL] += 1
256
259 """Add a TSIG signature to the message.
260
261 @param keyname: the TSIG key name
262 @type keyname: dns.name.Name object
263 @param secret: the secret to use
264 @type secret: string
265 @param fudge: TSIG time fudge
266 @type fudge: int
267 @param id: the message id to encode in the tsig signature
268 @type id: int
269 @param tsig_error: TSIG error code; default is 0.
270 @type tsig_error: int
271 @param other_data: TSIG other data.
272 @type other_data: string
273 @param request_mac: This message is a response to the request which
274 had the specified MAC.
275 @type request_mac: string
276 @param algorithm: the TSIG algorithm to use
277 @type algorithm: dns.name.Name object
278 """
279
280 self._set_section(ADDITIONAL)
281 before = self.output.tell()
282 s = self.output.getvalue()
283 (tsig_rdata, self.mac, ctx) = dns.tsig.sign(s,
284 keyname,
285 secret,
286 int(time.time()),
287 fudge,
288 id,
289 tsig_error,
290 other_data,
291 request_mac,
292 algorithm=algorithm)
293 keyname.to_wire(self.output, self.compress, self.origin)
294 self.output.write(struct.pack('!HHIH', dns.rdatatype.TSIG,
295 dns.rdataclass.ANY, 0, 0))
296 rdata_start = self.output.tell()
297 self.output.write(tsig_rdata)
298 after = self.output.tell()
299 assert after - rdata_start < 65536
300 if after >= self.max_size:
301 self._rollback(before)
302 raise dns.exception.TooBig
303 self.output.seek(rdata_start - 2)
304 self.output.write(struct.pack('!H', after - rdata_start))
305 self.counts[ADDITIONAL] += 1
306 self.output.seek(10)
307 self.output.write(struct.pack('!H', self.counts[ADDITIONAL]))
308 self.output.seek(0, 2)
309
311 """Write the DNS message header.
312
313 Writing the DNS message header is done after all sections
314 have been rendered, but before the optional TSIG signature
315 is added.
316 """
317
318 self.output.seek(0)
319 self.output.write(struct.pack('!HHHHHH', self.id, self.flags,
320 self.counts[0], self.counts[1],
321 self.counts[2], self.counts[3]))
322 self.output.seek(0, 2)
323
325 """Return the wire format message.
326
327 @rtype: string
328 """
329
330 return self.output.getvalue()
331