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