If you wanted to check the attached slightly tweaked version of dns.py, I'd 
push it to dart.

-- Murphy
# Copyright 2011,2012 James McCauley
# Copyright 2008 (C) Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This file is derived from the packet library in NOX, which was
# developed by Nicira, Inc.

#======================================================================
#
#                     DNS Message Format
#
#     0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                      ID                       |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |QR|   Opcode  |AA|TC|RD|RA|Z |AD|CD|   RCODE   |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                 Total Questions               |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                 Total Answerrs                |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |              Total Authority RRs              |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |               Total Additional RRs            |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                 Questions ...                 |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |               Answer RRs  ...                 |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |               Authority RRs..                 |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |               Additional RRs.                 |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#
# Question format:
#
#                                   1  1  1  1  1  1
#     0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                                               |
#   /                     QNAME                     /
#   /                                               /
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                     QTYPE                     |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                     QCLASS                    |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#
#
#
# All RRs have the following format:
#                                   1  1  1  1  1  1
#     0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                                               |
#   /                                               /
#   /                      NAME                     /
#   |                                               |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                      TYPE                     |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                     CLASS                     |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                      TTL                      |
#   |                                               |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#   |                   RDLENGTH                    |
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
#   /                     RDATA                     /
#   /                                               /
#   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
#
#
#======================================================================

# TODO:
#   SOA data
#   General cleaup/rewrite (code is/has gotten pretty bad)

import struct
from packet_utils import *
from packet_utils import TruncatedException as Trunc

from packet_base import packet_base

from pox.lib.addresses import IPAddr,IPAddr6,EthAddr

rrtype_to_str = {
   1: "A",  # host address
   2: "NS", #an authoritative name server
   3: "MD",        # a mail destination (Obsolete - use MX)
   4: "MF",        # a mail forwarder (Obsolete - use MX)
   5: "CNAME",     # the canonical name for an alias
   6: "SOA",       # marks the start of a zone of authority
   7: "MB" ,       # a mailbox domain name (EXPERIMENTAL)
   8: "MG" ,       # a mail group member (EXPERIMENTAL)
   9: "MR" ,       # a mail rename domain name (EXPERIMENTAL)
   10: "NULL" ,    # a null RR (EXPERIMENTAL)
   11: "WKS"  ,    # a well known service description
   12: "PTR"  ,    # a domain name pointer
   13: "HINFO",    # host information
   14: "MINFO",    # mailbox or mail list information
   15: "MX"   ,    # mail exchange
   16: "TXT",      # text strings
   28: "AAAA" # IPV6 address request
}

rrclass_to_str = {
  1   :"IN", #  1 the Internet
  2   :"CS", #  2 the CSNET class (Obsolete)
  3   :"CH", #  3 the CHAOS class
  4   :"HS", #  4 Hesiod [Dyer 87]
  255 :"* "  #  255 any class
}


