######################################################################
#
# All files associated with the OpenFlow Python Test (oftest) are
# made available for public use and benefit with the expectation
# that others will use, modify and enhance the Software and contribute
# those enhancements back to the community. However, since we would
# like to make the Software available for broadest use, with as few
# restrictions as possible permission is hereby granted, free of
# charge, to any person obtaining a copy of this Software to deal in
# the Software under the copyrights without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# 
######################################################################
import os

"""
OpenFlow packet class

This class implements the basic abstraction of an OpenFlow packet.  It
includes parsing functionality and OpenFlow actions related to 
packet modifications.
"""

import socket
import struct
import logging
import ofp
import unittest
import binascii
import string
import collections #@UnresolvedImport

ETHERTYPE_IP = 0x0800
ETHERTYPE_VLAN = 0x8100
ETHERTYPE_VLAN_QinQ = 0x88a8
ETHERTYPE_ARP = 0x0806
ETHERTYPE_MPLS = 0x8847
ETHERTYPE_MPLS_MCAST = 0x8848
ETHERTYPES_MPLS = [ETHERTYPE_MPLS, ETHERTYPE_MPLS_MCAST]

DL_MASK_ALL = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
NW_MASK_ALL = 0xffffffff

MPLS_BOTTOM_OF_STACK = 0x00000100

# Sigh.. not python26
#MplsTag = collections.namedtuple("MplsTag", "label tc ttl")

class MplsTag(object):
    def __init__(self, label, tc, ttl):
        self.label = label
        self.tc = tc
        self.ttl = ttl
    def pack(self, bos = 0):
        packed_tag = ((self.label & 0xfffff) << 12) | \
                     ((self.tc & 0x7) << 9) | \
                     (self.ttl & 0xFF) | \
                     (MPLS_BOTTOM_OF_STACK if bos else 0)
        return packed_tag
    
    def unpack(packed_tag):
        tag = MplsTag(packed_tag >> 12,
                      (packed_tag >> 9) & 0x0007,
                      packed_tag & 0xFF)
        bos = bool(packed_tag & MPLS_BOTTOM_OF_STACK)
        return (tag, bos)
    unpack = staticmethod(unpack)

