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

Source Code for Module dns.rdata

  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  """DNS rdata. 
 17   
 18  @var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to 
 19  the module which implements that type. 
 20  @type _rdata_modules: dict 
 21  @var _module_prefix: The prefix to use when forming modules names.  The 
 22  default is 'dns.rdtypes'.  Changing this value will break the library. 
 23  @type _module_prefix: string 
 24  @var _hex_chunk: At most this many octets that will be represented in each 
 25  chunk of hexstring that _hexify() produces before whitespace occurs. 
 26  @type _hex_chunk: int""" 
 27   
 28  import cStringIO 
 29   
 30  import dns.exception 
 31  import dns.name 
 32  import dns.rdataclass 
 33  import dns.rdatatype 
 34  import dns.tokenizer 
 35  import dns.wiredata 
 36   
 37  _hex_chunksize = 32 
 38   
39 -def _hexify(data, chunksize=None):
40 """Convert a binary string into its hex encoding, broken up into chunks 41 of I{chunksize} characters separated by a space. 42 43 @param data: the binary string 44 @type data: string 45 @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize} 46 @rtype: string 47 """ 48 49 if chunksize is None: 50 chunksize = _hex_chunksize 51 hex = data.encode('hex_codec') 52 l = len(hex) 53 if l > chunksize: 54 chunks = [] 55 i = 0 56 while i < l: 57 chunks.append(hex[i : i + chunksize]) 58 i += chunksize 59 hex = ' '.join(chunks) 60 return hex
61 62 _base64_chunksize = 32 63
64 -def _base64ify(data, chunksize=None):
65 """Convert a binary string into its base64 encoding, broken up into chunks 66 of I{chunksize} characters separated by a space. 67 68 @param data: the binary string 69 @type data: string 70 @param chunksize: the chunk size. Default is 71 L{dns.rdata._base64_chunksize} 72 @rtype: string 73 """ 74 75 if chunksize is None: 76 chunksize = _base64_chunksize 77 b64 = data.encode('base64_codec') 78 b64 = b64.replace('\n', '') 79 l = len(b64) 80 if l > chunksize: 81 chunks = [] 82 i = 0 83 while i < l: 84 chunks.append(b64[i : i + chunksize]) 85 i += chunksize 86 b64 = ' '.join(chunks) 87 return b64
88 89 __escaped = { 90 '"' : True, 91 '\\' : True, 92 } 93
94 -def _escapify(qstring):
95 """Escape the characters in a quoted string which need it. 96 97 @param qstring: the string 98 @type qstring: string 99 @returns: the escaped string 100 @rtype: string 101 """ 102 103 text = '' 104 for c in qstring: 105 if c in __escaped: 106 text += '\\' + c 107 elif ord(c) >= 0x20 and ord(c) < 0x7F: 108 text += c 109 else: 110 text += '\\%03d' % ord(c) 111 return text
112
113 -def _truncate_bitmap(what):
114 """Determine the index of greatest byte that isn't all zeros, and 115 return the bitmap that contains all the bytes less than that index. 116 117 @param what: a string of octets representing a bitmap. 118 @type what: string 119 @rtype: string 120 """ 121 122 for i in xrange(len(what) - 1, -1, -1): 123 if what[i] != '\x00': 124 break 125 return ''.join(what[0 : i + 1])
126
127 -class Rdata(object):
128 """Base class for all DNS rdata types. 129 """ 130 131 __slots__ = ['rdclass', 'rdtype'] 132
133 - def __init__(self, rdclass, rdtype):
134 """Initialize an rdata. 135 @param rdclass: The rdata class 136 @type rdclass: int 137 @param rdtype: The rdata type 138 @type rdtype: int 139 """ 140 141 self.rdclass = rdclass 142 self.rdtype = rdtype
143
144 - def covers(self):
145 """DNS SIG/RRSIG rdatas apply to a specific type; this type is 146 returned by the covers() function. If the rdata type is not 147 SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when 148 creating rdatasets, allowing the rdataset to contain only RRSIGs 149 of a particular type, e.g. RRSIG(NS). 150 @rtype: int 151 """ 152 153 return dns.rdatatype.NONE
154
155 - def extended_rdatatype(self):
156 """Return a 32-bit type value, the least significant 16 bits of 157 which are the ordinary DNS type, and the upper 16 bits of which are 158 the "covered" type, if any. 159 @rtype: int 160 """ 161 162 return self.covers() << 16 | self.rdtype
163
164 - def to_text(self, origin=None, relativize=True, **kw):
165 """Convert an rdata to text format. 166 @rtype: string 167 """ 168 raise NotImplementedError
169
170 - def to_wire(self, file, compress = None, origin = None):
171 """Convert an rdata to wire format. 172 @rtype: string 173 """ 174 175 raise NotImplementedError
176
177 - def to_digestable(self, origin = None):
178 """Convert rdata to a format suitable for digesting in hashes. This 179 is also the DNSSEC canonical form.""" 180 f = cStringIO.StringIO() 181 self.to_wire(f, None, origin) 182 return f.getvalue()
183
184 - def validate(self):
185 """Check that the current contents of the rdata's fields are 186 valid. If you change an rdata by assigning to its fields, 187 it is a good idea to call validate() when you are done making 188 changes. 189 """ 190 dns.rdata.from_text(self.rdclass, self.rdtype, self.to_text())
191
192 - def __repr__(self):
193 covers = self.covers() 194 if covers == dns.rdatatype.NONE: 195 ctext = '' 196 else: 197 ctext = '(' + dns.rdatatype.to_text(covers) + ')' 198 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ 199 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \ 200 str(self) + '>'
201
202 - def __str__(self):
203 return self.to_text()
204
205 - def _cmp(self, other):
206 """Compare an rdata with another rdata of the same rdtype and 207 rdclass. Return < 0 if self < other in the DNSSEC ordering, 208 0 if self == other, and > 0 if self > other. 209 """ 210 211 raise NotImplementedError
212
213 - def __eq__(self, other):
214 if not isinstance(other, Rdata): 215 return False 216 if self.rdclass != other.rdclass or \ 217 self.rdtype != other.rdtype: 218 return False 219 return self._cmp(other) == 0
220
221 - def __ne__(self, other):
222 if not isinstance(other, Rdata): 223 return True 224 if self.rdclass != other.rdclass or \ 225 self.rdtype != other.rdtype: 226 return True 227 return self._cmp(other) != 0
228
229 - def __lt__(self, other):
230 if not isinstance(other, Rdata) or \ 231 self.rdclass != other.rdclass or \ 232 self.rdtype != other.rdtype: 233 return NotImplemented 234 return self._cmp(other) < 0
235
236 - def __le__(self, other):
237 if not isinstance(other, Rdata) or \ 238 self.rdclass != other.rdclass or \ 239 self.rdtype != other.rdtype: 240 return NotImplemented 241 return self._cmp(other) <= 0
242
243 - def __ge__(self, other):
244 if not isinstance(other, Rdata) or \ 245 self.rdclass != other.rdclass or \ 246 self.rdtype != other.rdtype: 247 return NotImplemented 248 return self._cmp(other) >= 0
249
250 - def __gt__(self, other):
251 if not isinstance(other, Rdata) or \ 252 self.rdclass != other.rdclass or \ 253 self.rdtype != other.rdtype: 254 return NotImplemented 255 return self._cmp(other) > 0
256
257 - def __hash__(self):
258 return hash(self.to_digestable(dns.name.root))
259
260 - def _wire_cmp(self, other):
261 # A number of types compare rdata in wire form, so we provide 262 # the method here instead of duplicating it. 263 # 264 # We specifiy an arbitrary origin of '.' when doing the 265 # comparison, since the rdata may have relative names and we 266 # can't convert a relative name to wire without an origin. 267 b1 = cStringIO.StringIO() 268 self.to_wire(b1, None, dns.name.root) 269 b2 = cStringIO.StringIO() 270 other.to_wire(b2, None, dns.name.root) 271 return cmp(b1.getvalue(), b2.getvalue())
272
273 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
274 """Build an rdata object from text format. 275 276 @param rdclass: The rdata class 277 @type rdclass: int 278 @param rdtype: The rdata type 279 @type rdtype: int 280 @param tok: The tokenizer 281 @type tok: dns.tokenizer.Tokenizer 282 @param origin: The origin to use for relative names 283 @type origin: dns.name.Name 284 @param relativize: should names be relativized? 285 @type relativize: bool 286 @rtype: dns.rdata.Rdata instance 287 """ 288 289 raise NotImplementedError
290 291 from_text = classmethod(from_text) 292
293 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
294 """Build an rdata object from wire format 295 296 @param rdclass: The rdata class 297 @type rdclass: int 298 @param rdtype: The rdata type 299 @type rdtype: int 300 @param wire: The wire-format message 301 @type wire: string 302 @param current: The offet in wire of the beginning of the rdata. 303 @type current: int 304 @param rdlen: The length of the wire-format rdata 305 @type rdlen: int 306 @param origin: The origin to use for relative names 307 @type origin: dns.name.Name 308 @rtype: dns.rdata.Rdata instance 309 """ 310 311 raise NotImplementedError
312 313 from_wire = classmethod(from_wire) 314
315 - def choose_relativity(self, origin = None, relativize = True):
316 """Convert any domain names in the rdata to the specified 317 relativization. 318 """ 319 320 pass
321 322
323 -class GenericRdata(Rdata):
324 """Generate Rdata Class 325 326 This class is used for rdata types for which we have no better 327 implementation. It implements the DNS "unknown RRs" scheme. 328 """ 329 330 __slots__ = ['data'] 331
332 - def __init__(self, rdclass, rdtype, data):
333 super(GenericRdata, self).__init__(rdclass, rdtype) 334 self.data = data
335
336 - def to_text(self, origin=None, relativize=True, **kw):
337 return r'\# %d ' % len(self.data) + _hexify(self.data)
338
339 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
340 token = tok.get() 341 if not token.is_identifier() or token.value != '\#': 342 raise dns.exception.SyntaxError(r'generic rdata does not start with \#') 343 length = tok.get_int() 344 chunks = [] 345 while 1: 346 token = tok.get() 347 if token.is_eol_or_eof(): 348 break 349 chunks.append(token.value) 350 hex = ''.join(chunks) 351 data = hex.decode('hex_codec') 352 if len(data) != length: 353 raise dns.exception.SyntaxError('generic rdata hex data has wrong length') 354 return cls(rdclass, rdtype, data)
355 356 from_text = classmethod(from_text) 357
358 - def to_wire(self, file, compress = None, origin = None):
359 file.write(self.data)
360
361 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
362 return cls(rdclass, rdtype, wire[current : current + rdlen])
363 364 from_wire = classmethod(from_wire) 365
366 - def _cmp(self, other):
367 return cmp(self.data, other.data)
368 369 _rdata_modules = {} 370 _module_prefix = 'dns.rdtypes' 371
372 -def get_rdata_class(rdclass, rdtype):
373 374 def import_module(name): 375 mod = __import__(name) 376 components = name.split('.') 377 for comp in components[1:]: 378 mod = getattr(mod, comp) 379 return mod
380 381 mod = _rdata_modules.get((rdclass, rdtype)) 382 rdclass_text = dns.rdataclass.to_text(rdclass) 383 rdtype_text = dns.rdatatype.to_text(rdtype) 384 rdtype_text = rdtype_text.replace('-', '_') 385 if not mod: 386 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype)) 387 if not mod: 388 try: 389 mod = import_module('.'.join([_module_prefix, 390 rdclass_text, rdtype_text])) 391 _rdata_modules[(rdclass, rdtype)] = mod 392 except ImportError: 393 try: 394 mod = import_module('.'.join([_module_prefix, 395 'ANY', rdtype_text])) 396 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod 397 except ImportError: 398 mod = None 399 if mod: 400 cls = getattr(mod, rdtype_text) 401 else: 402 cls = GenericRdata 403 return cls 404
405 -def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
406 """Build an rdata object from text format. 407 408 This function attempts to dynamically load a class which 409 implements the specified rdata class and type. If there is no 410 class-and-type-specific implementation, the GenericRdata class 411 is used. 412 413 Once a class is chosen, its from_text() class method is called 414 with the parameters to this function. 415 416 If I{tok} is a string, then a tokenizer is created and the string 417 is used as its input. 418 419 @param rdclass: The rdata class 420 @type rdclass: int 421 @param rdtype: The rdata type 422 @type rdtype: int 423 @param tok: The tokenizer or input text 424 @type tok: dns.tokenizer.Tokenizer or string 425 @param origin: The origin to use for relative names 426 @type origin: dns.name.Name 427 @param relativize: Should names be relativized? 428 @type relativize: bool 429 @rtype: dns.rdata.Rdata instance""" 430 431 if isinstance(tok, str): 432 tok = dns.tokenizer.Tokenizer(tok) 433 cls = get_rdata_class(rdclass, rdtype) 434 if cls != GenericRdata: 435 # peek at first token 436 token = tok.get() 437 tok.unget(token) 438 if token.is_identifier() and \ 439 token.value == r'\#': 440 # 441 # Known type using the generic syntax. Extract the 442 # wire form from the generic syntax, and then run 443 # from_wire on it. 444 # 445 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin, 446 relativize) 447 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data), 448 origin) 449 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
450
451 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
452 """Build an rdata object from wire format 453 454 This function attempts to dynamically load a class which 455 implements the specified rdata class and type. If there is no 456 class-and-type-specific implementation, the GenericRdata class 457 is used. 458 459 Once a class is chosen, its from_wire() class method is called 460 with the parameters to this function. 461 462 @param rdclass: The rdata class 463 @type rdclass: int 464 @param rdtype: The rdata type 465 @type rdtype: int 466 @param wire: The wire-format message 467 @type wire: string 468 @param current: The offet in wire of the beginning of the rdata. 469 @type current: int 470 @param rdlen: The length of the wire-format rdata 471 @type rdlen: int 472 @param origin: The origin to use for relative names 473 @type origin: dns.name.Name 474 @rtype: dns.rdata.Rdata instance""" 475 476 wire = dns.wiredata.maybe_wrap(wire) 477 cls = get_rdata_class(rdclass, rdtype) 478 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
479