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

# Source Code for Module dns.rdataset

      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 rdatasets (an rdataset is a set of rdatas of a given type and class)"""
     17
     18  import random
     19  from io import StringIO
     20  import struct
     21
     22  import dns.exception
     23  import dns.rdatatype
     24  import dns.rdataclass
     25  import dns.rdata
     26  import dns.set
     27  from ._compat import string_types
     28
     29  # define SimpleSet here for backwards compatibility
     30  SimpleSet = dns.set.Set
     31
     32
     33 -class DifferingCovers(dns.exception.DNSException):
     34
     35      """An attempt was made to add a DNS SIG/RRSIG whose covered type
     36      is not the same as that of the other rdatas in the rdataset."""
     37
     38
     39 -class IncompatibleTypes(dns.exception.DNSException):
     40
     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      @ivar rdclass: The class of the rdataset
     49      @type rdclass: int
     50      @ivar rdtype: The type of the rdataset
     51      @type rdtype: int
     52      @ivar covers: The covered type.  Usually this value is
     53      dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
     54      dns.rdatatype.RRSIG, then the covers value will be the rdata
     55      type the SIG/RRSIG covers.  The library treats the SIG and RRSIG
     56      types as if they were a family of
     57      types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA).  This makes RRSIGs much
     58      easier to work with than if RRSIGs covering different rdata
     59      types were aggregated into a single RRSIG rdataset.
     60      @type covers: int
     61      @ivar ttl: The DNS TTL (Time To Live) value
     62      @type ttl: int
     63      """
     64
     65      __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
     66
     67 -    def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
     68          """Create a new rdataset of the specified class and type.
     69
     70          @see: the description of the class instance variables for the
     71          meaning of I{rdclass} and I{rdtype}"""
     72
     73          super(Rdataset, self).__init__()
     74          self.rdclass = rdclass
     75          self.rdtype = rdtype
     76          self.covers = covers
     77          self.ttl = 0
     78
     79 -    def _clone(self):
     80          obj = super(Rdataset, self)._clone()
     81          obj.rdclass = self.rdclass
     82          obj.rdtype = self.rdtype
     83          obj.covers = self.covers
     84          obj.ttl = self.ttl
     85          return obj
     86
     87 -    def update_ttl(self, ttl):
     88          """Set the TTL of the rdataset to be the lesser of the set's current
     89          TTL or the specified TTL.  If the set contains no rdatas, set the TTL
     90          to the specified TTL.
     91          @param ttl: The TTL
     92          @type ttl: int"""
     93
     94          if len(self) == 0:
     95              self.ttl = ttl
     96          elif ttl < self.ttl:
     97              self.ttl = ttl
     98
     99 -    def add(self, rd, ttl=None):
    100          """Add the specified rdata to the rdataset.
    101
    102          If the optional I{ttl} parameter is supplied, then
    103          self.update_ttl(ttl) will be called prior to adding the rdata.
    104
    105          @param rd: The rdata
    106          @type rd: dns.rdata.Rdata object
    107          @param ttl: The TTL
    108          @type ttl: int"""
    109
    110          #
    111          # If we're adding a signature, do some special handling to
    112          # check that the signature covers the same type as the
    113          # other rdatas in this rdataset.  If this is the first rdata
    114          # in the set, initialize the covers field.
    115          #
    116          if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype:
    117              raise IncompatibleTypes
    118          if ttl is not None:
    119              self.update_ttl(ttl)
    120          if self.rdtype == dns.rdatatype.RRSIG or \
    121             self.rdtype == dns.rdatatype.SIG:
    122              covers = rd.covers()
    123              if len(self) == 0 and self.covers == dns.rdatatype.NONE:
    124                  self.covers = covers
    125              elif self.covers != covers:
    126                  raise DifferingCovers
    127          if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0:
    128              self.clear()
    129          super(Rdataset, self).add(rd)
    130
    131 -    def union_update(self, other):
    132          self.update_ttl(other.ttl)
    133          super(Rdataset, self).union_update(other)
    134
    135 -    def intersection_update(self, other):
    136          self.update_ttl(other.ttl)
    137          super(Rdataset, self).intersection_update(other)
    138
    139 -    def update(self, other):
    140          """Add all rdatas in other to self.
    141
    142          @param other: The rdataset from which to update
    143          @type other: dns.rdataset.Rdataset object"""
    144
    145          self.update_ttl(other.ttl)
    146          super(Rdataset, self).update(other)
    147
    148 -    def __repr__(self):
    149          if self.covers == 0:
    150              ctext = ''
    151          else:
    152              ctext = '(' + dns.rdatatype.to_text(self.covers) + ')'
    153          return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
    154                 dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>'
    155
    156 -    def __str__(self):
    157          return self.to_text()
    158
    159 -    def __eq__(self, other):
    160          """Two rdatasets are equal if they have the same class, type, and
    161          covers, and contain the same rdata.
    162          @rtype: bool"""
    163
    164          if not isinstance(other, Rdataset):
    165              return False
    166          if self.rdclass != other.rdclass or \
    167             self.rdtype != other.rdtype or \
    168             self.covers != other.covers:
    169              return False
    170          return super(Rdataset, self).__eq__(other)
    171
    172 -    def __ne__(self, other):
    173          return not self.__eq__(other)
    174
    175 -    def to_text(self, name=None, origin=None, relativize=True,
    176                  override_rdclass=None, **kw):
    177          """Convert the rdataset into DNS master file format.
    178
    179          @see: L{dns.name.Name.choose_relativity} for more information
    180          on how I{origin} and I{relativize} determine the way names
    181          are emitted.
    182
    183          Any additional keyword arguments are passed on to the rdata
    184          to_text() method.
    185
    186          @param name: If name is not None, emit a RRs with I{name} as
    187          the owner name.
    188          @type name: dns.name.Name object
    189          @param origin: The origin for relative names, or None.
    190          @type origin: dns.name.Name object
    191          @param relativize: True if names should names be relativized
    192          @type relativize: bool"""
    193          if name is not None:
    194              name = name.choose_relativity(origin, relativize)
    195              ntext = str(name)
    196              pad = ' '
    197          else:
    198              ntext = ''
    199              pad = ''
    200          s = StringIO()
    201          if override_rdclass is not None:
    202              rdclass = override_rdclass
    203          else:
    204              rdclass = self.rdclass
    205          if len(self) == 0:
    206              #
    207              # Empty rdatasets are used for the question section, and in
    208              # some dynamic updates, so we don't need to print out the TTL
    209              # (which is meaningless anyway).
    210              #
    211              s.write(u'%s%s%s %s\n' % (ntext, pad,
    212                                        dns.rdataclass.to_text(rdclass),
    213                                        dns.rdatatype.to_text(self.rdtype)))
    214          else:
    215              for rd in self:
    216                  s.write(u'%s%s%d %s %s %s\n' %
    217                          (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
    218                           dns.rdatatype.to_text(self.rdtype),
    219                           rd.to_text(origin=origin, relativize=relativize,
    220                           **kw)))
    221          #
    222          # We strip off the final \n for the caller's convenience in printing
    223          #
    224          return s.getvalue()[:-1]
    225
    226 -    def to_wire(self, name, file, compress=None, origin=None,
    227                  override_rdclass=None, want_shuffle=True):
    228          """Convert the rdataset to wire format.
    229
    230          @param name: The owner name of the RRset that will be emitted
    231          @type name: dns.name.Name object
    232          @param file: The file to which the wire format data will be appended
    233          @type file: file
    234          @param compress: The compression table to use; the default is None.
    235          @type compress: dict
    236          @param origin: The origin to be appended to any relative names when
    237          they are emitted.  The default is None.
    238          @returns: the number of records emitted
    239          @rtype: int
    240          """
    241
    242          if override_rdclass is not None:
    243              rdclass = override_rdclass
    244              want_shuffle = False
    245          else:
    246              rdclass = self.rdclass
    247          file.seek(0, 2)
    248          if len(self) == 0:
    249              name.to_wire(file, compress, origin)
    250              stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
    251              file.write(stuff)
    252              return 1
    253          else:
    254              if want_shuffle:
    255                  l = list(self)
    256                  random.shuffle(l)
    257              else:
    258                  l = self
    259              for rd in l:
    260                  name.to_wire(file, compress, origin)
    261                  stuff = struct.pack("!HHIH", self.rdtype, rdclass,
    262                                      self.ttl, 0)
    263                  file.write(stuff)
    264                  start = file.tell()
    265                  rd.to_wire(file, compress, origin)
    266                  end = file.tell()
    267                  assert end - start < 65536
    268                  file.seek(start - 2)
    269                  stuff = struct.pack("!H", end - start)
    270                  file.write(stuff)
    271                  file.seek(0, 2)
    272              return len(self)
    273
    274 -    def match(self, rdclass, rdtype, covers):
    275          """Returns True if this rdataset matches the specified class, type,
    276          and covers"""
    277          if self.rdclass == rdclass and \
    278             self.rdtype == rdtype and \
    279             self.covers == covers:
    280              return True
    281          return False
    282
    283
    284 -def from_text_list(rdclass, rdtype, ttl, text_rdatas):
    285      """Create an rdataset with the specified class, type, and TTL, and with
    286      the specified list of rdatas in text format.
    287
    288      @rtype: dns.rdataset.Rdataset object
    289      """
    290
    291      if isinstance(rdclass, string_types):
    292          rdclass = dns.rdataclass.from_text(rdclass)
    293      if isinstance(rdtype, string_types):
    294          rdtype = dns.rdatatype.from_text(rdtype)
    295      r = Rdataset(rdclass, rdtype)
    296      r.update_ttl(ttl)
    297      for t in text_rdatas:
    298          rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
    299          r.add(rd)
    300      return r
    301
    302
    303 -def from_text(rdclass, rdtype, ttl, *text_rdatas):
    304      """Create an rdataset with the specified class, type, and TTL, and with
    305      the specified rdatas in text format.
    306
    307      @rtype: dns.rdataset.Rdataset object
    308      """
    309
    310      return from_text_list(rdclass, rdtype, ttl, text_rdatas)
    311
    312
    313 -def from_rdata_list(ttl, rdatas):
    314      """Create an rdataset with the specified TTL, and with
    315      the specified list of rdata objects.
    316
    317      @rtype: dns.rdataset.Rdataset object
    318      """
    319
    320      if len(rdatas) == 0:
    321          raise ValueError("rdata list must not be empty")
    322      r = None
    323      for rd in rdatas:
    324          if r is None:
    325              r = Rdataset(rd.rdclass, rd.rdtype)
    326              r.update_ttl(ttl)
    327          r.add(rd)
    328      return r
    329
    330
    331 -def from_rdata(ttl, *rdatas):
    332      """Create an rdataset with the specified TTL, and with
    333      the specified rdata objects.
    334
    335      @rtype: dns.rdataset.Rdataset object
    336      """
    337
    338      return from_rdata_list(ttl, rdatas)
    339