class Packet(object):
    """
    Packet abstraction

    This is meant to support the abstraction of a packet in an
    OpenFlow 1.1 switch so it includes an action set, ingress port,
    and metadata.  These members may be ignored and the rest of the
    packet parsing and modification functions used to manipulate
    a packet.
    """
    
    icmp_counter = 1

    def __init__(self, in_port=None, data=""):
        # Use entries in match when possible.
        self.in_port = in_port
        self.data = data
        self.bytes = len(data)
        self.match = ofp.ofp_match()
        self.logger = logging.getLogger("packet")  
        self.instructions = []
        # parsable tags
        self.ip_header_offset = None
        self.tcp_header_offset = None
        self.mpls_tag_offset = None         # pointer to outer mpls tag
        self.vlan_tag_offset = None         # pointer to outer vlan tag
        self.action_set = {}
        self.queue_id = 0

        if self.data != "":
            self.parse()

    def show(self):
        """ Return a ascii hex representation of the packet's data"""
        ret = ""
        c = 0
        for b in list(self.data):
            if c != 0:
                if c % 16  == 0:
                    ret += '\n'
                elif c % 8 == 0:
                    ret += '  '
            c += 1
            ret += "%0.2x " % struct.unpack('B', b)
        return ret

    def __repr__(self):
        return self.data
    
    def __str__(self):
        return  self.__repr__()

    def __len__(self):
        return len(self.data)

    def simple_tcp_packet(self,
                          pktlen=100, 
                          dl_dst='00:01:02:03:04:05',
                          dl_src='00:06:07:08:09:0a',
                          dl_vlan_enable=False,
                          dl_vlan_type=ETHERTYPE_VLAN,
                          dl_vlan=0,
                          dl_vlan_pcp=0,
                          dl_vlan_cfi=0,
                          mpls_type=None,
                          mpls_tags=None,
                          ip_src='192.168.0.1',
                          ip_dst='192.168.0.2',
                          ip_tos=0,
                          ip_ttl=64,
                          tcp_sport=1234,
                          tcp_dport=80):
        """
        Return a simple dataplane TCP packet

        Supports a few parameters:
        @param len Length of packet in bytes w/o CRC
        @param dl_dst Destinatino MAC
        @param dl_src Source MAC
        @param dl_vlan_enable True if the packet is with vlan, False otherwise
        @param dl_vlan_type Ether type for VLAN
        @param dl_vlan VLAN ID
        @param dl_vlan_pcp VLAN priority
        @param ip_src IP source
        @param ip_dst IP destination
        @param ip_tos IP ToS
        @param tcp_dport TCP destination port
        @param tcp_sport TCP source port

        Generates a simple TCP request.  Users shouldn't assume anything 
        about this packet other than that it is a valid ethernet/IP/TCP frame.
        """
        self.data = ""
        self._make_ip_packet(dl_dst, dl_src, dl_vlan_enable, dl_vlan_type, 
                             dl_vlan, dl_vlan_pcp, dl_vlan_cfi, 
                             mpls_type, mpls_tags, 
                             ip_tos, ip_ttl, ip_src, ip_dst, socket.IPPROTO_TCP)

        # Add TCP header
        self.data += struct.pack("!HHLLBBHHH",
                                 tcp_sport,
                                 tcp_dport,
                                 1,     # tcp.seq
                                 0,     # tcp.ack
                                 0x50,  # tcp.doff + tcp.res1
                                 0x12,  # tcp.syn + tcp.ack
                                 0,     # tcp.wnd
                                 0,     # tcp.check
                                 0,     # tcp.urgent pointer
                                 )

        # Fill out packet
        self.data += "D" * (pktlen - len(self.data))
        return self
    
    def simple_icmp_packet(self,
                          pktlen=100, 
                          dl_dst='00:01:02:03:04:05',
                          dl_src='00:06:07:08:09:0a',
                          dl_vlan_enable=False,
                          dl_vlan_type=ETHERTYPE_VLAN,
                          dl_vlan=0,
                          dl_vlan_pcp=0,
                          dl_vlan_cfi=0,
                          mpls_type=None,
                          mpls_tags=None,
                          ip_src='192.168.0.1',
                          ip_dst='192.168.0.2',
                          ip_tos=0,
                          ip_ttl=64,
                          icmp_type=8, # ICMP_ECHO_REQUEST
                          icmp_code=0, 
                          ):
        """
        Return a simple dataplane ICMP packet

        Supports a few parameters:
        @param len Length of packet in bytes w/o CRC
        @param dl_dst Destinatino MAC
        @param dl_src Source MAC
        @param dl_vlan_enable True if the packet is with vlan, False otherwise
        @param dl_vlan_type Ether type for VLAN
        @param dl_vlan VLAN ID
        @param dl_vlan_pcp VLAN priority
        @param ip_src IP source
        @param ip_dst IP destination
        @param ip_tos IP ToS
        @param tcp_dport TCP destination port
        @param ip_sport TCP source port

        Generates a simple TCP request.  Users shouldn't assume anything 
        about this packet other than that it is a valid ethernet/IP/TCP frame.
        """
        self.data = ""
        self._make_ip_packet(dl_dst, dl_src, dl_vlan_enable, dl_vlan_type, 
                             dl_vlan, dl_vlan_pcp, dl_vlan_cfi, 
                             mpls_type, mpls_tags,
                             ip_tos,
                              ip_ttl, ip_src, ip_dst, socket.IPPROTO_ICMP)

        # Add ICMP header
        self.data += struct.pack("!BBHHH",
                                 icmp_type,
                                 icmp_code,
                                 0,  # icmp.checksum
                                 os.getpid() & 0xffff,  # icmp.echo.id
                                 Packet.icmp_counter   # icmp.echo.seq
                                 )                  
        Packet.icmp_counter += 1       

        # Fill out packet
        self.data += "D" * (pktlen - len(self.data))

        return self

    def _make_ip_packet(self, dl_dst, dl_src, dl_vlan_enable, dl_vlan_type, 
                          dl_vlan, dl_vlan_pcp, dl_vlan_cfi, mpls_type, mpls_tags,
                          ip_tos, ip_ttl, ip_src, ip_dst, ip_proto):
        self.data = ""
        addr = dl_dst.split(":")
        for byte in map(lambda z: int(z, 16), addr):
            self.data += struct.pack("!B", byte)
        addr = dl_src.split(":")
        for byte in map(lambda z: int(z, 16), addr):
            self.data += struct.pack("!B", byte)

        if (dl_vlan_enable):
            # Form and add VLAN tag
            self.data += struct.pack("!H", dl_vlan_type)
            vtag = (dl_vlan & 0x0fff) | \
                            (dl_vlan_pcp & 0x7) << 13 | \
                            (dl_vlan_cfi & 0x1) << 12
            self.data += struct.pack("!H", vtag)
            
        if mpls_tags:
            # Add type/len field
            self.data += struct.pack("!H", mpls_type)
            mpls_tags = list(mpls_tags)          
            while len(mpls_tags):
                tag = mpls_tags.pop(0)
                packed_tag = tag.pack(bos = not len(mpls_tags))
                self.data += struct.pack("!I", packed_tag)
            
        else:
            # Add type/len field
            self.data += struct.pack("!H", ETHERTYPE_IP)

        # Add IP header
        v_and_hlen = 0x45  # assumes no ip or tcp options
        ip_len = 120 + 40  # assumes no ip or tcp options
        self.data += struct.pack("!BBHHHBBH", v_and_hlen, ip_tos, ip_len, 
                                 0, # ip.id 
                                 0, # ip.frag_off
                                 ip_ttl, # ip.ttl
                                 ip_proto,
                                 0)  # ip.checksum
        # convert  ipsrc/dst to ints
        self.data += struct.pack("!LL", ascii_ip_to_bin(ip_src), 
                                 ascii_ip_to_bin(ip_dst))

    def length(self):
        return len(self.data)

    def clear_actions(self):
        self.action_set = {}

    def parse(self):
        """
        Update the headers in self.match based on self.data 
        
        Parses the relevant header features out of the packet, using
        the table outlined in the OF1.1 spec, Figure 4
        """
        self.bytes = len(self.data)
        self.match.in_port = self.in_port
        self.match.type = ofp.OFPMT_STANDARD
        self.match.length = ofp.OFPMT_STANDARD_LENGTH
        self.match.wildcards = 0
        self.match.nw_dst_mask = 0
        self.match.nw_dst_mask = 0
        self.match.dl_dst_mask = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
        self.match.dl_src_mask = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
        self.mpls_tag_offset = None
        self.ip_header_offset = None
        
        idx = 0
        try:
            idx = self._parse_l2(idx)
            
            if self.match.dl_type == ETHERTYPE_IP:
                self.ip_header_offset = idx 
                idx = self._parse_ip(idx)
                if self.match.nw_proto in [ socket.IPPROTO_TCP,
                                            socket.IPPROTO_UDP,
                                            socket.IPPROTO_ICMP]:
                    self.tcp_header_offset = idx
                    if self.match.nw_proto != socket.IPPROTO_ICMP:
                        idx = self._parse_l4(idx)
                    else:
                        idx = self._parse_icmp(idx)
            elif self.match.dl_type == ETHERTYPE_ARP:
                self._parse_arp(idx)
        except (parse_error), e:
            self.logger.warn("Giving up on parsing packet, got %s" % 
                             (str(e)))
            return None
        return self.match

    def _parse_arp(self, idx):
        # @todo Implement
        pass

    def _parse_l2(self, idx):
        """
        Parse Layer2 Headers of packet
        
        Parse ether src,dst,type (and vlan and QinQ headers if exists) from 
        self.data starting at idx
        """
        if self.bytes < 14 :
            raise parse_error("_parse_l2:: packet too shorter <14 bytes")
            
        self.match.dl_dst = list(struct.unpack("!6B", self.data[idx:idx+6]))
        self.match.dl_dst_mask = DL_MASK_ALL
        idx += 6
        self.match.dl_src = list(struct.unpack("!6B", self.data[idx:idx+6]))
        self.match.dl_src_mask = DL_MASK_ALL
        idx += 6
        #pdb.set_trace()
        l2_type = struct.unpack("!H", self.data[idx:idx+2])[0]
        idx += 2
        if l2_type in [ETHERTYPE_VLAN, ETHERTYPE_VLAN_QinQ] :
            self.vlan_tag_offset = 12
            blob = struct.unpack("!H", self.data[idx:idx+2])[0]
            idx += 2
            self.match.dl_vlan_pcp = (blob & 0xe000) >> 13
            #cfi = blob & 0x1000     #@todo figure out what to do if cfi!=0
            self.match.dl_vlan = blob & 0x0fff
            l2_type = struct.unpack("!H", self.data[idx:idx+2])[0]
            # now skip past any more nest VLAN tags (per the spec)
            while l2_type in [ETHERTYPE_VLAN, ETHERTYPE_VLAN_QinQ] :
                idx += 4
                if self.bytes < idx :
                    raise parse_error("_parse_l2(): Too many vlan tags")
                l2_type = struct.unpack("!H", self.data[idx:idx+2])[0]
            idx += 2
        else:
            self.vlan_tag_offset = None
            self.match.dl_vlan = ofp.OFPVID_NONE
            self.match.dl_vlan_pcp = 0
            
        if l2_type in ETHERTYPES_MPLS:
            if self.bytes < (idx + 4):
                raise parse_error("_parse_l2:  Invalid MPLS header")
            self.mpls_tag_offset = idx
            tag = struct.unpack("!I", self.data[idx:idx+4])[0]
            self.match.mpls_label = tag >> 12
            self.match.mpls_tc = (tag >> 9) & 0x0007
            idx += 4
        else:
            self.match.mpls_label = 0
            self.match.mpls_tc = 0
            
        self.match.dl_type = l2_type
        return idx
            
    def _parse_ip(self, idx):
        """
        Parse IP Headers of a packet starting at self.data[idx]
        """
        if self.bytes < (idx + 20) :
            raise parse_error("_parse_ip: Invalid IP header")
        # the three blanks are id (2bytes), frag offset (2bytes), 
        # and ttl (1byte)
        (hlen_and_v, self.match.nw_tos, len, _,_,_, self.match.nw_proto) = \
            struct.unpack("!BBHHHBB", self.data[idx:idx+10])
        #@todo add fragmentation parsing
        hlen = hlen_and_v & 0x0f
        (self.match.nw_src, self.match.nw_dst) = \
            struct.unpack("!II", self.data[idx + 12:idx+20])
        self.match.nw_dst_mask = NW_MASK_ALL
        self.match.nw_src_mask = NW_MASK_ALL
        return idx + (hlen *4) # this should correctly skip IP options
    
    def _parse_l4(self, idx):
        """
        Parse the src/dst ports of UDP and TCP packets
        """
        if self.bytes < (idx + 8):
            raise parse_error("_parse_l4: Invalid L4 header")
        (self.match.tp_src, self.match.tp_dst) = \
            struct.unpack("!HH", self.data[idx:idx+4])

    def _parse_icmp(self, idx):
        """
        Parse the type/code of ICMP Packets 
        """
        if self.bytes < (idx + 4):
            raise parse_error("_parse_icmp: Invalid icmp header")
        # yes, type and code get stored into tp_dst and tp_src...
        (self.match.tp_src, self.match.tp_dst) = \
            struct.unpack("!BB", self.data[idx:idx+2])


    #
    # NOTE:  See comment string in write_action regarding exactly
    # when actions are executed (for apply vs write instructions)
    #

    def write_action(self, action):
        """
        Write the action into the packet's action set

        Note that we do nothing to the packet when the write_action
        instruction is executed.  We only record the actions for 
        later processing.  Because of this, the output port is not
        explicitly recorded in the packet; that state is recorded
        in the action_set[set_output] item.
        """
        self.action_set[action.__class__] = action

    def _set_1bytes(self,offset,byte):
        """ Writes the byte at data[offset] 
        
        This function only exists to match the other _set_Xbytes() and
        is trivial
    
        """
        tmp= "%s%s" % (self.data[0:offset], 
                               struct.pack('B',byte & 0xff))
        if len(self.data) > offset + 1 :
            tmp += self.data[offset+1:len(self.data)]
        self.data=tmp

    def _set_2bytes(self,offset,short):
        """ Writes the 2 byte short in network byte order at data[offset] """
        tmp= "%s%s" % (self.data[0:offset], 
                               struct.pack('!H',short & 0xffff))
        if len(self.data) > offset + 2 :
            tmp += self.data[offset+2:len(self.data)]
        self.data=tmp

    def _set_4bytes(self,offset,word,forceNBO=True):
        """ Writes the 4 byte word at data[offset] 
        
        Use network byte order if forceNBO is True,
        else it's assumed that word is already in NBO
        
        """
        # @todo Verify byte order
        #pdb.set_trace()
        fmt ="L"
        if forceNBO:
            fmt = '!' + fmt 
        
        tmp= "%s%s" % (self.data[0:offset], 
                               struct.pack(fmt,word & 0xffffffff))
        if len(self.data) > offset + 4 :
            tmp += self.data[offset+4:len(self.data)]
        self.data=tmp
        
    def _set_6bytes(self,offset,byte_list):
        """ Writes the 6 byte sequence in the given order to data[offset] """
        # @todo Do as a slice
        tmp= self.data[0:offset] 
        tmp += struct.pack("BBBBBB", *byte_list)
        if len(self.data) > offset + 6 :
            tmp += self.data[offset+6:len(self.data)]
        self.data=tmp
    
    def _update_l4_checksum(self):
        """ Recalculate the L4 checksum, if there
        
        Can be safely called on non-tcp/non-udp packets as a NOOP
        """
        if (self.ip_header_offset is None or 
            self.tcp_header_offset is None):
            return
        if self.match.nw_proto == socket.IPPROTO_TCP:
            return self._update_tcp_checksum()
        elif self.match.nw_proto == socket.IPPROTO_UDP:
            return self._update_udp_checksum()
        
    def _update_tcp_checksum(self):
        """ Recalculate the TCP checksum
        
        @warning:  Must only be called on actual TCP Packets
        """
        #@todo implemnt tcp checksum update
        pass
    
    def _update_udp_checksum(self):
        """ Recalculate the TCP checksum
        
        @warning:  Must only be called on actual TCP Packets
        """
        #@todo implemnt udp checksum update
        pass

    def set_metadata(self, value, mask):
        self.match.metadata = (self.match.metadata & ~mask) | \
            (value & mask)

    #
    # These are the main action operations that take the 
    # required parameters
    # 
    # Note that 'group', 'experimenter' and 'set_output' are only 
    # implemented for the action versions.

    def set_queue(self, queue_id):
        self.queue_id = queue_id

    def set_vlan_vid(self, vid):
        # @todo Verify proper location of VLAN id
        if self.vlan_tag_offset is None:
            self.logger.debug("set_vlan_vid(): Adding new vlan tag to untagged packet")
            self.push_vlan(ETHERTYPE_VLAN)
        offset = self.vlan_tag_offset + 2
        short = struct.unpack('!H', self.data[offset:offset+2])[0]
        short = (short & 0xf000) | ((vid & 0x0fff) )
        self.data = self.data[0:offset] + struct.pack('!H',short) + \
                self.data[offset+2:len(self.data)]
        self.match.dl_vlan = vid & 0x0fff
        self.logger.debug("set_vlan_vid(): setting packet vlan_vid to 0x%x " % 
                          self.match.dl_vlan)

    def set_vlan_pcp(self, pcp):
        # @todo Verify proper location of VLAN pcp
        if self.vlan_tag_offset is None:
            return
        offset = self.vlan_tag_offset + 2
        short = struct.unpack('!H', self.data[offset:offset+2])[0]
        short = (pcp<<13 & 0xf000) | ((short & 0x0fff) )
        self.data = self.data[0:offset] + struct.pack('!H',short) + \
                self.data[offset+2:len(self.data)]
        self.match.dl_vlan_pcp = pcp & 0xf

    def set_dl_src(self, dl_src):
        self._set_6bytes(6, dl_src)
        self.match.dl_src = dl_src

    def set_dl_dst(self, dl_dst):
        self._set_6bytes(0, dl_dst)
        self.match.dl_dst = dl_dst
        
    def set_nw_src(self, nw_src):
        if self.ip_header_offset is None:
            return
        self._set_4bytes(self.ip_header_offset + 12, nw_src)
        self._update_l4_checksum()
        self.match.nw_src = nw_src
    
    def set_nw_dst(self, nw_dst):
        # @todo Verify byte order
        if self.ip_header_offset is None:
            return
        self._set_4bytes(self.ip_header_offset + 16, nw_dst)
        self._update_l4_checksum()
        self.match.nw_dst = nw_dst

    def set_nw_tos(self, tos):
        if self.ip_header_offset is None:
            return
        self._set_1bytes(self.ip_header_offset + 1, tos)
        self.match.nw_tos = tos

    def set_nw_ecn(self, ecn):
        #@todo look up ecn implementation details
        pass

    def set_tp_src(self, tp_src):
        if self.tcp_header_offset is None:
            return
        if (self.match.nw_proto == socket.IPPROTO_TCP or
            self.match.nw_proto == socket.IPPROTO_UDP): 
            self._set_2bytes(self.tcp_header_offset, tp_src)
        elif (self.match.nw_proto == socket.IPPROTO_ICMP):
            self._set_1bytes(self.tcp_header_offset, tp_src)
        self._update_l4_checksum()
        self.match.tp_src = tp_src
            
    def set_tp_dst(self, tp_dst):
        if self.tcp_header_offset is None:
            return
        if (self.match.nw_proto == socket.IPPROTO_TCP or
            self.match.nw_proto == socket.IPPROTO_UDP): 
            self._set_2bytes(self.tcp_header_offset +2, tp_dst)
        elif (self.match.nw_proto == socket.IPPROTO_ICMP):
            self._set_1bytes(self.tcp_header_offset + 1, tp_dst)
        self._update_l4_checksum()
        self.match.tp_dst = tp_dst

    IP_OFFSET_TTL = 8
    
    def copy_ttl_out(self):
        if self.mpls_tag_offset is None:
            # No MPLS tag.
            return
        outerTag = struct.unpack("!I", self.data[self.mpls_tag_offset:
                                                 self.mpls_tag_offset+4])[0]
        if not (outerTag & MPLS_BOTTOM_OF_STACK):
            # Payload is another MPLS tag:
            innerTag = struct.unpack("!I", self.data[self.mpls_tag_offset+4:
                                                     self.mpls_tag_offset+8])[0]
            outerTag = (outerTag & 0xFFFFFF00) | (innerTag & 0x000000FF)
            self._set_4bytes(self.mpls_tag_offset, outerTag)
        else:
            # This MPLS tag is the bottom of the stack.
            # See if the payload looks like it might be IPv4.
            versionLen = struct.unpack("B", 
                                       self.data[self.mpls_tag_offset+4])[0]
            if versionLen >> 4 != 4:
                # This is not IPv4.
                return;
            # This looks like IPv4, so copy the TTL.
            ipTTL = struct.unpack("B", self.data[self.mpls_tag_offset + 4 +
                                                 Packet.IP_OFFSET_TTL])[0]
            outerTag = (outerTag & 0xFFFFFF00) | (ipTTL & 0xFF)
            self._set_4bytes(self.mpls_tag_offset, outerTag)      
            return

    def copy_ttl_in(self):
        if self.mpls_tag_offset is None:
            # No MPLS tag.
            return
        outerTag = struct.unpack("!I", self.data[self.mpls_tag_offset:
                                                 self.mpls_tag_offset+4])[0]
        if not (outerTag & MPLS_BOTTOM_OF_STACK):
            # Payload is another MPLS tag:
            innerTag = struct.unpack("!I", self.data[self.mpls_tag_offset+4:
                                                     self.mpls_tag_offset+8])[0]
            innerTag = (innerTag & 0xFFFFFF00) | (outerTag & 0x000000FF)
            self._set_4bytes(self.mpls_tag_offset+4, innerTag)
        else:
            # This MPLS tag is the bottom of the stack.
            # See if the payload looks like it might be IPv4.
            versionLen = struct.unpack("B", self.data[self.mpls_tag_offset+4])[0]
            if versionLen >> 4 != 4:
                # This is not IPv4.
                return;
            # This looks like IPv4, so copy the TTL.
            self._set_1bytes(self.mpls_tag_offset + 4 + Packet.IP_OFFSET_TTL,
                             outerTag & 0x000000FF) 
            #@todo update checksum
            return

    def set_mpls_label(self, mpls_label):
        if self.mpls_tag_offset is None:
            return
        tag = struct.unpack("!I", self.data[self.mpls_tag_offset:
                                            self.mpls_tag_offset+4])[0]
        tag = ((mpls_label & 0xfffff) << 12) | (tag & 0x00000fff)
        self.match.mpls_label = mpls_label
        self._set_4bytes(self.mpls_tag_offset, tag)

    def set_mpls_tc(self, mpls_tc):
        if self.mpls_tag_offset is None:
            return
        tag = struct.unpack("!I", self.data[self.mpls_tag_offset:
                                            self.mpls_tag_offset+4])[0]
        tag = ((mpls_tc & 0x7) << 9) | (tag & 0xfffff1ff)
        self.match.mpls_tc = mpls_tc
        self._set_4bytes(self.mpls_tag_offset, tag)

    def set_mpls_ttl(self, ttl):
        if self.mpls_tag_offset is None:
            return   
        self._set_1bytes(self.mpls_tag_offset + 3, ttl)

    def dec_mpls_ttl(self):
        if self.mpls_tag_offset is None:
            return
        ttl = struct.unpack("B", self.data[self.mpls_tag_offset + 3])[0]
        self.set_mpls_ttl(ttl - 1)

    def push_vlan(self, ethertype):
        if len(self) < 14: 
            self.logger.error("NOT Pushing a new VLAN tag: packet too short!")
            pass    # invalid ethernet frame, can't add vlan tag

        # from 4.8.1 of the spec, default values are zero
        # on a push operation if no VLAN tag already exists
        l2_type = struct.unpack("!H", self.data[12:14])[0]
        if ((l2_type == ETHERTYPE_VLAN) or (l2_type == ETHERTYPE_VLAN_QinQ)):
            current_tag = struct.unpack("!H", self.data[14:16])[0]
        else:
            current_tag = 0
        new_tag = struct.pack('!HH',
                                  # one of 0x8100 or x88a8
                                  # could check to enforce this?
                                  ethertype & 0xffff,
                                  current_tag
                                  )
        self.data = self.data[0:12] + new_tag + self.data[12:len(self.data)]  
        self.parse()

    def pop_vlan(self):
        if self.vlan_tag_offset is None:
            pass
        self.data = self.data[0:12] + self.data[16:len(self.data)]
        self.parse()

    def push_mpls(self, ethertype):
        tag = MplsTag(0, 0, 0)
        bos = False
        
        if self.mpls_tag_offset:
            # The new tag defaults to the old one.
            packed_tag = struct.unpack("!I", self.data[self.mpls_tag_offset:
                                                       self.mpls_tag_offset+4])[0]
            (tag, _) = MplsTag.unpack(packed_tag)
            
        else:
            # Pushing a new label stack, set the BoS bit and get TTL from IP.
            bos = True
            if self.ip_header_offset:
                ttl = struct.unpack("B", self.data[self.ip_header_offset + \
                                                       Packet.IP_OFFSET_TTL])[0]
                tag = MplsTag(0, 0, ttl)
                                                       
        self.data = self.data[0:14] + \
                    struct.pack("!I", tag.pack(bos)) + \
                    self.data[14:]
        self._set_2bytes(12, ethertype)   
        # Reparse to update offsets, ethertype, etc.
        self.parse()
            
    def pop_mpls(self, ethertype):
        # Ignore if no existing tags.
        if self.mpls_tag_offset:
            self.data = self.data[0:self.mpls_tag_offset] + \
                        self.data[self.mpls_tag_offset + 4:]
            self._set_2bytes(12, ethertype)
            
            # Reparse to update offsets, ethertype, etc.
            self.parse()
    
    def set_nw_ttl(self, ttl):
        if self.ip_header_offset is None:
            return
        self._set_1bytes(self.ip_header_offset + Packet.IP_OFFSET_TTL, ttl)
        self._update_l4_checksum()
        # don't need to update self.match; no ttl in it

    def dec_nw_ttl(self):
        if self.ip_header_offset is None:
            return
        offset = self.ip_header_offset + Packet.IP_OFFSET_TTL
        old_ttl = struct.unpack("b",self.data[offset:offset + 1])[0]
        self.set_nw_ttl( old_ttl - 1)

    #
    # All action functions need to take the action object for params
    # These take an action object to facilitate the switch implementation
    #

    def action_output(self, action, switch):
        if action.port < ofp.OFPP_MAX:
            switch.dataplane.send(action.port, self.data, 
                                  queue_id=self.queue_id)
        elif action.port == ofp.OFPP_ALL:
            for of_port in switch.ports.iterkeys():
                if of_port != self.in_port: 
                    switch.dataplane.send(of_port, self.data, 
                                          queue_id=self.queue_id)
        elif action.port == ofp.OFPP_IN_PORT:
            switch.dataplane.send(self.in_port, self.data, 
                                  queue_id=self.queue_id)
        else:
            switch.logger.error("NEED to implement action_output" + 
                                " for port %d" % action.port)        

    def action_set_queue(self, action, switch):
        self.set_queue(action.queue_id)

    def action_set_vlan_vid(self, action, switch):
        self.set_vlan_vid(action.vlan_vid)

    def action_set_vlan_pcp(self, action, switch):
        self.set_vlan_pcp(action.vlan_pcp)

    def action_set_dl_src(self, action, switch):
        self.set_dl_src(action.dl_addr)

    def action_set_dl_dst(self, action, switch):
        self.set_dl_dst(action.dl_addr)

    def action_set_nw_src(self, action, switch):
        self.set_nw_src(action.nw_addr)

    def action_set_nw_dst(self, action, switch):
        self.set_nw_dst(action.nw_addr)

    def action_set_nw_tos(self, action, switch):
        self.set_nw_tos(action.nw_tos)

    def action_set_nw_ecn(self, action, switch):
        self.set_nw_ecn(action.nw_ecn)

    def action_set_tp_src(self, action, switch):
        self.set_tp_src(action.tp_port)

    def action_set_tp_dst(self, action, switch):
        self.set_tp_dst(action.tp_port)

    def action_copy_ttl_out(self, action, switch):
        self.copy_ttl_out()

    def action_copy_ttl_in(self, action, switch):
        self.copy_ttl_in()

    def action_set_mpls_label(self, action, switch):
        self.set_mpls_label(action.mpls_label)

    def action_set_mpls_tc(self, action, switch):
        self.set_mpls_tc(action.mpls_tc)

    def action_set_mpls_ttl(self, action, switch):
        self.set_mpls_ttl(action.mpls_ttl)

    def action_dec_mpls_ttl(self, action, switch):
        self.dec_mpls_ttl()

    def action_push_vlan(self, action, switch):
        self.push_vlan(action.ethertype)

    def action_pop_vlan(self, action, switch):
        self.pop_vlan()

    def action_push_mpls(self, action, switch):
        self.push_mpls(action.ethertype)

    def action_pop_mpls(self, action, switch):
        self.pop_mpls(action.ethertype)
    
    def action_experimenter(self, action, switch):
        pass

    def action_set_nw_ttl(self, action, switch):
        self.set_nw_ttl(action.nw_ttl)

    def action_dec_nw_ttl(self, action, switch):
        self.dec_nw_ttl()

    def action_group(self, action, switch):
        pass

    def execute_action_set(self, switch):
        """
        Execute the actions in the action set for the packet
        according to the order given in ordered_action_list.

        This determines the order in which
        actions in the packet's action set are executed

        @param switch The parent switch object (for sending pkts out)

        @todo Verify the ordering in this list
        """
        cls = action.action_copy_ttl_in
        if cls in self.action_set.keys():
            self.logger.debug("Action copy_ttl_in")
            self.action_copy_ttl_in(self.action_set[cls], switch)

        cls = action.action_pop_mpls
        if cls in self.action_set.keys():
            self.logger.debug("Action pop_mpls")
            self.action_pop_mpls(self.action_set[cls], switch)
        cls = action.action_pop_vlan
        if cls in self.action_set.keys():
            self.logger.debug("Action pop_vlan")
            self.action_pop_vlan(self.action_set[cls], switch)
        cls = action.action_push_mpls
        if cls in self.action_set.keys():
            self.logger.debug("Action push_mpls")
            self.action_push_mpls(self.action_set[cls], switch)
        cls = action.action_push_vlan
        if cls in self.action_set.keys():
            self.logger.debug("Action push_vlan")
            self.action_push_vlan(self.action_set[cls], switch)

        cls = action.action_dec_mpls_ttl
        if cls in self.action_set.keys():
            self.logger.debug("Action dec_mpls_ttl")
            self.action_dec_mpls_ttl(self.action_set[cls], switch)
        cls = action.action_dec_nw_ttl
        if cls in self.action_set.keys():
            self.logger.debug("Action dec_nw_ttl")
            self.action_dec_nw_ttl(self.action_set[cls], switch)
        cls = action.action_copy_ttl_out
        if cls in self.action_set.keys():
            self.logger.debug("Action copy_ttl_out")
            self.action_copy_ttl_out(self.action_set[cls], switch)

        cls = action.action_set_dl_dst
        if cls in self.action_set.keys():
            self.logger.debug("Action set_dl_dst")
            self.action_set_dl_dst(self.action_set[cls], switch)
        cls = action.action_set_dl_src
        if cls in self.action_set.keys():
            self.logger.debug("Action set_dl_src")
            self.action_set_dl_src(self.action_set[cls], switch)
        cls = action.action_set_mpls_label
        if cls in self.action_set.keys():
            self.logger.debug("Action set_mpls_label")
            self.action_set_mpls_label(self.action_set[cls], switch)
        cls = action.action_set_mpls_tc
        if cls in self.action_set.keys():
            self.logger.debug("Action set_mpls_tc")
            self.action_set_mpls_tc(self.action_set[cls], switch)
        cls = action.action_set_mpls_ttl
        if cls in self.action_set.keys():
            self.logger.debug("Action set_mpls_ttl")
            self.action_set_mpls_ttl(self.action_set[cls], switch)
        cls = action.action_set_nw_dst
        if cls in self.action_set.keys():
            self.logger.debug("Action set_nw_dst")
            self.action_set_nw_dst(self.action_set[cls], switch)
        cls = action.action_set_nw_ecn
        if cls in self.action_set.keys():
            self.logger.debug("Action set_nw_ecn")
            self.action_set_nw_ecn(self.action_set[cls], switch)
        cls = action.action_set_nw_src
        if cls in self.action_set.keys():
            self.logger.debug("Action set_nw_src")
            self.action_set_nw_src(self.action_set[cls], switch)
        cls = action.action_set_nw_tos
        if cls in self.action_set.keys():
            self.logger.debug("Action set_nw_tos")
            self.action_set_nw_tos(self.action_set[cls], switch)
        cls = action.action_set_nw_ttl
        if cls in self.action_set.keys():
            self.logger.debug("Action set_nw_ttl")
            self.action_set_nw_ttl(self.action_set[cls], switch)
        cls = action.action_set_queue
        if cls in self.action_set.keys():
            self.logger.debug("Action set_queue")
            self.action_set_queue(self.action_set[cls], switch)
        cls = action.action_set_tp_dst
        if cls in self.action_set.keys():
            self.logger.debug("Action set_tp_dst")
            self.action_set_tp_dst(self.action_set[cls], switch)
        cls = action.action_set_tp_src
        if cls in self.action_set.keys():
            self.logger.debug("Action set_tp_src")
            self.action_set_tp_src(self.action_set[cls], switch)
        cls = action.action_set_vlan_pcp
        if cls in self.action_set.keys():
            self.logger.debug("Action set_vlan_pcp")
            self.action_set_vlan_pcp(self.action_set[cls], switch)
        cls = action.action_set_vlan_vid
        if cls in self.action_set.keys():
            self.logger.debug("Action set_vlan_vid")
            self.action_set_vlan_vid(self.action_set[cls], switch)

        cls = action.action_group
        if cls in self.action_set.keys():
            self.logger.debug("Action group")
            self.action_group(self.action_set[cls], switch)
        cls = action.action_experimenter
        if cls in self.action_set.keys():
            self.logger.debug("Action experimenter")
            self.action_experimenter(self.action_set[cls], switch)
        cls = action.action_output
        if cls in self.action_set.keys():
            self.logger.debug("Action set_output")
            self.action_output(self.action_set[cls], switch)


