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