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

Source Code for Module dns.rdataset

  1  # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 
  2   
  3  # Copyright (C) 2001-2017 Nominum, Inc. 
  4  # 
  5  # Permission to use, copy, modify, and distribute this software and its 
  6  # documentation for any purpose with or without fee is hereby granted, 
  7  # provided that the above copyright notice and this permission notice 
  8  # appear in all copies. 
  9  # 
 10  # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 
 11  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
 12  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 
 13  # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
 14  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
 15  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 
 16  # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 17   
 18  """DNS rdatasets (an rdataset is a set of rdatas of a given type and class)""" 
 19   
 20  import random 
 21  from io import StringIO 
 22  import struct 
 23   
 24  import dns.exception 
 25  import dns.rdatatype 
 26  import dns.rdataclass 
 27  import dns.rdata 
 28  import dns.set 
 29  from ._compat import string_types 
 30   
 31  # define SimpleSet here for backwards compatibility 
 32  SimpleSet = dns.set.Set 
 33   
 34   
35 -class DifferingCovers(dns.exception.DNSException):
36 """An attempt was made to add a DNS SIG/RRSIG whose covered type 37 is not the same as that of the other rdatas in the rdataset."""
38 39
40 -class IncompatibleTypes(dns.exception.DNSException):
41 """An attempt was made to add DNS RR data of an incompatible type."""
42 43
44 -class Rdataset(dns.set.Set):
45 46 """A DNS rdataset.""" 47 48 __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl'] 49
50 - def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE, ttl=0):
51 """Create a new rdataset of the specified class and type. 52 53 *rdclass*, an ``int``, the rdataclass. 54 55 *rdtype*, an ``int``, the rdatatype. 56 57 *covers*, an ``int``, the covered rdatatype. 58 59 *ttl*, an ``int``, the TTL. 60 """ 61 62 super(Rdataset, self).__init__() 63 self.rdclass = rdclass 64 self.rdtype = rdtype 65 self.covers = covers 66 self.ttl = ttl
67
68 - def _clone(self):
69 obj = super(Rdataset, self)._clone() 70 obj.rdclass = self.rdclass 71 obj.rdtype = self.rdtype 72 obj.covers = self.covers 73 obj.ttl = self.ttl 74 return obj
75
76 - def update_ttl(self, ttl):
77 """Perform TTL minimization. 78 79 Set the TTL of the rdataset to be the lesser of the set's current 80 TTL or the specified TTL. If the set contains no rdatas, set the TTL 81 to the specified TTL. 82 83 *ttl*, an ``int``. 84 """ 85 86 if len(self) == 0: 87 self.ttl = ttl 88 elif ttl < self.ttl: 89 self.ttl = ttl
90
91 - def add(self, rd, ttl=None):
92 """Add the specified rdata to the rdataset. 93 94 If the optional *ttl* parameter is supplied, then 95 ``self.update_ttl(ttl)`` will be called prior to adding the rdata. 96 97 *rd*, a ``dns.rdata.Rdata``, the rdata 98 99 *ttl*, an ``int``, the TTL. 100 101 Raises ``dns.rdataset.IncompatibleTypes`` if the type and class 102 do not match the type and class of the rdataset. 103 104 Raises ``dns.rdataset.DifferingCovers`` if the type is a signature 105 type and the covered type does not match that of the rdataset. 106 """ 107 108 # 109 # If we're adding a signature, do some special handling to 110 # check that the signature covers the same type as the 111 # other rdatas in this rdataset. If this is the first rdata 112 # in the set, initialize the covers field. 113 # 114 if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype: 115 raise IncompatibleTypes 116 if ttl is not None: 117 self.update_ttl(ttl) 118 if self.rdtype == dns.rdatatype.RRSIG or \ 119 self.rdtype == dns.rdatatype.SIG: 120 covers = rd.covers() 121 if len(self) == 0 and self.covers == dns.rdatatype.NONE: 122 self.covers = covers 123 elif self.covers != covers: 124 raise DifferingCovers 125 if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0: 126 self.clear() 127 super(Rdataset, self).add(rd)
128
129 - def union_update(self, other):
130 self.update_ttl(other.ttl) 131 super(Rdataset, self).union_update(other)
132
133 - def intersection_update(self, other):
134 self.update_ttl(other.ttl) 135 super(Rdataset, self).intersection_update(other)
136
137 - def update(self, other):
138 """Add all rdatas in other to self. 139 140 *other*, a ``dns.rdataset.Rdataset``, the rdataset from which 141 to update. 142 """ 143 144 self.update_ttl(other.ttl) 145 super(Rdataset, self).update(other)
146
147 - def __repr__(self):
148 if self.covers == 0: 149 ctext = '' 150 else: 151 ctext = '(' + dns.rdatatype.to_text(self.covers) + ')' 152 return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ 153 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>'
154
155 - def __str__(self):
156 return self.to_text()
157
158 - def __eq__(self, other):
159 if not isinstance(other, Rdataset): 160 return False 161 if self.rdclass != other.rdclass or \ 162 self.rdtype != other.rdtype or \ 163 self.covers != other.covers: 164 return False 165 return super(Rdataset, self).__eq__(other)
166
167 - def __ne__(self, other):
168 return not self.__eq__(other)
169
170 - def to_text(self, name=None, origin=None, relativize=True, 171 override_rdclass=None, **kw):
172 """Convert the rdataset into DNS master file format. 173 174 See ``dns.name.Name.choose_relativity`` for more information 175 on how *origin* and *relativize* determine the way names 176 are emitted. 177 178 Any additional keyword arguments are passed on to the rdata 179 ``to_text()`` method. 180 181 *name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with 182 *name* as the owner name. 183 184 *origin*, a ``dns.name.Name`` or ``None``, the origin for relative 185 names. 186 187 *relativize*, a ``bool``. If ``True``, names will be relativized 188 to *origin*. 189 """ 190 191 if name is not None: 192 name = name.choose_relativity(origin, relativize) 193 ntext = str(name) 194 pad = ' ' 195 else: 196 ntext = '' 197 pad = '' 198 s = StringIO() 199 if override_rdclass is not None: 200 rdclass = override_rdclass 201 else: 202 rdclass = self.rdclass 203 if len(self) == 0: 204 # 205 # Empty rdatasets are used for the question section, and in 206 # some dynamic updates, so we don't need to print out the TTL 207 # (which is meaningless anyway). 208 # 209 s.write(u'{}{}{} {}\n'.format(ntext, pad, 210 dns.rdataclass.to_text(rdclass), 211 dns.rdatatype.to_text(self.rdtype))) 212 else: 213 for rd in self: 214 s.write(u'%s%s%d %s %s %s\n' % 215 (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), 216 dns.rdatatype.to_text(self.rdtype), 217 rd.to_text(origin=origin, relativize=relativize, 218 **kw))) 219 # 220 # We strip off the final \n for the caller's convenience in printing 221 # 222 return s.getvalue()[:-1]
223
224 - def to_wire(self, name, file, compress=None, origin=None, 225 override_rdclass=None, want_shuffle=True):
226 """Convert the rdataset to wire format. 227 228 *name*, a ``dns.name.Name`` is the owner name to use. 229 230 *file* is the file where the name is emitted (typically a 231 BytesIO file). 232 233 *compress*, a ``dict``, is the compression table to use. If 234 ``None`` (the default), names will not be compressed. 235 236 *origin* is a ``dns.name.Name`` or ``None``. If the name is 237 relative and origin is not ``None``, then *origin* will be appended 238 to it. 239 240 *override_rdclass*, an ``int``, is used as the class instead of the 241 class of the rdataset. This is useful when rendering rdatasets 242 associated with dynamic updates. 243 244 *want_shuffle*, a ``bool``. If ``True``, then the order of the 245 Rdatas within the Rdataset will be shuffled before rendering. 246 247 Returns an ``int``, the number of records emitted. 248 """ 249 250 if override_rdclass is not None: 251 rdclass = override_rdclass 252 want_shuffle = False 253 else: 254 rdclass = self.rdclass 255 file.seek(0, 2) 256 if len(self) == 0: 257 name.to_wire(file, compress, origin) 258 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0) 259 file.write(stuff) 260 return 1 261 else: 262 if want_shuffle: 263 l = list(self) 264 random.shuffle(l) 265 else: 266 l = self 267 for rd in l: 268 name.to_wire(file, compress, origin) 269 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 270 self.ttl, 0) 271 file.write(stuff) 272 start = file.tell() 273 rd.to_wire(file, compress, origin) 274 end = file.tell() 275 assert end - start < 65536 276 file.seek(start - 2) 277 stuff = struct.pack("!H", end - start) 278 file.write(stuff) 279 file.seek(0, 2) 280 return len(self)
281
282 - def match(self, rdclass, rdtype, covers):
283 """Returns ``True`` if this rdataset matches the specified class, 284 type, and covers. 285 """ 286 if self.rdclass == rdclass and \ 287 self.rdtype == rdtype and \ 288 self.covers == covers: 289 return True 290 return False
291 292
293 -def from_text_list(rdclass, rdtype, ttl, text_rdatas):
294 """Create an rdataset with the specified class, type, and TTL, and with 295 the specified list of rdatas in text format. 296 297 Returns a ``dns.rdataset.Rdataset`` object. 298 """ 299 300 if isinstance(rdclass, string_types): 301 rdclass = dns.rdataclass.from_text(rdclass) 302 if isinstance(rdtype, string_types): 303 rdtype = dns.rdatatype.from_text(rdtype) 304 r = Rdataset(rdclass, rdtype) 305 r.update_ttl(ttl) 306 for t in text_rdatas: 307 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t) 308 r.add(rd) 309 return r
310 311
312 -def from_text(rdclass, rdtype, ttl, *text_rdatas):
313 """Create an rdataset with the specified class, type, and TTL, and with 314 the specified rdatas in text format. 315 316 Returns a ``dns.rdataset.Rdataset`` object. 317 """ 318 319 return from_text_list(rdclass, rdtype, ttl, text_rdatas)
320 321
322 -def from_rdata_list(ttl, rdatas):
323 """Create an rdataset with the specified TTL, and with 324 the specified list of rdata objects. 325 326 Returns a ``dns.rdataset.Rdataset`` object. 327 """ 328 329 if len(rdatas) == 0: 330 raise ValueError("rdata list must not be empty") 331 r = None 332 for rd in rdatas: 333 if r is None: 334 r = Rdataset(rd.rdclass, rd.rdtype) 335 r.update_ttl(ttl) 336 r.add(rd) 337 return r
338 339
340 -def from_rdata(ttl, *rdatas):
341 """Create an rdataset with the specified TTL, and with 342 the specified rdata objects. 343 344 Returns a ``dns.rdataset.Rdataset`` object. 345 """ 346 347 return from_rdata_list(ttl, rdatas)
348