def ascii_ip_to_bin(ip):
        """ Take '192.168.0.1' and return the NBO decimal equivalent 0xc0a80101 """
        #Lookup the cleaner, one-line way of doing this
        # or if there isn't just a library (!?)
        s = ip.split('.')
        return struct.unpack("!L", struct.pack("BBBB", int(s[0]), 
                                               int(s[1]), 
                                               int(s[2]), 
                                               int(s[3]) ))[0]
    

class parse_error(Exception):
    """
    Thrown internally if there is an error in packet parsing
    """
    
    def __init__(self, why):
        self.why = why
        
    def __str__(self):
        return "%s:: %s" % (super.__str__(self), self.why)
        
class packet_test(unittest.TestCase):
    """
    Unit tests for packet class
    """
    
    def ascii_to_data(self, str):
        return binascii.unhexlify(str.translate(string.maketrans('',''),
                                                string.whitespace))
    
    def setUp(self):
        """
        Simple packet data for parsing tests.  

        Ethernet II, Src: Fujitsu_ef:cd:8d (00:17:42:ef:cd:8d), 
            Dst: ZhsZeitm_5d:24:00 (00:d0:05:5d:24:00)
        Internet Protocol, Src: 172.24.74.96 (172.24.74.96), 
            Dst: 171.64.74.58 (171.64.74.58)
        Transmission Control Protocol, Src Port: 59581 (59581), 
            Dst Port: ssh (22), Seq: 2694, Ack: 2749, Len: 48
        """
        pktdata = self.ascii_to_data(
            """00 d0 05 5d 24 00 00 17 42 ef cd 8d 08 00 45 10
               00 64 65 67 40 00 40 06 e9 29 ac 18 4a 60 ab 40
               4a 3a e8 bd 00 16 7c 28 2f 88 f2 bd 7a 03 80 18
               00 b5 ec 49 00 00 01 01 08 0a 00 d1 46 8b 32 ed
               7c 88 78 4b 8a dc 0a 1f c4 d3 02 a3 ae 1d 3c aa
               6f 1a 36 9f 27 11 12 71 5b 5d 88 f2 97 fa e7 f9
               99 c1 9f 9c 7f c5 1e 3e 45 c6 a6 ac ec 0b 87 64
               98 dd""")
        self.pkt = Packet(data=pktdata)
        
        """
        MPLS packet data for MPLS parsing tests.  

        Ethernet II, Src: Fujitsu_ef:cd:8d (00:17:42:ef:cd:8d), 
            Dst: ZhsZeitm_5d:24:00 (00:d0:05:5d:24:00)
        MPLS, Label: 0xFEFEF, TC: 5, S: 1, TTL: 0xAA
        Internet Protocol, Src: 172.24.74.96 (172.24.74.96), 
            Dst: 171.64.74.58 (171.64.74.58)
        Transmission Control Protocol, Src Port: 59581 (59581), 
            Dst Port: ssh (22), Seq: 2694, Ack: 2749, Len: 48
        """
        mplspktdata = self.ascii_to_data(
            """00 d0 05 5d 24 00 00 17 42 ef cd 8d 88 47
               FE FE FB AA
               45 10 00 64 65 67 40 00 40 06 e9 29 ac 18 4a 60 
               ab 40 4a 3a 
               e8 bd 00 16 7c 28 2f 88 f2 bd 7a 03 80 18
               00 b5 ec 49 00 00 01 01 08 0a 00 d1 46 8b 32 ed
               7c 88 78 4b 8a dc 0a 1f c4 d3 02 a3 ae 1d 3c aa
               6f 1a 36 9f 27 11 12 71 5b 5d 88 f2 97 fa e7 f9
               99 c1 9f 9c 7f c5 1e 3e 45 c6 a6 ac ec 0b 87 64
               98 dd""")
        self.mplspkt = Packet(data=mplspktdata)

    def runTest(self):
        self.assertTrue(self.pkt)
        
