Package dns :: Module renderer
[hide private]
[frames] | no frames]

Source Code for Module dns.renderer

  1  # Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. 
  2  # 
  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. 
  7  # 
  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. 
 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   
35 -class Renderer(object):
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
108 - def _rollback(self, where):
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
126 - def _set_section(self, section):
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
143 - def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN):
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
164 - def add_rrset(self, section, rrset, **kw):
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
185 - def add_rdataset(self, section, name, rdataset, **kw):
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 # make sure the EDNS version in ednsflags agrees with edns 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
257 - def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data, 258 request_mac, algorithm=dns.tsig.default_algorithm):
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
310 - def write_header(self):
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
324 - def get_wire(self):
325 """Return the wire format message. 326 327 @rtype: string 328 """ 329 330 return self.output.getvalue()
331