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

Source Code for Module dns.rdata

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