class l2_parsing_test(packet_test):
    def runTest(self):
        match = self.pkt.match
        self.assertEqual(match.dl_dst,[0x00,0xd0,0x05,0x5d,0x24,0x00])
        self.assertEqual(match.dl_src,[0x00,0x17,0x42,0xef,0xcd,0x8d])
        self.assertEqual(match.dl_type,ETHERTYPE_IP)
        
class mpls_parsing_test(packet_test):
    def runTest(self):
        match = self.mplspkt.match
        self.assertEqual(match.mpls_label, 0xFEFEF)
        self.assertEqual(match.mpls_tc, 5)

class ip_parsing_test(packet_test):
    def runTest(self):
        match = self.pkt.match
        # @todo Verify byte ordering of the following
        self.assertEqual(match.nw_dst,ascii_ip_to_bin('171.64.74.58'))
        self.assertEqual(match.nw_src,ascii_ip_to_bin('172.24.74.96'))
        self.assertEqual(match.nw_proto, socket.IPPROTO_TCP)

class mpls_setting_test(packet_test):
    def runTest(self):
        orig_len = len(self.mplspkt)
        label = 0x12345
        tc = 6
        ttl = 0x78
        self.mplspkt.set_mpls_label(label)
        self.mplspkt.set_mpls_tc(tc)
        self.mplspkt.set_mpls_ttl(ttl)
        self.mplspkt.dec_mpls_ttl()
        self.mplspkt.parse()
        
        self.assertEqual(len(self.mplspkt), orig_len)
        self.assertTrue(self.mplspkt.mpls_tag_offset)
        match = self.mplspkt.match
        
        self.assertEqual(match.mpls_label, label)
        self.assertEqual(match.mpls_tc, tc)
        new_ttl = struct.unpack("B", self.mplspkt.data[self.mplspkt.mpls_tag_offset + 3:
                                                       self.mplspkt.mpls_tag_offset + 4])[0]
        self.assertEqual(new_ttl, ttl - 1)

