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 return cmp(self.to_digestable(dns.name.root), 211 other.to_digestable(dns.name.root))
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 from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
261 """Build an rdata object from text format. 262 263 @param rdclass: The rdata class 264 @type rdclass: int 265 @param rdtype: The rdata type 266 @type rdtype: int 267 @param tok: The tokenizer 268 @type tok: dns.tokenizer.Tokenizer 269 @param origin: The origin to use for relative names 270 @type origin: dns.name.Name 271 @param relativize: should names be relativized? 272 @type relativize: bool 273 @rtype: dns.rdata.Rdata instance 274 """ 275 276 raise NotImplementedError
277 278 from_text = classmethod(from_text) 279
280 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
281 """Build an rdata object from wire format 282 283 @param rdclass: The rdata class 284 @type rdclass: int 285 @param rdtype: The rdata type 286 @type rdtype: int 287 @param wire: The wire-format message 288 @type wire: string 289 @param current: The offet in wire of the beginning of the rdata. 290 @type current: int 291 @param rdlen: The length of the wire-format rdata 292 @type rdlen: int 293 @param origin: The origin to use for relative names 294 @type origin: dns.name.Name 295 @rtype: dns.rdata.Rdata instance 296 """ 297 298 raise NotImplementedError
299 300 from_wire = classmethod(from_wire) 301
302 - def choose_relativity(self, origin = None, relativize = True):
303 """Convert any domain names in the rdata to the specified 304 relativization. 305 """ 306 307 pass
308 309
310 -class GenericRdata(Rdata):
311 """Generate Rdata Class 312 313 This class is used for rdata types for which we have no better 314 implementation. It implements the DNS "unknown RRs" scheme. 315 """ 316 317 __slots__ = ['data'] 318
319 - def __init__(self, rdclass, rdtype, data):
320 super(GenericRdata, self).__init__(rdclass, rdtype) 321 self.data = data
322
323 - def to_text(self, origin=None, relativize=True, **kw):
324 return r'\# %d ' % len(self.data) + _hexify(self.data)
325
326 - def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True):
327 token = tok.get() 328 if not token.is_identifier() or token.value != '\#': 329 raise dns.exception.SyntaxError(r'generic rdata does not start with \#') 330 length = tok.get_int() 331 chunks = [] 332 while 1: 333 token = tok.get() 334 if token.is_eol_or_eof(): 335 break 336 chunks.append(token.value) 337 hex = ''.join(chunks) 338 data = hex.decode('hex_codec') 339 if len(data) != length: 340 raise dns.exception.SyntaxError('generic rdata hex data has wrong length') 341 return cls(rdclass, rdtype, data)
342 343 from_text = classmethod(from_text) 344
345 - def to_wire(self, file, compress = None, origin = None):
346 file.write(self.data)
347
348 - def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None):
349 return cls(rdclass, rdtype, wire[current : current + rdlen])
350 351 from_wire = classmethod(from_wire)
352 353 _rdata_modules = {} 354 _module_prefix = 'dns.rdtypes' 355
356 -def get_rdata_class(rdclass, rdtype):
357 358 def import_module(name): 359 mod = __import__(name) 360 components = name.split('.') 361 for comp in components[1:]: 362 mod = getattr(mod, comp) 363 return mod
364 365 mod = _rdata_modules.get((rdclass, rdtype)) 366 rdclass_text = dns.rdataclass.to_text(rdclass) 367 rdtype_text = dns.rdatatype.to_text(rdtype) 368 rdtype_text = rdtype_text.replace('-', '_') 369 if not mod: 370 mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype)) 371 if not mod: 372 try: 373 mod = import_module('.'.join([_module_prefix, 374 rdclass_text, rdtype_text])) 375 _rdata_modules[(rdclass, rdtype)] = mod 376 except ImportError: 377 try: 378 mod = import_module('.'.join([_module_prefix, 379 'ANY', rdtype_text])) 380 _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod 381 except ImportError: 382 mod = None 383 if mod: 384 cls = getattr(mod, rdtype_text) 385 else: 386 cls = GenericRdata 387 return cls 388
389 -def from_text(rdclass, rdtype, tok, origin = None, relativize = True):
390 """Build an rdata object from text format. 391 392 This function attempts to dynamically load a class which 393 implements the specified rdata class and type. If there is no 394 class-and-type-specific implementation, the GenericRdata class 395 is used. 396 397 Once a class is chosen, its from_text() class method is called 398 with the parameters to this function. 399 400 If I{tok} is a string, then a tokenizer is created and the string 401 is used as its input. 402 403 @param rdclass: The rdata class 404 @type rdclass: int 405 @param rdtype: The rdata type 406 @type rdtype: int 407 @param tok: The tokenizer or input text 408 @type tok: dns.tokenizer.Tokenizer or string 409 @param origin: The origin to use for relative names 410 @type origin: dns.name.Name 411 @param relativize: Should names be relativized? 412 @type relativize: bool 413 @rtype: dns.rdata.Rdata instance""" 414 415 if isinstance(tok, (str, unicode)): 416 tok = dns.tokenizer.Tokenizer(tok) 417 cls = get_rdata_class(rdclass, rdtype) 418 if cls != GenericRdata: 419 # peek at first token 420 token = tok.get() 421 tok.unget(token) 422 if token.is_identifier() and \ 423 token.value == r'\#': 424 # 425 # Known type using the generic syntax. Extract the 426 # wire form from the generic syntax, and then run 427 # from_wire on it. 428 # 429 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin, 430 relativize) 431 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data), 432 origin) 433 return cls.from_text(rdclass, rdtype, tok, origin, relativize)
434
435 -def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None):
436 """Build an rdata object from wire format 437 438 This function attempts to dynamically load a class which 439 implements the specified rdata class and type. If there is no 440 class-and-type-specific implementation, the GenericRdata class 441 is used. 442 443 Once a class is chosen, its from_wire() class method is called 444 with the parameters to this function. 445 446 @param rdclass: The rdata class 447 @type rdclass: int 448 @param rdtype: The rdata type 449 @type rdtype: int 450 @param wire: The wire-format message 451 @type wire: string 452 @param current: The offet in wire of the beginning of the rdata. 453 @type current: int 454 @param rdlen: The length of the wire-format rdata 455 @type rdlen: int 456 @param origin: The origin to use for relative names 457 @type origin: dns.name.Name 458 @rtype: dns.rdata.Rdata instance""" 459 460 wire = dns.wiredata.maybe_wrap(wire) 461 cls = get_rdata_class(rdclass, rdtype) 462 return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin)
463