class dns(packet_base):
    "DNS Packet struct"

    MDNS_ADDRESS  = IPAddr('224.0.0.251')
    MDNS6_ADDRESS = IPAddr6('ff02::fb')
    MDNS_ETH      = EthAddr('01:00:5E:00:00:fb')
    MDNS6_ETH     = EthAddr('33:33:00:00:00:fb')

    SERVER_PORT = 53
    MDNS_PORT   = 5353
    MIN_LEN     = 12

    def __init__(self, raw=None, prev=None, **kw):
        packet_base.__init__(self)

        self.prev = prev

        self.questions   = []
        self.answers     = []
        self.authorities = []
        self.additional  = []

        self.id = 0
        self.qr = False # Is Query
        self.opcode = 0
        self.aa = False # Authoritative Answer
        self.tc = False # Truncated
        self.rd = False # Recursion Desired
        self.ra = False # Recursion Available
        self.z = False
        self.ad = False
        self.cd = False
        self.rcode = 0
        # TODO: everything else here

        if raw is not None:
            self.parse(raw)

        self._init(kw)

    def _exc (self, e, part = None):
      """
      Turn exception into log message
      """
      msg = "(dns)"
      if part is not None:
        msg += " " + part
      msg += ": "
      msg += str(e)
      if isinstance(e, Trunc):
        self.msg(msg)
      else:
        self.err(msg)

    def hdr (self, payload):
        bits0 = 0
        if self.qr: bits0 |= 0x80
        bits0 |= (self.opcode & 0x7) << 4
        if self.rd: bits0 |= 1
        if self.tc: bits0 |= 2
        if self.aa: bits0 |= 4
        bits1 = 0
        if self.ra: bits1 |= 0x80
        if self.z: bits1 |= 0x40
        if self.ad: bits1 |= 0x20
        if self.cd: bits1 |= 0x10
        bits1 |= (self.rcode & 0xf)

        s = struct.pack("!HBBHHHH", self.id, bits0, bits1,
                        len(self.questions), len(self.answers),
                        len(self.authorities), len(self.additional))

        def makeName (labels, term):
          o = '' #TODO: unicode
          for l in labels.split('.'):
            o += chr(len(l))
            o += l
          if term: o += '\x00'
          return o

        name_map = {}

        def putName (s, name):
          pre = ''
          post = name
          while True:
            at = s.find(makeName(post, True))
            if at == -1:
              if post in name_map:
                at = name_map[post]
            if at == -1:
              post = post.split('.', 1)
              if pre: pre += '.'
              pre += post[0]
              if len(post) == 1:
                if len(pre) == 0:
                  s += '\x00'
                else:
                  name_map[name] = len(s)
                  s += makeName(pre, True)
                break
              post = post[1]
            else:
              if len(pre) > 0:
                name_map[name] = len(s)
                s += makeName(pre, False)
              s += struct.pack("!H", at | 0xc000)
              break
          return s

        def putData (s, r):
          if r.qtype in (2,12,5,15):
            return putName(s, r.rddata)
          elif r.qtype in (1, 28):
            assert isinstance(r.rddata, (IPAddr, IPAddr6))
            return s + r.rddata.raw
          else:
            return s + r.rddata

        for r in self.questions:
          s = putName(s, r.name)
          s += struct.pack("!HH", r.qtype, r.qclass)

        rest = self.answers + self.authorities + self.additional
        for r in rest:
          s = putName(s, r.name)
          s += struct.pack("!HHIH", r.qtype, r.qclass, r.ttl, 0)
          fixup = len(s) - 2
          s = putData(s, r)
          fixlen = len(s) - fixup - 2
          s = s[:fixup] + struct.pack('!H', fixlen) + s[fixup+2:]

        return s

    def parse(self, raw):
        assert isinstance(raw, bytes)
        self.raw = raw
        dlen = len(raw)
        if dlen < dns.MIN_LEN:
            self.msg('(dns) packet data too short to '
                     + 'parse header: data len %u' % (dlen,))
            return None

        bits0 = 0
        bits1 = 0
        total_questions = 0
        total_answers = 0
        total_auth_rr = 0
        total_add_rr = 0
        (self.id, bits0,bits1, total_questions, total_answers,
         total_auth_rr, total_add_rr)\
             = struct.unpack('!HBBHHHH', raw[:12])

        self.qr = True if (bits0 & 0x80) else False
        self.opcode = (bits0 >> 4) & (0x07)
        self.aa     = True if (bits0 & (0x04)) else False
        self.tc     = True if (bits0 & (0x02)) else False
        self.rd     = True if (bits0 & (0x01)) else False
        self.ra     = True if (bits1 & 0x80) else False
        self.z      = True if (bits1 & 0x40) else False
        self.ad     = True if (bits1 & 0x20) else False
        self.cd     = True if (bits1 & 0x10) else False
        self.rcode  = bits1 & 0x0f

        query_head = 12

        # questions
        for i in range(0,total_questions):
            try:
                query_head = self.next_question(raw, query_head)
            except Exception, e:
                self._exc(e, 'parsing questions')
                return None

        # answers
        for i in range(0,total_answers):
            try:
                query_head = self.next_rr(raw, query_head, self.answers)
            except Exception, e:
                self._exc(e, 'parsing answers')
                return None

        # authoritative name servers
        for i in range(0,total_auth_rr):
            try:
                query_head = self.next_rr(raw, query_head, self.authorities)
            except Exception, e:
                self._exc(e, 'parsing authoritative name servers')
                return None

        # additional resource records
        for i in range(0,total_add_rr):
            try:
                query_head = self.next_rr(raw, query_head, self.additional)
            except Exception, e:
                self._exc(e, 'parsing additional resource records')
                return None

        self.parsed = True

    def _to_str(self):
        flags = "|"

        if self.qr != 0:
            flags += "QR "
        if self.tc != 0:
            flags += "TR "
        if self.rd != 0:
            flags += "RD "
        if self.ra != 0:
            flags += "RA "
        if self.z != 0:
            flags += "Z "

        flags += "|"

        s = "(id:%x fl:%s op:%d nq:%d na:%d nath:%d nadd:%d)" % (self.id,
         flags, self.opcode, len(self.questions), len(self.answers),
         len(self.authorities), len(self.additional))

        if len(self.questions) > 0:
            for q in self.questions:
                s += "(q? "+str(q)+")"

        if len(self.answers) > 0:
            for a in self.answers:
                s += "(answ: "+str(a)+")"

        if len(self.authorities) > 0:
            for a in self.authorities:
                s += "(auth: "+str(a)+")"

        if len(self.additional) > 0:
            for a in self.additional:
                s += "(add: "+str(a)+")"

        return s

    # Utility methods for parsing.  Generally these would be pulled out
    # into a separate class. However, because the lengths are not known
    # until the fields have been parsed, it is more convenient to keep
    # them in the DNS class

    @classmethod
    def _read_dns_name_from_index(cls, l, index, retlist):
      try:
        while True:
            chunk_size = ord(l[index])

            # check whether we have an internal pointer
            if (chunk_size & 0xc0) == 0xc0:
                # pull out offset from last 14 bits
                offset = ((ord(l[index]) & 0x3) << 8 ) | ord(l[index+1])
                cls._read_dns_name_from_index(l, offset, retlist)
                index += 1
                break
            if chunk_size == 0:
                break
            index += 1
            retlist.append(l[index : index + chunk_size])
            index += chunk_size
        return index
      except IndexError:
        raise Trunc("incomplete name")

    @classmethod
    def read_dns_name_from_index(cls, l, index):
        retlist = []
        next = cls._read_dns_name_from_index(l, index, retlist)
        return (next + 1, ".".join(retlist))

    def next_rr(self, l, index, rr_list):
        array_len = len(l)

        # verify whether name is offset within packet
        if index > array_len:
            raise Trunc("next_rr: name truncated")

        index,name = self.read_dns_name_from_index(l, index)

        if index + 10 > array_len:
            raise Trunc("next_rr: truncated")

        (qtype,qclass,ttl,rdlen) = struct.unpack('!HHIH', l[index:index+10])
        if index+10+rdlen > array_len:
            raise Trunc("next_rr: data truncated")

        rddata = self.get_rddata(l, qtype, rdlen, index + 10)
        rr_list.append(dns.rr(name, qtype, qclass,ttl,rdlen,rddata))

        return index + 10 + rdlen

    def get_rddata(self, l, type, dlen, beg_index):
        if beg_index + dlen > len(l):
            raise Trunc('(dns) truncated rdata')
        # A
        if type == 1:
            if dlen != 4:
                raise Exception('(dns) invalid a data size',system='packet')
            return IPAddr(l[beg_index : beg_index + 4])
        # AAAA
        elif type == 28:
            if dlen != 16:
                raise Exception('(dns) invalid a data size',system='packet')
            return IPAddr6.from_raw(l[beg_index : beg_index + dlen])
        # NS
        elif type == 2:
            return self.read_dns_name_from_index(l, beg_index)[1]
        # PTR
        elif type == 12:
            return  self.read_dns_name_from_index(l, beg_index)[1]
        # CNAME
        elif type == 5:
            return self.read_dns_name_from_index(l, beg_index)[1]
        # MX
        elif type == 15:
            #TODO: Save priority (don't just jump past it)
            return self.read_dns_name_from_index(l, beg_index + 2)[1]
        else:
            return l[beg_index : beg_index + dlen]

    def next_question(self, l, index):
        array_len = len(l)

        index,name = self.read_dns_name_from_index(l, index)

        if index + 4 > array_len:
            raise Trunc("next_question: truncated")

        (qtype,qclass) = struct.unpack('!HH', l[index:index+4])
        self.questions.append(dns.question(name, qtype, qclass))
        return index + 4

    # Utility classes for questions and RRs

    class question:

        def __init__(self, name, qtype, qclass):
            self.name   = name
            self.qtype  = qtype
            self.qclass = qclass

        def __str__(self):
            s = self.name
            if self.qtype in rrtype_to_str:
                s += " " + rrtype_to_str[self.qtype]
            else:
                s += " ??? "
            if self.qclass in rrclass_to_str:
                s += " " + rrclass_to_str[self.qclass]
            else:
                s += " ??? "

            return s

    class rr (object):
        A_TYPE     = 1
        NS_TYPE    = 2
        MD_TYPE    = 3
        MF_TYPE    = 4
        CNAME_TYPE = 5
        SOA_TYPE   = 6
        MB_TYPE    = 7
        MG_TYPE    = 8
        MR_TYPE    = 9
        NULL_TYPE  = 10
        WKS_TYPE   = 11
        PTR_TYPE   = 12
        HINFO_TYPE = 13
        MINFO_TYPE = 14
        MX_TYPE    = 15
        TXT_TYPE   = 16
        AAAA_TYPE  = 28

        def __init__ (self, _name, _qtype, _qclass, _ttl, _rdlen, _rddata):
            self.name   = _name
            self.qtype  = _qtype
            self.qclass = _qclass
            self.ttl    = _ttl
            self.rdlen  = _rdlen
            self.rddata = _rddata

        def __str__ (self):
            s = self.name
            if self.qtype in rrtype_to_str:
                s += " " + rrtype_to_str[self.qtype]
            else:
                s += " ??? "
            if self.qclass in rrclass_to_str:
                s += " " + rrclass_to_str[self.qclass]
            else:
                s += " ??? "
            s += " ttl:"+str(self.ttl)
            s += " rdlen:"+str(self.rdlen)
            s += " datalen:" + str(len(self.rddata))
            if len(self.rddata) == 4:
              #FIXME: can be smarter about whether this is an IP
              s+= " data:" + str(IPAddr(self.rddata))

            return s