class mpls_pop_test(packet_test):
    def runTest(self):
        orig_len = len(self.mplspkt)
        self.mplspkt.pop_mpls(ETHERTYPE_IP)
        self.mplspkt.parse()
        
        self.assertEqual(len(self.mplspkt), orig_len - 4)
        self.assertFalse(self.mplspkt.mpls_tag_offset)
        match = self.mplspkt.match
        
        self.assertEqual(match.dl_type,ETHERTYPE_IP)
        self.assertEqual(match.nw_dst,ascii_ip_to_bin('171.64.74.58'))
        self.assertEqual(match.nw_src,ascii_ip_to_bin('172.24.74.96'))
        self.assertEqual(match.nw_proto, socket.IPPROTO_TCP)
        
class mpls_push_test(packet_test):
    def runTest(self):
        orig_len = len(self.pkt)
        self.pkt.push_mpls(ETHERTYPE_MPLS)
        self.pkt.parse()
        
        self.assertEqual(len(self.pkt), orig_len + 4)
        self.assertTrue(self.pkt.mpls_tag_offset)
        match = self.pkt.match
        
        self.assertEqual(match.dl_type, ETHERTYPE_MPLS)
        self.assertEqual(match.mpls_label, 0)
        self.assertEqual(match.mpls_tc, 0)

