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 import io
19 import struct
20 import random
21 import time
22
23 import dns.exception
24 import dns.tsig
25
26 QUESTION = 0
27 ANSWER = 1
28 AUTHORITY = 2
29 ADDITIONAL = 3
30
32 """Helper class for building DNS wire-format messages.
33
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.
38
39 Typical use::
40
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)
49 r.write_header()
50 r.add_tsig(keyname, secret, 300, 1, 0, b'', request_mac)
51 wire = r.get_wire()
52
53 @ivar output: where rendering is written
54 @type output: io.BytesIO object
55 @ivar id: the message id
56 @type id: int
57 @ivar flags: the message flags
58 @type flags: int
59 @ivar max_size: the maximum size of the message
60 @type max_size: int
61 @ivar origin: the origin to use when rendering relative names
62 @type origin: dns.name.Name object
63 @ivar compress: the compression table
64 @type compress: dict
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)
71 @type mac: bytes
72 """
73
74 - def __init__(self, id=None, flags=0, max_size=65535, origin=None):
75 """Initialize a new renderer.
76
77 @param id: the message id
78 @type id: int
79 @param flags: the DNS message flags
80 @type flags: int
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.
84 @type max_size: int
85 @param origin: the origin to use when rendering relative names
86 @type origin: dns.name.Namem or None.
87 """
88
89 self.output = io.BytesIO()
90 if id is None:
91 self.id = random.randint(0, 65535)
92 else:
93 self.id = id
94 self.flags = flags
95 self.max_size = max_size
96 self.origin = origin
97 self.compress = {}
98 self.section = QUESTION
99 self.counts = [0, 0, 0, 0]
100 self.output.write(b'\x00' * 12)
101 self.mac = b''
102
104 """Truncate the output buffer at offset I{where}, and remove any
105 compression table entries that pointed beyond the truncation
106 point.
107
108 @param where: the offset
109 @type where: int
110 """
111
112 self.output.seek(where)
113 self.output.truncate()
114 keys_to_delete = []
115 for k, v in self.compress.items():
116 if v >= where:
117 keys_to_delete.append(k)
118 for k in keys_to_delete:
119 del self.compress[k]
120
122 """Set the renderer's current section.
123
124 Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
125 ADDITIONAL. Sections may be empty.
126
127 @param section: the section
128 @type section: int
129 @raises dns.exception.FormError: an attempt was made to set
130 a section value less than the current section.
131 """
132
133 if self.section != section:
134 if self.section > section:
135 raise dns.exception.FormError
136 self.section = section
137
139 """Add a question to the message.
140
141 @param qname: the question name
142 @type qname: dns.name.Name
143 @param rdtype: the question rdata type
144 @type rdtype: int
145 @param rdclass: the question rdata class
146 @type rdclass: int
147 """
148
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
158
160 """Add the rrset to the specified section.
161
162 Any keyword arguments are passed on to the rdataset's to_wire()
163 routine.
164
165 @param section: the section
166 @type section: int
167 @param rrset: the rrset
168 @type rrset: dns.rrset.RRset object
169 """
170
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
179
181 """Add the rdataset to the specified section, using the specified
182 name as the owner name.
183
184 Any keyword arguments are passed on to the rdataset's to_wire()
185 routine.
186
187 @param section: the section
188 @type section: int
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
193 """
194
195 self._set_section(section)
196 before = self.output.tell()
197 n = rdataset.to_wire(name, self.output, self.compress, self.origin,
198 **kw)
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
204
205 - def add_edns(self, edns, ednsflags, payload, options=None):
206 """Add an EDNS OPT record to the message.
207
208 @param edns: The EDNS level to use.
209 @type edns: int
210 @param ednsflags: EDNS flag values.
211 @type ednsflags: int
212 @param payload: The EDNS sender's payload field, which is the maximum
213 size of UDP datagram the sender can handle.
214 @type payload: int
215 @param options: The EDNS options list
216 @type options: list of dns.edns.Option instances
217 @see: RFC 2671
218 """
219
220
221 ednsflags &= 0xFF00FFFF
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,
226 ednsflags, 0))
227 if not options is None:
228 lstart = self.output.tell()
229 for opt in options:
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
251
254 """Add a TSIG signature to the message.
255
256 @param keyname: the TSIG key name
257 @type keyname: dns.name.Name object
258 @param secret: the secret to use
259 @type secret: string
260 @param fudge: TSIG time fudge
261 @type fudge: int
262 @param id: the message id to encode in the tsig signature
263 @type id: int
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 @type request_mac: bytes
271 @param algorithm: the TSIG algorithm to use
272 @type algorithm: dns.name.Name object
273 """
274
275 self._set_section(ADDITIONAL)
276 before = self.output.tell()
277 s = self.output.getvalue()
278 (tsig_rdata, self.mac, ctx) = dns.tsig.sign(s,
279 keyname,
280 secret,
281 int(time.time()),
282 fudge,
283 id,
284 tsig_error,
285 other_data,
286 request_mac,
287 algorithm=algorithm)
288 keyname.to_wire(self.output, self.compress, self.origin)
289 self.output.write(struct.pack('!HHIH', dns.rdatatype.TSIG,
290 dns.rdataclass.ANY, 0, 0))
291 rdata_start = self.output.tell()
292 self.output.write(tsig_rdata)
293 after = self.output.tell()
294 assert after - rdata_start < 65536
295 if after >= self.max_size:
296 self._rollback(before)
297 raise dns.exception.TooBig
298 self.output.seek(rdata_start - 2)
299 self.output.write(struct.pack('!H', after - rdata_start))
300 self.counts[ADDITIONAL] += 1
301 self.output.seek(10)
302 self.output.write(struct.pack('!H', self.counts[ADDITIONAL]))
303 self.output.seek(0, 2)
304
306 """Write the DNS message header.
307
308 Writing the DNS message header is done asfter all sections
309 have been rendered, but before the optional TSIG signature
310 is added.
311 """
312
313 self.output.seek(0)
314 self.output.write(struct.pack('!HHHHHH', self.id, self.flags,
315 self.counts[0], self.counts[1],
316 self.counts[2], self.counts[3]))
317 self.output.seek(0, 2)
318
320 """Return the wire format message.
321
322 @rtype: string
323 """
324
325 return self.output.getvalue()
326