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