class ip_setting_test(packet_test):
    def runTest(self):
        orig_len = len(self.pkt)
        ip1 = '11.22.33.44'
        ip2 = '55.66.77.88'
        self.pkt.set_nw_src(ascii_ip_to_bin(ip1))
        self.pkt.set_nw_dst(ascii_ip_to_bin(ip2))
        self.pkt.parse()
        self.assertEqual(len(self.pkt), orig_len)
        match = self.pkt.match
        
        # @todo Verify byte ordering of the following
        self.assertEqual(match.nw_src,ascii_ip_to_bin(ip1))
        self.assertEqual(match.nw_dst,ascii_ip_to_bin(ip2))
        



class l4_parsing_test(packet_test):
    def runTest(self):
        match = self.pkt.match
        self.assertEqual(match.tp_dst,22)
        self.assertEqual(match.tp_src,59581)

class l4_setting_test(packet_test):
    def runTest(self):
        orig_len = len(self.pkt)
        self.pkt.set_tp_src(777)
        self.pkt.set_tp_dst(666)
        self.pkt.parse()
        self.assertEqual(len(self.pkt), orig_len)
        match = self.pkt.match
        self.assertEqual(match.tp_src,777)
        self.assertEqual(match.tp_dst,666)
        
class simple_tcp_test(unittest.TestCase):
    """ Make sure that simple_tcp_test does what it should 
                          pktlen=100, 
                          dl_dst='00:01:02:03:04:05',
                          dl_src='00:06:07:08:09:0a',
                          dl_vlan_enable=False,
                          dl_vlan=0,
                          dl_vlan_pcp=0,
                          dl_vlan_cfi=0,
                          ip_src='192.168.0.1',
                          ip_dst='192.168.0.2',
                          ip_tos=0,
                          tcp_sport=1234,
                          tcp_dport=80):
    """
    def setUp(self):
        self.pkt = Packet().simple_tcp_packet()
        self.pkt.parse()
        logging.basicConfig(filename="", level=logging.DEBUG)
        self.logger = logging.getLogger('unittest')
       
    def runTest(self):
        match = self.pkt.match
        self.assertEqual(match.dl_dst, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05])
        self.assertEqual(match.dl_src, [0x00, 0x06, 0x07, 0x08, 0x09, 0x0a])
        self.assertEqual(match.dl_type, ETHERTYPE_IP)
        self.assertEqual(match.nw_src, ascii_ip_to_bin('192.168.0.1'))
        self.assertEqual(match.nw_dst, ascii_ip_to_bin('192.168.0.2'))
        self.assertEqual(match.tp_dst, 80)
        self.assertEqual(match.tp_src, 1234)