On Mar 11, 2014, at 9:11 PM, Curtis Taylor <[email protected]> wrote:

> Murphy,
> 
> Exactly as you suggested. 3 changes did the trick.
> 
> * Added at Line 244 packet.dns:
>    elif r.qtype == 28:
>      assert isinstance(r.rddata, IPAddr6)
>      return s + r.rddata.toRaw()
> 
> * Changed Line 435 packet.dns:
>  return IPAddr6.from_raw(l[beg_index : beg_index + dlen])
> 
> * Added at line 521 (semi-arbitrary) addresses.py:
>  def toRaw (self):
>    return self.raw
> 
> Thanks for the help!
> 
> —Curtis
> On Mar 11, 2014, at 11:13 PM, Murphy McCauley <[email protected]> 
> wrote:
> 
>> On Mar 11, 2014, at 7:35 PM, Curtis Taylor <[email protected]> wrote:
>> 
>>> Hi,
>>> 
>>> I’m trying to parse and alter AAAA (IPv6) DNS responses with POX, but I get 
>>> the following error:
>>> ��B�)'packet:(dns) parsing answers: Bad address format
>>> 
>>> The error stems from around line 398 in addresses.py. At that point in the 
>>> execution, my IPv6 address (converted to hex from binary) is 
>>> 20010db8000000000000ff0000428329, which is correct. However, it appears 
>>> that POX is attempting to split, based on colons, but these shouldn’t be in 
>>> the binary representation of the IP -- (isinstance(addr, bytes) and not 
>>> raw) is True.
>> 
>> I don't know what "binary representation" means in this context.  Is this a 
>> string containing 32 hex characters?  Or is it 16 raw bytes?
>> 
>> The latter one (16 raw bytes) should be supported by IPAddr6 if it's passed 
>> into the constructor with raw=True (or using the from_raw() factory).  The 
>> former isn't supported -- it needs to be a properly formatted IPv6 address 
>> (e.g., as per rfc5952), which contains colons.
>> 
>> It seems like packet.dns maybe has a bug here where line 432 should use the 
>> from_raw() factory but doesn't.  If you make that fix does that help?
>> 
>>> I’ve attempted to alter addresses.py change addr from bytes to a IPv6 
>>> string separated by colons (2001:0db8:0000:0000:0000:ff00:0042:8329), but I 
>>> only break things later down the line (dns.py for me). I also see nothing 
>>> irregular with the OpenFlow traffic to the controller.
>> 
>> Can you elaborate on what breaks and how it breaks?  Does it break in the 
>> dns class's hdr() method?  If so, a quick guess is that putData() needs an 
>> if clause for qtype == 28 which is much like the one for qtype == 1.
>> 
>>> My current setup:
>>> POX - 0.2.0 (carp) - l3_learning (also shows parsing error as above)
>>> Mininet with custom topology
>>> BIND9 - the entry in question looks like "server1   IN      AAAA    
>>> 2001:db8::ff00:42:8329”. Wireshark shows nothing wrong with the DNS 
>>> request/response.
>>> 
>>> Any help would be appreciated. 
>>> 
>>> Cheers,
>>> Curtis
>> 
> 

Reply via email to