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

# Source Code for Module dns.renderer

      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  """Help for building DNS wire format messages"""
     17
     18  from io import BytesIO
     19  import struct
     20  import random
     21  import time
     22
     23  import dns.exception
     24  import dns.tsig
     25  from ._compat import long
     26
     27
     28  QUESTION = 0
     29  ANSWER = 1
     30  AUTHORITY = 2
     31  ADDITIONAL = 3
     32
     33
     34 -class Renderer(object):
     35
     36      """Helper class for building DNS wire-format messages.
     37
     38      Most applications can use the higher-level L{dns.message.Message}
     39      class and its to_wire() method to generate wire-format messages.
     40      This class is for those applications which need finer control
     41      over the generation of messages.
     42
     43      Typical use::
     44
     45          r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512)
     46          r.add_question(qname, qtype, qclass)
     47          r.add_rrset(dns.renderer.ANSWER, rrset_1)
     48          r.add_rrset(dns.renderer.ANSWER, rrset_2)
     49          r.add_rrset(dns.renderer.AUTHORITY, ns_rrset)
     50          r.add_edns(0, 0, 4096)
     51          r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_1)
     52          r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_2)
     53          r.write_header()
     54          r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac)
     55          wire = r.get_wire()
     56
     57      @ivar output: where rendering is written
     58      @type output: BytesIO object
     59      @ivar id: the message id
     60      @type id: int
     61      @ivar flags: the message flags
     62      @type flags: int
     63      @ivar max_size: the maximum size of the message
     64      @type max_size: int
     65      @ivar origin: the origin to use when rendering relative names
     66      @type origin: dns.name.Name object
     67      @ivar compress: the compression table
     68      @type compress: dict
     69      @ivar section: the section currently being rendered
     70      @type section: int (dns.renderer.QUESTION, dns.renderer.ANSWER,
     71      dns.renderer.AUTHORITY, or dns.renderer.ADDITIONAL)
     72      @ivar counts: list of the number of RRs in each section
     73      @type counts: int list of length 4
     74      @ivar mac: the MAC of the rendered message (if TSIG was used)
     75      @type mac: string
     76      """
     77
     78 -    def __init__(self, id=None, flags=0, max_size=65535, origin=None):
     79          """Initialize a new renderer.
     80
     81          @param id: the message id
     82          @type id: int
     83          @param flags: the DNS message flags
     84          @type flags: int
     85          @param max_size: the maximum message size; the default is 65535.
     86          If rendering results in a message greater than I{max_size},
     87          then L{dns.exception.TooBig} will be raised.
     88          @type max_size: int
     89          @param origin: the origin to use when rendering relative names
     90          @type origin: dns.name.Name or None.
     91          """
     92
     93          self.output = BytesIO()
     94          if id is None:
     95              self.id = random.randint(0, 65535)
     96          else:
     97              self.id = id
     98          self.flags = flags
     99          self.max_size = max_size
    100          self.origin = origin
    101          self.compress = {}
    102          self.section = QUESTION
    103          self.counts = [0, 0, 0, 0]
    104          self.output.write(b'\x00' * 12)
    105          self.mac = ''
    106
    107 -    def _rollback(self, where):
    108          """Truncate the output buffer at offset I{where}, and remove any
    109          compression table entries that pointed beyond the truncation
    110          point.
    111
    112          @param where: the offset
    113          @type where: int
    114          """
    115
    116          self.output.seek(where)
    117          self.output.truncate()
    118          keys_to_delete = []
    119          for k, v in self.compress.items():
    120              if v >= where:
    121                  keys_to_delete.append(k)
    122          for k in keys_to_delete:
    123              del self.compress[k]
    124
    125 -    def _set_section(self, section):
    126          """Set the renderer's current section.
    127
    128          Sections must be rendered order: QUESTION, ANSWER, AUTHORITY,
    129          ADDITIONAL.  Sections may be empty.
    130
    131          @param section: the section
    132          @type section: int
    133          @raises dns.exception.FormError: an attempt was made to set
    134          a section value less than the current section.
    135          """
    136
    137          if self.section != section:
    138              if self.section > section:
    139                  raise dns.exception.FormError
    140              self.section = section
    141
    142 -    def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN):
    143          """Add a question to the message.
    144
    145          @param qname: the question name
    146          @type qname: dns.name.Name
    147          @param rdtype: the question rdata type
    148          @type rdtype: int
    149          @param rdclass: the question rdata class
    150          @type rdclass: int
    151          """
    152
    153          self._set_section(QUESTION)
    154          before = self.output.tell()
    155          qname.to_wire(self.output, self.compress, self.origin)
    156          self.output.write(struct.pack("!HH", rdtype, rdclass))
    157          after = self.output.tell()
    158          if after >= self.max_size:
    159              self._rollback(before)
    160              raise dns.exception.TooBig
    161          self.counts[QUESTION] += 1
    162
    163 -    def add_rrset(self, section, rrset, **kw):
    164          """Add the rrset to the specified section.
    165
    166          Any keyword arguments are passed on to the rdataset's to_wire()
    167          routine.
    168
    169          @param section: the section
    170          @type section: int
    171          @param rrset: the rrset
    172          @type rrset: dns.rrset.RRset object
    173          """
    174
    175          self._set_section(section)
    176          before = self.output.tell()
    177          n = rrset.to_wire(self.output, self.compress, self.origin, **kw)
    178          after = self.output.tell()
    179          if after >= self.max_size:
    180              self._rollback(before)
    181              raise dns.exception.TooBig
    182          self.counts[section] += n
    183
    184 -    def add_rdataset(self, section, name, rdataset, **kw):
    185          """Add the rdataset to the specified section, using the specified
    186          name as the owner name.
    187
    188          Any keyword arguments are passed on to the rdataset's to_wire()
    189          routine.
    190
    191          @param section: the section
    192          @type section: int
    193          @param name: the owner name
    194          @type name: dns.name.Name object
    195          @param rdataset: the rdataset
    196          @type rdataset: dns.rdataset.Rdataset object
    197          """
    198
    199          self._set_section(section)
    200          before = self.output.tell()
    201          n = rdataset.to_wire(name, self.output, self.compress, self.origin,
    202                               **kw)
    203          after = self.output.tell()
    204          if after >= self.max_size:
    205              self._rollback(before)
    206              raise dns.exception.TooBig
    207          self.counts[section] += n
    208
    209 -    def add_edns(self, edns, ednsflags, payload, options=None):
    210          """Add an EDNS OPT record to the message.
    211
    212          @param edns: The EDNS level to use.
    213          @type edns: int
    214          @param ednsflags: EDNS flag values.
    215          @type ednsflags: int
    216          @param payload: The EDNS sender's payload field, which is the maximum
    217          size of UDP datagram the sender can handle.
    218          @type payload: int
    219          @param options: The EDNS options list
    220          @type options: list of dns.edns.Option instances
    221          @see: RFC 2671
    222          """
    223
    224          # make sure the EDNS version in ednsflags agrees with edns
    225          ednsflags &= long(0xFF00FFFF)
    226          ednsflags |= (edns << 16)
    227          self._set_section(ADDITIONAL)
    228          before = self.output.tell()
    229          self.output.write(struct.pack('!BHHIH', 0, dns.rdatatype.OPT, payload,
    230                                        ednsflags, 0))
    231          if options is not None:
    232              lstart = self.output.tell()
    233              for opt in options:
    234                  stuff = struct.pack("!HH", opt.otype, 0)
    235                  self.output.write(stuff)
    236                  start = self.output.tell()
    237                  opt.to_wire(self.output)
    238                  end = self.output.tell()
    239                  assert end - start < 65536
    240                  self.output.seek(start - 2)
    241                  stuff = struct.pack("!H", end - start)
    242                  self.output.write(stuff)
    243                  self.output.seek(0, 2)
    244              lend = self.output.tell()
    245              assert lend - lstart < 65536
    246              self.output.seek(lstart - 2)
    247              stuff = struct.pack("!H", lend - lstart)
    248              self.output.write(stuff)
    249              self.output.seek(0, 2)
    250          after = self.output.tell()
    251          if after >= self.max_size:
    252              self._rollback(before)
    253              raise dns.exception.TooBig
    254          self.counts[ADDITIONAL] += 1
    255
    256 -    def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data,
    257                   request_mac, algorithm=dns.tsig.default_algorithm):
    258          """Add a TSIG signature to the message.
    259
    260          @param keyname: the TSIG key name
    261          @type keyname: dns.name.Name object
    262          @param secret: the secret to use
    263          @type secret: string
    264          @param fudge: TSIG time fudge
    265          @type fudge: int
    266          @param id: the message id to encode in the tsig signature
    267          @type id: int
    268          @param tsig_error: TSIG error code; default is 0.
    269          @type tsig_error: int
    270          @param other_data: TSIG other data.
    271          @type other_data: string
    272          @param request_mac: This message is a response to the request which
    273          had the specified MAC.
    274          @type request_mac: string
    275          @param algorithm: the TSIG algorithm to use
    276          @type algorithm: dns.name.Name object
    277          """
    278
    279          self._set_section(ADDITIONAL)
    280          before = self.output.tell()
    281          s = self.output.getvalue()
    282          (tsig_rdata, self.mac, ctx) = dns.tsig.sign(s,
    283                                                      keyname,
    284                                                      secret,
    285                                                      int(time.time()),
    286                                                      fudge,
    287                                                      id,
    288                                                      tsig_error,
    289                                                      other_data,
    290                                                      request_mac,
    291                                                      algorithm=algorithm)
    292          keyname.to_wire(self.output, self.compress, self.origin)
    293          self.output.write(struct.pack('!HHIH', dns.rdatatype.TSIG,
    294                                        dns.rdataclass.ANY, 0, 0))
    295          rdata_start = self.output.tell()
    296          self.output.write(tsig_rdata)
    297          after = self.output.tell()
    298          assert after - rdata_start < 65536
    299          if after >= self.max_size:
    300              self._rollback(before)
    301              raise dns.exception.TooBig
    302          self.output.seek(rdata_start - 2)
    303          self.output.write(struct.pack('!H', after - rdata_start))
    304          self.counts[ADDITIONAL] += 1
    305          self.output.seek(10)
    306          self.output.write(struct.pack('!H', self.counts[ADDITIONAL]))
    307          self.output.seek(0, 2)
    308
    309 -    def write_header(self):
    310          """Write the DNS message header.
    311
    312          Writing the DNS message header is done after all sections
    313          have been rendered, but before the optional TSIG signature
    314          is added.
    315          """
    316
    317          self.output.seek(0)
    318          self.output.write(struct.pack('!HHHHHH', self.id, self.flags,
    319                                        self.counts[0], self.counts[1],
    320                                        self.counts[2], self.counts[3]))
    321          self.output.seek(0, 2)
    322
    323 -    def get_wire(self):
    324          """Return the wire format message.
    325
    326          @rtype: string
    327          """
    328
    329          return self.output.getvalue()
    330