class simple_vlan_test(simple_tcp_test):
    """ Make sure that simple_tcp_test does what it should with vlans 
                         
    """    
       
    def runTest(self):
        self.pkt = Packet().simple_tcp_packet(dl_vlan_enable=True,dl_vlan=0x0abc)
        self.pkt.parse()
        match = self.pkt.match
        #self.logger.debug("Packet=\n%s" % self.pkt.show())
        self.assertEqual(match.dl_dst, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05])
        self.assertEqual(match.dl_src, [0x00, 0x06, 0x07, 0x08, 0x09, 0x0a])
        self.assertEqual(match.dl_type, ETHERTYPE_IP)
        self.assertEqual(match.nw_src, ascii_ip_to_bin('192.168.0.1'))
        self.assertEqual(match.nw_dst, ascii_ip_to_bin('192.168.0.2'))
        self.assertEqual(match.tp_dst, 80)
        self.assertEqual(match.tp_src, 1234)
        self.assertEqual(match.dl_vlan, 0xabc)
        
class vlan_mod(simple_tcp_test):
    """ Start with a packet with no vlan, add one, change it, remove it"""
    def runTest(self):
        old_len = len(self.pkt)
        match = self.pkt.match
        self.assertEqual(match.dl_vlan, 0xffff)
        self.assertEqual(len(self.pkt), old_len)
        #self.logger.debug("PKT=\n" + self.pkt.show())
        self.pkt.push_vlan(ETHERTYPE_VLAN) # implicitly pushes vid=0
        self.assertEqual(len(self.pkt), old_len + 4)
        self.assertEqual(match.dl_vlan, 0)
        #self.logger.debug("PKT=\n" + self.pkt.show())
        self.assertEqual(match.dl_type,ETHERTYPE_IP)
        self.pkt.set_vlan_vid(0xbabe)
        self.assertEqual(match.dl_vlan, 0x0abe)

class simple_tcp_with_mpls_test(unittest.TestCase):
    """ Make sure that simple_tcp_packet does what it should 
                          pktlen=100, 
                          dl_dst='00:01:02:03:04:05',
                          dl_src='00:06:07:08:09:0a',
                          dl_vlan_enable=False,
                          dl_vlan=0,
                          dl_vlan_pcp=0,
                          dl_vlan_cfi=0,
                          ip_src='192.168.0.1',
                          ip_dst='192.168.0.2',
                          ip_tos=0,
                          tcp_sport=1234,
                          tcp_dport=80):
    """
    def setUp(self):
        tag1 = MplsTag(0xabcde, 0x5, 0xAA)
        tag2 = MplsTag(0x54321, 0x2, 0xBB)
        mpls_tags = (tag1, tag2)        
        
        self.pkt = Packet().simple_tcp_packet(mpls_type=ETHERTYPE_MPLS,
                                              mpls_tags=mpls_tags)
        self.pkt.parse()
       
    def runTest(self):
        match = self.pkt.match
        self.assertEqual(match.dl_dst, [0x00, 0x01, 0x02, 0x03, 0x04, 0x05])
        self.assertEqual(match.dl_src, [0x00, 0x06, 0x07, 0x08, 0x09, 0x0a])
        self.assertEqual(match.dl_type, ETHERTYPE_MPLS)
        self.assertEqual(match.mpls_label, 0xabcde)
        self.assertEqual(match.mpls_tc, 0x5)

if __name__ == '__main__':
    print("Running packet tests\n")
    unittest.main()

